Skip to content

Commit

Permalink
pw_protobuf: Generate kValue-style aliases for enum cases
Browse files Browse the repository at this point in the history
To make pw_protobuf code more beautiful at the point of use, generate
aliases for enum cases that match the kValue style, with common prefixes
removed.

e.g. given

  enum State {
    STATE_UNKNOWN = 0;
    STATE_OFF = 1;
    STATE_ON = 2;
  };

in addition to the basic enum class generated:

  enum class State {
    STATE_UNKNOWN = 0,
    STATE_OFF = 1,
    STATE_ON = 2

it will also generate:

    kUnknown = STATE_UNKNOWN,
    kOff = STATE_OFF,
    kOn = STATE_ON
  };

Change-Id: Ifff30034cffce52cdee665320bc24a08fec72194
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/92042
Pigweed-Auto-Submit: Scott James Remnant <keybuk@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
  • Loading branch information
keybuk authored and CQ Bot Account committed Apr 29, 2022
1 parent 49098a4 commit f1d78b5
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 1 deletion.
15 changes: 15 additions & 0 deletions pw_protobuf/codegen_message_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,21 @@ TEST(CodegenMessage, WriteNestedForcedCallback) {
0);
}

TEST(CodegenMessage, EnumAliases) {
// Unprefixed enum.
EXPECT_EQ(Bool::kTrue, Bool::TRUE);
EXPECT_EQ(Bool::kFalse, Bool::FALSE);
EXPECT_EQ(Bool::kFileNotFound, Bool::FILE_NOT_FOUND);

// Prefixed enum has the prefix removed.
EXPECT_EQ(Error::kNone, Error::ERROR_NONE);
EXPECT_EQ(Error::kNotFound, Error::ERROR_NOT_FOUND);
EXPECT_EQ(Error::kUnknown, Error::ERROR_UNKNOWN);

// Single-value enum.
EXPECT_EQ(AlwaysBlue::kBlue, AlwaysBlue::BLUE);
}

class BreakableEncoder : public KeyValuePair::MemoryEncoder {
public:
constexpr BreakableEncoder(ByteSpan buffer)
Expand Down
41 changes: 40 additions & 1 deletion pw_protobuf/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ This results in the following generated structure:
enum class Customer::Status {
NEW = 1,
ACTIVE = 2,
INACTIVE = 3
INACTIVE = 3,

kNew = NEW,
kActive = ACTIVE,
kInactive = INACTIVE,
};

struct Customer::Message {
Expand Down Expand Up @@ -498,10 +502,45 @@ that can hold the set of values encoded by it, following these rules.
.. code:: c++

enum class Award::Service {
BRONZE = 1,
SILVER = 2,
GOLD = 3,

kBronze = BRONZE,
kSilver = SILVER,
kGold = GOLD,
};

struct Award::Message {
Award::Service service;
};

Aliases to the enum values are also included in the "constant" style to match
your preferred coding style. These aliases have any common prefix to the
enumeration values removed, such that:

.. code::
enum Activity {
ACTIVITY_CYCLING = 1;
ACTIVITY_RUNNING = 2;
ACTIVITY_SWIMMING = 3;
}
.. code:: c++

enum class Activity {
ACTIVITY_CYCLING = 1,
ACTIVITY_RUNNING = 2,
ACTIVITY_SWIMMING = 3,

kCycling = ACTIVITY_CYCLING,
kRunning = ACTIVITY_RUNNING,
kSwimming = ACTIVITY_SWIMMING,
};


* Nested messages are represented by their own ``struct Message`` provided that
a reference cycle does not exist.

Expand Down
10 changes: 10 additions & 0 deletions pw_protobuf/pw_protobuf_test_protos/full_test.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ enum Bool {
FILE_NOT_FOUND = 2;
}

// Prefixed enum
enum Error {
ERROR_NONE = 0;
ERROR_NOT_FOUND = 1;
ERROR_UNKNOWN = 2;
}

// Single-value enum
enum AlwaysBlue { BLUE = 0; }

// A message!
message Pigweed {
// Nested messages and enums.
Expand Down
40 changes: 40 additions & 0 deletions pw_protobuf/py/pw_protobuf/codegen_pwpb.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import enum
# Type ignore here for graphlib-backport on Python 3.8
from graphlib import CycleError, TopologicalSorter # type: ignore
from itertools import takewhile
import os
import sys
from typing import Dict, Iterable, List, Tuple
Expand Down Expand Up @@ -1815,16 +1816,55 @@ def define_not_in_class_methods(message: ProtoMessage, root: ProtoNode,
output.write_line('}')


def _common_value_prefix(proto_enum: ProtoEnum) -> str:
"""Calculate the common prefix of all enum values.
Given an enumeration:
enum Thing {
THING_ONE = 1;
THING_TWO = 2;
THING_THREE = 3;
}
If will return 'THING_', resulting in generated "style" aliases of
'kOne', 'kTwo', and 'kThree'.
The prefix is walked back to the last _, so that the enumeration:
enum Activity {
ACTIVITY_RUN = 1;
ACTIVITY_ROW = 2;
}
Returns 'ACTIVITY_' and not 'ACTIVITY_R'.
"""
if len(proto_enum.values()) <= 1:
return ''

common_prefix = "".join(
ch[0]
for ch in takewhile(lambda ch: all(ch[0] == c for c in ch),
zip(*[name for name, _ in proto_enum.values()])))
(left, under, _) = common_prefix.rpartition('_')
return left + under


def generate_code_for_enum(proto_enum: ProtoEnum, root: ProtoNode,
output: OutputFile) -> None:
"""Creates a C++ enum for a proto enum."""
assert proto_enum.type() == ProtoNode.Type.ENUM

common_prefix = _common_value_prefix(proto_enum)
output.write_line(f'enum class {proto_enum.cpp_namespace(root)} '
f': uint32_t {{')
with output.indent():
for name, number in proto_enum.values():
output.write_line(f'{name} = {number},')

style_name = 'k' + ProtoMessageField.upper_camel_case(
name[len(common_prefix):])
if style_name != name:
output.write_line(f'{style_name} = {name},')

output.write_line('};')


Expand Down

0 comments on commit f1d78b5

Please sign in to comment.