1
0

56 Commits

Author SHA1 Message Date
10d5d8f002 feat: add new rule in BMapInspector 2026-02-05 14:38:25 +08:00
d3af894d2f feat: add target camera in BMapInspector 2026-02-05 14:32:49 +08:00
1eb9d3f805 feat: add camera support for BMap 2026-02-05 14:12:16 +08:00
11abbe2c35 feat: add new rule for BMapInspector 2026-02-04 22:51:13 +08:00
2240f55964 feat: add new rule in BMapInspector 2026-02-04 21:32:34 +08:00
7b40c64470 feat: add more rules in BMapInspector 2026-02-04 20:46:04 +08:00
58ee7accff feat: add lost ironpad and console color enable for BMapInspector. 2026-02-04 17:11:52 +08:00
c11220d54b feat: finish one rule in BMapInspector.
- finish one rule in BMapInspector.
- fix CKObjectManager find object by name feature.
2026-02-04 17:03:53 +08:00
e6e714f2c9 feat: add note for disabled function in CKCamera 2026-02-03 19:48:45 +08:00
4985c6d3d0 fix: fix github action build error 2026-02-03 19:21:18 +08:00
37904fd5a4 fix: fix github action build error 2026-02-03 16:11:11 +08:00
a654370b82 chore: add lost PIC flag 2026-02-03 15:56:01 +08:00
90fe7ddcaf chore: use new github action build layout 2026-02-03 15:49:47 +08:00
b06bd587f6 feat: add detailed rule in BMapInspector 2026-02-02 22:26:41 +08:00
ebbea473a4 feat: finish BMapInspector framework 2026-02-02 14:17:31 +08:00
def46d1b8f feat: move all print work into main file in BMapInspector 2026-01-31 23:38:43 +08:00
c664eaba0e feat: update cli for BMapInspector 2026-01-31 23:16:50 +08:00
8f5cc51de4 feat: add cli support for BMapInspector 2026-01-31 11:28:03 +08:00
103cb496a2 feat: update BMapInspector 2026-01-30 20:40:21 +08:00
8dfa4bd039 feat: initialize BMapInspector project 2026-01-30 20:23:39 +08:00
6b0d73177b chore: write github action 2026-01-30 16:12:39 +08:00
2f59e16590 feat: change project layout for new added project 2026-01-30 15:23:01 +08:00
2b9c0296d1 refactor: fix BMap build issue.
- fix BMap build issue with new YYCC.
- rename most "General" into "Generic".
- remove useless code.
2026-01-30 14:38:03 +08:00
333ff0ab17 fix: fix unvirt build issue 2026-01-29 21:03:09 +08:00
6f10f96f97 refactor: finish unvirt context refactor 2026-01-29 20:41:20 +08:00
07d180f2cb refactor: finish CmdHelper refactor in Unvirt 2026-01-29 16:46:26 +08:00
ada432fbe7 fix: finish StructFmt refactor in Unvirt 2026-01-29 13:30:27 +08:00
ca4fab4612 fix: fix half struct fmt in unvirt 2026-01-29 11:09:07 +08:00
307676f9c8 refactor: finish unvirt docstring refactor 2026-01-28 21:54:44 +08:00
e53195fa1d refactor: finish migration for libcmo self 2026-01-28 20:05:57 +08:00
52ad4cec99 refactor: rename all general to generic in EnumsAnalyzer 2026-01-28 16:35:42 +08:00
29d98edbdc refactor: basically finish ExpFctsRender 2026-01-28 16:19:59 +08:00
69ac25a70b refactor: finish loading in ExpFctsRender 2026-01-28 13:50:59 +08:00
f5645a06de refactor: finish refactor ExpFctsAnalyzer in BMapBinder 2026-01-28 12:00:40 +08:00
0202266ce5 refactor: finish expfcts extractor refactor 2026-01-27 21:33:19 +08:00
3152d7dd52 refactor: change bmap binding generator name, layout for future refactor 2026-01-27 20:58:29 +08:00
f601782370 fix: finish enums migration
confirm project works as expected comparing before-refactor one.
2026-01-27 17:23:58 +08:00
0419dc3939 fix: finish enums migration 2026-01-27 16:38:29 +08:00
9cb4d50f22 refactor: refactor enum migration but not finished 2026-01-26 22:52:56 +08:00
c68bdce37b refactor: refactor EnumsMigration but not finished 2026-01-26 11:11:58 +08:00
e0e5c9b090 doc: add dev notes for java, antlr and python 2026-01-25 22:53:47 +08:00
fd591f8a62 fix: fix misc naming issue 2026-01-25 21:07:55 +08:00
a79d09a66c refactor: finish all yycc adaption for ck2 except CKContext 2026-01-25 21:03:43 +08:00
97b33b131a refactor: adapt new yycc for ck2 module kernel except CKContext 2026-01-25 20:17:23 +08:00
9319425237 fix: fix VxMath part 2026-01-25 11:35:01 +08:00
09ca976fd9 refactor: refactor VectorGen
- use uv to manage VectorGen
- use std::partial_ordering to replace auto in template because auto is not works for seperated implementation and declaration.
- rename YYCC macro due to the upgrade of YYCC.
2026-01-25 10:37:19 +08:00
5477072d70 refactor: write readme and change layout 2026-01-24 22:43:29 +08:00
940ffeecf2 refactor: re-organize the layout of asset directory and write some readme 2026-01-24 22:38:32 +08:00
440bc63432 refactor: refactor debugging tools 2026-01-24 22:26:49 +08:00
f7acb3bfa9 chore: update doc build 2026-01-24 21:25:10 +08:00
43984685bc refactor: remove build script 2026-01-24 20:13:23 +08:00
c2dafab217 refactor: change repo layout 2026-01-24 19:46:23 +08:00
34de35dd31 fix: fix XContainer 2026-01-24 17:55:57 +08:00
ff2600c8fb fix: fix libcom top headers 2026-01-24 17:49:59 +08:00
9228f343ff chore: change build script to make BMap can be used by CMake
- change script for installing BMap like LibCmo although no one will use it.
- move package install command into respective cmake script.
- change BMap project layout
2026-01-24 17:32:22 +08:00
f9ab66dfc2 chore: update build script
- change project layout for better understanding.
- update build script for more close to standard cmake way.
2026-01-24 17:16:13 +08:00
314 changed files with 9097 additions and 6992 deletions

317
.clang-format Normal file
View File

@@ -0,0 +1,317 @@
# yaml-language-server: $schema=https://json.schemastore.org/clang-format.json
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseArrows: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseExpressionOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
BreakTemplateDeclarations: Yes
ColumnLimit: 140
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- forever
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<Q.*'
Priority: 200
SortPriority: 200
CaseSensitive: true
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: false
AtStartOfFile: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MainIncludeChar: Quote
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 150
PenaltyBreakBeforeFirstCallParameter: 300
PenaltyBreakComment: 500
PenaltyBreakFirstLessLess: 400
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 600
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 50
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: Lexicographic
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
ExceptDoubleParentheses: false
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
- emit
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
- Q_CLASSINFO
- Q_ENUM
- Q_ENUM_NS
- Q_FLAG
- Q_FLAG_NS
- Q_GADGET
- Q_GADGET_EXPORT
- Q_INTERFACES
- Q_LOGGING_CATEGORY
- Q_MOC_INCLUDE
- Q_NAMESPACE
- Q_NAMESPACE_EXPORT
- Q_OBJECT
- Q_PROPERTY
- Q_REVISION
- Q_DISABLE_COPY
- Q_DISABLE_COPY_MOVE
- Q_SET_OBJECT_NAME
- QT_BEGIN_NAMESPACE
- QT_END_NAMESPACE
- QML_ADDED_IN_MINOR_VERSION
- QML_ANONYMOUS
- QML_ATTACHED
- QML_DECLARE_TYPE
- QML_DECLARE_TYPEINFO
- QML_ELEMENT
- QML_EXTENDED
- QML_EXTENDED_NAMESPACE
- QML_EXTRA_VERSION
- QML_FOREIGN
- QML_FOREIGN_NAMESPACE
- QML_IMPLEMENTS_INTERFACES
- QML_INTERFACE
- QML_NAMED_ELEMENT
- QML_REMOVED_IN_MINOR_VERSION
- QML_SINGLETON
- QML_UNAVAILABLE
- QML_UNCREATABLE
- QML_VALUE_TYPE
- YYCC_DELETE_COPY
- YYCC_DELETE_MOVE
- YYCC_DELETE_COPY_MOVE
- YYCC_DEFAULT_COPY
- YYCC_DEFAULT_MOVE
- YYCC_DEFAULT_COPY_MOVE
- YYCC_DECL_COPY
- YYCC_DECL_MOVE
- YYCC_DECL_COPY_MOVE
TableGenBreakInsideDAGArg: DontBreak
TabWidth: 4
UseTab: ForIndentation
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

4
.github/scripts/README.md vendored Normal file
View File

@@ -0,0 +1,4 @@
# GitHub Scripts
These script files should be only used by GitHub Action.
These script files should be executed at the root directory of each project respectively.

19
.github/scripts/linux.sh vendored Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail
# Create build directory and enter it
mkdir bin
cd bin
# Create internal build and install directory
mkdir build
mkdir install
# Build in Release mode
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DNEMO_BUILD_UNVIRT=ON -DNEMO_BUILD_BALLANCE=ON -DNEMO_BUILD_BMAP=ON -DNEMO_BUILD_BMAPINSPECTOR=ON -DYYCCommonplace_ROOT=$YYCCommonplace_ROOT -DSTB_ROOT=$STB_ROOT -DZLIB_ROOT=$ZLIB_ROOT ../..
cmake --build .
cmake --install . --prefix=../install
cd ..
# Back to root directory
cd ..

19
.github/scripts/macos.sh vendored Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail
# Create build directory and enter it
mkdir bin
cd bin
# Create internal build and install directory
mkdir build
mkdir install
# Build in Release mode
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DNEMO_BUILD_UNVIRT=ON -DNEMO_BUILD_BALLANCE=ON -DNEMO_BUILD_BMAP=ON -DNEMO_BUILD_BMAPINSPECTOR=ON -DYYCCommonplace_ROOT=$YYCCommonplace_ROOT -DSTB_ROOT=$STB_ROOT -DZLIB_ROOT=$ZLIB_ROOT ../..
cmake --build .
cmake --install . --prefix=../install
cd ..
# Back to root directory
cd ..

5
.github/scripts/stb/linux.sh vendored Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
set -euo pipefail
# Just directly record self as root directory
export STB_ROOT=$(pwd)

5
.github/scripts/stb/macos.sh vendored Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
set -euo pipefail
# Just directly record self as root directory
export STB_ROOT=$(pwd)

4
.github/scripts/stb/windows.bat vendored Normal file
View File

@@ -0,0 +1,4 @@
@ECHO OFF
:: Just directly record self as root directory
SET STB_ROOT=%CD%

18
.github/scripts/windows.bat vendored Normal file
View File

@@ -0,0 +1,18 @@
@ECHO OFF
:: Create build directory and enter it
MKDIR bin
CD bin
:: Create internal build and install directory
MKDIR build
MKDIR install
:: Build with x64 architecture in Release mode
CD build
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -DNEMO_BUILD_UNVIRT=ON -DNEMO_BUILD_BALLANCE=ON -DNEMO_BUILD_BMAP=ON -DNEMO_BUILD_BMAPINSPECTOR=ON -DYYCCommonplace_ROOT=%YYCCommonplace_ROOT% -DSTB_ROOT=%STB_ROOT% -DZLIB_ROOT=%ZLIB_ROOT% ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Release
CD ..
:: Back to root directory
CD ..

24
.github/scripts/yycc/linux.sh vendored Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
# Create build directory and enter it
mkdir bin
cd bin
# Create internal build and install directory
mkdir build
mkdir install
# Build in Release mode
cd build
cmake -DCMAKE_CXX_STANDARD=23 -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=True ../..
cmake --build .
cmake --install . --prefix=../install
cd ..
# Record install directory
cd install
export YYCCommonplace_ROOT=$(pwd)
cd ..
# Back to root directory
cd ..

24
.github/scripts/yycc/macos.sh vendored Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
# Create build directory and enter it
mkdir bin
cd bin
# Create internal build and install directory
mkdir build
mkdir install
# Build in Release mode
cd build
cmake -DCMAKE_CXX_STANDARD=23 -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=True ../..
cmake --build .
cmake --install . --prefix=../install
cd ..
# Record install directory
cd install
export YYCCommonplace_ROOT=$(pwd)
cd ..
# Back to root directory
cd ..

23
.github/scripts/yycc/windows.bat vendored Normal file
View File

@@ -0,0 +1,23 @@
@ECHO OFF
:: Create build directory and enter it
MKDIR bin
CD bin
:: Create internal build and install directory
MKDIR build
MKDIR install
:: Build with x64 architecture in Release mode
CD build
cmake -A x64 -DCMAKE_CXX_STANDARD=23 ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Release
CD ..
:: Record install directory
CD install
SET YYCCommonplace_ROOT=%CD%
CD ..
:: Back to root directory
CD ..

18
.github/scripts/zlib/linux.sh vendored Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail
# Create build and install directory
mkdir build
mkdir install
# Record install directory first because build step require it
cd install
export ZLIB_ROOT=$(pwd)
cd ..
# Build in Release mode
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DZLIB_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=$ZLIB_ROOT ..
cmake --build .
cmake --install .
cd ..

