From dd926f514b1a3642a724e970537f834285c8e53b Mon Sep 17 00:00:00 2001 From: Misha Chernetsov Date: Wed, 25 Mar 2015 00:20:25 -0700 Subject: support for dialect definition file include directives minor tweaks --- .gitignore | 3 + .../com/github/jodersky/mavlink/Context.scala | 2 +- .../scala/com/github/jodersky/mavlink/Parser.scala | 85 +++++++++++++--------- .../com/github/jodersky/mavlink/Reporter.scala | 6 +- .../github/jodersky/mavlink/trees/package.scala | 2 +- mavlink-library/src/test/resources/including.xml | 4 + .../com/github/jodersky/mavlink/MainTest.scala | 11 ++- .../github/jodersky/mavlink/sbt/SbtMavlink.scala | 9 +-- 8 files changed, 72 insertions(+), 50 deletions(-) create mode 100644 mavlink-library/src/test/resources/including.xml diff --git a/.gitignore b/.gitignore index 1d1019c..3202827 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ project/plugins/project/ .cache .history +# intellij-ide specific +.idea + # ensime .ensime 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 index d025285..7986b62 100644 --- a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala @@ -2,5 +2,5 @@ package com.github.jodersky.mavlink /** Represents the context under which MAVLink scala code was generated. */ case class Context( - version: String + version: Option[String] ) \ 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 3010121..e6f0f07 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 @@ -1,12 +1,10 @@ package com.github.jodersky.mavlink +import java.io.File + import scala.language.postfixOps -import scala.xml.Attribute -import scala.xml.Elem -import scala.xml.Node -import scala.xml.Null -import scala.xml.Text +import scala.xml._ import scala.util.Try import trees._ @@ -18,91 +16,110 @@ import trees._ class Parser(reporter: Reporter) { import reporter._ - def parseDialect(node: Node): Dialect = parse(node) match { + def parseDialect(dialectDefinitionFile: File): Dialect = { + val xml = XML.loadFile(dialectDefinitionFile) + parseDialect(xml, dialectDefinitionFile) + } + + private def parseDialect(node: Node, file: File): Dialect = parse(node, file) match { case p: Dialect => p - case _ => fatal("expected mavlink protocol definition", node) + case _ => fatal("expected mavlink protocol definition", node, file) } - def parse(node: Node): Tree = node match { + def parse(node: Node, file: File): Tree = node match { case {_*} => val description = node.text - val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for field", node) + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for field", node, file) 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) + case Some(t) => (parseType(t, node, file), t) + case None => fatal("no field type specified", node, file) } 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) + Try { Integer.parseInt(str) } getOrElse fatal("value must be an integer", node, file) + } getOrElse fatal("no value defined", node, file) + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum entry", node, file) 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 name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum", node, file) 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 val nodeWithValue = if ((n \ "@value").isEmpty) { - warn("no value defined for enum entry, using index instead", n) + warn("no value defined for enum entry, using index instead", n, file) n.asInstanceOf[Elem] % Attribute(None, "value", Text(i.toString), Null) } else { n } - parse(nodeWithValue) match { + parse(nodeWithValue, file) match { case e: EnumEntry => e - case _ => fatal("illegal definition in enum, only entries are allowed", n) + case _ => fatal("illegal definition in enum, only entries are allowed", n, file) } } Enum(name, entries, description) 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) + val id = Try { Integer.parseInt(str) } getOrElse fatal("id must be an integer", node, file) + if (id < 0 || id > 255) warn("message id is not in the range [0-255]", node, file) id.toByte - } getOrElse fatal("no id defined", node) - val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for message", node) + } getOrElse fatal("no id defined", node, file) + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for message", node, file) val description = (node \ "description").text val fields = (node \ "field") map { n: Node => - parse(n) match { + parse(n, file) match { case e: Field => e - case _ => fatal("illegal definition in message, only fields are allowed", n) + case _ => fatal("illegal definition in message, only fields are allowed", n, file) } } Message(id, name, description, fields) case {_*} => - val version = (node \ "version").text + val version = (node \ "version").headOption.map(_.text) + + val include = (node \ "include").headOption.map(_.text).map { includeFileName => + val includeFile: File = new File(file.getParentFile, includeFileName) + parseDialect(includeFile) + } val enums = (node \ "enums" \ "_").toSet map { n: Node => - parse(n) match { + parse(n, file) match { case e: Enum => e - case _ => fatal("illegal definition in enums, only enum declarations are allowed", n) + case _ => fatal("illegal definition in enums, only enum declarations are allowed", n, file) } } val messages = (node \ "messages" \ "_").toSet map { n: Node => - parse(n) match { + parse(n, file) match { case e: Message => e - case e => fatal("illegal definition in messages, only message declarations are allowed", n) + case e => fatal("illegal definition in messages, only message declarations are allowed", n, file) } } - Dialect(version, enums, messages) + + include match { + case None => Dialect(version, enums, messages) + case Some(includeDialect) => Dialect( + (version ++ includeDialect.version).headOption, // included version overridden by local version if any + enums ++ includeDialect.enums, + messages ++ includeDialect.messages + ) + } + - case x => fatal("unknown", x) + case x => fatal("unknown", x, file) } val ArrayPattern = """(.*)\[(\d+)\]""".r - def parseType(typeStr: String, node: Node): Type = typeStr match { + def parseType(typeStr: String, node: Node, file: File): Type = typeStr match { case "int8_t" => IntType(1, true) case "uint8_t_mavlink_version" => IntType(1, false) case "int16_t" => IntType(2, true) @@ -116,8 +133,8 @@ class Parser(reporter: Reporter) { 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) + case ArrayPattern(u, l) => ArrayType(parseType(u, node, file), l.toInt) + case unknown => fatal("unknown field type " + unknown, node, file) } } \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Reporter.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Reporter.scala index 629c3a8..cec6572 100644 --- a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Reporter.scala +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Reporter.scala @@ -1,13 +1,15 @@ package com.github.jodersky.mavlink +import java.io.File + import scala.xml.Node trait Reporter { protected def printWarning(msg: String): Unit - def fatal(error: String, node: Node) = throw new ParseError("Parse error: " + error + " at " + node) - def warn(warning: String, node: Node) = printWarning("Warning: " + warning +" at " + node) + def fatal(message: String, node: Node, file: File) = throw new ParseError(s"Parse error: $message at $node in file ${file.getAbsolutePath}") + def warn(message: String, node: Node, file: File) = printWarning(s"Parse warning: $message at ${node} in file ${file.getAbsolutePath}") } 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 c1e2993..f59eda6 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 @@ -4,7 +4,7 @@ package trees { sealed trait Tree - case class Dialect(version: String, enums: Set[Enum], messages: Set[Message]) extends Tree + case class Dialect(version: Option[String], enums: Set[Enum], messages: Set[Message]) 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 diff --git a/mavlink-library/src/test/resources/including.xml b/mavlink-library/src/test/resources/including.xml new file mode 100644 index 0000000..e1b026f --- /dev/null +++ b/mavlink-library/src/test/resources/including.xml @@ -0,0 +1,4 @@ + + + concise.xml + \ No newline at end of file diff --git a/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala b/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala index faf252a..be46212 100644 --- a/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala +++ b/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala @@ -1,16 +1,15 @@ package com.github.jodersky.mavlink -import scala.io.Source -import scala.xml.XML -import trees._ +import java.io.File object MainTest { def main(args: Array[String]): Unit = { - val definition = XML.load(getClass.getResource("/concise.xml")) - val dialect = Parser.parseDialect(definition) + val parser: Parser = new Parser(StandardReporter) + val dialectDefinitionFile: File = new File("mavlink-library/src/test/resources/including.xml") + val dialect = parser.parseDialect(dialectDefinitionFile) val generator = new Generator(dialect) - println(generator.generate()) + println(generator.targets.map(_.generate()).mkString("\n\n")) } } \ No newline at end of file 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 afd2c94..9f9884d 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 @@ -10,8 +10,6 @@ import sbt._ import sbt.Keys._ import sbt.plugins._ -import scala.xml.XML - object SbtMavlink extends AutoPlugin { override def trigger = allRequirements @@ -42,11 +40,10 @@ object SbtMavlink extends AutoPlugin { 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 + val dialect = new Parser(reporter).parseDialect(dialectDefinitionFile) + val targets = new Generator(dialect).targets for (tgt <- targets) yield { - val file = (outDirectory / tgt.path) + val file = outDirectory / tgt.path if (dialectDefinitionFile.lastModified > file.lastModified) { streams.value.log.info("Generating mavlink binding " + file) -- cgit v1.2.3