Using pest to Parse ROS Message Definitions

This is a continuation of the previous post using PEG.js to parse ROS message definitions. However, it is alway better if we can use Rust :)

Code

The code is hosted at https://github.com/CoreRC/rcgenmsg. For now it's only a parser adapted from the official pest documentation. The parser definition is listed below:

// ROS Message Definition Parser
// ==========================

file = {
    (result)*
}
result = _{
    (" " | "\t")* ~ (comment | definition)? ~ sp ~ comment? ~ linebreak+
}

sp = _{
    (" " | "\t")*
}

linebreak = _{
    "\n" | "\r"
}

types = {
    ("bool" | "uint8" | "float32" | "string" | "Header" | !("\n" | "\r" | "\t")+)
}

array = {
    types ~ ("[]")
}

itype = {
    array | types
}

identifier = {
    ('a'..'z' | 'A'..'Z' | "_")+
}

value = {
    ('a'..'z' | 'A'..'Z' | '0'..'9')+
}

constant = {
    itype ~ sp ~ identifier ~ sp ~ "=" ~ sp ~ value
}

variable = {
    itype ~ sp ~ identifier ~ sp ~ !("=")
}

definition = {
    (variable | constant)
}

nonl = _{ !linebreak ~ any }
comment = { "#" ~ nonl* }

Test

We tested it with the following ROS msg definition file with constants, variables, arrays and inline comments:


# Constants are chosen to match the enums in the linux kernel
# defined in include/linux/power_supply.h as of version 3.7
# The one difference is for style reasons the constants are
# all uppercase not mixed case.

# Power supply status constants
uint8 POWER_SUPPLY_STATUS_UNKNOWN = 0
uint8 POWER_SUPPLY_STATUS_CHARGING = 1
uint8 POWER_SUPPLY_STATUS_DISCHARGING = 2
uint8 POWER_SUPPLY_STATUS_NOT_CHARGING = 3
uint8 POWER_SUPPLY_STATUS_FULL = 4

# Power supply health constants
uint8 POWER_SUPPLY_HEALTH_UNKNOWN = 0
uint8 POWER_SUPPLY_HEALTH_GOOD = 1
uint8 POWER_SUPPLY_HEALTH_OVERHEAT = 2 # test
uint8 POWER_SUPPLY_HEALTH_DEAD = 3
uint8 POWER_SUPPLY_HEALTH_OVERVOLTAGE = 4
uint8 POWER_SUPPLY_HEALTH_UNSPEC_FAILURE = 5
uint8 POWER_SUPPLY_HEALTH_COLD = 6
uint8 POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE = 7
uint8 POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE = 8

# Power supply technology (chemistry) constants
uint8 POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0
uint8 POWER_SUPPLY_TECHNOLOGY_NIMH = 1
uint8 POWER_SUPPLY_TECHNOLOGY_LION = 2
uint8 POWER_SUPPLY_TECHNOLOGY_LIPO = 3
uint8 POWER_SUPPLY_TECHNOLOGY_LIFE = 4
uint8 POWER_SUPPLY_TECHNOLOGY_NICD = 5
uint8 POWER_SUPPLY_TECHNOLOGY_LIMN = 6

Header  header
float32 voltage          # Voltage in Volts (Mandatory)
float32 current          # Negative when discharging (A)  (If unmeasured NaN)
float32 charge           # Current charge in Ah  (If unmeasured NaN)
float32 capacity         # Capacity in Ah (last full capacity)  (If unmeasured NaN)
float32 design_capacity  # Capacity in Ah (design capacity)  (If unmeasured NaN)
float32 percentage       # Charge percentage on 0 to 1 range  (If unmeasured NaN)
uint8   power_supply_status     # The charging status as reported. Values defined above
uint8   power_supply_health     # The battery health metric. Values defined above
uint8   power_supply_technology # The battery chemistry. Values defined above
bool    present          # True if the battery is present

float32[] cell_voltage   # An array of individual cell voltages for each cell in the pack
                         # If individual voltages unknown but number of cells known set each to NaN
string location          # The location into which the battery is inserted. (slot number or plug)
string serial_number     # The best approximation of the battery serial number

And the result is here:

