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.
- 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
- `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)
- 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
- 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
[i196]: https://github.com/nimterop/nimterop/issues/196
[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
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.
- 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.
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.
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.

View file

@ -704,6 +704,17 @@ proc newRecListTree(gState: State, name: string, node: TSNode): PNode =
edecl = node[i].anyChildInTree("enumerator_list")
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
# added, passed on as type name for field in `newIdentDefs()`
(processed, tname) =
@ -725,11 +736,48 @@ proc newRecListTree(gState: State, name: string, node: TSNode): PNode =
if processed != success:
return nil
# 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
if not fdecl.isNil and not isNamedField:
# Since anonymous, add fields directly to this struct/union
# nkTypeDef( <= last
# 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) =
# Add a type of object
@ -1444,14 +1492,16 @@ proc addEnum(gState: State, node: TSNode) =
fval = ""
if prev.Bl:
# Starting default value
fval = &"(0).{name}"
fval = &"(0)"
else:
# One greater than previous
fval = &"({prev} + 1).{name}"
fval = &"({prev} + 1)"
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))
else:
# Set calculated value
fieldDeclarations.add((fname, forigname, fval, "", commentNodes))
fnames.incl fname
@ -1464,10 +1514,18 @@ proc addEnum(gState: State, node: TSNode) =
# parseCExpression requires all const identifiers to be present for the enum
for (fname, forigname, fval, cexpr, commentNodes) in fieldDeclarations:
let
fval =
fval = block:
var fval = fval
if fval.Bl:
"(" & $gState.parseCExpression(cexpr, name) & ")." & name
else: fval
# Evaluate enum value from expression
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
constNode = gState.parseString(&"const {fname}* = {fval}")[0][0]

View file

@ -271,6 +271,27 @@ struct TestMyInt {
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
@ -546,6 +567,26 @@ struct TestMyInt {
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

View file

@ -353,6 +353,7 @@ checkPragmas(U2, pHeaderBy & @["union"], istype = false)
var u2: U2
u2.f1 = addr a15.a2[0]
assert PANEL_WINDOW is nk_panel_type
assert PANEL_WINDOW == 1
assert PANEL_GROUP == 2
assert PANEL_POPUP == 4
@ -497,4 +498,21 @@ when not defined(NOHEADER):
when declared(MyInt):
assert false, "MyInt is defined!"
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
e2: ENUM2 = enum5
e3: Enum_testh1 = enum7
e3 = enum7
e4: ENUM4 = enum11
vptr: VOIDPTR