diff --git a/CHANGELOG.md b/CHANGELOG.md index f8cbea17..cf8c0e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ + * Improve `Parser` capabilities for `operator` and function templates ([pull #732](https://github.com/bytedeco/javacpp/pull/732)) * Fix `Parser` failing on nested initializer lists and on attributes found inside `enum` declarations * Fix `Parser` for basic containers like `std::optional >` ([issue #718](https://github.com/bytedeco/javacpp/issues/718)) * Add support for `std::basic_string` basic container ([issue bytedeco/javacpp-presets#1311](https://github.com/bytedeco/javacpp-presets/issues/1311)) diff --git a/src/main/java/org/bytedeco/javacpp/tools/DeclarationList.java b/src/main/java/org/bytedeco/javacpp/tools/DeclarationList.java index 701c7046..7fe7ca6c 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/DeclarationList.java +++ b/src/main/java/org/bytedeco/javacpp/tools/DeclarationList.java @@ -66,21 +66,21 @@ public boolean add(Declaration decl, String fullName) { boolean add = true; if (templateMap != null && templateMap.empty() && !decl.custom && (decl.type != null || decl.declarator != null)) { // method templates cannot be declared in Java, but make sure to make their - // info available on request (when Info.javaNames is set) to be able to create instances + // info available on request (when Info.javaNames or Info.define is set) to be able to create instances if (infoIterator == null) { Type type = templateMap.type = decl.type; Declarator dcl = templateMap.declarator = decl.declarator; - for (String name : new String[] {fullName, dcl != null ? dcl.cppName : type.cppName}) { + for (String name : new String[] {fullName, dcl != null ? (dcl.type.constructor ? Parser.constructorName(dcl.cppName) : dcl.cppName) : type.cppName}) { if (name == null) { continue; } List infoList = infoMap.get(name); - boolean hasJavaName = false; + boolean hasJavaNameOrDefine = false; for (Info info : infoList) { - hasJavaName |= info.javaNames != null && info.javaNames.length > 0; + hasJavaNameOrDefine |= info.javaNames != null && info.javaNames.length > 0 || info.define; } - if (!decl.function || hasJavaName) { - infoIterator = infoList.size() > 0 ? infoList.listIterator() : null; + if (!decl.function || hasJavaNameOrDefine) { + infoIterator = infoList.size() > 0 ? new ArrayList<>(infoList).listIterator() : null; break; } } diff --git a/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java b/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java index ba6ee471..a380b167 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java +++ b/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java @@ -47,7 +47,6 @@ public class InfoMap extends HashMap> { "std::function", "std::basic_string")) .put(new Info("basic/types").cppTypes("signed", "unsigned", "char", "short", "int", "long", "bool", "float", "double", "__int8", "__int16", "__int32", "__int64", "_Bool", "_Complex", "_Imaginary", "complex", "imaginary")) - .put(new Info("deprecated").annotations("@Deprecated")) .put(new Info("noexcept").annotations("@NoException(true)")) .put(new Info("__COUNTER__").cppText("#define __COUNTER__ 0")) @@ -185,6 +184,21 @@ String normalize(String name, boolean unconst, boolean untemplate) { if (name == null || name.length() == 0 || name.startsWith("basic/")) { return name; } + if (untemplate) { + // Remove template arguments in the last NS component only, and not in parameters, if any + List comps = Templates.splitNamespace(name, true); + int paramsIdx = comps.size() - 1; + String lastComp = comps.get(paramsIdx - 1); + comps.set(paramsIdx - 1, Templates.strip(lastComp)); + name = comps.get(0); + for (int i = 1; i < paramsIdx; i++) { + name += "::" + comps.get(i); + } + name += comps.get(paramsIdx); + if (name.isEmpty()) { + return name; + } + } boolean foundConst = false, simpleType = true; String prefix = null; Token[] tokens = new Tokenizer(name, null, 0).tokenize(); @@ -216,43 +230,6 @@ String normalize(String name, boolean unconst, boolean untemplate) { for (int i = 1; i < n; i++) { name += " " + tokens[i].value; } - } else if (untemplate) { - int count = 0, lastColon = -1, template = -1, parameters = n; - for (int i = 0; i < n; i++) { - if (tokens[i].match('<')) { - count++; - } else if (tokens[i].match('>')) { - count--; - } - if (count == 0 && tokens[i].match("::")) { - lastColon = i; - } else if (count == 0 && tokens[i].match('(')) { - parameters = i; - break; - } - } - for (int i = lastColon + 1; i < parameters; i++) { - if (tokens[i].match('<')) { - if (count == 0) { - template = i; - } - count++; - } else if (tokens[i].match('>')) { - count--; - if (count == 0 && i + 1 != parameters) { - template = -1; - } - } - } - if (template >= 0) { - name = foundConst ? "const " : ""; - for (int i = 0; i < template; i++) { - name += tokens[i]; - } - for (int i = parameters; i < n; i++) { - name += tokens[i].spacing + tokens[i]; - } - } } if (unconst && foundConst) { name = name.substring(name.indexOf("const") + 5); diff --git a/src/main/java/org/bytedeco/javacpp/tools/Parser.java b/src/main/java/org/bytedeco/javacpp/tools/Parser.java index ca9c3c45..05d669bf 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Parser.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Parser.java @@ -124,7 +124,17 @@ static String upcastMethodName(String javaName) { return "as" + Character.toUpperCase(shortName.charAt(0)) + shortName.substring(1); } - /** Returns the name of the constructor of class cppName, to be used as keys in infoMap */ + /** + * Constructors have 2 kinds of fully qualified name: + * the calling name of a constructor, used when calling the constructor, e.g.: + * NS::CN(int) + * and the declaration name, used when defining the constructor outside its class or when referencing a constructor + * with using to inherit constructors of base class. + * NS::CN::CN(int) + * Declarator.cppName contains the calling name, and this method returns the declaration name. + * Keys in info map should use the declaration name, because the calling name cannot specify + * arguments in case of constructor templates, and to avoid confusion between classes and constructores info. + */ static String constructorName(String cppName) { String constructorName = Templates.strip(cppName); int namespace = constructorName.lastIndexOf("::"); @@ -752,6 +762,28 @@ Type[] templateArguments(Context context) throws ParserException { return arguments.toArray(new Type[0]); } + /** + * Read and return the operator following an operator keyword: + * any of new, delete, + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=>(since C++20) && || ++ -- , ->* -> ( ) [ ] + * taking care of template arguments, if any. + */ + private String operator(Context context) throws ParserException { + String res = tokens.get().toString(); // Can be '(' + int lenFirstToken = res.length(); + String s = ""; + tokens.next(); + int backIndex = tokens.index; + for (Token token = tokens.get(); !token.match('(', ';', Token.EOF); token = tokens.next()) { + s += token; + } + s = Templates.strip(s); + tokens.index = backIndex; + for (Token token = tokens.get(); s.length() > res.length() - lenFirstToken; token = tokens.next()) { + res += token; + } + return res; + } + Type type(Context context) throws ParserException { return type(context, false); } @@ -865,7 +897,8 @@ Type type(Context context, boolean definition) throws ParserException { } else if (type.cppName.endsWith("::")) { type.operator = true; tokens.next(); - break; + type.cppName += operator(context); + continue; } else { break; } @@ -957,18 +990,20 @@ Type type(Context context, boolean definition) throws ParserException { // perform template substitution if (context.templateMap != null) { - List types = Templates.splitNamespace(type.cppName); + List types = Templates.splitNamespace(type.cppName, true); String separator = ""; type.cppName = ""; List arguments = new ArrayList<>(); - for (String t : types) { - Type t2 = context.templateMap.get(t); - type.cppName += separator + (t2 != null ? t2.cppName : t); + int paramsIdx = types.size() - 1; + for (int i = 0; i < paramsIdx; i++) { + Type t2 = context.templateMap.get(types.get(i)); + type.cppName += separator + (t2 != null ? t2.cppName : types.get(i)); if (t2 != null && t2.arguments != null) { arguments.addAll(Arrays.asList(t2.arguments)); } separator = "::"; } + type.cppName += types.get(paramsIdx); if (arguments.size() > 0) { type.arguments = arguments.toArray(new Type[0]); } @@ -1109,22 +1144,15 @@ Type type(Context context, boolean definition) throws ParserException { } } if (context.cppName != null && type.javaName.length() > 0) { - String cppName = type.cppName; - String groupName = context.cppName; - String cppNameStripped = Templates.strip(cppName); - String groupNameStripped = Templates.strip(groupName); - if (cppNameStripped.length() == cppName.length() && groupNameStripped.length() != groupName.length()) { - groupName = groupNameStripped; - } else if (cppNameStripped.length() != cppName.length() && groupNameStripped.length() == groupName.length()) { - cppName = cppNameStripped; - } + String cppName = Templates.strip(type.cppName); + String groupName = Templates.strip(context.cppName); List cppNameSplit = Templates.splitNamespace(cppName); List groupNameSplit = Templates.splitNamespace(groupName); if (cppNameSplit.size() == 1 && groupNameSplit.size() > 1) groupName = groupNameSplit.get(groupNameSplit.size() - 1); else if (cppNameSplit.size() > 1 && groupNameSplit.size() == 1) cppName = cppNameSplit.get(cppNameSplit.size() - 1); - if (cppName.equals(groupName) || groupName.startsWith(cppName + "<")) { + if (cppName.equals(groupName)) { type.constructor = !type.destructor && !type.operator && type.indirections == 0 && !type.reference && tokens.get().match('(', ':'); } @@ -1347,11 +1375,8 @@ Declarator declarator(Context context, String defaultName, int infoNumber, boole } else if (token.match(Token.OPERATOR)) { dcl.operator = true; if (!tokens.get(1).match(Token.IDENTIFIER) || tokens.get(1).match(Token.NEW, Token.DELETE)) { - // assume we can have any symbols until the first open parenthesis - dcl.cppName += "operator " + tokens.next(); - for (token = tokens.next(); !token.match(Token.EOF, '('); token = tokens.next()) { - dcl.cppName += token; - } + tokens.next(); + dcl.cppName += "operator " + operator(context); break; } } else if (token.match('<')) { @@ -2130,14 +2155,16 @@ Parameters parameters(Context context, int infoNumber, boolean useDefaults) thro // perform template substitution String cppName = token.value; if (context.templateMap != null) { - List types = Templates.splitNamespace(cppName); + List types = Templates.splitNamespace(cppName, true); String separator = ""; cppName = ""; - for (String t : types) { - Type t2 = context.templateMap.get(t); - cppName += separator + (t2 != null ? t2.cppName : t); + int paramsIdx = types.size() - 1; + for (int i = 0; i < paramsIdx; i++) { + Type t2 = context.templateMap.get(types.get(i)); + cppName += separator + (t2 != null ? t2.cppName : types.get(i)); separator = "::"; } + cppName += types.get(paramsIdx); } // try to qualify all the identifiers @@ -2343,14 +2370,16 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti } boolean isQualified = Templates.splitNamespace(dcl.cppName).size() > 1; - if (context.namespace != null && !isQualified) { + if (context.namespace != null && !isQualified && !(type.constructor || type.destructor)) { dcl.cppName = context.namespace + "::" + dcl.cppName; } Info info = null, fullInfo = null; - String fullname = dcl.cppName, fullname2 = dcl.cppName; + String templateArgs = declList.templateMap != null ? declList.templateMap.toString() : ""; + String fullname = dcl.cppName + templateArgs; + String param1 = "", param2 = ""; if (dcl.parameters != null) { - fullname += "("; - fullname2 += "("; + param1 = "("; + param2 = "("; String separator = ""; for (Declarator d : dcl.parameters.declarators) { if (d != null) { @@ -2376,25 +2405,42 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti if (d.type.constPointer && !s.endsWith(" const")) { s = s + " const"; } - fullname += separator + s; - fullname2 += separator + s2; + param1 += separator + s; + param2 += separator + s2; separator = ", "; } } - info = fullInfo = infoMap.getFirst(fullname += ")", false); + param1 += ")"; + param2 += ")"; + fullname += param1; + info = fullInfo = infoMap.getFirst(fullname, false); if (info == null) { - info = infoMap.getFirst(fullname2 += ")", false); + info = infoMap.getFirst(dcl.cppName + templateArgs + param2, false); + if (info == null && !templateArgs.isEmpty()) { + info = infoMap.getFirst(dcl.cppName + param1, false); + if (info == null) { + info = infoMap.getFirst(dcl.cppName + param2, false); + } + } } } if (info == null) { if (type.constructor) { // get Info explicitly associated with all constructors - List cppNameSplit = Templates.splitNamespace(dcl.cppName); - String name = Templates.strip(cppNameSplit.get(cppNameSplit.size() - 1)); - info = fullInfo = infoMap.getFirst(dcl.cppName + "::" + name); + String name = constructorName(dcl.cppName); + fullname = name + templateArgs + param1; + info = fullInfo = infoMap.getFirst(fullname); + if (info == null) { + info = fullInfo = infoMap.getFirst(name + templateArgs + param2); + if (info == null) { + info = fullInfo = infoMap.getFirst(name + templateArgs); + } + } } + // For constructor, we'd better not make this lookup, because of confusion + // with the class info. Kept for now for backwards compatibility. if (info == null) { - info = infoMap.getFirst(dcl.cppName); + info = infoMap.getFirst(dcl.cppName + templateArgs); } if (!type.constructor && !type.destructor && !type.operator && (context.templateMap == null || context.templateMap.full())) { infoMap.put(info != null ? new Info(info).cppNames(fullname).javaNames(null) : new Info(fullname)); @@ -2466,7 +2512,12 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti type = functionAfter(context, decl, dcl, type); context = new Context(context); - context.virtualize = (context.virtualize && type.virtual) || (info != null && info.virtualize); + + // Virtualize the function if class is virtualized and C++ function is virtual + // or if function is explicitly virtualized with info. + // Exclude constructor case since we may have looked up the info of the class in lieu of + // the info of the constructor, and constructors cannot be virtualized. + context.virtualize = (context.virtualize && type.virtual) || (info != null && info.virtualize && !type.constructor); List prevDcl = new ArrayList(); boolean first = true; @@ -2526,8 +2577,7 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti } // use Java names that we may get here but that declarator() did not catch - String parameters = fullname.substring(dcl.cppName.length()); - for (String name : context.qualify(dcl.cppName, parameters)) { + for (String name : context.qualify(dcl.cppName, param1)) { if ((infoMap.getFirst(name, false)) != null) { dcl.cppName = name; break; @@ -2539,8 +2589,8 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti if (context.namespace != null && localName2.startsWith(context.namespace + "::")) { localName2 = dcl.cppName.substring(context.namespace.length() + 2); } - if (localName2.endsWith(parameters)) { - localName2 = localName2.substring(0, localName2.length() - parameters.length()); + if (localName2.endsWith(param1)) { + localName2 = localName2.substring(0, localName2.length() - param1.length()); } if (fullInfo != null && fullInfo.javaNames != null && fullInfo.javaNames.length > 0) { dcl.javaName = fullInfo.javaNames[0]; diff --git a/src/main/java/org/bytedeco/javacpp/tools/TemplateMap.java b/src/main/java/org/bytedeco/javacpp/tools/TemplateMap.java index 696a7e9b..825cb5c7 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/TemplateMap.java +++ b/src/main/java/org/bytedeco/javacpp/tools/TemplateMap.java @@ -23,6 +23,7 @@ package org.bytedeco.javacpp.tools; import java.util.LinkedHashMap; +import java.util.Map; /** * @@ -67,4 +68,20 @@ Type get(String key) { return value; } } + + @Override + public String toString() { + String s = "<"; + for (Map.Entry e : entrySet()) { + if (s.length() > 1) { + s += ","; + } + Type t = e.getValue(); + s += t != null ? t.cppName : e.getKey(); + } + if (s.endsWith(">")) { + s += " "; + } + return s + ">"; + } } diff --git a/src/main/java/org/bytedeco/javacpp/tools/Templates.java b/src/main/java/org/bytedeco/javacpp/tools/Templates.java index ebd2f8b8..ee5e1c97 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Templates.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Templates.java @@ -47,8 +47,17 @@ static boolean notExists(String s) { return strip(s).length() == s.length(); } - /** Split s at ::, but taking care of qualified template arguments */ + /** Returns {@code splitNamespace(s, false)}. */ static List splitNamespace(String s) { + return splitNamespace(s, false); + } + + /** + * Split s at ::, but taking care of qualified template arguments and qualified function parameters. + * If returnParams is true, returned list contains an extra element with function parameters, or the empty + * string if none are present. + */ + static List splitNamespace(String s, boolean returnParams) { String sTemplatesMasked = s; for (;;) { Matcher m = templatePattern.matcher(sTemplatesMasked); @@ -61,17 +70,43 @@ static List splitNamespace(String s) { } } ArrayList comps = new ArrayList<>(); + int pIndex = sTemplatesMasked.lastIndexOf(')'); // last because of function pointer types like void(*)() + if (pIndex > 0) { + // Pointer list may contain function pointer types with parentheses + int count = 1; + for (pIndex--; pIndex >= 0; pIndex--) { + char c = sTemplatesMasked.charAt(pIndex); + if (c == ')') { + count++; + } else if (c == '(') { + count--; + if (count == 0) { + break; + } + } + } + } int start = 0; for (;;) { int i = sTemplatesMasked.indexOf("::", start); - if (i >= 0) { + if (i >= 0 && (i < pIndex || pIndex == -1)) { comps.add(s.substring(start, i)); start = i + 2; } else { break; } } - comps.add(s.substring(start)); + String params; + if (pIndex >= 0) { + comps.add(s.substring(start, pIndex)); + params = s.substring(pIndex); + } else { + comps.add(s.substring(start)); + params = ""; + } + if (returnParams) { + comps.add(params); + } return comps; } }