1
0

feat: update flag enum and enum underlying type decision rules for codegen

This commit is contained in:
2026-02-11 22:49:54 +08:00
parent 3310cac100
commit 4619cb5d1a
10 changed files with 211 additions and 64 deletions

View File

@@ -72,9 +72,13 @@ public class ClassidWalker extends CKDefinesParserBaseListener {
mLevel = 0; mLevel = 0;
mLevelStack = null; mLevelStack = null;
// classid is signed int and do not have flags feature. // update self
mCurrentEnum.mCanUnsigned = false; mCurrentEnum.updateByEntries();
mCurrentEnum.mUseFlags = false; // we forcely set classid is signed and do not have flags feature.
mCurrentEnum.mIsFlag = false;
mCurrentEnum.mIsUnsigned = false;
// and return
mResult = mCurrentEnum; mResult = mCurrentEnum;
mCurrentEnum = null; mCurrentEnum = null;
} }
@@ -90,6 +94,11 @@ public class ClassidWalker extends CKDefinesParserBaseListener {
mCurrentEntry.mEntryName = ctx.CKGENERIC_ID(0).getText(); mCurrentEntry.mEntryName = ctx.CKGENERIC_ID(0).getText();
mCurrentEntry.mEntryValue = ctx.CKGENERIC_NUM().getText(); mCurrentEntry.mEntryValue = ctx.CKGENERIC_NUM().getText();
// All classid number is positive.
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Positive;
// And all in ordinary number style so it doesn't have flag feature.
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.NotFlag;
// fill entry level info // fill entry level info
int this_level = getClassidLevel(ctx.getStart()); int this_level = getClassidLevel(ctx.getStart());
if (this_level > mLevel) { if (this_level > mLevel) {

View File

@@ -30,6 +30,9 @@ public class DefinesWalker extends CKDefinesParserBaseListener {
@Override @Override
public void exitProg(CKDefinesParser.ProgContext ctx) { public void exitProg(CKDefinesParser.ProgContext ctx) {
// update enum
mCurrentEnum.updateByEntries();
// and return
mResult = mCurrentEnum; mResult = mCurrentEnum;
mCurrentEnum = null; mCurrentEnum = null;
} }
@@ -48,19 +51,26 @@ public class DefinesWalker extends CKDefinesParserBaseListener {
if (ctx.CKGENERIC_NUM() == null) { if (ctx.CKGENERIC_NUM() == null) {
// define with id // define with id
mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID(1).getText(); mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID(1).getText();
// it refers other memeber, so its sign is unknown
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Unknown;
// it refers other memeber, so it may flag.
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MayFlag;
} else { } else {
// define with number // define with number
String num = ctx.CKGENERIC_NUM().getText(); String num = ctx.CKGENERIC_NUM().getText();
mCurrentEntry.mEntryValue = num; mCurrentEntry.mEntryValue = num;
// check whether this enum can be unsigned // check the sign of this number
if (CommonHelper.isNegativeNumber(num)) { if (CommonHelper.isNegativeNumber(num)) {
mCurrentEnum.mCanUnsigned = false; mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Negative;
} else {
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Positive;
} }
// if the number is in hex form, this enum MIGHT have flags feature // if the number is in hex form, it may belong to flag enum
if (CommonHelper.isHexNumber(num)) { if (CommonHelper.isHexNumber(num)) {
mCurrentEnum.mUseFlags = true; mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MayFlag;
} else {
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.NotFlag;
} }
} }

View File

@@ -1,6 +1,45 @@
import java.util.Vector; import java.util.Vector;
public class EnumsHelper { public class EnumsHelper {
/**
* The kind of enum entry value.
* This kind indicates whether this enum entry belong to a flag enum.
*/
public enum BEnumEntryFlagKind {
/**
* This enum entry can not belong to a flag enum.
* Because its value is ordinary.
*/
NotFlag,
/**
* This enum entry may belong to a flag enum.
* Because its value is in HEX format, and refering other members.
*/
MayFlag,
/**
* This enum entry must belong to a flag enum.
* Because its value use bitwise operation.
*/
MustFlag,
}
/**
* The kind of enum entry value.
* This kind indicates the sign of this enum entry value.
*/
public enum BEnumEntrySignKind {
/** The value of this enum entry is positive number or zero. */
Positive,
/** The value of this enum entry is negative. */
Negative,
/**
* The value of this enum entry is unknown.
* This is may be caused by that it refer other memeber.
*/
Unknown,
}
/** /**
* The struct to describe the entry of an enum. * The struct to describe the entry of an enum.
*/ */
@@ -8,6 +47,8 @@ public class EnumsHelper {
public BEnumEntry() { public BEnumEntry() {
mEntryName = null; mEntryName = null;
mEntryValue = null; mEntryValue = null;
mEntryFlagKind = null;
mEntrySignKind = null;
mEntryComment = null; mEntryComment = null;
} }
@@ -15,6 +56,10 @@ public class EnumsHelper {
public String mEntryName; public String mEntryName;
/** The value of this entry. null if this entry do not have explicit value. */ /** The value of this entry. null if this entry do not have explicit value. */
public String mEntryValue; public String mEntryValue;
/** The flag kind of this entry value. */
public BEnumEntryFlagKind mEntryFlagKind;
/** The sign kind of this entry value. */
public BEnumEntrySignKind mEntrySignKind;
/** The comment of this entry. null if no comment. */ /** The comment of this entry. null if no comment. */
public String mEntryComment; public String mEntryComment;
} }
@@ -44,8 +89,8 @@ public class EnumsHelper {
public BEnum() { public BEnum() {
mEnumName = null; mEnumName = null;
mEnumComment = null; mEnumComment = null;
mCanUnsigned = true; mIsUnsigned = true;
mUseFlags = false; mIsFlag = false;
mEntries = new Vector<BEnumEntry>(); mEntries = new Vector<BEnumEntry>();
} }
@@ -53,12 +98,48 @@ public class EnumsHelper {
public String mEnumName; public String mEnumName;
/** The comment of this enum. null if no comment. */ /** The comment of this enum. null if no comment. */
public String mEnumComment; public String mEnumComment;
/** True if this enum can use unsigned integer as its underlying type. */ /** True if this enum should use unsigned integer as its underlying type, otherwise false. */
public boolean mCanUnsigned; public boolean mIsUnsigned;
/** True if this enum will use flags feature (supporting OR, AND, operators). */ /** True if this enum shoule have flags feature (supporting OR, AND, operators), otherwise false. */
public boolean mUseFlags; public boolean mIsFlag;
/** The list to store entries of this enum. */ /** The list to store entries of this enum. */
public Vector<BEnumEntry> mEntries; public Vector<BEnumEntry> mEntries;
/**
* Update some properties located in this class according to existing entries.
*/
public void updateByEntries() {
// If there is at least one negative entry, the enum should be signed,
// Otherwise, it is unsigned.
// For unknown entries, ignore them.
boolean has_negative = false;
for (BEnumEntry entry : this.mEntries) {
if (entry.mEntrySignKind == BEnumEntrySignKind.Negative) {
has_negative = true;
}
}
this.mIsUnsigned = !has_negative;
// For flag kind, if there is "Must Flag" entry, the enum should be a flag enum.
// Then, if "May Flag" entry is more than "Not Flag", the enum would be a flag enum.
// Otherwise, it is not flag.
boolean has_must_flag = false;
int cnt_may_flag = 0, cnt_not_flag = 0;
for (BEnumEntry entry : this.mEntries) {
switch (entry.mEntryFlagKind) {
case NotFlag -> ++cnt_not_flag;
case MayFlag -> ++cnt_may_flag;
case MustFlag -> has_must_flag = true;
}
}
if (has_must_flag) {
this.mIsFlag = true;
} else if (cnt_may_flag > cnt_not_flag) {
this.mIsFlag = true;
} else {
this.mIsFlag = false;
}
}
} }
/** /**

View File

@@ -59,6 +59,8 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
List<TerminalNode> allNames = ctx.CKGENERIC_ID(); List<TerminalNode> allNames = ctx.CKGENERIC_ID();
mCurrentEnum.mEnumName = allNames.get(allNames.size() - 1).getText(); mCurrentEnum.mEnumName = allNames.get(allNames.size() - 1).getText();
// update self and add into list
mCurrentEnum.updateByEntries();
mCurrentProg.mEnums.add(mCurrentEnum); mCurrentProg.mEnums.add(mCurrentEnum);
mCurrentEnum = null; mCurrentEnum = null;
} }
@@ -75,6 +77,14 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
// get entry name // get entry name
mCurrentEntry.mEntryName = ctx.CKGENERIC_ID().getText(); mCurrentEntry.mEntryName = ctx.CKGENERIC_ID().getText();
// if its value is null, we manually fill 2 kinds
if (mCurrentEntry.mEntryValue == null) {
// the sign kind is unknown because it relys on other value (+1)
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Unknown;
// because it just adds one from previous member, it should not belong to a flag enum
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.NotFlag;
}
mCurrentEnum.mEntries.add(mCurrentEntry); mCurrentEnum.mEntries.add(mCurrentEntry);
mCurrentEntry = null; mCurrentEntry = null;
} }
@@ -85,34 +95,42 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
List<TerminalNode> nums = ctx.CKGENERIC_NUM(); List<TerminalNode> nums = ctx.CKGENERIC_NUM();
switch (nums.size()) { switch (nums.size()) {
case 1: { case 1: {
// set value // value is immediate number
TerminalNode node = nums.get(0); TerminalNode node = nums.get(0);
mCurrentEntry.mEntryValue = node.getText(); String num = node.getText();
mCurrentEntry.mEntryValue = num;
// check whether this enum can be unsigned // check whether this enum can be unsigned
if (CommonHelper.isNegativeNumber(node.getText())) { if (CommonHelper.isNegativeNumber(num)) {
mCurrentEnum.mCanUnsigned = false; mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Negative;
} else {
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Positive;
}
// if the number is in hex form, this entry may belong to flag enum
if (CommonHelper.isHexNumber(num)) {
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MayFlag;
} else {
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.NotFlag;
}
break;
} }
// if the number is in hex form, this enum MIGHT have flags feature case 2: {
if (CommonHelper.isHexNumber(node.getText())) { // value is bitwise operation
mCurrentEnum.mUseFlags = true; TerminalNode num = nums.get(0), offset = nums.get(1);
mCurrentEntry.mEntryValue = String.format("%s << %s", num.getText(), offset.getText());
// << operator appears.
// it shoud be unsigned.
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Positive;
// and it must belong to flag enum
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MustFlag;
break;
} }
default:
break; throw new IllegalArgumentException("Unexpected value: " + nums.size());
}
case 2: {
// set value
TerminalNode num = nums.get(0), offset = nums.get(1);
mCurrentEntry.mEntryValue = String.format("%s << %s", num.getText(), offset.getText());
// << operator appears. this enum must have flags feature
mCurrentEnum.mUseFlags = true;
break;
}
default:
throw new IllegalArgumentException("Unexpected value: " + nums.size());
} }
} }
@@ -123,9 +141,19 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID().stream().map(value -> value.getText()) mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID().stream().map(value -> value.getText())
.collect(Collectors.joining(" | ")); .collect(Collectors.joining(" | "));
// | operator appears. this enum must have flags feature if (ctx.CKGENERIC_ID().size() > 1) {
mCurrentEnum.mUseFlags = true; // If there is more than one ID, it means | operator appears.
// It should be unsigned.
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Positive;
// And it must belong to flag enum.
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MustFlag;
} else {
// Otherwise it just refer other member.
// The sign of its value is unclear.
mCurrentEntry.mEntrySignKind = EnumsHelper.BEnumEntrySignKind.Unknown;
// And it may belong to flag enum because it refers other memeber.
mCurrentEntry.mEntryFlagKind = EnumsHelper.BEnumEntryFlagKind.MayFlag;
}
} }
} }

View File

@@ -6,10 +6,28 @@ import com.google.gson.GsonBuilder;
public class JsonWriter { public class JsonWriter {
private static String writeBEnumEntryFlagKind(EnumsHelper.BEnumEntryFlagKind kind) {
return switch (kind) {
case NotFlag -> "not-flag";
case MayFlag -> "may-flag";
case MustFlag -> "must-flag";
};
}
private static String writeBEnumEntrySignKind(EnumsHelper.BEnumEntrySignKind kind) {
return switch (kind) {
case Positive -> "positive";
case Negative -> "negative";
case Unknown -> "unknown";
};
}
private static JsonObject writeBEnumEntry(EnumsHelper.BEnumEntry enumEntry) { private static JsonObject writeBEnumEntry(EnumsHelper.BEnumEntry enumEntry) {
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
data.addProperty("name", enumEntry.mEntryName); data.addProperty("name", enumEntry.mEntryName);
data.addProperty("value", enumEntry.mEntryValue); data.addProperty("value", enumEntry.mEntryValue);
data.addProperty("flag_kind", writeBEnumEntryFlagKind(enumEntry.mEntryFlagKind));
data.addProperty("sign_kind", writeBEnumEntrySignKind(enumEntry.mEntrySignKind));
data.addProperty("comment", enumEntry.mEntryComment); data.addProperty("comment", enumEntry.mEntryComment);
// Export hierarchy if possible // Export hierarchy if possible
@@ -30,9 +48,8 @@ public class JsonWriter {
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
data.addProperty("name", benum.mEnumName); data.addProperty("name", benum.mEnumName);
data.addProperty("comment", benum.mEnumComment); data.addProperty("comment", benum.mEnumComment);
data.addProperty("can_unsigned", benum.mCanUnsigned); data.addProperty("is_unsigned", benum.mIsUnsigned);
data.addProperty("use_flags", benum.mUseFlags); data.addProperty("is_flag", benum.mIsFlag);
data.addProperty("use_flags", benum.mUseFlags);
JsonArray entries = new JsonArray(); JsonArray entries = new JsonArray();
for (EnumsHelper.BEnumEntry enumEntry : benum.mEntries) { for (EnumsHelper.BEnumEntry enumEntry : benum.mEntries) {

View File

@@ -87,9 +87,9 @@ class BEnum:
"""The name of this enum.""" """The name of this enum."""
__enum_comment: str | None __enum_comment: str | None
"""The comment of this enum. None if no comment.""" """The comment of this enum. None if no comment."""
__can_unsigned: bool __is_unsigned: bool
"""True if this enum can use unsigned integer as its underlying type.""" """True if this enum can use unsigned integer as its underlying type."""
__use_flags: bool __is_flag: bool
"""True if this enum will use flags feature (supporting OR, AND, operators).""" """True if this enum will use flags feature (supporting OR, AND, operators)."""
__entries: list[BEnumEntry] __entries: list[BEnumEntry]
"""The list to store entries of this enum.""" """The list to store entries of this enum."""
@@ -101,14 +101,14 @@ class BEnum:
self, self,
enum_name: str, enum_name: str,
enum_comment: str | None, enum_comment: str | None,
can_unsigned: bool, is_unsigned: bool,
use_flags: bool, is_flag: bool,
entries: list[BEnumEntry], entries: list[BEnumEntry],
): ):
self.__enum_name = enum_name self.__enum_name = enum_name
self.__enum_comment = enum_comment self.__enum_comment = enum_comment
self.__can_unsigned = can_unsigned self.__is_unsigned = is_unsigned
self.__use_flags = use_flags self.__is_flag = is_flag
self.__entries = entries self.__entries = entries
self.__entries_map = {e.get_entry_name(): e for e in entries} self.__entries_map = {e.get_entry_name(): e for e in entries}
@@ -120,13 +120,13 @@ class BEnum:
"""Get the comment of this enum. None if no comment.""" """Get the comment of this enum. None if no comment."""
return self.__enum_comment return self.__enum_comment
def get_can_unsigned(self) -> bool: def is_unsigned(self) -> bool:
"""True if this enum can use unsigned integer as its underlying type.""" """True if this enum can use unsigned integer as its underlying type."""
return self.__can_unsigned return self.__is_unsigned
def get_use_flags(self) -> bool: def is_flag(self) -> bool:
"""True if this enum will use flags feature (supporting OR, AND, operators).""" """True if this enum will use flags feature (supporting OR, AND, operators)."""
return self.__use_flags return self.__is_flag
def iter_entries(self) -> typing.Iterator[BEnumEntry]: def iter_entries(self) -> typing.Iterator[BEnumEntry]:
"""Get the iterator of entries of this enum.""" """Get the iterator of entries of this enum."""
@@ -140,8 +140,8 @@ class BEnum:
return BEnum( return BEnum(
data["name"], data["name"],
data.get("comment", None), data.get("comment", None),
data["can_unsigned"], data["is_unsigned"],
data["use_flags"], data["is_flag"],
list(map(lambda i: BEnum.__create_entry_by_content(i), data["entries"])), list(map(lambda i: BEnum.__create_entry_by_content(i), data["entries"])),
) )

View File

@@ -11,6 +11,7 @@ def main():
render.render_cpp_enum("CKERROR.hpp", ckerror) render.render_cpp_enum("CKERROR.hpp", ckerror)
render.render_py_enum("CKERROR.py", ckerror) render.render_py_enum("CKERROR.py", ckerror)
render.render_cs_enum("CKERROR.cs", ckerror) render.render_cs_enum("CKERROR.cs", ckerror)
render.render_rs_enum("CKERROR.rs", ckerror)
render.render_cpp_ckerror_docstring("CKERROR.docstring.hpp", "CKERROR.docstring.cpp", ckerror) render.render_cpp_ckerror_docstring("CKERROR.docstring.hpp", "CKERROR.docstring.cpp", ckerror)
render.render_py_enum_docstring("CKERROR.docstring.py", ckerror) render.render_py_enum_docstring("CKERROR.docstring.py", ckerror)
render.render_cs_enum_docstring("CKERROR.docstring.cs", ckerror) render.render_cs_enum_docstring("CKERROR.docstring.cs", ckerror)

View File

@@ -1,9 +1,9 @@
{%- for benum in payload.iter_enums() %} {%- for benum in payload.iter_enums() %}
{%- if benum.get_enum_comment() is not none %} {%- if benum.get_enum_comment() is not none %}
{{ benum.get_enum_comment() | block_comment('/// ') }} {{ benum.get_enum_comment() | block_comment('/// ') }}
{%- endif %} {%- if benum.get_use_flags() %} {%- endif %} {%- if benum.is_flag() %}
[Flags]{%- endif %} [Flags]{%- endif %}
public enum {{ benum.get_enum_name() }} : {% if benum.get_can_unsigned() -%} uint {%- else -%} int {%- endif %} { public enum {{ benum.get_enum_name() }} : {% if benum.is_unsigned() -%} uint {%- else -%} int {%- endif %} {
{%- for entry in benum.iter_entries() %} {%- for entry in benum.iter_entries() %}
{%- if entry.get_entry_comment() is not none %} {%- if entry.get_entry_comment() is not none %}
/// <summary> /// <summary>

View File

@@ -4,7 +4,7 @@
{{ benum.get_enum_comment() | block_comment(' * ') }} {{ benum.get_enum_comment() | block_comment(' * ') }}
*/ */
{%- endif %} {%- endif %}
enum class {{ benum.get_enum_name() }} : {% if benum.get_can_unsigned() -%} CKDWORD {%- else -%} CKINT {%- endif %} { enum class {{ benum.get_enum_name() }} : {% if benum.is_unsigned() -%} CKDWORD {%- else -%} CKINT {%- endif %} {
{%- for entry in benum.iter_entries() %} {%- for entry in benum.iter_entries() %}
{{ entry.get_entry_name() }} {%- if entry.get_entry_value() is not none %} = {{ entry.get_entry_value() }} {%- endif %}, {%- if entry.get_entry_comment() is not none %} /**< {{ entry.get_entry_comment() | line_comment }} */ {%- endif %} {{ entry.get_entry_name() }} {%- if entry.get_entry_value() is not none %} = {{ entry.get_entry_value() }} {%- endif %}, {%- if entry.get_entry_comment() is not none %} /**< {{ entry.get_entry_comment() | line_comment }} */ {%- endif %}
{%- endfor %} {%- endfor %}

View File

@@ -2,9 +2,10 @@
{%- if benum.get_enum_comment() is not none %} {%- if benum.get_enum_comment() is not none %}
{{ benum.get_enum_comment() | block_comment('/// ') }} {{ benum.get_enum_comment() | block_comment('/// ') }}
{%- endif %} {%- endif %}
{%- if not benum.get_use_flags() %} {%- if not benum.is_flag() %}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr({% if benum.get_can_unsigned() -%} u32 {%- else -%} i32 {%- endif %})] #[repr({% if benum.is_unsigned() -%} u32 {%- else -%} i32 {%- endif %})]
pub enum {{ benum.get_enum_name() }} { pub enum {{ benum.get_enum_name() }} {
{%- for entry in benum.iter_entries() %} {%- for entry in benum.iter_entries() %}
{%- if entry.get_entry_comment() is not none %} {%- if entry.get_entry_comment() is not none %}
@@ -16,10 +17,10 @@ pub enum {{ benum.get_enum_name() }} {
{%- else %} {%- else %}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)] #[repr(transparent)]
pub struct {{ benum.get_enum_name() }}({% if benum.get_can_unsigned() -%} u32 {%- else -%} i32 {%- endif %}); pub struct {{ benum.get_enum_name() }}({% if benum.is_unsigned() -%} u32 {%- else -%} i32 {%- endif %});
bitflags! { bitflags! {
impl {{ benum.get_enum_name() }}: {% if benum.get_can_unsigned() -%} u32 {%- else -%} i32 {%- endif %} { impl {{ benum.get_enum_name() }}: {% if benum.is_unsigned() -%} u32 {%- else -%} i32 {%- endif %} {
{%- for entry in benum.iter_entries() %} {%- for entry in benum.iter_entries() %}
{%- if entry.get_entry_comment() is not none %} {%- if entry.get_entry_comment() is not none %}
/// {{ entry.get_entry_comment() | line_comment }} /// {{ entry.get_entry_comment() | line_comment }}