18
.github/scripts/zlib/macos.sh vendored Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail
# Create build and install directory
mkdir build
mkdir install
# Record install directory first because build step require it
cd install
export ZLIB_ROOT=$(pwd)
cd ..
# Build in Release mode
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DZLIB_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=$ZLIB_ROOT ..
cmake --build .
cmake --install .
cd ..

17
.github/scripts/zlib/windows.bat vendored Normal file
View File

@@ -0,0 +1,17 @@
@ECHO OFF
:: Create build and install directory
MKDIR build
MKDIR install
:: Record install directory first because build step require it
CD install
SET ZLIB_ROOT=%CD%
CD ..
:: Build with x64 architecture in Release mode
CD build
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -DZLIB_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=%ZLIB_ROOT% ..
cmake --build . --config Release
cmake --install . --config Release
CD ..

71
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
name: LibCmo Linux Build
on: [workflow_dispatch]
jobs:
linux-build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Dependencies
shell: bash
run: |
sudo apt update
sudo apt install -y build-essential cmake git
- name: Fetch YYCCommonplace
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: bash
run: |
cd extern/YYCCommonplace
source ../../.github/scripts/yycc/linux.sh
echo "YYCCommonplace_ROOT=$YYCCommonplace_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Fetch ZLIB
uses: actions/checkout@v4
with:
repository: 'madler/zlib'
ref: 'v1.3.1'
path: 'extern/zlib'
- name: Build ZLIB
shell: bash
run: |
cd extern/zlib
source ../../.github/scripts/zlib/linux.sh
echo "ZLIB_ROOT=$ZLIB_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Fetch STB
uses: actions/checkout@v4
with:
repository: 'nothings/stb'
ref: '2e2bef463a5b53ddf8bb788e25da6b8506314c08'
path: 'extern/stb'
- name: Build STB
shell: bash
run: |
cd extern/stb
source ../../.github/scripts/stb/linux.sh
echo "STB_ROOT=$STB_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Build LibCmo
shell: bash
run: |
source ./.github/scripts/linux.sh
- name: Upload Built Artifact
uses: actions/upload-artifact@v4
with:
name: LibCmo-linux-build
path: bin/install/*
retention-days: 30
- name: Upload Built Dependencies
uses: actions/upload-artifact@v4
with:
name: LibCmo-linux-dep
path: extern/zlib/install/*
retention-days: 30

66
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: LibCmo macOS Build
on: [workflow_dispatch]
jobs:
macos-build:
runs-on: macos-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Fetch YYCCommonplace
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: bash
run: |
cd extern/YYCCommonplace
source ../../.github/scripts/yycc/macos.sh
echo "YYCCommonplace_ROOT=$YYCCommonplace_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Fetch ZLIB
uses: actions/checkout@v4
with:
repository: 'madler/zlib'
ref: 'v1.3.1'
path: 'extern/zlib'
- name: Build ZLIB
shell: bash
run: |
cd extern/zlib
source ../../.github/scripts/zlib/macos.sh
echo "ZLIB_ROOT=$ZLIB_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Fetch STB
uses: actions/checkout@v4
with:
repository: 'nothings/stb'
ref: '2e2bef463a5b53ddf8bb788e25da6b8506314c08'
path: 'extern/stb'
- name: Build STB
shell: bash
run: |
cd extern/stb
source ../../.github/scripts/stb/macos.sh
echo "STB_ROOT=$STB_ROOT" >> "$GITHUB_ENV"
cd ../..
- name: Build LibCmo
shell: bash
run: |
source ./.github/scripts/macos.sh
- name: Upload Built Artifact
uses: actions/upload-artifact@v4
with:
name: LibCmo-macos-build
path: bin/install/*
retention-days: 30
- name: Upload Built Dependencies
uses: actions/upload-artifact@v4
with:
name: LibCmo-macos-dep
path: extern/zlib/install/*
retention-days: 30

82
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: LibCmo Windows Build
on: [workflow_dispatch]
jobs:
windows-build:
strategy:
matrix:
include:
- vs: '2022'
msvc_arch: 'x64'
runs-on: windows-2022
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Fetch YYCCommonplace
uses: actions/checkout@v4
with:
repository: 'yyc12345/YYCCommonplace'
ref: 'master'
path: 'extern/YYCCommonplace'
- name: Build YYCCommonplace
shell: cmd
run: |
CD extern\YYCCommonplace
CALL ..\..\.github\scripts\yycc\windows.bat
ECHO SET YYCCommonplace_ROOT=%YYCCommonplace_ROOT% > ..\envs.bat
CD ..\..
- name: Fetch ZLIB
uses: actions/checkout@v4
with:
repository: 'madler/zlib'
ref: 'v1.3.1'
path: 'extern/zlib'
- name: Build ZLIB
shell: cmd
run: |
CD extern\zlib
CALL ..\..\.github\scripts\zlib\windows.bat
ECHO SET ZLIB_ROOT=%ZLIB_ROOT% >> ..\envs.bat
CD ..\..
- name: Fetch STB
uses: actions/checkout@v4
with:
repository: 'nothings/stb'
ref: '2e2bef463a5b53ddf8bb788e25da6b8506314c08'
path: 'extern/stb'
- name: Build STB
shell: cmd
run: |
CD extern\stb
CALL ..\..\.github\scripts\stb\windows.bat
ECHO SET STB_ROOT=%STB_ROOT% >> ..\envs.bat
CD ..\..
- name: Build LibCmo
shell: cmd
run: |
:: Prepare Visual Studio
set VS=${{ matrix.vs }}
set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"
if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"
call %VCVARS% ${{ matrix.msvc_arch }}
:: Extract saved environment variables
CALL .\extern\envs.bat
:: Build Project
CALL .\.github\scripts\windows.bat
- name: Upload Built Artifact
uses: actions/upload-artifact@v4
with:
name: LibCmo-windows-build
path: bin/install/*
retention-days: 30
- name: Upload Built Dependencies
uses: actions/upload-artifact@v4
with:
name: LibCmo-windows-dep
path: extern/zlib/install/*
retention-days: 30

22
.gitignore vendored
View File

@@ -1,22 +1,24 @@
# -------------------- Personal --------------------
## ======== Personal ========
# Ignore build resources
[Oo]ut/
[Bb]uild/
[Ii]nstall/
[Ee]xtern/
[Tt]emp/
# Ignore all possible test used Virtools files
*.nmo
*.cmo
*.nms
*.vmo
# Ignore CMake generated version header
LibCmo/VTVersion.hpp
# Ignore temporary Visual Studio files and folders
temp/
out/
# Ignore CMake generated stuff
CMakeSettings.json
# -------------------- VSCode --------------------
## ======== VSCode ========
.vscode/
# -------------------- CMake --------------------
## ======== CMake ========
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
@@ -29,7 +31,7 @@ compile_commands.json
CTestTestfile.cmake
_deps
# -------------------- Visual Studio --------------------
## ======== Visual Studio ========
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##

View File

@@ -0,0 +1,4 @@
<Solution>
<Project Path="BMapSharp/BMapSharp.csproj" />
<Project Path="BMapSharpTestbench/BMapSharpTestbench.csproj" />
</Solution>

View File

@@ -0,0 +1,8 @@
# CKStateChunk Format
This directory stores the format specification for the CKStateChunk format.
CKStateChunk is a core data structure used by Virtools engines.
So it is important to know the format of CKStateChunk.
These concluded formats basically are based on the decompile result.
For view them, just use a web browser to open `CKStateChunk.html` in this directory.

8
Assets/CodeGen/BMapBinder/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
## ======== Personal ========
# Ignore intermediate stuff and output stuff.
Extracted/*
!Extracted/*.gitkeep
Analyzed/*
!Analyzed/*.gitkeep
Output/*
!Output/*.gitkeep

View File

@@ -0,0 +1,125 @@
## ======== Personal ========
# Additional remove for JetBrains IDEA
.idea/
*.iml
## ======== ANTLR Output ========
*.interp
*.tokens
ExpFctsLexer*.java
ExpFctsParser*.java
## ======== Java ========
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
## ======== JetBrains ========
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based HTTP Client
.idea/httpRequests
http-client.private.env.json
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Apifox Helper cache
.idea/.cache/.Apifox_Helper
.idea/ApifoxUploaderProjectSetting.xml
# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215
.idea/**/copilot.data.migration.*.xml

View File

@@ -0,0 +1,34 @@
//import java.io.FileInputStream;
import java.io.FileOutputStream;
//import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
public class CommonHelper {
// =========== File Operations ===========
private static Path getRootDirectoryPath() throws Exception {
String rootDir = System.getenv("BMAP_BINDER_ROOT");
if (rootDir == null) {
throw new RuntimeException("Can not find essential environment variable BMAP_BINDER_ROOT");
} else {
return Paths.get(rootDir);
}
}
public static String getInputFilePath(String filename) throws Exception {
Path rootDir = getRootDirectoryPath();
Path filePath = rootDir.resolve("Extracted").resolve(filename);
return filePath.toString();
}
public static String getOutputFilePath(String filename) throws Exception {
Path rootDir = getRootDirectoryPath();
Path filePath = rootDir.resolve("Analyzed").resolve(filename);
return filePath.toString();
}
}

View File

@@ -0,0 +1,94 @@
import java.util.Vector;
public class ExpFctsHelper {
/**
* The class represent a single parameter (argument) of function.
*/
public static class ExpFctParam {
/**
* The type of this parameter.
*/
public String mVarType;
/**
* The name of this parameter.
*/
public String mVarName;
/**
* True if this parameter is marked as input parameter, otherwise false.
* <p>
* Input parameter and output parameter is commonly used in C/C++ code. By using
* this feature, each function can receive multiple arguments and return
* multiple arguments without defining a struct to hold it.
* <p>
* The type of input parameter is itself. However, the type of output parameter
* is the pointer of itself. So you may need get its pointer type when
* processing output parameter, especially for the scenario that the target
* language do not support explicit output parameter keyword.
*/
public boolean mIsInput;
/**
* The description of this parameter.
* <p>
* This description is generated by this program. It will indicate the
* underlying C++ type to tell end user how to treat this parameter because some
* target languages' native calling style can not represent these detail.
* <p>
* In this program, this field must be written as a annotation of corresponding
* function.
*/
public String mVarDesc;
public ExpFctParam() {
mVarType = "";
mVarName = "";
mVarDesc = "";
mIsInput = true;
}
}
/**
* The class represent an export BMap function.
*/
public static class ExpFct {
/**
* The name of this function.
*/
public String mFctName;
/**
* The return value type of this function.
*/
public String mFctRvType;
/**
* The parameters (arguments) list of this function. Each item are
* {@linkplain ExpFctParam} and represent parameter one by one from left to
* right.
*/
public Vector<ExpFctParam> mFctParams;
public ExpFct() {
mFctName = "";
mFctRvType = "";
mFctParams = new Vector<ExpFctParam>();
}
}
/**
* The class represent a collection of export BMap functions.
*/
public static class ExpFctCollection {
/**
* The collection of exported BMap functions.
*/
public Vector<ExpFct> mFcts;
public ExpFctCollection() {
mFcts = new Vector<ExpFct>();
}
}
}

View File

