///|
/// A protobuf scalar a MoonBit type stands for.
#derive(Eq)
priv enum Scalar {
Str
Raw
Boolean
I32
I64
U32
U64
F64
F32
}
///|
/// The protobuf type of a value: a scalar, or a message or an enum by its
/// MoonBit name.
priv enum Base {
Scalar(Scalar)
Message(String)
Enum(String)
}
///|
/// How a field holds its values: once, optionally (proto3 `optional`), as a
/// list, or as a `map` keyed by a scalar of the given MoonBit type.
#derive(Eq)
priv enum Card {
Single
Optional
Repeated
Keyed(Scalar, String)
}
///|
fn scalar_of(ty : String) -> Scalar? {
match ty {
"String" => Some(Str)
"Bytes" => Some(Raw)
"Bool" => Some(Boolean)
"Int" | "Int32" => Some(I32)
"Int64" => Some(I64)
"UInt" | "UInt32" => Some(U32)
"UInt64" => Some(U64)
"Double" => Some(F64)
"Float" => Some(F32)
_ => None
}
}
///|
/// `T` out of `Name[T]`, or `None`.
fn inside(ty : StringView, name : String) -> String? {
let ty = ty.trim(chars=" ")
let open = name + "["
if ty.has_prefix(open) && ty.has_suffix("]") {
Some(
ty
.exact_view(start=open.length(), end=ty.length() - 1)
.trim(chars=" ")
.to_owned(),
)
} else {
None
}
}
///|
/// `K` and `V` out of `K, V`, split at the comma outside any brackets.
fn pair(text : String) -> (String, String)? {
let chars = text.to_array()
let mut depth = 0
for i, c in chars {
match c {
'[' | '(' => depth += 1
']' | ')' => depth -= 1
',' if depth == 0 => {
let key = String::from_array(chars[:i]).trim(chars=" ").to_owned()
let value = String::from_array(chars[i + 1:]).trim(chars=" ").to_owned()
return Some((key, value))
}
_ => ()
}
}
None
}
///|
/// A MoonBit type as its protobuf base and how it repeats; `None` for a type
/// protobuf has no field for. The element type, the value type of a map, is
/// returned alongside. A map key is a string, an integer or a bool, as the
/// protobuf language allows.
fn field_of(
ty : String,
messages : Map[String, String],
enums : Map[String, String],
) -> (Base, Card, String)? {
let ty = ty.trim(chars=" ").to_owned()
let (inner, card) = if ty.has_suffix("?") {
(ty.exact_view(end=ty.length() - 1).trim(chars=" ").to_owned(), Optional)
} else if inside(ty, "Option") is Some(t) {
(t, Optional)
} else if inside(ty, "Array") is Some(t) {
(t, Repeated)
} else if inside(ty, "Map") is Some(kv) {
guard pair(kv) is Some((k, v)) &&
scalar_of(k) is Some(key) &&
key is (Str | Boolean | I32 | I64 | U32 | U64) else {
return None
}
(v, Keyed(key, k))
} else {
(ty, Single)
}
let base = match scalar_of(inner) {
Some(s) => Scalar(s)
None if messages.contains(inner) => Message(inner)
None if enums.contains(inner) => Enum(inner)
None => return None
}
Some((base, card, inner))
}
///|
/// The descriptor's `FieldType` for a base.
fn field_type(base : Base) -> String {
match base {
Scalar(Str) => "TypeString"
Scalar(Raw) => "TypeBytes"
Scalar(Boolean) => "TypeBool"
Scalar(I32) => "TypeInt32"
Scalar(I64) => "TypeInt64"
Scalar(U32) => "TypeUint32"
Scalar(U64) => "TypeUint64"
Scalar(F64) => "TypeDouble"
Scalar(F32) => "TypeFloat"
Message(_) => "TypeMessage"
Enum(_) => "TypeEnum"
}
}
///|
/// The wire type a base is written with.
fn wire_of(base : Base) -> String {
match base {
Scalar(Str | Raw) | Message(_) => "LengthDelim"
Scalar(F64) => "Fixed64"
Scalar(F32) => "Fixed32"
Scalar(_) | Enum(_) => "Varint"
}
}
///|
/// Whether a repeated field of this base is written packed: the varint
/// scalars and enums. Doubles and floats go unpacked, which every parser reads
/// too.
fn packed(base : Base) -> Bool {
base is (Scalar(Boolean | I32 | I64 | U32 | U64) | Enum(_))
}
///|
/// The call writing field `n` with value `e` on writer `w`.
fn write_call(base : Base, n : Int32, e : String) -> String {
match base {
Scalar(Str) => "w.string_(\{n}, \{e})"
Scalar(Raw) => "w.bytes_(\{n}, \{e})"
Scalar(Boolean) => "w.bool_(\{n}, \{e})"
Scalar(I32) => "w.int32(\{n}, \{e})"
Scalar(I64) => "w.int64(\{n}, \{e})"
Scalar(U32) => "w.uint32(\{n}, \{e})"
Scalar(U64) => "w.uint64(\{n}, \{e})"
Scalar(F64) => "w.double_(\{n}, \{e})"
Scalar(F32) => "w.float_(\{n}, \{e})"
Message(_) => "w.message_(\{n}, @moonrpc.Message::encode(\{e}))"
Enum(_) => "w.enum_(\{n}, @moonrpc.Enum::code(\{e}))"
}
}
///|
/// The raw varint of a packed element `e` on writer `p`.
fn packed_write(base : Base, e : String) -> String {
match base {
Scalar(Boolean) => "p.write_varint(if \{e} { 1UL } else { 0UL })"
Scalar(I32) => "p.write_varint(\{e}.to_int64().reinterpret_as_uint64())"
Scalar(I64) => "p.write_varint(\{e}.reinterpret_as_uint64())"
Scalar(U32) => "p.write_varint(\{e}.to_uint64())"
Enum(_) =>
"p.write_varint(@moonrpc.Enum::code(\{e}).to_int64().reinterpret_as_uint64())"
_ => "p.write_varint(\{e})"
}
}
///|
/// The expression reading one value of this base off reader `r`.
fn read_call(base : Base, r : String) -> String {
match base {
Scalar(Str) => "\{r}.read_string()"
Scalar(Raw) => "\{r}.read_bytes()"
Scalar(Boolean) => "\{r}.read_bool()"
Scalar(I32) => "\{r}.read_int32()"
Scalar(I64) => "\{r}.read_int64()"
Scalar(U32) => "\{r}.read_uint32()"
Scalar(U64) => "\{r}.read_uint64()"
Scalar(F64) => "\{r}.read_double()"
Scalar(F32) => "\{r}.read_float()"
Message(_) => "@moonrpc.Message::decode(\{r}.read_len_delim())"
Enum(_) => "@moonrpc.Enum::of(\{r}.read_int32())"
}
}
///|
/// The value a field holds when the message does not carry it.
fn zero_of(base : Base) -> String {
match base {
Scalar(Str) => "\"\""
Scalar(Raw) => "b\"\""
Scalar(Boolean) => "false"
Scalar(I32) => "0"
Scalar(I64) => "0L"
Scalar(U32) => "0U"
Scalar(U64) => "0UL"
Scalar(F64) => "0.0"
Scalar(F32) => "(0.0 : Float)"
Message(_) => "@moonrpc.Message::decode(b\"\")"
Enum(_) => "@moonrpc.Enum::of(0)"
}
}
///|
/// The test that `e` differs from the value a field leaves unwritten; `None`
/// for a message, which is written whenever it is there.
fn nonzero(base : Base, e : String) -> String? {
match base {
Message(_) => None
Enum(_) => Some("@moonrpc.Enum::code(\{e}) != 0")
Scalar(_) => Some("\{e} != \{zero_of(base)}")
}
}
///|
/// `say_hello` as `SayHello`.
fn pascal(name : String) -> String {
let out = StringBuilder()
let mut up = true
for c in name {
if c == '_' {
up = true
} else {
out.write_char(if up { c.to_ascii_uppercase() } else { c })
up = false
}
}
out.to_string()
}
///|
/// `InProgress` as `IN_PROGRESS`, and `HTTPError` as `HTTP_ERROR`.
fn upper_snake(name : String) -> String {
let chars = name.to_array()
let out = StringBuilder()
for i, c in chars {
if i > 0 && c.is_ascii_uppercase() {
let before = chars[i - 1]
let after_lower = i + 1 < chars.length() &&
chars[i + 1].is_ascii_lowercase()
if before.is_ascii_lowercase() ||
before.is_ascii_digit() ||
(before.is_ascii_uppercase() && after_lower) {
out.write_char('_')
}
}
out.write_char(c.to_ascii_uppercase())
}
out.to_string()
}
///|
fn quoted(text : String) -> String {
@quote.string(text).to_string()
}