Skip to content

Grammar ​

A program is four files: the robot (.core), the world (.world), the task (.task), and optionally the evaluation (.eval). Each file holds exactly one declaration of its kind. The files are given to the compiler on the command line, and the extension says which form a file is. There is no project manifest: the target, the component libraries, and the output directory are compile-time settings, so they are command-line options, not something the author writes next to the program.

The grammar fixes the statement forms and nothing else. Component types, world entity types, task node types, and check names are ordinary names that later phases resolve against the standard vocabulary, the component libraries, and the target. A file that names a component or an action the compiler has never seen is syntactically valid; whether the name exists is decided by name resolution, not here. Adding vocabulary never changes the grammar.

The normative grammar is src/mobiledsl/frontend/grammar/robotdsl.lark, read by Lark with the LALR algorithm and a contextual lexer. The test suite builds every start rule in strict mode, so no two terminals can match the same text. When this page and the file disagree, the file is right and this page has a bug.

Common forms ​

Every declaration is a keyword, a name, and a braced body. Inside a body, items have one of two shapes:

text
Type name(parameter = value, ...)          a named item of some type
keyword(parameter = value, ...)            a setting with no name

Arguments are always named. Items may be separated by commas, by line breaks, or by both, and a trailing comma is allowed. Comments are // to end of line and /* ... */; block comments do not nest. Line endings may be LF or CRLF.

Values ​

ValueFormExample
stringdouble quotes, JSON escapes"mecanum_black", "a \"quoted\" word"
numberoptional sign, decimal, optional exponent2, -0.0216, 1.5e-1
booleantrue, falseis_static = true
tupletwo or more numbers in parentheses(0, 0, -9.81), (2.5, 0, 0, 0, 0, 0, 1)
reference@namespace/name@core/wheel_lf, @world/goal, @task/drone_demo

Numbers carry no unit; the parameter name does, by suffix: timeout_s, distance_m, angle_rad. A tuple's meaning (a pose of seven numbers, a size of three, a glue of six or seven) is decided by the parameter that takes it, not by the grammar. The reference namespaces are core, world, and task; any other namespace is a diagnostic, but the tree is still built. Identifiers are [A-Za-z_][A-Za-z0-9_]*.

CoreDSL (.core) ​

text
robot_decl: "Robot" NAME "{" intent? component* "}"
intent:     "intent" STRING
component:  NAME NAME params

The first name of a component is its type, the second its instance name. intent is a sentence for people and for the tools that generate programs; it comes before the components.

text
Robot drone_mecanum {
    intent "Drone mecanum mobile base with four black mecanum wheels and a top lidar"

    Mecanum_four_wheel chassis(ref = "tslot_1515_drone")
    Mecanum wheel_lf(ref = "mecanum_black", glue = (0.1275, 0.1494, -0.0216, 0, 1, 0))
    Lidar top_lidar(ref = "lidar_ld06", glue = (0, 0.0001, 0.0344, 0, 0, -1))
}

WorldDSL (.world) ​

text
world_decl: "World" NAME "{" world_item* "}"
world_item: physics | spawn | scenario | goal
physics:    "physics" params
spawn:      "spawn" params
scenario:   "Scenario" NAME params "{" entity* "}"
entity:     NAME NAME params
goal:       "Goal" NAME params

physics and spawn are settings of the world and have no name. A scenario groups the entities of one environment; each entity has a type (for example Obstacle, SupportSurface, Marker) and a name that tasks and checks refer to as @world/<name>. A goal is a named location.

text
World demo {
    physics(gravity = (0, 0, -9.81))
    spawn(robot = @core/drone_mecanum, pose = (0, 0, 0.05, 0, 0, 0, 1))
    Scenario room(ref = "empty", ground_plane = true) {
        Obstacle box(type = "box", pos = (1.2, 0, 0.25, 0, 0, 0, 1), size = (0.3, 0.3, 0.5), is_static = true)
    }
    Goal goal(pose = (2.5, 0, 0, 0, 0, 0, 1))
}

TaskDSL (.task) ​

text
task_decl: "Task" NAME params? "{" node* "}"
node:      NAME params block?
         | NAME block