@@ -1,34 +1,31 @@
import java.util.Collections;
import java.util.Vector;
import java.util.Objects;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class ExpFctsWalker extends ExpFctsParserBaseListener {
public ExpFctsWalker() {
mFctList = new Vector<ExpFctDecl>();
mFctCollection = null;
mCurrentFct = null;
mCurrentParam = null;
}
public Vector<ExpFctDecl> getResult() {
return mFctList;
public ExpFctsHelper.ExpFctCollection getResult() {
return mFctCollection;
}
private Vector<ExpFctDecl> mFctList;
private ExpFctDecl mCurrentFct;
private ExpFctParamDecl mCurrentParam;
private ExpFctsHelper.ExpFctCollection mFctCollection;
private ExpFctsHelper.ExpFct mCurrentFct;
private ExpFctsHelper.ExpFctParam mCurrentParam;
@Override
public void enterProgram(ExpFctsParser.ProgramContext ctx) {
mFctList.clear();
mFctCollection = new ExpFctsHelper.ExpFctCollection();
}
@Override
public void enterFctDecl(ExpFctsParser.FctDeclContext ctx) {
mCurrentFct = new ExpFctDecl();
mCurrentFct = new ExpFctsHelper.ExpFct();
}
@Override
@@ -36,55 +33,54 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
// set name
mCurrentFct.mFctName = ctx.EXPFCTS_IDENTIFIER().getText();
// check return type
if (!mCurrentFct.mFctRetType.isValid() || mCurrentFct.mFctRetType.isPointer()
|| !mCurrentFct.mFctRetType.getBaseType().equals("bool"))
if (!Objects.equals(mCurrentFct.mFctRvType, "bool"))
throw new IllegalArgumentException("invalid interface function return type. must be bool.");
// add into list
mFctList.add(mCurrentFct);
mFctCollection.mFcts.add(mCurrentFct);
mCurrentFct = null;
}
@Override
public void exitFctArgFileDecl(ExpFctsParser.FctArgFileDeclContext ctx) {
ExpFctParamDecl decl = new ExpFctParamDecl();
decl.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
decl.mVarDesc = "The pointer to corresponding BMFile.";
decl.mIsInput = true;
decl.mVarType.fromCType("BMap::BMFile*");
mCurrentFct.mFctParams.add(decl);
ExpFctsHelper.ExpFctParam param = new ExpFctsHelper.ExpFctParam();
param.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
param.mVarDesc = "The pointer to corresponding BMFile.";
param.mIsInput = true;
param.mVarType = "BMap::BMFile*";
mCurrentFct.mFctParams.add(param);
}
@Override
public void exitFctArgMeshTransDecl(ExpFctsParser.FctArgMeshTransDeclContext ctx) {
ExpFctParamDecl decl = new ExpFctParamDecl();
decl.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
decl.mVarDesc = "The pointer to corresponding BMMeshTransition.";
decl.mIsInput = true;
decl.mVarType.fromCType("BMap::BMMeshTransition*");
mCurrentFct.mFctParams.add(decl);
ExpFctsHelper.ExpFctParam param = new ExpFctsHelper.ExpFctParam();
param.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
param.mVarDesc = "The pointer to corresponding BMMeshTransition.";
param.mIsInput = true;
param.mVarType = "BMap::BMMeshTransition*";
mCurrentFct.mFctParams.add(param);
}
@Override
public void exitFctArgObjDecl(ExpFctsParser.FctArgObjDeclContext ctx) {
ExpFctParamDecl first_decl = new ExpFctParamDecl();
first_decl.mVarName = ctx.EXPFCTS_IDENTIFIER(0).getText();
first_decl.mVarDesc = "The pointer to corresponding BMFile.";
first_decl.mIsInput = true;
first_decl.mVarType.fromCType("BMap::BMFile*");
mCurrentFct.mFctParams.add(first_decl);
ExpFctsHelper.ExpFctParam firstParam = new ExpFctsHelper.ExpFctParam();
firstParam.mVarName = ctx.EXPFCTS_IDENTIFIER(0).getText();
firstParam.mVarDesc = "The pointer to corresponding BMFile.";
firstParam.mIsInput = true;
firstParam.mVarType = "BMap::BMFile*";
mCurrentFct.mFctParams.add(firstParam);
ExpFctParamDecl second_decl = new ExpFctParamDecl();
second_decl.mVarName = ctx.EXPFCTS_IDENTIFIER(1).getText();
second_decl.mVarDesc = "The CKID of object you accessing.";
second_decl.mIsInput = true;
second_decl.mVarType.fromCType("LibCmo::CK2::CK_ID");
mCurrentFct.mFctParams.add(second_decl);
ExpFctsHelper.ExpFctParam secondParam = new ExpFctsHelper.ExpFctParam();
secondParam.mVarName = ctx.EXPFCTS_IDENTIFIER(1).getText();
secondParam.mVarDesc = "The CKID of object you accessing.";
secondParam.mIsInput = true;
secondParam.mVarType = "LibCmo::CK2::CK_ID";
mCurrentFct.mFctParams.add(secondParam);
}
@Override
public void enterFctArgParamIn(ExpFctsParser.FctArgParamInContext ctx) {
mCurrentParam = new ExpFctParamDecl();
mCurrentParam = new ExpFctsHelper.ExpFctParam();
}
@Override
@@ -98,7 +94,7 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
@Override
public void enterFctArgParamOut(ExpFctsParser.FctArgParamOutContext ctx) {
mCurrentParam = new ExpFctParamDecl();
mCurrentParam = new ExpFctsHelper.ExpFctParam();
}
@Override
@@ -122,12 +118,15 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
ctype += String.join("", Collections.nCopies(ctx.EXPFCTS_STAR().size(), "*"));
}
if (!mCurrentFct.mFctRetType.isValid()) {
// if there is function return value is not filled,
// we fill it first because return value type is the first captured type in function statement.
// otherwise we fill parameter type.
if (mCurrentFct.mFctRvType.isEmpty()) {
// fill function ret type first
mCurrentFct.mFctRetType.fromCType(ctype);
mCurrentFct.mFctRvType = ctype;
} else {
// otherwise, fill param data
mCurrentParam.mVarType.fromCType(ctype);
mCurrentParam.mVarType = ctype;
}
}

View File

@@ -0,0 +1,52 @@
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonWriter {
private static JsonObject writeExpFctParam(ExpFctsHelper.ExpFctParam param) {
JsonObject data = new JsonObject();
data.addProperty("type", param.mVarType);
data.addProperty("name", param.mVarName);
data.addProperty("is_input", param.mIsInput);
data.addProperty("desc", param.mVarDesc);
return data;
}
private static JsonObject writeExpFct(ExpFctsHelper.ExpFct fct) {
JsonObject data = new JsonObject();
data.addProperty("name", fct.mFctName);
data.addProperty("return", fct.mFctRvType);
JsonArray paramList = new JsonArray();
for (ExpFctsHelper.ExpFctParam param : fct.mFctParams) {
paramList.add(writeExpFctParam(param));
}
data.add("params", paramList);
return data;
}
private static JsonArray writeExpFctCollection(ExpFctsHelper.ExpFctCollection fctCollection) {
JsonArray data = new JsonArray();
for (ExpFctsHelper.ExpFct fct : fctCollection.mFcts) {
data.add(writeExpFct(fct));
}
return data;
}
public static void writeJson(ExpFctsHelper.ExpFctCollection data) throws Exception {
FileOutputStream fs = new FileOutputStream(CommonHelper.getOutputFilePath("BMExports.json"));
OutputStreamWriter writer = new OutputStreamWriter(fs, StandardCharsets.UTF_8);
//Gson gsonInstance = new GsonBuilder().serializeNulls().setPrettyPrinting().disableHtmlEscaping().create();
Gson gsonInstance = new GsonBuilder().serializeNulls().disableHtmlEscaping().create();
writer.write(gsonInstance.toJson(writeExpFctCollection(data)));
writer.close();
}
}

View File

