defmodule Rclex.Parsers.Helpers do @moduledoc false # The ROS 2 .msg specification URL is as follows, # https://docs.ros.org/en/humble/Concepts/Basic/About-Interfaces.html import NimbleParsec def parse_type(combinator \\ empty()) do combinator |> choice([ builtin_type_array(), msg_type_array(), builtin_type(), msg_type() ]) end def parse_message(combinator \\ empty()) do combinator |> choice([ field_line(), ignore(constant_line()), ignore(comment_line()), ignore(empty_line()) ]) |> wrap() |> times(min: 0) end def field_line(combinator \\ empty()) do combinator |> optional(whitespace()) |> field_type() |> ignore(whitespace()) |> field_name() |> ignore(optional(whitespace())) |> optional(field_default_value()) |> ignore(optional(whitespace())) |> ignore(optional(comment())) |> ignore(new_line()) end def constant_line(combinator \\ empty()) do combinator |> optional(whitespace()) |> field_type() |> ignore(whitespace()) |> ascii_string([?A..?Z, ?_, ?0..?9], min: 1) |> ignore(optional(whitespace())) |> string("=") |> ignore(optional(whitespace())) |> choice([value_float(), value_integer(), value_string()]) |> ignore(optional(whitespace())) |> ignore(optional(comment())) |> ignore(new_line()) end defp comment_line(combinator \\ empty()) do combinator |> optional(whitespace()) |> comment() |> new_line() end defp empty_line(combinator \\ empty()) do combinator |> optional(whitespace()) |> new_line() end defp field_type(combinator) do combinator |> choice([ builtin_type_array() |> reduce({Enum, :join, []}) |> unwrap_and_tag(:builtin_type_array), msg_type_array() |> reduce({Enum, :join, []}) |> unwrap_and_tag(:msg_type_array), builtin_type() |> unwrap_and_tag(:builtin_type), msg_type() |> unwrap_and_tag(:msg_type) ]) end defp field_name(combinator) do combinator |> ascii_string([?a..?z, ?0..?9, ?_], min: 1) end defp field_default_value(combinator \\ empty()) do combinator |> choice([ value_float(), value_integer(), value_string(), value_array() ]) end defp bounded_string(combinator \\ empty()) do combinator |> string("string<=") |> integer(min: 1) |> reduce({Enum, :join, []}) end defp builtin_type(combinator \\ empty()) do combinator |> choice([ bounded_string(), string("bool"), string("byte"), string("char"), string("float32"), string("float64"), string("int8"), string("uint8"), string("int16"), string("uint16"), string("int32"), string("uint32"), string("int64"), string("uint64"), string("string"), string("wstring") ]) end defp msg_type(combinator \\ empty()) do combinator |> ascii_string([?a..?z, ?A..?Z, ?0..?9, ?_, ?/], min: 1) end defp builtin_type_array(combinator \\ empty()) do combinator |> builtin_type() |> choice([ static_array(), unbounded_dynamic_array(), bounded_dynamic_array() ]) end defp msg_type_array(combinator \\ empty()) do combinator |> msg_type() |> choice([ static_array(), unbounded_dynamic_array(), bounded_dynamic_array() ]) end defp static_array(combinator \\ empty()) do combinator |> string("[") |> integer(min: 1) |> string("]") end defp unbounded_dynamic_array(combinator \\ empty()) do combinator |> string("[]") end defp bounded_dynamic_array(combinator \\ empty()) do combinator |> string("[<=") |> integer(min: 1) |> string("]") end defp value_integer(combinator \\ empty()) do combinator |> optional(ascii_string([?-, ?+], 1)) |> ascii_string([?0..?9], min: 1) |> reduce({Enum, :join, []}) |> map({String, :to_integer, []}) end defp value_float(combinator \\ empty()) do combinator |> optional(ascii_string([?-, ?+], 1)) |> ascii_string([?0..?9], min: 1) |> string(".") |> ascii_string([?0..?9], min: 1) |> reduce({Enum, :join, []}) |> map({String, :to_float, []}) end defp value_string(combinator \\ empty()) do combinator |> ignore(ascii_string([?", ?'], 1)) |> ascii_string([?\s, ?!, ?#..?&, ?(..?~], min: 1) |> ignore(ascii_string([?", ?'], 1)) end defp value_array(combinator \\ empty()) do combinator |> ignore(string("[")) |> times( choice([value_float(), value_integer(), value_string()]) |> ignore(optional(whitespace())) |> ignore(optional(string(","))) |> ignore(optional(whitespace())), min: 1 ) |> ignore(string("]")) |> wrap() end defp comment(combinator \\ empty()) do combinator |> string("#") |> optional(ascii_string([?\s..?~, ?\t], min: 1)) end defp whitespace(combinator \\ empty()) do combinator |> ascii_string([?\s, ?\t], min: 1) end defp new_line(combinator \\ empty()) do combinator |> choice([string("\n"), string("\r\n")]) end end