block:     "{" node* "}"

A node is a type followed by arguments, a block of children, or both. Which types take children (Sequence, Fallback, Retry, ...) and which are actions (Navigate, Spin, Wait, ...) is vocabulary, not grammar. A leaf keeps its parentheses even when it has no arguments, Stop(), so that two nodes in a row never read as one.

The task vocabulary names what the robot must accomplish. It does not name how a target accomplishes it: there is no node for a navigation stack's planner, controller, or costmap, and no syntax to select one. Those choices belong to the target and its configuration.

text
Task drone_demo(timeout_s = 120) {
    Retry(count = 2) {
        Navigate(goal = @world/goal)
    }
    Sequence {
        Spin(angle_rad = 1.57),
        Wait(duration_s = 2),
        Stop()
    }
}

EvaluateDSL (.eval) ​

text
eval_decl: "Evaluate" NAME "{" check* "}"
check:     NAME params
text
Evaluate demo_eval {
    Check_clearance(target = @world/goal, min = 0.3)
    Check_vel(target = @core/wheel_lf, min = 0, max = 60)
    Check_time(target = @task/drone_demo, max = 300)
}

Accepted and rejected ​

Accepted:

SourceWhy it is interesting
Robot r {}the smallest declaration of each kind is valid
Robot r { Hovercraft body(ref = "x") }unknown vocabulary is a name-resolution question
Task t { Dock(station = @world/dock) }so are actions the compiler has never seen
Robot r { A a(ref = "1"), B b(ref = "2"), }commas are optional, a trailing one is fine
Task t { Sequence { } Stop() Retry(count = 2) { Stop() } }block only, arguments only, both
Task t { Spin(angle_rad = -1.5e-1) }signs and exponents
// line /* block */both comment styles

Rejected, with the diagnostic it produces:

SourceDiagnostic
World w {} in a .core filesyntax.unexpected_token: unexpected 'World'; expected 'Robot'
Robot a {} Robot b {}syntax.unexpected_token: unexpected 'Robot'; expected end of file
Lidar l("lidar_ld06")syntax.unexpected_token: expected one of ')', a name — arguments are named
Stop Wait(duration_s = 1)syntax.unexpected_token: unexpected 'Wait'; expected one of '(', '{'
Goal g(pose = (1))syntax.unexpected_token: unexpected ')'; expected ',' — a tuple has two or more numbers
Robot r {} # commentsyntax.unexpected_character: # comments are gone
/* a /* b */ c */syntax.unexpected_token at c — block comments do not nest
Robot r {syntax.unexpected_eof: expected one of 'intent', '}', a name
Navigate(goal = @wrold/goal)syntax.unknown_reference_namespace
Lidar l(ref = "a", ref = "b")syntax.duplicate_parameter, pointing at both occurrences

The diagnostics are specified in Diagnostics.

Changes from the previous compiler ​

The previous compiler's four grammars are the starting point. What changed, and why:

  • Component types, task node types, and check names were grammar keywords (21, 15, and 9 of them). They are names now, so a new sensor, action, or check is a vocabulary entry, not a grammar change, and the task language is no longer tied to one navigation stack's node set.
  • Poses, vectors, and references were strings: glue="0.1275 0.1494 ...", goal="@world/goal". They are tuples and references now, so the parser checks them and every number has its own span.
  • spawn { "@core/x": "pose" } is spawn(robot = @core/x, pose = (...)), and physics { gravity="..." } is physics(gravity = (...)), the same shape as every other setting.
  • ComputePathToPose, FollowPath, path="{path}", ClearLocalCostmap, ClearGlobalCostmap, controller=, planner=, PipelineSequence, and TransformAvailable are gone from the task language: they described the navigation stack, not the task.
  • tick_rate_hz on Task is gone: the previous compiler accepted it and never used it.
  • # comments are gone; /* */ comments are new.

What the grammar leaves to later phases ​

  • Whether a name exists and what it is.
  • Which parameters a type takes, their types, units, and tuple lengths.
  • Whether a reference resolves, and whether its namespace fits the parameter.
  • Which node types take children, and whether a task or a robot is complete.
  • Whether the target supports an action.