Processing: hello.txt
LINE:    # Constants are chosen to match the enums in the linux kernel
LINE:    # defined in include/linux/power_supply.h as of version 3.7
LINE:    # The one difference is for style reasons the constants are
LINE:    # all uppercase not mixed case.
LINE:    # Power supply status constants
LINE:    uint8 POWER_SUPPLY_STATUS_UNKNOWN = 0
PART itype:   uint8
PART identifier:   POWER_SUPPLY_STATUS_UNKNOWN
PART value:   0
LINE:    uint8 POWER_SUPPLY_STATUS_CHARGING = 1
PART itype:   uint8
PART identifier:   POWER_SUPPLY_STATUS_CHARGING
PART value:   1
LINE:    uint8 POWER_SUPPLY_STATUS_DISCHARGING = 2
PART itype:   uint8
PART identifier:   POWER_SUPPLY_STATUS_DISCHARGING
PART value:   2
LINE:    uint8 POWER_SUPPLY_STATUS_NOT_CHARGING = 3
PART itype:   uint8
PART identifier:   POWER_SUPPLY_STATUS_NOT_CHARGING
PART value:   3
LINE:    uint8 POWER_SUPPLY_STATUS_FULL = 4
PART itype:   uint8
PART identifier:   POWER_SUPPLY_STATUS_FULL
PART value:   4
LINE:    # Power supply health constants
LINE:    uint8 POWER_SUPPLY_HEALTH_UNKNOWN = 0
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_UNKNOWN
PART value:   0
LINE:    uint8 POWER_SUPPLY_HEALTH_GOOD = 1
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_GOOD
PART value:   1
LINE:    uint8 POWER_SUPPLY_HEALTH_OVERHEAT = 2
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_OVERHEAT
PART value:   2
LINE:    # test
LINE:    uint8 POWER_SUPPLY_HEALTH_DEAD = 3
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_DEAD
PART value:   3
LINE:    uint8 POWER_SUPPLY_HEALTH_OVERVOLTAGE = 4
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_OVERVOLTAGE
PART value:   4
LINE:    uint8 POWER_SUPPLY_HEALTH_UNSPEC_FAILURE = 5
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
PART value:   5
LINE:    uint8 POWER_SUPPLY_HEALTH_COLD = 6
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_COLD
PART value:   6
LINE:    uint8 POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE = 7
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE
PART value:   7
LINE:    uint8 POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE = 8
PART itype:   uint8
PART identifier:   POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE
PART value:   8
LINE:    # Power supply technology (chemistry) constants
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_UNKNOWN
PART value:   0
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_NIMH = 1
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_NIMH
PART value:   1
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_LION = 2
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_LION
PART value:   2
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_LIPO = 3
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_LIPO
PART value:   3
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_LIFE = 4
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_LIFE
PART value:   4
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_NICD = 5
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_NICD
PART value:   5
LINE:    uint8 POWER_SUPPLY_TECHNOLOGY_LIMN = 6
PART itype:   uint8
PART identifier:   POWER_SUPPLY_TECHNOLOGY_LIMN
PART value:   6
LINE:    Header  header
PART itype:   Header
PART identifier:   header
LINE:    float32 voltage          # Voltage in Volts (Mandatory)
PART itype:   float32
PART identifier:   voltage
PART comment:   # Voltage in Volts (Mandatory)
LINE:    float32 current          # Negative when discharging (A)  (If unmeasured NaN)
PART itype:   float32
PART identifier:   current
PART comment:   # Negative when discharging (A)  (If unmeasured NaN)
LINE:    float32 charge           # Current charge in Ah  (If unmeasured NaN)
PART itype:   float32
PART identifier:   charge
PART comment:   # Current charge in Ah  (If unmeasured NaN)
LINE:    float32 capacity         # Capacity in Ah (last full capacity)  (If unmeasured NaN)
PART itype:   float32
PART identifier:   capacity
PART comment:   # Capacity in Ah (last full capacity)  (If unmeasured NaN)
LINE:    float32 design_capacity  # Capacity in Ah (design capacity)  (If unmeasured NaN)
PART itype:   float32
PART identifier:   design_capacity
PART comment:   # Capacity in Ah (design capacity)  (If unmeasured NaN)
LINE:    float32 percentage       # Charge percentage on 0 to 1 range  (If unmeasured NaN)
PART itype:   float32
PART identifier:   percentage
PART comment:   # Charge percentage on 0 to 1 range  (If unmeasured NaN)
LINE:    uint8   power_supply_status     # The charging status as reported. Values defined above
PART itype:   uint8
PART identifier:   power_supply_status
PART comment:   # The charging status as reported. Values defined above
LINE:    uint8   power_supply_health     # The battery health metric. Values defined above
PART itype:   uint8
PART identifier:   power_supply_health
PART comment:   # The battery health metric. Values defined above
LINE:    uint8   power_supply_technology # The battery chemistry. Values defined above
PART itype:   uint8
PART identifier:   power_supply_technology
PART comment:   # The battery chemistry. Values defined above
LINE:    bool    present          # True if the battery is present
PART itype:   bool
PART identifier:   present
PART comment:   # True if the battery is present
LINE:    float32[] cell_voltage   # An array of individual cell voltages for each cell in the pack
PART itype:   float32[]
PART identifier:   cell_voltage
PART comment:   # An array of individual cell voltages for each cell in the pack
LINE:    # If individual voltages unknown but number of cells known set each to NaN
LINE:    string location          # The location into which the battery is inserted. (slot number or plug)
PART itype:   string
PART identifier:   location
PART comment:   # The location into which the battery is inserted. (slot number or plug)
LINE:    string serial_number     # The best approximation of the battery serial number
PART itype:   string
PART identifier:   serial_number
PART comment:   # The best approximation of the battery serial number

We can see it correctly parsed both normal types and arrays correctly.