aboutsummaryrefslogtreecommitdiff
path: root/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala
blob: e6f0f07d6dddfb6f098b1c3e6e62005806319cff (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package com.github.jodersky.mavlink

import java.io.File

import scala.language.postfixOps

import scala.xml._
import scala.util.Try

import trees._

/**
 * Provides means to parse a MAVLink dialect definition into a
 * scala object representation.
 */
class Parser(reporter: Reporter) {
  import reporter._

  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, file)
  } 
  
  def parse(node: Node, file: File): 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, file)
      val enum = (node \ "@enum").map(_.text).headOption
      val (tpe, native) = (node \ "@type") map (_.text) headOption match {
        case Some(t) => (parseType(t, node, file), t)
        case None => fatal("no field type specified", node, file)
      }
      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, 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 <enum>{_*}</enum> =>
      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, file)
          n.asInstanceOf[Elem] % Attribute(None, "value", Text(i.toString), Null)
        } else {
          n
        }

        parse(nodeWithValue, file) match {
          case e: EnumEntry => e
          case _ => fatal("illegal definition in enum, only entries are allowed", n, file)
        }
      }
      Enum(name, entries, description)

    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, 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, 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, file) match {
          case e: Field => e
          case _ => fatal("illegal definition in message, only fields are allowed", n, file)
        }
      }
      Message(id, name, description, fields)

    case <mavlink>{_*}</mavlink> =>
      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, file) match {
          case e: Enum => e
          case _ => fatal("illegal definition in enums, only enum declarations are allowed", n, file)
        }
      }

      val messages = (node \ "messages" \ "_").toSet map { n: Node =>
        parse(n, file) match {
          case e: Message => e
          case e => fatal("illegal definition in messages, only message declarations are allowed", n, file)
        }
      }

      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, file)

  }
  
  val ArrayPattern = """(.*)\[(\d+)\]""".r
  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)
    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, file), l.toInt)
    case unknown => fatal("unknown field type " + unknown, node, file)
  }

}