Fix #237 - anonymous nested struct/unions, fix #236 - unnamed enums

This commit is contained in:
Ganesh Viswanathan 2020-07-02 23:55:26 -05:00
commit f191ea7244
6 changed files with 139 additions and 15 deletions

View file

@ -25,6 +25,8 @@ https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.1
- `git.nim` has been removed. This module was an artifact from the early days and was renamed to `build.nim` back in v0.2.0. - `git.nim` has been removed. This module was an artifact from the early days and was renamed to `build.nim` back in v0.2.0.
- Nameless enum values are no longer typed to the made-up enum type name, they are instead typed as `cint` to match the underlying type. This allows using such enums without having to depend on the made-up name which could change if enum ordering changes upstream. [#236][i236] (since v0.6.1)
### New functionality ### New functionality
- `getHeader()` now detects and links against `.lib` files as part of enabling Conan.io. Not all `.lib` files are compatible with MinGW as already stated above but for those that work, this is a required capability. - `getHeader()` now detects and links against `.lib` files as part of enabling Conan.io. Not all `.lib` files are compatible with MinGW as already stated above but for those that work, this is a required capability.
@ -35,6 +37,8 @@ https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.1
- `cImport()` can now write the generated wrapper output to a user-defined file with the `nimFile` param. [#127][i127] (since v0.6.1) - `cImport()` can now write the generated wrapper output to a user-defined file with the `nimFile` param. [#127][i127] (since v0.6.1)
- Nimterop now supports anonymous nested structs/unions but it only works correctly for unions when `noHeader` is turned off (the default). This is because Nim does not support nested structs/unions and is unaware of the underlying memory structure. [#237][i237] (since v0.6.1)
### Other improvements ### Other improvements
- Generated wrappers no longer depend on nimterop being present - no more `import nimterop/types`. Supporting code is directly included in the wrapper output and only when required. E.g. enum macro is only included if wrapper contains enums. [#125][i125] (since v0.6.1) - Generated wrappers no longer depend on nimterop being present - no more `import nimterop/types`. Supporting code is directly included in the wrapper output and only when required. E.g. enum macro is only included if wrapper contains enums. [#125][i125] (since v0.6.1)
@ -128,4 +132,6 @@ https://github.com/nimterop/nimterop/compare/v0.4.4...v0.5.4
[i181]: https://github.com/nimterop/nimterop/issues/181 [i181]: https://github.com/nimterop/nimterop/issues/181
[i196]: https://github.com/nimterop/nimterop/issues/196 [i196]: https://github.com/nimterop/nimterop/issues/196
[i197]: https://github.com/nimterop/nimterop/issues/197 [i197]: https://github.com/nimterop/nimterop/issues/197
[i200]: https://github.com/nimterop/nimterop/issues/200 [i200]: https://github.com/nimterop/nimterop/issues/200
[i236]: https://github.com/nimterop/nimterop/issues/236
[i237]: https://github.com/nimterop/nimterop/issues/237

View file

@ -28,8 +28,9 @@ This will download and install nimterop in the standard Nimble package location,
## Usage ## Usage
Nimterop can be used in two ways: Nimterop can be used in three ways:
- Creating a wrapper file - a `.nim` file that contains calls to the high-level API that can download and build the C library as well as generate the required Nim code to interface with the library. This wrapper file can then be imported into Nim code like any other module and it will be processed at compile time. - Creating a wrapper file - a `.nim` file that contains calls to the high-level API that can download and build the C library as well as generate the required Nim code to interface with the library. This wrapper file can then be imported into Nim code like any other module and it will be processed at compile time.
- Same as the first option except using the `nimFile` param to `cImport()` to write the generated wrapper to a file during build time just once and then importing that generated wrapper into the application like any other Nim module.
- Using the command line `toast` tool to generate the Nim code which can then be stored into a file and imported separately. - Using the command line `toast` tool to generate the Nim code which can then be stored into a file and imported separately.
Any combination of the above is possible - only download, build or wrapping and nimterop avoids imposing any particular workflow. Any combination of the above is possible - only download, build or wrapping and nimterop avoids imposing any particular workflow.
@ -169,7 +170,7 @@ For types, `{.header: "header.h".}` informs Nim that `header.h` has the symbol a
For functions, `{.header.}` works the same as types and can be omitted if preferred. The `{.importc.}` pragma is still required, unlike types since functions need to be linked to the implementation in the library. The user will need to provide this information at link time with `{.passL.}` and linking to a library with `-lheader` or `path/to/libheader.a`. It is also possible to just use `cCompile()` or `{.compile.}` to compile some C source files which contain the implementation. For functions, `{.header.}` works the same as types and can be omitted if preferred. The `{.importc.}` pragma is still required, unlike types since functions need to be linked to the implementation in the library. The user will need to provide this information at link time with `{.passL.}` and linking to a library with `-lheader` or `path/to/libheader.a`. It is also possible to just use `cCompile()` or `{.compile.}` to compile some C source files which contain the implementation.
While `{.header.}` can be omitted for convenience, it does prevent wrapping of `static inline` functions as well as type checking of the wrapper ABI with `-d:checkAbi` at compile time. The user will need to choose based on the library in question. While `{.header.}` can be omitted for convenience, it does prevent wrapping of `static inline` functions as well as type checking of the wrapper ABI with `-d:checkAbi` at compile time. Further, anonymous nested structs/unions within unions will be rendered incorrectly by Nim since it is unaware of the true memory structure of the type. The user will need to choose based on the library in question.
Going further, the `{.dynlib: "path/to/libheader.so".}` pragma can be used to inform Nim to load the library at runtime and link the function instead of linking at compile time. This enables creation of a wrapper that does not need the library present at compile time. Going further, the `{.dynlib: "path/to/libheader.so".}` pragma can be used to inform Nim to load the library at runtime and link the function instead of linking at compile time. This enables creation of a wrapper that does not need the library present at compile time.

View file

@ -704,6 +704,17 @@ proc newRecListTree(gState: State, name: string, node: TSNode): PNode =
edecl = node[i].anyChildInTree("enumerator_list") edecl = node[i].anyChildInTree("enumerator_list")
commentNodes = gState.getCommentNodes(node[i]) commentNodes = gState.getCommentNodes(node[i])
# Check if struct/union field is anonymous
isNamedField = block:
var found = false
if not fdecl.isNil:
var sibling = fdecl.tsNodeParent().tsNodeNextNamedSibling()
while not sibling.isNil and sibling.getName() != "field_identifier":
sibling = sibling.tsNodeNextNamedSibling()
if not sibling.isNil:
found = true
found
# `tname` is name of nested struct / union / enum just # `tname` is name of nested struct / union / enum just
# added, passed on as type name for field in `newIdentDefs()` # added, passed on as type name for field in `newIdentDefs()`
(processed, tname) = (processed, tname) =
@ -725,11 +736,48 @@ proc newRecListTree(gState: State, name: string, node: TSNode): PNode =
if processed != success: if processed != success:
return nil return nil
# Add nkIdentDefs for each field if not fdecl.isNil and not isNamedField:
for field in gState.newIdentDefs(name, node[i], i, ftname = tname, exported = true): # Since anonymous, add fields directly to this struct/union
if not field.isNil:
field.comment = gState.getCommentsStr(commentNodes) # nkTypeDef( <= last
result.add field # nkPragmaExpr(
# ..
# ),
# nkEmpty(),
# nkObjectTy( <= last[2]
# nkEmpty(),
# nkEmpty(),
# nkRecList( <= last[2][2]
# nkIdentDefs( <= field1
# ..
# ),
# nkIdentDefs( <= field2
# ..
# )
# )
# )
# )
let
last = gState.typeSection[^1]
obj =
if last.len > 2 and last[2].kind == nkObjectTy:
last[2]
else: nil
recList =
if not obj.isNil and obj.len > 2 and obj[2].kind == nkRecList:
obj[2]
else: nil
if not recList.isNil:
for identdef in recList:
result.add identdef
gState.typeSection.sons.del(gState.typeSection.len-1)
else:
# Add nkIdentDefs for each field
for field in gState.newIdentDefs(name, node[i], i, ftname = tname, exported = true):
if not field.isNil:
field.comment = gState.getCommentsStr(commentNodes)
result.add field
proc addTypeObject(gState: State, node: TSNode, typeDef: PNode = nil, fname = "", istype = false, union = false) = proc addTypeObject(gState: State, node: TSNode, typeDef: PNode = nil, fname = "", istype = false, union = false) =
# Add a type of object # Add a type of object
@ -1444,14 +1492,16 @@ proc addEnum(gState: State, node: TSNode) =
fval = "" fval = ""
if prev.Bl: if prev.Bl:
# Starting default value # Starting default value
fval = &"(0).{name}" fval = &"(0)"
else: else:
# One greater than previous # One greater than previous
fval = &"({prev} + 1).{name}" fval = &"({prev} + 1)"
if en.len > 1 and en[1].getName() in gEnumVals: if en.len > 1 and en[1].getName() in gEnumVals:
# Enum value specified, evaluate later, don't use calculated value
fieldDeclarations.add((fname, forigname, "", gState.getNodeVal(en[1]), commentNodes)) fieldDeclarations.add((fname, forigname, "", gState.getNodeVal(en[1]), commentNodes))
else: else:
# Set calculated value
fieldDeclarations.add((fname, forigname, fval, "", commentNodes)) fieldDeclarations.add((fname, forigname, fval, "", commentNodes))
fnames.incl fname fnames.incl fname
@ -1464,10 +1514,18 @@ proc addEnum(gState: State, node: TSNode) =
# parseCExpression requires all const identifiers to be present for the enum # parseCExpression requires all const identifiers to be present for the enum
for (fname, forigname, fval, cexpr, commentNodes) in fieldDeclarations: for (fname, forigname, fval, cexpr, commentNodes) in fieldDeclarations:
let let
fval = fval = block:
var fval = fval
if fval.Bl: if fval.Bl:
"(" & $gState.parseCExpression(cexpr, name) & ")." & name # Evaluate enum value from expression
else: fval fval = &"({$gState.parseCExpression(cexpr, name)})"
if origname.nBl:
# Named enum so cast to type - #236
fval &= &".{name}"
else:
# Cast to cint to match underlying type
fval &= ".cint"
fval
# Cannot use newConstDef() since parseString(fval) adds backticks to and/or # Cannot use newConstDef() since parseString(fval) adds backticks to and/or
constNode = gState.parseString(&"const {fname}* = {fval}")[0][0] constNode = gState.parseString(&"const {fname}* = {fval}")[0][0]

View file

@ -271,6 +271,27 @@ struct TestMyInt {
MyInt f1; MyInt f1;
}; };
// Issue #237
typedef union sx_ivec3 {
struct {
int x;
int y;
struct z {
int z;
};
};
int n[3];
} sx_ivec3;
// Issue #236
enum {
SG_INVALID_ID = 0,
SG_NUM_SHADER_STAGES = 2,
SG_MAX_MIPMAPS = 16,
SG_MAX_TEXTUREARRAY_LAYERS = 128
};
// DUPLICATES // DUPLICATES
@ -546,6 +567,26 @@ struct TestMyInt {
MyInt f1; MyInt f1;
}; };
// Issue #237
typedef union sx_ivec3 {
struct {
int x;
int y;
struct z {
int z;
};
};
int n[3];
} sx_ivec3;
// Issue #236
enum {
SG_INVALID_ID = 0,
SG_NUM_SHADER_STAGES = 2,
SG_MAX_MIPMAPS = 16,
SG_MAX_TEXTUREARRAY_LAYERS = 128
};
#endif #endif

View file

@ -353,6 +353,7 @@ checkPragmas(U2, pHeaderBy & @["union"], istype = false)
var u2: U2 var u2: U2
u2.f1 = addr a15.a2[0] u2.f1 = addr a15.a2[0]
assert PANEL_WINDOW is nk_panel_type
assert PANEL_WINDOW == 1 assert PANEL_WINDOW == 1
assert PANEL_GROUP == 2 assert PANEL_GROUP == 2
assert PANEL_POPUP == 4 assert PANEL_POPUP == 4
@ -497,4 +498,21 @@ when not defined(NOHEADER):
when declared(MyInt): when declared(MyInt):
assert false, "MyInt is defined!" assert false, "MyInt is defined!"
testFields(TestMyInt, "f1!cint") testFields(TestMyInt, "f1!cint")
checkPragmas(TestMyInt, pHeaderBy, isType = false) checkPragmas(TestMyInt, pHeaderBy, isType = false)
# #237
assert sx_ivec3 is object
testFields(sx_ivec3, "x|y|z|n!cint|cint|cint|array[3, cint]")
checkPragmas(sx_ivec3, pHeaderBy & @["union"], istype = false)
var sx: sx_ivec3
sx.x = 5
assert sx.n[0] == 5
when not defined(NOHEADER):
# Nim doesn't know of the anonymous nested struct so when the header
# isn't present, the test below breaks
sx.n[1] = 4
assert sx.y == 4
# #236
assert SG_MAX_MIPMAPS is cint
assert SG_MAX_MIPMAPS == 16

View file

@ -70,7 +70,7 @@ var
e: ENUM e: ENUM
e2: ENUM2 = enum5 e2: ENUM2 = enum5
e3: Enum_testh1 = enum7 e3 = enum7
e4: ENUM4 = enum11 e4: ENUM4 = enum11
vptr: VOIDPTR vptr: VOIDPTR