// Synapse language grammar (.syn)
//
// Language-agnostic message/type definition for code generation targeting Rust and C++.
//
// Example:
//
// namespace geometry
//
// ## A 3-dimensional point.
// struct Point {
// ## X axis.
// x: f64 = 0.0
// y: f64 = 0.0
// z: f64 = 0.0
// }
//
// enum DriveMode {
// Idle = 0
// Forward = 1
// Error = 2
// }
//
// const MAX_SPEED: f64 = 2.5
//
// telemetry RobotState {
// mode: DriveMode = DriveMode::Idle
// position: geometry::Point
// label: string[<=64] = "robot"
// sensor_data: u8[]
// error_code?: i32 # optional field
// }
//
// command SetMode {
// mode: DriveMode
// }
//
// table RobotConfig {
// max_speed: f64
// }
// ============================================================
// Whitespace and comments — insignificant everywhere
// ============================================================
WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
// Single '#' = silent comment (like TOML).
// '##' = doc comment — captured and forwarded to generated code.
COMMENT = _{ "#" ~ !"#" ~ (!("\n" | "\r") ~ ANY)* }
doc_comment = @{ "##" ~ (!("\n" | "\r") ~ ANY)* }
doc_block = { doc_comment+ }
// Attribute: @name(literal) e.g. @mid(0x0801) @mid(nav_app::NAV_TLM_MID)
attribute = { "@" ~ ident ~ "(" ~ literal ~ ")" }
// ============================================================
// File
// ============================================================
file = { SOI ~ item* ~ EOI }
item = _{
import_decl
| namespace_decl
| const_decl
| enum_def
| struct_def
| command_def
| telemetry_def
| table_def
| message_def
}
// ============================================================
// Top-level declarations
// ============================================================
// import "path/to/file.syn"
import_decl = { "import" ~ string_lit }
// namespace geometry::msgs
namespace_decl = { "namespace" ~ scoped_ident }
// const MAX: f64 = 3.14
const_decl = { doc_block? ~ attribute* ~ "const" ~ ident ~ ":" ~ type_expr ~ "=" ~ literal }
// ============================================================
// Enum
// ============================================================
// enum Status {
// Idle = 0
// Moving = 1
// }
enum_def = { doc_block? ~ attribute* ~ "enum" ~ ident ~ "{" ~ enum_variant* ~ "}" }
enum_variant = { doc_block? ~ ident ~ ("=" ~ int_lit)? }
// ============================================================
// Struct, Software Bus packets, and table data
// ============================================================
// struct — plain data aggregate (no implied serialization)
struct_def = { doc_block? ~ attribute* ~ "struct" ~ ident ~ "{" ~ field* ~ "}" }
// command — cFS Software Bus command packet
command_def = { doc_block? ~ attribute* ~ "command" ~ ident ~ "{" ~ field* ~ "}" }
// telemetry — cFS Software Bus telemetry packet
telemetry_def = { doc_block? ~ attribute* ~ "telemetry" ~ ident ~ "{" ~ field* ~ "}" }
// table — cFS Table Services data payload, not a Software Bus packet
table_def = { doc_block? ~ attribute* ~ "table" ~ ident ~ "{" ~ field* ~ "}" }
// message — legacy generic Software Bus packet; code generators may infer command/telemetry
message_def = { doc_block? ~ attribute* ~ "message" ~ ident ~ "{" ~ field* ~ "}" }
// field: Type
// field?: Type (optional — maps to Option<T> / std::optional<T>)
// field: Type = val (with default value)
// field?: Type = val (optional with default)
field = { doc_block? ~ ident ~ optional_marker? ~ ":" ~ type_expr ~ ("=" ~ literal)? }
optional_marker = { "?" }
// ============================================================
// Types
// ============================================================
type_expr = { base_type ~ array_suffix? }
// string before primitive: catch "string" keyword before it falls through to type_ref.
// primitive before type_ref: prevent "i32", "f64" etc. matching as user-defined names.
base_type = { string_type | primitive_type | type_ref }
// "string" is its own base type; bounds use array_suffix: string[<=N]
string_type = { "string" }
// Primitive type names map directly to target languages:
// Rust: i8/i16/i32/i64/u8/u16/u32/u64/f32/f64/bool
// C++: int8_t … uint64_t / float / double / bool
// bytes → Vec<u8> / std::vector<uint8_t>
primitive_type = {
"f64" | "f32"
| "i64" | "i32" | "i16" | "i8"
| "u64" | "u32" | "u16" | "u8"
| "bool" | "bytes"
}
// User-defined type reference, optionally namespace-qualified
// e.g. Point geometry::Point nav::msgs::Odometry
type_ref = { scoped_ident }
// Array suffixes — same syntax for all base types including string:
// [] dynamic (unbounded) Vec<T> / std::vector<T>
// [N] fixed-size [T; N] / std::array<T, N>
// [<=N] bounded dynamic Vec<T> with max N / std::vector<T>
//
// For string: string[<=64] means a string of at most 64 chars (not array-of-strings)
array_suffix = { "[" ~ array_size? ~ "]" }
array_size = { bounded_size | pos_int }
bounded_size = { "<=" ~ pos_int }
// ============================================================
// Identifiers
// ============================================================
ident = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
scoped_ident = { ident ~ ("::" ~ ident)* }
pos_int = @{ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
// ============================================================
// Literals
// ============================================================
// float before hex before int: "1.0" must not match as integer "1";
// hex before int: "0x1F" must not match as integer "0".
// ident_lit last: fallback for enum variant refs (Idle, DriveMode::Idle)
literal = { float_lit | hex_lit | int_lit | bool_lit | string_lit | ident_lit }
float_lit = @{
"-"? ~ ASCII_DIGIT* ~ "." ~ ASCII_DIGIT+ ~ float_exp?
| "-"? ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT* ~ float_exp?
| "-"? ~ ASCII_DIGIT+ ~ float_exp
}
float_exp = @{ ^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+ }
hex_lit = @{ ("0x" | "0X") ~ ASCII_HEX_DIGIT+ }
int_lit = @{ "-"? ~ ASCII_DIGIT+ }
// Word-boundary check prevents "true_val" matching as bool "true"
bool_lit = @{ ("true" | "false") ~ !(ASCII_ALPHANUMERIC | "_") }
string_lit = @{ "\"" ~ ((!("\"" | "\\") ~ ANY) | "\\" ~ ANY)* ~ "\"" }
// Enum variant or constant reference: Idle, Status::Idle, pkg::Status::Idle
ident_lit = { scoped_ident }