@@ -9,9 +9,9 @@ public class MainRunner {
public static void main(String[] args) throws Exception {
// get interface structure
FileInputStream fs = new FileInputStream("dest/BMExports.hpp");
CharStream antlrfs = CharStreams.fromStream(fs, StandardCharsets.UTF_8);
ExpFctsLexer lexer = new ExpFctsLexer(antlrfs);
FileInputStream fs = new FileInputStream(CommonHelper.getInputFilePath("BMExports.hpp"));
CharStream antlrStream = CharStreams.fromStream(fs, StandardCharsets.UTF_8);
ExpFctsLexer lexer = new ExpFctsLexer(antlrStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpFctsParser parser = new ExpFctsParser(tokens);
@@ -23,12 +23,10 @@ public class MainRunner {
fs.close();
// get data and write them
Vector<ExpFctDecl> result = worker.getResult();
PythonWriter.writePythonCode(result);
CSharpWriter.writeCSharpCode(result);
ExpFctsHelper.ExpFctCollection result = worker.getResult();
JsonWriter.writeJson(result);
// print message.
System.out.println("DONE!");
System.out.println("Done");
}
}

View File

@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -0,0 +1 @@
3.11

View File

@@ -0,0 +1,3 @@
# ExpFcts Extractor
See upper level [README.md](../README.md) for informations.

View File

@@ -0,0 +1,64 @@
import os
import re
from pathlib import Path
# region: Path Getter
def _get_libcmo21_repo_directory() -> Path:
repo_root = os.environ.get("LIBCMO21_REPO_ROOT", None)
if repo_root is None:
return Path(__file__).resolve().parent.parent.parent.parent.parent
else:
return Path(repo_root).resolve()
def get_input_file_path() -> Path:
return _get_libcmo21_repo_directory() / "BMap" / "BMap" / "BMExports.hpp"
def _get_bmap_binder_directory() -> Path:
bmap_binder_root = os.environ.get("BMAP_BINDER_ROOT", None)
if bmap_binder_root is None:
return Path(__file__).resolve().parent.parent
else:
return Path(bmap_binder_root).resolve()
def get_output_file_path() -> Path:
return _get_bmap_binder_directory() / "Extracted" / "BMExports.hpp"
# endregion
# region: Extractor
# We should not only match BMAP_EXPORT,
# because it may match the defination of BMAP_EXPORT.
# So we add a bool at head because all BMap functions return bool.
EXPFCTS_PATTERN: re.Pattern = re.compile(
"^BMAP_EXPORT[ \\t]+bool[ \\t]+[^;]+;", re.MULTILINE
)
def extract_expfcts(infile: Path, outfile: Path) -> None:
with open(infile, "r", encoding="utf-8") as fin:
# read full text
fulltext: str = fin.read()
# do findall and write into file
with open(outfile, "w", encoding="utf-8") as fout:
for item in EXPFCTS_PATTERN.findall(fulltext):
fout.write(item)
fout.write("\n")
# endregion
def main():
extract_expfcts(get_input_file_path(), get_output_file_path())
print("Done")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
[project]
name = "exp-fcts-extractor"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

View File

@@ -0,0 +1,8 @@
version = 1
revision = 2
requires-python = ">=3.11"
[[package]]
name = "exp-fcts-extractor"
version = "0.1.0"
source = { virtual = "." }

View File

@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -0,0 +1 @@
3.11

View File

@@ -0,0 +1,3 @@
# ExpFcts Render
See upper level [README.md](../README.md) for informations.

View File

@@ -0,0 +1,225 @@
import json
import typing
import utils
from dataclasses import dataclass
class VariableType:
"""The class represent the type of each parameters and function return value."""
__base_type_hierarchy: list[str]
"""
The base type of this variable removing all ending stars (remove all pointer levels).
Each item in this list is a part of namespace and the last one must be the type itself
(without any namespace constraint).
If no namespace constraint for this type, this list will only have one item.
For end user, it is enough that knowing the last item is type itself.
"""
__pointer_level: int
"""
The pointer level of this type.
It is equal to the count of trailing star of this field in C style representation.
"""
def __init__(
self, base_type_hierarchy: list[str] = [], pointer_level: int = 0
) -> None:
"""Construct a new varible type."""
self.__base_type_hierarchy = base_type_hierarchy
self.__pointer_level = pointer_level
def clone(self) -> "VariableType":
"""CLone self into a new instance."""
return VariableType(list(self.__base_type_hierarchy), self.__pointer_level)
@staticmethod
def from_c_type(ctype: str) -> "VariableType":
"""
Set this variable type with a type string in C/C++ style.
For example, "NSTest::NSTest2::MyType**" will produce 2 pointer level
with ('NSTest', 'NSTest2', 'MyType') as its base type hierarchy.
:param ctype: The type string in C/C++ style.
:return: The parsed VariableType instance.
"""
if len(ctype) == 0:
raise RuntimeError("empty string can not be parsed")
# get pointer part and name part
length = len(ctype)
star_index = ctype.find("*")
name_part: str
pointer_level: int
if star_index == -1:
# No star, no pointer level
name_part = ctype
pointer_level = 0
else:
# Has star
if star_index == 0:
raise RuntimeError("base type not found")
name_part = ctype[0:star_index]
pointer_level = length - star_index
# resolve name part
base_type_hierarchy = list(name_part.split("::"))
# return value
return VariableType(base_type_hierarchy, pointer_level)
def to_c_type(self) -> str:
"""
Build a type string represented by this variable type in C/C++ style.
:return: The type string in C/C++ style.
"""
return "::".join(self.__base_type_hierarchy) + ("*" * self.__pointer_level)
def get_base_type(self) -> str:
"""
Get the base type of this variable type without any namespace.
It just simply get the last entry in type hierarchy.
:return: The base type string without namespace prefix.
"""
return self.__base_type_hierarchy[-1]
def is_pointer(self) -> bool:
"""
Check whether this variable type is a pointer.
This function just check whether the pointer level of this variavle type is zero.
:return: True if it is pointer, otherwise false.
"""
return self.__pointer_level != 0
def get_pointer_level(self) -> int:
"""
Return the pointer level of this variable type.
You can simply assume the pointer level is equal to the count of trailing star.
:return: The pointer level integer. Zero means that this type is not a pointer.
"""
return self.__pointer_level
def iter_base_type_hierarchy(self) -> typing.Iterator[str]:
"""
Return the clone of the type hierarchy of this variable type.
It is rarely used.
This only should be used when you need the namespace hierarchy of this variable type.
:return: The clone of current variable type hierarchy.
"""
return iter(self.__base_type_hierarchy)
# def is_valid(self) -> bool:
# """
# Check whether this type is a valid one.
# It actually check whether type hierarchy include at least one entry.
# :return: True if no problem of this type, otherwise false.
# """
# return len(self.__base_type_hierarchy) != 0
def get_pointer_of_this(self) -> "VariableType":
"""
Return a new created variable type which is the pointer of this variable type.
In internal implementation, it just create a clone of current variable type
with the increase of pointer level by 1.
:return: The new created pointer type of this variable type.
"""
return VariableType(list(self.__base_type_hierarchy), self.__pointer_level + 1)
@staticmethod
def from_json(data: str) -> "VariableType":
return VariableType.from_c_type(data)
@dataclass(frozen=True)
class ExpFctParam:
"""The class represent a single parameter (argument) of function."""
var_type: VariableType
"""The type of this parameter."""
var_name: str
"""The name of this parameter."""
is_input: bool
"""
True if this parameter is marked as input parameter, otherwise false.
Input parameter and output parameter is commonly used in C/C++ code.
By using this feature, each function can receive multiple arguments
and return multiple arguments without defining a struct to hold it.
The type of input parameter is itself.
However, the type of output parameter is the pointer of itself.
So you may need get its pointer type when processing output parameter,
especially for the scenario that the target language do not support explicit output parameter keyword.
"""
var_desc: str
"""
The description of this parameter.
This description is generated by this program.
It will indicate the underlying C++ type to tell end user how to treat this parameter
because some target languages' native calling style can not represent these detail.
In this program, this field must be written as a docstring of corresponding function.
"""
@staticmethod
def from_json(data: dict[str, typing.Any]) -> "ExpFctParam":
return ExpFctParam(
VariableType.from_c_type(data["type"]),
data["name"],
data["is_input"],
data["desc"],
)
@dataclass(frozen=True)
class ExpFct:
"""The class represent an export BMap function."""
fct_name: str
"""The name of this function."""
fct_rv_type: VariableType
"""The return value type of this function."""
fct_params: list[ExpFctParam]
"""
The parameters (arguments) list of this function.
Each item represent parameter accepted by this function one by one from left to right.
"""
@staticmethod
def from_json(data: dict[str, typing.Any]) -> "ExpFct":
return ExpFct(
data["name"],
VariableType.from_json(data["return"]),
list(map(lambda i: ExpFctParam.from_json(i), data["params"])),
)
@dataclass(frozen=True)
class ExpFctCollection:
"""The class represent a collection of export BMap functions."""
fcts: list[ExpFct]
"""The collection of exported BMap functions."""
@staticmethod
def from_json(data: list[typing.Any]) -> "ExpFctCollection":
return ExpFctCollection(list(map(lambda i: ExpFct.from_json(i), data)))
def load_fcts(filename: str) -> ExpFctCollection:
with open(utils.get_input_file_path(filename), "r", encoding="utf-8") as f:
return ExpFctCollection.from_json(json.load(f))

View File

@@ -0,0 +1,17 @@
import json_loader
import template_render
import utils
def main():
render = template_render.TemplateRender()
fcts = json_loader.load_fcts("BMExports.json")
render.render_cs_expfcts("BMExports.cs", fcts)
render.render_py_expfcts("BMExports.py", fcts)
render.render_rs_expfcts("BMExports.rs", fcts)
print("Done")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
[project]
name = "exp-fcts-render"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"jinja2==3.1.6",
]

View File

@@ -0,0 +1,260 @@
import jinja2
import re
import typing
from dataclasses import dataclass
from json_loader import ExpFctCollection, ExpFctParam
import utils
CPP_PY_TYPE_MAP: dict[str, str] = {
"CKSTRING": "CKSTRING",
"CKDWORD": "CKDWORD",
"CKWORD": "CKWORD",
"CKINT": "CKINT",
"bool": "bool",
"CKFLOAT": "CKFLOAT",
"CKBYTE": "CKBYTE",
"CK_ID": "CKID",
"NakedOutputCallback": "callback",
"BMFile": "void",
"BMMeshTransition": "void",
"VxVector3": "VxVector3",
"VxVector2": "VxVector2",
"VxColor": "VxColor",
"VxMatrix": "VxMatrix",
"CK_TEXTURE_SAVEOPTIONS": "enum",
"VX_PIXELFORMAT": "enum",
"VXLIGHT_TYPE": "enum",
"VXTEXTURE_BLENDMODE": "enum",
"VXTEXTURE_FILTERMODE": "enum",
"VXTEXTURE_ADDRESSMODE": "enum",
"VXBLEND_MODE": "enum",
"VXFILL_MODE": "enum",
"VXSHADE_MODE": "enum",
"VXCMPFUNC": "enum",
"VXMESH_LITMODE": "enum",
}
CS_ENUM_LIKE: set[str] = set((
"CK_TEXTURE_SAVEOPTIONS",
"VX_PIXELFORMAT",
"VXLIGHT_TYPE",
"VXTEXTURE_BLENDMODE",
"VXTEXTURE_FILTERMODE",
"VXTEXTURE_ADDRESSMODE",
"VXBLEND_MODE",
"VXFILL_MODE",
"VXSHADE_MODE",
"VXCMPFUNC",
"VXMESH_LITMODE",
))
@dataclass(frozen=True)
class CsInteropType:
"""The class represent the C# type corresponding to extracted variable type."""
marshal_as: str
"""
The argument of MarshalAsAttribute constructor.
In generation, this field should be used like this: "[MarshalAs(THIS)]" (for parameter),
or "[return: MarshalAs(THIS)]" (for return value).
"""
cs_type: str
"""
The C# type used in interop function declaration for corresponding parameter.
"""
class RenderUtils:
"""Possible used functions for jinja when rendering templates"""
@staticmethod
def get_python_type(param: ExpFctParam) -> str:
vt = param.var_type
if not param.is_input:
vt = vt.get_pointer_of_this()
# add type prefix
sb: str = "bm_"
# try getting cpp type from base type and add it
cpp_type = CPP_PY_TYPE_MAP.get(vt.get_base_type(), None)
if cpp_type is None:
raise RuntimeError(f"unexpected type {vt.to_c_type()}")
else:
sb += cpp_type
# add pointer suffix
if vt.is_pointer():
sb += "_"
sb += "p" * vt.get_pointer_level()
# return built type string.
return sb
@staticmethod
def get_cs_interop_type(param: ExpFctParam) -> CsInteropType:
# get essential variable type properties first
vt = param.var_type
vt_base_type = vt.get_base_type()
vt_pointer_level = vt.get_pointer_level()
# declare return value
marshal_as: str | None = None
cs_type: str | None = None
# use "match" to check variable type
match vt_base_type:
case 'CKSTRING':
# decide direction cookies
direction_cookie = 'In' if param.is_input else 'Out'
# only allow 0 and 1 pointer level for string.
match vt_pointer_level:
case 0:
marshal_as = f'UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(BMStringMarshaler), MarshalCookie = "{direction_cookie}"'
cs_type = "string"
case 1:
marshal_as = f'UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(BMStringArrayMarshaler), MarshalCookie = "{direction_cookie}"'
cs_type = "string[]"
case "CKDWORD":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.U4"
cs_type = "uint"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "CKWORD":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.U2"
cs_type = "ushort"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "CKINT":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.I4"
cs_type = "int"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "bool":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.U1"
cs_type = "bool"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "CKFLOAT":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.R4"
cs_type = "float"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "CKBYTE":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.U1"
cs_type = "byte"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "CK_ID":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.U4"
cs_type = "uint"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "NakedOutputCallback":
# callback actually is a function pointer
# so it only allow base type without any pointer level.
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.FunctionPtr"
cs_type = "OutputCallback"
case "BMFile":
# In any case, BMFile only should be raw pointer
if vt_pointer_level != 0:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "BMMeshTransition":
# In any case, BMMeshTransition only should be raw pointer
if vt_pointer_level != 0:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "VxVector3":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.Struct"
cs_type = "VxVector3"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "VxVector2":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.Struct"
cs_type = "VxVector2"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "VxColor":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.Struct"
cs_type = "VxColor"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case "VxMatrix":
if vt_pointer_level == 0:
marshal_as = "UnmanagedType.Struct"
cs_type = "VxMatrix"
else:
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
case enumlike if enumlike in CS_ENUM_LIKE:
# all enum type use the same strategy
if vt_pointer_level == 0:
# all enum type should be marshaled as its underlying type
# but we can use its name in C# directly.
marshal_as = "UnmanagedType.U4"
cs_type = vt_base_type
else:
# for pointer type, use IntPtr instead.
marshal_as = "UnmanagedType.SysInt"
cs_type = "IntPtr"
# check whether we successfully get result
if marshal_as is None or cs_type is None:
raise RuntimeError(f'unexpected type: {vt.to_c_type()}')
# return value
return CsInteropType(marshal_as, cs_type)
class TemplateRender:
"""Render templates to code files"""
__loader: jinja2.BaseLoader
__environment: jinja2.Environment
def __init__(self) -> None:
self.__loader = jinja2.FileSystemLoader(utils.get_template_directory())
self.__environment = jinja2.Environment(loader=self.__loader)
def __render(
self, template_name: str, dest_filename: str, payload: ExpFctCollection
) -> None:
# prepare template argument
template_argument: dict[str, typing.Any] = {
"payload": payload,
"utils": RenderUtils,
}
# fetch template
template = self.__environment.get_template(str(template_name))
# render template and save
with open(utils.get_output_file_path(dest_filename), "w", encoding="utf-8") as f:
f.write(template.render(**template_argument))
def render_cs_expfcts(self, filename: str, fcts: ExpFctCollection) -> None:
self.__render("expfcts.cs.jinja", filename, fcts)
def render_py_expfcts(self, filename: str, fcts: ExpFctCollection) -> None:
self.__render("expfcts.py.jinja", filename, fcts)
def render_rs_expfcts(self, filename: str, fcts: ExpFctCollection) -> None:
self.__render("expfcts.rs.jinja", filename, fcts)

View File

@@ -0,0 +1,16 @@
{%- for fct in payload.fcts %}
/// <summary>{{ fct.fct_name }}</summary>
{%- for param in fct.fct_params %}
/// <param name="{{ param.var_name }}">Direction: {% if param.is_input -%} input {%- else -%} output {%- endif %}. C++ type: {{ param.var_type.to_c_type() }}. {{ param.var_desc }}</param>
{%- endfor %}
/// <returns>True if no error, otherwise False.</returns>
[DllImport(g_DllName, EntryPoint = "{{ fct.fct_name }}", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool {{ fct.fct_name }}(
{%- for param in fct.fct_params -%}
{%- set param_cs_interop_type = utils.get_cs_interop_type(param) -%}
[{% if param.is_input -%} In {%- else -%} Out {%- endif %}, MarshalAs({{ param_cs_interop_type.marshal_as }})] {% if not param.is_input %}out {% endif %} {{- param_cs_interop_type.cs_type }} {{ param.var_name }}
{%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
{%- endfor %}

View File

@@ -0,0 +1,17 @@
{%- for fct in payload.fcts %}
{{ fct.fct_name }} = _create_bmap_func('{{ fct.fct_name }}', [
{%- for param in fct.fct_params %}
{{- utils.get_python_type(param) }}
{%- if not loop.last %}, {% endif %}
{%- endfor -%}
])
"""
{{ fct.fct_name }}
{% for param in fct.fct_params %}
:param {{ param.var_name }}: Direction: {% if param.is_input -%} input {%- else -%} output {%- endif %}. {{ param.var_desc }}
:type {{ param.var_name }}: {{ utils.get_python_type(param) }} ({{ param.var_type.to_c_type() }} in C++). {% if param.is_input -%} Use ctypes.byref() pass it. {%- endif %}
{%- endfor %}
:return: True if no error, otherwise False.
:rtype: bool
"""
{%- endfor %}

View File

@@ -0,0 +1,22 @@
import os
from pathlib import Path
def _get_root_directory() -> Path:
bmap_binder_root = os.environ.get("BMAP_BINDER_ROOT", None)
if bmap_binder_root is None:
return Path(__file__).resolve().parent.parent
else:
return Path(bmap_binder_root).resolve()
def get_input_file_path(filename: str) -> Path:
return _get_root_directory() / "Analyzed" / filename
def get_output_file_path(filename: str) -> Path:
return _get_root_directory() / "Output" / filename
def get_template_directory() -> Path:
return Path(__file__).resolve().parent / "templates"

View File

@@ -0,0 +1,100 @@
version = 1
revision = 2
requires-python = ">=3.11"
[[package]]
name = "exp-fcts-render"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "jinja2" },
]
[package.metadata]
requires-dist = [{ name = "jinja2", specifier = "==3.1.6" }]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
{ url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
{ url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
{ url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
{ url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
{ url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
{ url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
{ url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
{ url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
{ url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
{ url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
{ url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
{ url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
{ url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
{ url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
{ url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
{ url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
{ url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
{ url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
{ url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
{ url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
{ url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]

View File

@@ -0,0 +1,65 @@
# BMap Binder
A helper program to generate BMap binding for Python, C# and Rust.
## Usage
This program is consisted by 3 parts: ExpFcts Extractor, ExpFcts Analyzer and ExpFcts Render.
"ExpFcts" is stand for "Exported Functions".
### Setup Environment
First we stay at the root directory of this project (this README file located).
And execute `set BMAP_BINDER_ROOT=$(pwd)` on POSIX-like OS, or `set BMAP_BINDER_ROOT=%CD%` on Windows, to set environment variable.
This environment variable will be used later by 3 parts conststing this project.
Then we navigate to the root directory of this repository (where you can find top-level `CMakeLists.txt` file). And execute `set LIBCMO21_REPO_ROOT=$(pwd)` on POSIX-like OS, or `set LIBCMO21_REPO_ROOT=%CD%` on Windows, to set environment variable.
This environment variable also will be used later.
### ExpFcts Extractor
We should first run ExpFcts Extractor to extract BMap exported function declarations fron header file.
BMap header file include various contents, not only the declaration of exported functions, but also many of other utilities.
We use regex to extract BMap exported function declarations only to avoid build a complete C lexer and parser in following steps and make our work easier.
For running this program, please following these steps:
* Enter `ExpFctsExtractor` directory and setup it with Astral UV.
* Execute `uv run main.py` to run program.
* Program will process BMap header file `BMExports.hpp` from BMap project located in root directory of this repository, and output extracted text to `Extracted` directory with name `BMExports.hpp`.
### ExpFcts Analyzer
Now we can run ExpFcts Analyzer to analyze extracted BMap exported function declarations.
#### Build
Enter `ExpFctsAnalyzer` directory, and execute following command to generate Antlr lexer and parser:
```
antlr4 ExpFctsLexer.g4
antlr4 ExpFctsParser.g4
```
Keep staying that directory, and execute following command to build Java code.
```
javac *.java
```
#### Run
Keep staying this directory, and execute following command to run program.
```
java MainRunner
```
After running, program will process input file `BMExports.hpp` located in `Extracted` directory, and output JSON file to `Analyzed` directory with name `BMExports.json`.
### ExpFcts Render
* Enter `ExpFctsRender` directory and setup it with Astral UV.
* Execute `uv run main.py` to run program.
* Program will process JSON file `BMExports.json` located in `Analyzed` directory, and output final artifacts to `Output` directory.

View File

@@ -0,0 +1,6 @@
## ======== Personal ========
# Ignore intermediate stuff and output stuff.
Intermediate/*
!Intermediate/*.gitkeep
Output/*
!Output/*.gitkeep

View File

@@ -0,0 +1,126 @@
## ======== Personal ========
# Additional remove for JetBrains IDEA
.idea/
*.iml
## ======== ANTLR Output ========
*.interp
*.tokens
CKGenericLexer*.java
CKEnumsParser*.java
CKDefinesParser*.java
## ======== Java ========
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
## ======== JetBrains ========
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based HTTP Client
.idea/httpRequests
http-client.private.env.json
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Apifox Helper cache
.idea/.cache/.Apifox_Helper
.idea/ApifoxUploaderProjectSetting.xml
# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215
.idea/**/copilot.data.migration.*.xml

View File

@@ -0,0 +1,6 @@
parser grammar CKDefinesParser;
options { tokenVocab = CKGenericLexer; }
prog: definePair+ ;
definePair: CKGENERIC_DEFINE CKGENERIC_ID (CKGENERIC_NUM | CKGENERIC_ID) ;

View File

@@ -0,0 +1,14 @@
parser grammar CKEnumsParser;
options { tokenVocab = CKGenericLexer; }
prog: enumBody* ;
enumBody: CKGENERIC_TYPEDEF? CKGENERIC_ENUM CKGENERIC_ID CKGENERIC_LBRACKET
entryPair+
CKGENERIC_RBRACKET CKGENERIC_ID? CKGENERIC_SEMICOLON ;
entryPair: CKGENERIC_ID (CKGENERIC_EQUAL entryValue)? CKGENERIC_COMMA? ;
entryValue: CKGENERIC_NUM (CKGENERIC_LSHIFT CKGENERIC_NUM)? # entryDirectValue
| CKGENERIC_ID (CKGENERIC_OR CKGENERIC_ID)* # entryRelativeValue
;

View File

@@ -0,0 +1,26 @@
lexer grammar CKGenericLexer;
channels { COMMENTS, WHITESPACE }
// keywords
CKGENERIC_TYPEDEF: 'typedef' ;
CKGENERIC_DEFINE: '#define' ;
CKGENERIC_ENUM: 'enum' ;
// symbols
CKGENERIC_LBRACKET: '{' ;
CKGENERIC_RBRACKET: '}' ;
CKGENERIC_EQUAL: '=';
CKGENERIC_SEMICOLON: ';' ;
CKGENERIC_LSHIFT: '<<' ;
CKGENERIC_OR: '|' ;
CKGENERIC_COMMA: ',' ;
// identifider and number
CKGENERIC_ID: [_a-zA-Z][_a-zA-Z0-9]* ;
CKGENERIC_NUM: (('0'[xX]) | '-')? [0-9a-fA-F]+ [uUlL]* ;
// comments
CKGENERIC_LINE_COMMENT: '//' ~[\r\n]* -> channel(COMMENTS);
CKGENERIC_BLOCK_COMMENT: '/*' .*? '*/' -> channel(COMMENTS);
// whitespace
CKGENERIC_WS: [ \t\r\n]+ -> channel(WHITESPACE);

View File

@@ -2,7 +2,6 @@
import java.util.Stack;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
/**
* The specialized walker for collecting CK_CLASSID and its inherit
@@ -19,12 +18,12 @@ public class ClassidWalker extends CKDefinesParserBaseListener {
mCurrentEntry = null;
}
public EnumsHelper.Enum_t getEnum() {
public EnumsHelper.BEnum getEnum() {
return mResult;
}
private int getClassidLevel(Token defineHead) {
Token ws = CommonHelper.getPreChannelToken(mTokenStream, defineHead, CKGeneralLexer.WHITESPACE);
Token ws = CommonHelper.getPreChannelToken(mTokenStream, defineHead, CKGenericLexer.WHITESPACE);
if (ws == null)
return 0;
@@ -54,18 +53,18 @@ public class ClassidWalker extends CKDefinesParserBaseListener {
}
private BufferedTokenStream mTokenStream;
private EnumsHelper.Enum_t mResult;
private EnumsHelper.BEnum mResult;
private int mLevel;
private Stack<EnumsHelper.EnumEntryWithHierarchy_t> mLevelStack;
private EnumsHelper.Enum_t mCurrentEnum;
private EnumsHelper.EnumEntryWithHierarchy_t mCurrentEntry;
private Stack<EnumsHelper.BHierarchyEnumEntry> mLevelStack;
private EnumsHelper.BEnum mCurrentEnum;
private EnumsHelper.BHierarchyEnumEntry mCurrentEntry;
@Override
public void enterProg(CKDefinesParser.ProgContext ctx) {
mLevel = 0;
mLevelStack = new Stack<EnumsHelper.EnumEntryWithHierarchy_t>();
mCurrentEnum = new EnumsHelper.Enum_t();
mLevelStack = new Stack<EnumsHelper.BHierarchyEnumEntry>();
mCurrentEnum = new EnumsHelper.BEnum();
}
@Override
@@ -82,14 +81,14 @@ public class ClassidWalker extends CKDefinesParserBaseListener {
@Override
public void enterDefinePair(CKDefinesParser.DefinePairContext ctx) {
mCurrentEntry = new EnumsHelper.EnumEntryWithHierarchy_t();
mCurrentEntry = new EnumsHelper.BHierarchyEnumEntry();
}
@Override
public void exitDefinePair(CKDefinesParser.DefinePairContext ctx) {
// fill entry info
mCurrentEntry.mEntryName = ctx.CKGENERAL_ID(0).getText();
mCurrentEntry.mEntryValue = ctx.CKGENERAL_NUM().getText();
mCurrentEntry.mEntryName = ctx.CKGENERIC_ID(0).getText();
mCurrentEntry.mEntryValue = ctx.CKGENERIC_NUM().getText();
// fill entry level info
int this_level = getClassidLevel(ctx.getStart());

View File

@@ -22,13 +22,13 @@ public class CommentsFinder {
// if we don't know where is our token,
// we should assume it is from precomment to postcomment
// and check it.
List<Token> precomment = CommonHelper.getPreChannelTokens(mTokenStream, preToken, CKGeneralLexer.COMMENTS);
List<Token> precomment = CommonHelper.getPreChannelTokens(mTokenStream, preToken, CKGenericLexer.COMMENTS);
if (precomment != null) {
mCommentsPos = CommentsPosition.Precomment;
return CommonHelper.cutComments(precomment);
}
List<Token> postcomment = CommonHelper.getPostChannelTokens(mTokenStream, postToken, CKGeneralLexer.COMMENTS);
List<Token> postcomment = CommonHelper.getPostChannelTokens(mTokenStream, postToken, CKGenericLexer.COMMENTS);
if (postcomment != null) {
mCommentsPos = CommentsPosition.Postcomment;
return CommonHelper.cutComments(postcomment);
@@ -38,10 +38,10 @@ public class CommentsFinder {
return null;
}
case Precomment: {
return CommonHelper.cutComments(CommonHelper.getPreChannelTokens(mTokenStream, preToken, CKGeneralLexer.COMMENTS));
return CommonHelper.cutComments(CommonHelper.getPreChannelTokens(mTokenStream, preToken, CKGenericLexer.COMMENTS));
}
case Postcomment: {
return CommonHelper.cutComments(CommonHelper.getPostChannelTokens(mTokenStream, postToken, CKGeneralLexer.COMMENTS));
return CommonHelper.cutComments(CommonHelper.getPostChannelTokens(mTokenStream, postToken, CKGenericLexer.COMMENTS));
}
default:
return null;

View File

@@ -2,6 +2,8 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
@@ -35,7 +37,7 @@ public class CommonHelper {
/**
* Cut the head and tail of comment
*
*
* @param comment The comment need to be cut.
* @return The cut comment.
*/
@@ -44,12 +46,36 @@ public class CommonHelper {
return null;
switch (comment.getType()) {
case CKGeneralLexer.CKGENERAL_LINE_COMMENT: {
return removeStars(comment.getText().substring(2));
case CKGenericLexer.CKGENERIC_LINE_COMMENT: {
// For line comment, we start to remove "//" prefix first
String slashRemoved = comment.getText().substring(2);
// Then remove successive starts
String starsRemoved = removeSeperator(slashRemoved);
// Do a strip for it.
String stripped = starsRemoved.strip();
// Then remove EOL to avoid unexpected line break.
String eolRemoved = removeEol(stripped);
// Okey
return eolRemoved;
}
case CKGeneralLexer.CKGENERAL_BLOCK_COMMENT: {
String cache = comment.getText();
return removeStars(cache.substring(2, cache.length() - 4));
case CKGenericLexer.CKGENERIC_BLOCK_COMMENT: {
// For block comment, we first cut "/*" head and "*/" tail.
String blockComment = comment.getText();
String slashRemoved = blockComment.substring(2, blockComment.length() - 4);
// Then we break it at line breaker and process each line one by one.
String beautyComment = slashRemoved.lines().map((String line) -> {
// Remove successive starts
String starsRemoved = removeSeperator(line);
// Do a strip for it.
String stripped = starsRemoved.strip();
// Then remove EOL to avoid unexpected line break.
String eolRemoved = removeEol(stripped);
// Line process is okey now
return eolRemoved;
}).collect(Collectors.joining("\n")); // Then re-join with fresh line breaker
// Then return
return beautyComment;
}
default:
return comment.getText();
@@ -70,15 +96,14 @@ public class CommonHelper {
return null;
return tokens.stream().map(value -> {
String text = cutComment(value).strip();
return text + " ";
}).collect(Collectors.joining(""));
return cutComment(value);
}).collect(Collectors.joining("\n"));
}
// =========== Number Operations ===========
/**
* Check whether Antlr captured CKGENERAL_NUM is a negative number.
* Check whether Antlr captured CKGENERIC_NUM is a negative number.
*
* @param numstr The captured number.
* @return true if it is negative number.
@@ -88,50 +113,26 @@ public class CommonHelper {
}
/**
* Check whether Altlr captured CKGENERAL_NUM is a hex number.
*
* Check whether Altlr captured CKGENERIC_NUM is a hex number.
*
* @param numstr The captured number.
* @return true if it is hex number.
*/
public static boolean isHexNumber(String numstr) {
return numstr.startsWith("0x") || numstr.startsWith("0X");
}
/**
* Convert accepted string into Python cupported format.
*
* It actually just remove trail "UL".
*
* @param numstr The captured number.
* @return The Python style number string.
*/
public static String convertToPythonNumber(String numstr) {
return numstr.replaceFirst("[ulUL]+$", "");
}
// =========== Parts ===========
enum CKParts {
CK2, VxMath
}
enum LangType {
Cpp, Python, CSharp
}
public static String getCKPartsNamespace(CKParts parts) {
switch (parts) {
case CK2:
return "CK2";
case VxMath:
return "VxMath";
default:
throw new IllegalArgumentException("Unexpected value: " + parts);
}
}
// =========== File Operations ===========
private static Path getRootDirectoryPath() throws Exception {
String rootDir = System.getenv("ENUMS_MIGRATION_ROOT");
if (rootDir == null) {
throw new RuntimeException("Can not find essential environment variable ENUMS_MIGRATION_ROOT");
} else {
return Paths.get(rootDir);
}
}
public static class InputFilePair {
public CharStream mAntlrStream;
public FileInputStream mUnderlyingStream;
@@ -144,6 +145,12 @@ public class CommonHelper {
return pair;
}
public static String getInputFilePath(String filename) throws Exception {
Path rootDir = getRootDirectoryPath();
Path filePath = rootDir.resolve("Input").resolve(filename);
return filePath.toString();
}
/**
* Get output file for writing.
*
@@ -156,82 +163,33 @@ public class CommonHelper {
return new OutputStreamWriter(fs, StandardCharsets.UTF_8);
}
// =========== String Process ===========
/**
* Escape String
*
* Escape all characters which are invalid in string quote.
*
* @param strl The string need to be escaped.
* @return The escaped string.
* @see removeEol
*/
public static String escapeString(String strl) {
return strl.replace("\\", "\\\\").replace("\t", "\\t").replace("\b", "\\b").replace("\n", "\\n")
.replace("\r", "\\r").replace("\f", "\\f").replace("\"", "\\\"");
public static String getOutputFilePath(String filename) throws Exception {
Path rootDir = getRootDirectoryPath();
Path filePath = rootDir.resolve("Intermediate").resolve(filename);
return filePath.toString();
}
// =========== String Process ===========
/**
* Remove all EOL (End of Line) characters.
*
* @param strl The string need to be processed.
* @return The string eliminated all EOL.
* @see escapeString
*/
public static String removeEol(String strl) {
return strl.replace("\n", "").replace("\r", "");
}
/**
* Remove redundent star '*' (at least 5 continuous star chars)
* Remove seperator bar consisted by '*' or '-' (at least 5 successive chars)
*
* @param cmt The string provided.
* @return The string processed.
*/
public static String removeStars(String cmt) {
public static String removeSeperator(String cmt) {
// only remove at least 5 continuous star chars.
return cmt.replaceAll("\\*{5,}", "");
}
/**
* Get indent char by style
*
* @param use_tab Whether indent use Tab, not Space.
* @return The indent chars
*/
public static String getIndentString(boolean use_tab) {
if (use_tab)
return "\t";
else
return " ";
}
/**
* Re-indent a block of string
*
* This function will first split block string by line. Then remove all indent
* (strip Tab and Space). At last, re-indent and join each line
*
* @param block_str The string provided.
* @param use_tab Use Tab, not Space as indent chars.
* @param indent_level The level of indent, started by 0.
* @return The re-indent string
*/
public static String reindentBlockString(String block_str, boolean use_tab, int indent_level) {
// pre-create indent string
String indentChars = getIndentString(use_tab);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indent_level; ++i) {
sb.append(indentChars);
}
String indents = sb.toString();
// split line
return block_str.lines().map((String line) -> {
// strip space and tab, then re-indent it.
return indents + line.trim();
}).collect(Collectors.joining(System.lineSeparator())); // then join with new line breaker and return.
return cmt.replaceAll("[\\*\\-]{5,}", "");
}
}

View File

@@ -1,6 +1,5 @@
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
/**
* The generic walker for collecting defines as a enum.
@@ -14,19 +13,19 @@ public class DefinesWalker extends CKDefinesParserBaseListener {
mCurrentEntry = null;
}
public EnumsHelper.Enum_t getEnum() {
public EnumsHelper.BEnum getEnum() {
return mResult;
}
private CommentsFinder mCommentsFinder;
private EnumsHelper.Enum_t mResult;
private EnumsHelper.BEnum mResult;
private EnumsHelper.Enum_t mCurrentEnum;
private EnumsHelper.EnumEntry_t mCurrentEntry;
private EnumsHelper.BEnum mCurrentEnum;
private EnumsHelper.BEnumEntry mCurrentEntry;
@Override
public void enterProg(CKDefinesParser.ProgContext ctx) {
mCurrentEnum = new EnumsHelper.Enum_t();
mCurrentEnum = new EnumsHelper.BEnum();
}
@Override
@@ -37,22 +36,22 @@ public class DefinesWalker extends CKDefinesParserBaseListener {
@Override
public void enterDefinePair(CKDefinesParser.DefinePairContext ctx) {
mCurrentEntry = new EnumsHelper.EnumEntry_t();
mCurrentEntry = new EnumsHelper.BEnumEntry();
}
@Override
public void exitDefinePair(CKDefinesParser.DefinePairContext ctx) {
// set values
mCurrentEntry.mEntryName = ctx.CKGENERAL_ID(0).getText();
mCurrentEntry.mEntryName = ctx.CKGENERIC_ID(0).getText();
mCurrentEntry.mEntryComment = mCommentsFinder.getComment(ctx.getStart(), ctx.getStop());
if (ctx.CKGENERAL_NUM() == null) {
if (ctx.CKGENERIC_NUM() == null) {
// define with id
mCurrentEntry.mEntryValue = ctx.CKGENERAL_ID(1).getText();
mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID(1).getText();
} else {
// define with number
String num = ctx.CKGENERAL_NUM().getText();
String num = ctx.CKGENERIC_NUM().getText();
mCurrentEntry.mEntryValue = num;
// check whether this enum can be unsigned

View File

@@ -0,0 +1,76 @@
import java.util.Vector;
public class EnumsHelper {
/**
* The struct to describe the entry of an enum.
*/
public static class BEnumEntry {
public BEnumEntry() {
mEntryName = null;
mEntryValue = null;
mEntryComment = null;
}
/** The name of this entry. Can not be null. */
public String mEntryName;
/** The value of this entry. null if this entry do not have explicit value. */
public String mEntryValue;
/** The comment of this entry. null if no comment. */
public String mEntryComment;
}
/**
* The specialized EnumEntry type which can store extra hierarchy info.
* Used in CK_CLASSID parsing.
*/
public static class BHierarchyEnumEntry extends BEnumEntry {
public BHierarchyEnumEntry() {
super();
mHierarchy = new Vector<BHierarchyEnumEntry>();
}
/**
* The list to store this CK_CLASSID inheritance relationship.
* The first item is the oldest parent in inheritance.
* The last item is self.
*/
public Vector<BHierarchyEnumEntry> mHierarchy;
}
/**
* The struct to describe an enum.
*/
public static class BEnum {
public BEnum() {
mEnumName = null;
mEnumComment = null;
mCanUnsigned = true;
mUseFlags = false;
mEntries = new Vector<BEnumEntry>();
}
/** The name of this enum. Can not be null. */
public String mEnumName;
/** The comment of this enum. null if no comment. */
public String mEnumComment;
/** True if this enum can use unsigned integer as its underlying type. */
public boolean mCanUnsigned;
/** True if this enum will use flags feature (supporting OR, AND, operators). */
public boolean mUseFlags;
/** The list to store entries of this enum. */
public Vector<BEnumEntry> mEntries;
}
/**
* The struct to describe a collection of enums.
*/
public static class BEnumCollection {
public BEnumCollection() {
mEnums = new Vector<BEnum>();
}
/** The list to store enums. */
public Vector<BEnum> mEnums;
}
}

View File

@@ -18,26 +18,26 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
mCurrentEntry = null;
}
public EnumsHelper.EnumCollection_t getEnums() {
public EnumsHelper.BEnumCollection getEnums() {
return mResult;
}
private String getEnumComment(Token enumHead) {
return CommonHelper
.cutComments(CommonHelper.getPreChannelTokens(mTokenStream, enumHead, CKGeneralLexer.COMMENTS));
.cutComments(CommonHelper.getPreChannelTokens(mTokenStream, enumHead, CKGenericLexer.COMMENTS));
}
private BufferedTokenStream mTokenStream;
private CommentsFinder mCommentsFinder;
private EnumsHelper.EnumCollection_t mResult;
private EnumsHelper.BEnumCollection mResult;
private EnumsHelper.EnumCollection_t mCurrentProg;
private EnumsHelper.Enum_t mCurrentEnum;
private EnumsHelper.EnumEntry_t mCurrentEntry;
private EnumsHelper.BEnumCollection mCurrentProg;
private EnumsHelper.BEnum mCurrentEnum;
private EnumsHelper.BEnumEntry mCurrentEntry;
@Override
public void enterProg(CKEnumsParser.ProgContext ctx) {
mCurrentProg = new EnumsHelper.EnumCollection_t();
mCurrentProg = new EnumsHelper.BEnumCollection();
}
@Override
@@ -48,7 +48,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
@Override
public void enterEnumBody(CKEnumsParser.EnumBodyContext ctx) {
mCurrentEnum = new EnumsHelper.Enum_t();
mCurrentEnum = new EnumsHelper.BEnum();
}
@Override
@@ -56,7 +56,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
// get enum comment
mCurrentEnum.mEnumComment = getEnumComment(ctx.getStart());
// get the last name (for typedef case)
List<TerminalNode> allNames = ctx.CKGENERAL_ID();
List<TerminalNode> allNames = ctx.CKGENERIC_ID();
mCurrentEnum.mEnumName = allNames.get(allNames.size() - 1).getText();
mCurrentProg.mEnums.add(mCurrentEnum);
@@ -65,7 +65,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
@Override
public void enterEntryPair(CKEnumsParser.EntryPairContext ctx) {
mCurrentEntry = new EnumsHelper.EnumEntry_t();
mCurrentEntry = new EnumsHelper.BEnumEntry();
}
@Override
@@ -73,7 +73,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
// get entry comment
mCurrentEntry.mEntryComment = mCommentsFinder.getComment(ctx.getStart(), ctx.getStop());
// get entry name
mCurrentEntry.mEntryName = ctx.CKGENERAL_ID().getText();
mCurrentEntry.mEntryName = ctx.CKGENERIC_ID().getText();
mCurrentEnum.mEntries.add(mCurrentEntry);
mCurrentEntry = null;
@@ -82,7 +82,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
@Override
public void exitEntryDirectValue(CKEnumsParser.EntryDirectValueContext ctx) {
// get all numbers
List<TerminalNode> nums = ctx.CKGENERAL_NUM();
List<TerminalNode> nums = ctx.CKGENERIC_NUM();
switch (nums.size()) {
case 1: {
@@ -120,7 +120,7 @@ public class EnumsWalker extends CKEnumsParserBaseListener {
@Override
public void exitEntryRelativeValue(CKEnumsParser.EntryRelativeValueContext ctx) {
// get all identifiers and join them
mCurrentEntry.mEntryValue = ctx.CKGENERAL_ID().stream().map(value -> value.getText())
mCurrentEntry.mEntryValue = ctx.CKGENERIC_ID().stream().map(value -> value.getText())
.collect(Collectors.joining(" | "));
// | operator appears. this enum must have flags feature

View File

@@ -0,0 +1,73 @@
import java.io.OutputStreamWriter;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonWriter {
private static JsonObject writeBEnumEntry(EnumsHelper.BEnumEntry enumEntry) {
JsonObject data = new JsonObject();
data.addProperty("name", enumEntry.mEntryName);
data.addProperty("value", enumEntry.mEntryValue);
data.addProperty("comment", enumEntry.mEntryComment);
// Export hierarchy if possible
if (enumEntry instanceof EnumsHelper.BHierarchyEnumEntry hierarchyEnumEntry) {
// We only export name in hierarchy.
// Otherwise, we may cause recursive calling.
JsonArray entries = new JsonArray();
for (EnumsHelper.BHierarchyEnumEntry subEntry : hierarchyEnumEntry.mHierarchy) {
entries.add(subEntry.mEntryName);
}
data.add("hierarchy", entries);
}
return data;
}
private static JsonObject writeBEnum(EnumsHelper.BEnum benum) {
JsonObject data = new JsonObject();
data.addProperty("name", benum.mEnumName);
data.addProperty("comment", benum.mEnumComment);
data.addProperty("can_unsigned", benum.mCanUnsigned);
data.addProperty("use_flags", benum.mUseFlags);
data.addProperty("use_flags", benum.mUseFlags);
JsonArray entries = new JsonArray();
for (EnumsHelper.BEnumEntry enumEntry : benum.mEntries) {
entries.add(writeBEnumEntry(enumEntry));
}
data.add("entries", entries);
return data;
}
private static JsonArray writeBEnumCollection(EnumsHelper.BEnumCollection enumCollection) {
JsonArray data = new JsonArray();
for (EnumsHelper.BEnum benum : enumCollection.mEnums) {
data.add(writeBEnum(benum));
}
return data;
}
private static void writeJson(String filename, EnumsHelper.BEnumCollection enumCollection) throws Exception {
OutputStreamWriter writer = CommonHelper.openOutputFile(filename);
//Gson gsonInstance = new GsonBuilder().serializeNulls().setPrettyPrinting().disableHtmlEscaping().create();
Gson gsonInstance = new GsonBuilder().serializeNulls().disableHtmlEscaping().create();
writer.write(gsonInstance.toJson(writeBEnumCollection(enumCollection)));
writer.close();
}
public static void writeEnums(String filename, EnumsHelper.BEnumCollection enumCollection) throws Exception {
writeJson(filename, enumCollection);
}
public static void writeEnum(String filename, EnumsHelper.BEnum benum) throws Exception {
// Build collection manually.
EnumsHelper.BEnumCollection collection = new EnumsHelper.BEnumCollection();
collection.mEnums.add(benum);
// Call underlying writer
writeEnums(filename, collection);
}
}

View File

@@ -0,0 +1,142 @@
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class MainRunner {
/**
* Extract an enums collection from given file.
* <p>
* This function is the most commonly used function for extracting enums.
* <p>
* This function is used for a file which only contain enum declarations. This
* is not suit for extracting CKERROR and CK_CLASSID. For these declarations,
* please use their specialized extractor as described following.
*
* @param filename The file name relative to input directory for reading.
* @return An {@linkplain EnumsHelper.BEnumCollection} instance.
* @throws Exception
*/
private static EnumsHelper.BEnumCollection getEnumsCollection(String filename) throws Exception {
String infile = CommonHelper.getInputFilePath(filename);
CommonHelper.InputFilePair pair = CommonHelper.openInputFile(infile);
CKGenericLexer lexer = new CKGenericLexer(pair.mAntlrStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CKEnumsParser parser = new CKEnumsParser(tokens);
ParseTree tree = parser.prog();
ParseTreeWalker walker = new ParseTreeWalker();
EnumsWalker worker = new EnumsWalker(tokens);
walker.walk(worker, tree);
pair.mUnderlyingStream.close();
return worker.getEnums();
}
/**
* Extract a series of "#define" syntax as an enum.
* <p>
* This function will assume that given file only contain C++ "#define" syntax.
* After reading it, it will re-organize it as an enum and return. This only is
* used by CKERROR now. But it is suit for more scenarios if there are something
* like CKERROR in the future.
*
* @param filename The file name relative to input directory for reading.
* @param assignedEnumName The desired name of organized enum instance.
* Contemporary this field should always be "CKERROR"
* because no one else is using it.
* @return An {@linkplain EnumsHelper.BEnum} instance.
* @throws Exception
*/
private static EnumsHelper.BEnum organiseDefines(String filename, String assignedEnumName) throws Exception {
String infile = CommonHelper.getInputFilePath(filename);
CommonHelper.InputFilePair pair = CommonHelper.openInputFile(infile);
CKGenericLexer lexer = new CKGenericLexer(pair.mAntlrStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CKDefinesParser parser = new CKDefinesParser(tokens);
ParseTree tree = parser.prog();
ParseTreeWalker walker = new ParseTreeWalker();
DefinesWalker worker = new DefinesWalker(tokens);
walker.walk(worker, tree);
pair.mUnderlyingStream.close();
EnumsHelper.BEnum result = worker.getEnum();
result.mEnumName = assignedEnumName;
return result;
}
/**
* Extract a series of macro define as an enum, considering its indent to build
* hierarchy.
* <p>
* This is specialized enum extractor of CK_CLASSID. The given file should use a
* series "#define" syntax to describe enum, and use Tab as the indent before
* each "#define" syntax to indicate its hierarchy.
*
* @param filename The file name relative to input directory for reading.
* @return An {@linkplain EnumsHelper.BEnum} instance. Actually it is an
* instance to {@linkplain EnumsHelper.BEnum} whose entries is
* {@linkplain EnumsHelper.BHierarchyEnumEntry}, the child class of
* {@linkplain EnumsHelper.BEnumEntry} (the entry type of common
* {@linkplain EnumsHelper.BEnum}) with extra hierarchy infos.
* @throws Exception
*/
private static EnumsHelper.BEnum organiseClassid(String filename) throws Exception {
String infile = CommonHelper.getInputFilePath(filename);
CommonHelper.InputFilePair pair = CommonHelper.openInputFile(infile);
CKGenericLexer lexer = new CKGenericLexer(pair.mAntlrStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CKDefinesParser parser = new CKDefinesParser(tokens);
ParseTree tree = parser.prog();
ParseTreeWalker walker = new ParseTreeWalker();
ClassidWalker worker = new ClassidWalker(tokens);
walker.walk(worker, tree);
pair.mUnderlyingStream.close();
EnumsHelper.BEnum result = worker.getEnum();
result.mEnumName = "CK_CLASSID";
return result;
}
public static void main(String[] args) throws Exception {
// =========== CKERROR ===========
EnumsHelper.BEnum ckerror = organiseDefines("CKERROR.txt", "CKERROR");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CKERROR.json"), ckerror);
// =========== CK_CLASSID ===========
EnumsHelper.BEnum classid = organiseClassid("CK_CLASSID.txt");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CK_CLASSID.json"), classid);
// =========== Define2 ===========
EnumsHelper.BEnumCollection def2 = getEnumsCollection("Defines2.txt");
JsonWriter.writeEnums(CommonHelper.getOutputFilePath("Defines2.json"), def2);
// =========== Combined enums ===========
EnumsHelper.BEnumCollection ck2Enums = getEnumsCollection("CKEnums.txt"),
vxEnums = getEnumsCollection("VxEnums.txt");
JsonWriter.writeEnums(CommonHelper.getOutputFilePath("CKEnums.json"), ck2Enums);
JsonWriter.writeEnums(CommonHelper.getOutputFilePath("VxEnums.json"), vxEnums);
// =========== Single enums ===========
EnumsHelper.BEnum single;
single = organiseDefines("CK_STATECHUNK_CHUNKVERSION.txt", "CK_STATECHUNK_CHUNKVERSION");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CK_STATECHUNK_CHUNKVERSION.json"), single);
single = organiseDefines("CK_STATECHUNK_DATAVERSION.txt", "CK_STATECHUNK_DATAVERSION");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CK_STATECHUNK_DATAVERSION.json"), single);
single = organiseDefines("CK_BITMAPDATA_FLAGS.txt", "CK_BITMAPDATA_FLAGS");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CK_BITMAPDATA_FLAGS.json"), single);
single = organiseDefines("CK_CAMERA_PROJECTION.txt", "CK_CAMERA_PROJECTION");
JsonWriter.writeEnum(CommonHelper.getOutputFilePath("CK_CAMERA_PROJECTION.json"), single);
// print message.
System.out.println("Done");
}
}

View File

@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -0,0 +1 @@
3.11

View File

@@ -0,0 +1,3 @@
# Enums Render
See upper level [README.md](../README.md) for informations.

View File

@@ -0,0 +1,190 @@
import json
import typing
import utils
class BEnumEntry:
"""The struct to describe the entry of an enum."""
__entry_name: str
"""The name of this entry."""
__entry_value: str | None
"""The value of this entry. None if this entry do not have explicit value."""
__entry_comment: str | None
"""The comment of this entry. None if no comment."""
def __init__(
self, entry_name: str, entry_value: str | None, entry_comment: str | None
):
self.__entry_name = entry_name
self.__entry_value = entry_value
self.__entry_comment = entry_comment
def get_entry_name(self) -> str:
"""Get the name of this entry."""
return self.__entry_name
def get_entry_value(self) -> str | None:
"""Get the value of this entry. None if this entry do not have explicit value."""
return self.__entry_value
def get_entry_comment(self) -> str | None:
"""Get the comment of this entry. None if no comment."""
return self.__entry_comment
@staticmethod
def from_json(data: dict[str, typing.Any]) -> "BEnumEntry":
return BEnumEntry(
data["name"],
data.get("value", None),
data.get("comment", None),
)
class BHierarchyEnumEntry(BEnumEntry):
"""
The specialized EnumEntry type which can store extra hierarchy info.
Used in CK_CLASSID parsing.
"""
__hierarchy: list[str]
"""
The list to store this CK_CLASSID inheritance relationship.
The first item is the oldest parent in inheritance.
The last item is self.
"""
def __init__(
self,
entry_name: str,
entry_value: str | None,
entry_comment: str | None,
hierarchy: list[str],
):
super().__init__(entry_name, entry_value, entry_comment)
self.__hierarchy = hierarchy
def iter_hierarchy(self, benum: "BEnum") -> typing.Iterator["BHierarchyEnumEntry"]:
return map(
lambda e: typing.cast(BHierarchyEnumEntry, benum.get_entry_by_name(e)),
self.__hierarchy,
)
@staticmethod
def from_json(data: dict[str, typing.Any]) -> "BHierarchyEnumEntry":
return BHierarchyEnumEntry(
data["name"],
data.get("value", None),
data.get("comment", None),
data["hierarchy"],
)
class BEnum:
"""The struct to describe an enum."""
__enum_name: str
"""The name of this enum."""
__enum_comment: str | None
"""The comment of this enum. None if no comment."""
__can_unsigned: bool
"""True if this enum can use unsigned integer as its underlying type."""
__use_flags: bool
"""True if this enum will use flags feature (supporting OR, AND, operators)."""
__entries: list[BEnumEntry]
"""The list to store entries of this enum."""
__entries_map: dict[str, BEnumEntry]
"""The name map for `entries`."""
def __init__(
self,
enum_name: str,
enum_comment: str | None,
can_unsigned: bool,
use_flags: bool,
entries: list[BEnumEntry],
):
self.__enum_name = enum_name
self.__enum_comment = enum_comment
self.__can_unsigned = can_unsigned
self.__use_flags = use_flags
self.__entries = entries
self.__entries_map = {e.get_entry_name(): e for e in entries}
def get_enum_name(self) -> str:
"""Get the name of this enum."""
return self.__enum_name
def get_enum_comment(self) -> str | None:
"""Get the comment of this enum. None if no comment."""
return self.__enum_comment
def get_can_unsigned(self) -> bool:
"""True if this enum can use unsigned integer as its underlying type."""
return self.__can_unsigned
def get_use_flags(self) -> bool:
"""True if this enum will use flags feature (supporting OR, AND, operators)."""
return self.__use_flags
def iter_entries(self) -> typing.Iterator[BEnumEntry]:
"""Get the iterator of entries of this enum."""
return iter(self.__entries)
def get_entry_by_name(self, name: str) -> BEnumEntry:
return self.__entries_map[name]
@staticmethod
def from_json(data: dict[str, typing.Any]) -> "BEnum":
return BEnum(
data["name"],
data.get("comment", None),
data["can_unsigned"],
data["use_flags"],
list(map(lambda i: BEnum.__create_entry_by_content(i), data["entries"])),
)
@staticmethod
def __create_entry_by_content(data: dict[str, typing.Any]) -> "BEnumEntry":
if "hierarchy" in data:
return BHierarchyEnumEntry.from_json(data)
else:
return BEnumEntry.from_json(data)
class BEnumCollection:
"""The struct to describe a collection of enums."""
__enums: list[BEnum]
"""The list to store enums."""
def __init__(self, enums: list[BEnum]):
self.__enums = enums
def iter_enums(self) -> typing.Iterator[BEnum]:
"""Get the iterator of enums."""
return iter(self.__enums)
def get_enums_count(self) -> int:
"""Get the count of enums."""
return len(self.__enums)
def get_enum_by_index(self, index: int) -> BEnum:
"""Get the enum by index."""
return self.__enums[index]
@staticmethod
def from_json(data: list[typing.Any]) -> "BEnumCollection":
return BEnumCollection(list(map(lambda i: BEnum.from_json(i), data)))
def load_enums(filename: str) -> BEnumCollection:
with open(utils.get_input_file_path(filename), "r", encoding="utf-8") as f:
return BEnumCollection.from_json(json.load(f))
def load_enum(filename: str) -> BEnum:
collection = load_enums(filename)
assert collection.get_enums_count() == 1
return collection.get_enum_by_index(0)

View File

@@ -0,0 +1,106 @@
import json_loader
import template_render
import utils
from utils import CKParts
def main():
render = template_render.TemplateRender()
# CKERROR
ckerror = json_loader.load_enum("CKERROR.json")
render.render_cpp_enum("CKERROR.hpp", ckerror)
render.render_py_enum("CKERROR.py", ckerror)
render.render_cs_enum("CKERROR.cs", ckerror)
render.render_cpp_ckerror_docstring("CKERROR.docstring.hpp", "CKERROR.docstring.cpp", ckerror)
render.render_py_enum_docstring("CKERROR.docstring.py", ckerror)
render.render_cs_enum_docstring("CKERROR.docstring.cs", ckerror)
render.render_rs_enum_docstring("CKERROR.docstring.rs", ckerror)
# CK_CLASSID
classid = json_loader.load_enum("CK_CLASSID.json")
render.render_cpp_enum("CK_CLASSID.hpp", classid)
render.render_py_enum("CK_CLASSID.py", classid)
render.render_cs_enum("CK_CLASSID.cs", classid)
render.render_rs_enum("CK_CLASSID.rs", classid)
render.render_cpp_ckclassid_docstring("CK_CLASSID.docstring.hpp", "CK_CLASSID.docstring.cpp", classid)
render.render_py_enum_docstring("CK_CLASSID.docstring.py", classid)
render.render_cs_enum_docstring("CK_CLASSID.docstring.cs", classid)
render.render_rs_enum_docstring("CK_CLASSID.docstring.rs", classid)
# Define2
def2 = json_loader.load_enums("Defines2.json")
render.render_cpp_enums("Defines2.hpp", def2)
render.render_py_enums("Defines2.py", def2)
render.render_cs_enums("Defines2.cs", def2)
render.render_rs_enums("Defines2.rs", def2)
# Define2 do not need annotation output.
# Because they are CKStateChunk used value which are not exposed to outside.
# Combined Enums
ck2Enums = json_loader.load_enums("CKEnums.json")
vxEnums = json_loader.load_enums("VxEnums.json")
render.render_cpp_enums("CKEnums.hpp", ck2Enums)
render.render_py_enums("CKEnums.py", ck2Enums)
render.render_cs_enums("CKEnums.cs", ck2Enums)
render.render_rs_enums("CKEnums.rs", ck2Enums)
render.render_cpp_enum_docstrings("CKEnums.docstring.hpp", "CKEnums.docstring.cpp", ck2Enums, CKParts.CK2)
render.render_py_enum_docstrings("CKEnums.docstring.py", ck2Enums)
render.render_cs_enum_docstrings("CKEnums.docstring.cs", ck2Enums)
render.render_rs_enum_docstrings("CKEnums.docstring.rs", ck2Enums)
render.render_cpp_enums("VxEnums.hpp", vxEnums)
render.render_py_enums("VxEnums.py", vxEnums)
render.render_cs_enums("VxEnums.cs", vxEnums)
render.render_rs_enums("VxEnums.rs", vxEnums)
render.render_cpp_enum_docstrings("VxEnums.docstring.hpp", "VxEnums.docstring.cpp", vxEnums, CKParts.VxMath)
render.render_py_enum_docstrings("VxEnums.docstring.py", vxEnums)
render.render_cs_enum_docstrings("VxEnums.docstring.cs", vxEnums)
render.render_rs_enum_docstrings("VxEnums.docstring.rs", vxEnums)
# Single enums
single = json_loader.load_enum("CK_STATECHUNK_CHUNKVERSION.json")
render.render_cpp_enum("CK_STATECHUNK_CHUNKVERSION.hpp", single)
render.render_py_enum("CK_STATECHUNK_CHUNKVERSION.py", single)
render.render_cs_enum("CK_STATECHUNK_CHUNKVERSION.cs", single)
render.render_rs_enum("CK_STATECHUNK_CHUNKVERSION.rs", single)
render.render_cpp_enum_docstring("CK_STATECHUNK_CHUNKVERSION.docstring.hpp", "CK_STATECHUNK_CHUNKVERSION.docstring.cpp", single, CKParts.CK2)
render.render_py_enum_docstring("CK_STATECHUNK_CHUNKVERSION.docstring.py", single)
render.render_cs_enum_docstring("CK_STATECHUNK_CHUNKVERSION.docstring.cs", single)
render.render_rs_enum_docstring("CK_STATECHUNK_CHUNKVERSION.docstring.rs", single)
single = json_loader.load_enum("CK_STATECHUNK_DATAVERSION.json")
render.render_cpp_enum("CK_STATECHUNK_DATAVERSION.hpp", single)
render.render_py_enum("CK_STATECHUNK_DATAVERSION.py", single)
render.render_cs_enum("CK_STATECHUNK_DATAVERSION.cs", single)
render.render_rs_enum("CK_STATECHUNK_DATAVERSION.rs", single)
render.render_cpp_enum_docstring("CK_STATECHUNK_DATAVERSION.docstring.hpp", "CK_STATECHUNK_DATAVERSION.docstring.cpp", single, CKParts.CK2)
render.render_py_enum_docstring("CK_STATECHUNK_DATAVERSION.docstring.py", single)
render.render_cs_enum_docstring("CK_STATECHUNK_DATAVERSION.docstring.cs", single)
render.render_rs_enum_docstring("CK_STATECHUNK_DATAVERSION.docstring.rs", single)
single = json_loader.load_enum("CK_BITMAPDATA_FLAGS.json")
render.render_cpp_enum("CK_BITMAPDATA_FLAGS.hpp", single)
render.render_py_enum("CK_BITMAPDATA_FLAGS.py", single)
render.render_cs_enum("CK_BITMAPDATA_FLAGS.cs", single)
render.render_rs_enum("CK_BITMAPDATA_FLAGS.rs", single)
render.render_cpp_enum_docstring("CK_BITMAPDATA_FLAGS.docstring.hpp", "CK_BITMAPDATA_FLAGS.docstring.cpp", single, CKParts.CK2)
render.render_py_enum_docstring("CK_BITMAPDATA_FLAGS.docstring.py", single)
render.render_cs_enum_docstring("CK_BITMAPDATA_FLAGS.docstring.cs", single)
render.render_rs_enum_docstring("CK_BITMAPDATA_FLAGS.docstring.rs", single)
single = json_loader.load_enum("CK_CAMERA_PROJECTION.json")
render.render_cpp_enum("CK_CAMERA_PROJECTION.hpp", single)
render.render_py_enum("CK_CAMERA_PROJECTION.py", single)
render.render_cs_enum("CK_CAMERA_PROJECTION.cs", single)
render.render_rs_enum("CK_CAMERA_PROJECTION.rs", single)
render.render_cpp_enum_docstring("CK_CAMERA_PROJECTION.docstring.hpp", "CK_CAMERA_PROJECTION.docstring.cpp", single, CKParts.CK2)
render.render_py_enum_docstring("CK_CAMERA_PROJECTION.docstring.py", single)
render.render_cs_enum_docstring("CK_CAMERA_PROJECTION.docstring.cs", single)
render.render_rs_enum_docstring("CK_CAMERA_PROJECTION.docstring.rs", single)
print("Done")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
[project]
name = "enums-render"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"jinja2==3.1.6",
]

View File

@@ -0,0 +1,195 @@
import jinja2
import jinja2.filters
import re
import typing
from json_loader import BEnumCollection, BEnum
from utils import CKParts
import utils
class RenderUtils:
"""Possible used functions for jinja when rendering templates"""
TRANTABLE_ESCAPE_STRING: typing.ClassVar[dict[int, str]] = str.maketrans({
"\\": "\\\\",
"\t": "\\t",
"\b": "\\b",
"\n": "\\n",
"\r": "\\r",
"\f": "\\f",
"\"": "\\\"",
})
@staticmethod
def escape_string(strl: str) -> str:
"""
Escape string
Escape all characters which are invalid in string quote.
:param strl: The string need to be escaped.
:return: The escaped string.
"""
return strl.translate(RenderUtils.TRANTABLE_ESCAPE_STRING)
TRANTABLE_REMOVE_EOL: typing.ClassVar[dict[int, str]] = str.maketrans({
"\n": "",
"\r": "",
})
@staticmethod
def remove_eol(strl: str) -> str:
"""
Remove EOL of given string.
When rendering code, adding line comment is a common case.
However, comment may have EOL.
So when adding this to line comment, we need to remove all EOL.
"""
return strl.translate(RenderUtils.TRANTABLE_REMOVE_EOL)
REGEX_PY_TO_LITERAL_NUMBER: typing.ClassVar[re.Pattern] = re.compile("[ulUL]+$")
@staticmethod
def to_py_num_literal(numstr: str) -> str:
"""
Convert given string into Python number literal style.
Number literal declaration in C++ is different with Python.
Previous one allow adding suffix like "UL" to indicate its type, but Python don't allow this.
So this function actually just remove "UL" suffix.
This function is only served for Python code generation.
:param numstr: The captured number.
:return: The Python style number string.
"""
return RenderUtils.REGEX_PY_TO_LITERAL_NUMBER.sub("", numstr, 1)
REGEX_PY_EXT_HUMANRDABLE_ENTRY_NAME: typing.ClassVar[re.Pattern] = re.compile("^[a-zA-Z0-9]+_")
@staticmethod
def underline_to_camel(entry_name: str) -> str:
"""
Convert given capital underlying name into camel name.
This function is only served for Python code generation.
As you noticed, almost entries of CK enums are fully capital and splitted by
underline. This is really not good for human reading, especially those who
are not programmer. So this function will try give these programmer-oriented
entry name a human readable name as its display name. However, this extract
method is not perfect. It simply do some split and replacement so the
generated content may still not good for reader.
:param entry_name: The name of enum entry
:return: A human readable entry name. No guaranteen that return value is must human readable.
"""
# remove first part (any content before underline '_')
entry_name = RenderUtils.REGEX_PY_EXT_HUMANRDABLE_ENTRY_NAME.sub("", entry_name, 1)
# then convert result into camel
return ' '.join(map(lambda s: s[0:1].upper() + s[1:].lower(), entry_name.split('_')))
class TemplateRender:
"""Render templates to code files"""
__loader: jinja2.BaseLoader
__environment: jinja2.Environment
def __init__(self) -> None:
self.__loader = jinja2.FileSystemLoader(utils.get_template_directory())
self.__environment = jinja2.Environment(loader=self.__loader)
# prepare filters
self.__environment.filters['some_or_blank'] = lambda s: "" if s is None else s
self.__environment.filters['escape_string'] = lambda s: RenderUtils.escape_string(s)
self.__environment.filters['block_comment'] = lambda s, fmt: jinja2.filters.do_indent(s, fmt, True, True)
self.__environment.filters['line_comment'] = lambda s: RenderUtils.remove_eol(s)
def __render(self, template_name: str, dest_filename: str, payload: BEnumCollection, extra: dict[str, typing.Any] = {}) -> None:
# prepare template argument
template_argument: dict[str, typing.Any] = {
'payload': payload,
'extra': extra,
'utils': RenderUtils,
}
# fetch template
template = self.__environment.get_template(str(template_name))
# render template and save
with open(utils.get_output_file_path(dest_filename), 'w', encoding='utf-8') as f:
f.write(template.render(**template_argument))
def __wrap_single_enum(self, benum: BEnum) -> BEnumCollection:
return BEnumCollection([benum, ])
# region: C++ Header and Sources
def render_cpp_enums(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.hpp.jinja', filename, benums)
def render_cpp_enum(self, filename: str, benum: BEnum) -> None:
self.render_cpp_enums(filename, self.__wrap_single_enum(benum))
def render_cpp_enum_docstrings(self, hpp_filename: str, cpp_filename: str, benums: BEnumCollection, parts: CKParts) -> None:
self.__render('generic.docstring.hpp.jinja', hpp_filename, benums, {'parts': parts})
self.__render('generic.docstring.cpp.jinja', cpp_filename, benums, {'parts': parts})
def render_cpp_enum_docstring(self, hpp_filename: str, cpp_filename: str, benum: BEnum, parts: CKParts) -> None:
self.render_cpp_enum_docstrings(hpp_filename, cpp_filename, self.__wrap_single_enum(benum), parts)
def render_cpp_ckerror_docstring(self, hpp_filename: str, cpp_filename: str, benums: BEnum) -> None:
self.__render('CKERROR.docstring.hpp.jinja', hpp_filename, self.__wrap_single_enum(benums))
self.__render('CKERROR.docstring.cpp.jinja', cpp_filename, self.__wrap_single_enum(benums))
def render_cpp_ckclassid_docstring(self, hpp_filename: str, cpp_filename: str, benums: BEnum) -> None:
self.__render('CK_CLASSID.docstring.hpp.jinja', hpp_filename, self.__wrap_single_enum(benums))
self.__render('CK_CLASSID.docstring.cpp.jinja', cpp_filename, self.__wrap_single_enum(benums))
# endregion
# region: Python
def render_py_enums(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.py.jinja', filename, benums)
def render_py_enum(self, filename: str, benum: BEnum) -> None:
self.render_py_enums(filename, self.__wrap_single_enum(benum))
def render_py_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.docstring.py.jinja', filename, benums)
def render_py_enum_docstring(self, filename: str, benum: BEnum) -> None:
self.render_py_enum_docstrings(filename, self.__wrap_single_enum(benum))
# endregion
# region: C#
def render_cs_enums(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.cs.jinja', filename, benums)
def render_cs_enum(self, filename: str, benum: BEnum) -> None:
self.render_cs_enums(filename, self.__wrap_single_enum(benum))
def render_cs_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.docstring.cs.jinja', filename, benums)
def render_cs_enum_docstring(self, filename: str, benum: BEnum) -> None:
self.render_cs_enum_docstrings(filename, self.__wrap_single_enum(benum))
# endregion
# region: Rust
def render_rs_enums(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.rs.jinja', filename, benums)
def render_rs_enum(self, filename: str, benum: BEnum) -> None:
self.render_rs_enums(filename, self.__wrap_single_enum(benum))
def render_rs_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None:
self.__render('generic.docstring.rs.jinja', filename, benums)
def render_rs_enum_docstring(self, filename: str, benum: BEnum) -> None:
self.render_rs_enum_docstrings(filename, self.__wrap_single_enum(benum))
# endregion

View File

@@ -0,0 +1,6 @@
{%- set benum = payload.iter_enums() | first %}
const CkErrorReflectionArray CKERROR {
{%- for entry in benum.iter_entries() %}
{ LibCmo::CK2::CKERROR::{{ entry.get_entry_name() }}, { u8"{{ entry.get_entry_name() }}", u8"{{- entry.get_entry_comment() | some_or_blank | escape_string }}" } },
{%- endfor %}
};

View File

@@ -0,0 +1,4 @@
struct CkErrorReflection { const char8_t* mName; const char8_t* mDescription; };
using CkErrorReflectionArray = std::vector<std::pair<LibCmo::CK2::CKERROR, CkErrorReflection>>;
const CkErrorReflectionArray CKERROR;

View File

@@ -0,0 +1,6 @@
{%- set benum = payload.iter_enums() | first %}
const CkClassidReflectionArray CK_CLASSID {
{%- for entry in benum.iter_entries() %}
{ LibCmo::CK2::CK_CLASSID::{{ entry.get_entry_name() }}, { { {%- for item in entry.iter_hierarchy(benum) %} u8"{{ item.get_entry_name() }}" {%- if not loop.last -%}, {%- endif %} {%- endfor %} } } },
{%- endfor %}
};

View File

@@ -0,0 +1,5 @@
struct CkClassidReflection { std::vector<const char8_t*> mHierarchy; };
using CkClassidReflectionArray = std::vector<std::pair<LibCmo::CK2::CK_CLASSID, CkClassidReflection>>;
const CkClassidReflectionArray CK_CLASSID;

View File

@@ -0,0 +1,11 @@
{%- for benum in payload.iter_enums() %}
{%- if benum.get_enum_comment() is not none %}
{{ benum.get_enum_comment() | block_comment('/// ') }}
{%- endif %} {%- if benum.get_use_flags() %}
[Flags]{%- endif %}
public enum {{ benum.get_enum_name() }} : {% if benum.get_can_unsigned() -%} uint {%- else -%} int {%- endif %} {
{%- 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 %}
{%- endfor %}
};
{%- endfor %}

View File

@@ -0,0 +1,7 @@
{%- for benum in payload.iter_enums() %}
const GenericReflectionArray<LibCmo::{{ extra.parts }}::{{ benum.get_enum_name() }}> {{ benum.get_enum_name() }} {
{%- for entry in benum.iter_entries() %}
{ LibCmo::{{ extra.parts }}::{{ benum.get_enum_name() }}::{{ entry.get_entry_name() }}, { u8"{{ entry.get_entry_name() }}" } },
{%- endfor %}
};
{%- endfor %}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
{% for benum in payload.iter_enums() %}
public static readonly Dictionary<{{ benum.get_enum_name() }}, string> {{ benum.get_enum_name() }} = new Dictionary<{{ benum.get_enum_name() }}, string>() {
{%- for entry in benum.iter_entries() %}
{ {{ benum.get_enum_name() }}.{{ entry.get_entry_name() }}, "{{ entry.get_entry_name() }}" },
{%- endfor %}
};
{%- endfor %}

View File

@@ -0,0 +1,8 @@
struct GenericReflection { const char8_t* mName; };
template<typename T>
requires std::is_enum_v<T>
using GenericReflectionArray = std::vector<std::pair<T, GenericReflection>>;
{% for benum in payload.iter_enums() %}
extern const GenericReflectionArray<LibCmo::{{ extra.parts }}::{{ benum.get_enum_name() }}> {{ benum.get_enum_name() }};
{%- endfor %}

Some files were not shown because too many files have changed in this diff Show More