diff --git a/CHANGES.md b/CHANGES.md index 43f1d34..e85d261 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 \ No newline at end of file +[i200]: https://github.com/nimterop/nimterop/issues/200 +[i236]: https://github.com/nimterop/nimterop/issues/236 +[i237]: https://github.com/nimterop/nimterop/issues/237 \ No newline at end of file diff --git a/README.md b/README.md index b054d97..f9e9307 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/nimterop/toastlib/ast2.nim b/nimterop/toastlib/ast2.nim index 43ec8d3..fb2b55f 100644 --- a/nimterop/toastlib/ast2.nim +++ b/nimterop/toastlib/ast2.nim @@ -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] diff --git a/tests/include/tast2.h b/tests/include/tast2.h index 89ad486..06a0078 100644 --- a/tests/include/tast2.h +++ b/tests/include/tast2.h @@ -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 diff --git a/tests/tast2.nim b/tests/tast2.nim index 669ec98..ade5dff 100644 --- a/tests/tast2.nim +++ b/tests/tast2.nim @@ -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) \ No newline at end of file +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 \ No newline at end of file diff --git a/tests/tnimterop_c.nim b/tests/tnimterop_c.nim index ef814ea..ac7193c 100644 --- a/tests/tnimterop_c.nim +++ b/tests/tnimterop_c.nim @@ -70,7 +70,7 @@ var e: ENUM e2: ENUM2 = enum5 - e3: Enum_testh1 = enum7 + e3 = enum7 e4: ENUM4 = enum11 vptr: VOIDPTR