| 1 | // Copyright (C) 2017-2020 Ford Motor Company |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include <qcommandlineoption.h> |
| 5 | #include <qcommandlineparser.h> |
| 6 | #include <qcoreapplication.h> |
| 7 | #include <qfileinfo.h> |
| 8 | #include <qjsondocument.h> |
| 9 | #include <qjsonobject.h> |
| 10 | #include <qjsonarray.h> |
| 11 | |
| 12 | #include "cppcodegenerator.h" |
| 13 | #include "repcodegenerator.h" |
| 14 | #include "repparser.h" |
| 15 | #include "utils.h" |
| 16 | |
| 17 | #include <cstdio> |
| 18 | |
| 19 | #define PROGRAM_NAME "repc" |
| 20 | #define REPC_VERSION "1.0.0" |
| 21 | |
| 22 | enum Mode { |
| 23 | InRep = 1, |
| 24 | InJson = 2, |
| 25 | OutRep = 4, |
| 26 | OutSource = 8, |
| 27 | OutReplica = 16, |
| 28 | OutMerged = OutSource | OutReplica |
| 29 | }; |
| 30 | |
| 31 | static const QLatin1String REP("rep" ); |
| 32 | |
| 33 | QT_USE_NAMESPACE |
| 34 | |
| 35 | int main(int argc, char **argv) |
| 36 | { |
| 37 | QCoreApplication app(argc, argv); |
| 38 | QCoreApplication::setApplicationVersion(QString::fromLatin1(REPC_VERSION)); |
| 39 | |
| 40 | QString outputFile; |
| 41 | QString inputFile; |
| 42 | int mode = 0; |
| 43 | QCommandLineParser parser; |
| 44 | parser.setApplicationDescription(QStringLiteral("repc tool v%1 (Qt %2).\n" ) |
| 45 | .arg(QStringLiteral(REPC_VERSION), args: QString::fromLatin1(QT_VERSION_STR))); |
| 46 | parser.addHelpOption(); |
| 47 | parser.addVersionOption(); |
| 48 | QCommandLineOption inputTypeOption(QStringLiteral("i" )); |
| 49 | inputTypeOption.setDescription(QLatin1String("Input file type:\n" |
| 50 | "rep: replicant template files.\n" |
| 51 | "json: JSON output from moc of a Qt header file." )); |
| 52 | inputTypeOption.setValueName(QStringLiteral("rep|json" )); |
| 53 | parser.addOption(commandLineOption: inputTypeOption); |
| 54 | |
| 55 | QCommandLineOption outputTypeOption(QStringLiteral("o" )); |
| 56 | outputTypeOption.setDescription(QLatin1String("Output file type:\n" |
| 57 | "source: generates source header. Is incompatible with \"-i src\" option.\n" |
| 58 | "replica: generates replica header.\n" |
| 59 | "merged: generates combined replica/source header.\n" |
| 60 | "rep: generates replicant template file from C++ QOject classes. Is not compatible with \"-i rep\" option." )); |
| 61 | outputTypeOption.setValueName(QStringLiteral("source|replica|merged|rep" )); |
| 62 | parser.addOption(commandLineOption: outputTypeOption); |
| 63 | |
| 64 | QCommandLineOption includePathOption(QStringLiteral("I" )); |
| 65 | includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files. This parameter is needed only if the input file type is src (.h file)." )); |
| 66 | includePathOption.setValueName(QStringLiteral("dir" )); |
| 67 | parser.addOption(commandLineOption: includePathOption); |
| 68 | |
| 69 | QCommandLineOption alwaysClassOption(QStringLiteral("c" )); |
| 70 | alwaysClassOption.setDescription(QStringLiteral("Always output `class` type for .rep files and never `POD`." )); |
| 71 | parser.addOption(commandLineOption: alwaysClassOption); |
| 72 | |
| 73 | QCommandLineOption debugOption(QStringLiteral("d" )); |
| 74 | debugOption.setDescription(QStringLiteral("Print out parsing debug information (for troubleshooting)." )); |
| 75 | parser.addOption(commandLineOption: debugOption); |
| 76 | |
| 77 | parser.addPositionalArgument(QStringLiteral("[json-file/rep-file]" ), |
| 78 | QStringLiteral("Input json/rep file to read from, otherwise stdin." )); |
| 79 | |
| 80 | parser.addPositionalArgument(QStringLiteral("[rep-file/header-file]" ), |
| 81 | QStringLiteral("Output header/rep file to write to, otherwise stdout." )); |
| 82 | |
| 83 | parser.process(arguments: app.arguments()); |
| 84 | |
| 85 | const QStringList files = parser.positionalArguments(); |
| 86 | |
| 87 | if (files.size() > 2) { |
| 88 | fprintf(stderr, format: "%s" , qPrintable(QLatin1String(PROGRAM_NAME ": Too many input, output files specified: '" ) + files.join(QStringLiteral("' '" )) + QStringLiteral("\'.\n" ))); |
| 89 | parser.showHelp(exitCode: 1); |
| 90 | } |
| 91 | |
| 92 | if (parser.isSet(option: inputTypeOption)) { |
| 93 | const QString &inputType = parser.value(option: inputTypeOption); |
| 94 | if (inputType == REP) |
| 95 | mode = InRep; |
| 96 | else if (inputType == u"json" ) |
| 97 | mode = InJson; |
| 98 | else { |
| 99 | fprintf(stderr, PROGRAM_NAME ": Unknown input type\"%s\".\n" , qPrintable(inputType)); |
| 100 | parser.showHelp(exitCode: 1); |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | if (parser.isSet(option: outputTypeOption)) { |
| 105 | const QString &outputType = parser.value(option: outputTypeOption); |
| 106 | if (outputType == REP) |
| 107 | mode |= OutRep; |
| 108 | else if (outputType == u"replica" ) |
| 109 | mode |= OutReplica; |
| 110 | else if (outputType == u"source" ) |
| 111 | mode |= OutSource; |
| 112 | else if (outputType == u"merged" ) |
| 113 | mode |= OutMerged; |
| 114 | else { |
| 115 | fprintf(stderr, PROGRAM_NAME ": Unknown output type\"%s\".\n" , qPrintable(outputType)); |
| 116 | parser.showHelp(exitCode: 1); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | switch (files.size()) { |
| 121 | case 2: |
| 122 | outputFile = files.last(); |
| 123 | if (!(mode & (OutRep | OutSource | OutReplica))) { |
| 124 | // try to figure out the Out mode from file extension |
| 125 | if (outputFile.endsWith(s: QLatin1String(".rep" ))) |
| 126 | mode |= OutRep; |
| 127 | } |
| 128 | Q_FALLTHROUGH(); |
| 129 | case 1: |
| 130 | inputFile = files.first(); |
| 131 | if (!(mode & (InRep | InJson))) { |
| 132 | // try to figure out the In mode from file extension |
| 133 | if (inputFile.endsWith(s: QLatin1String(".rep" ))) |
| 134 | mode |= InRep; |
| 135 | else |
| 136 | mode |= InJson; |
| 137 | } |
| 138 | break; |
| 139 | } |
| 140 | // check mode sanity |
| 141 | if (!(mode & (InRep | InJson))) { |
| 142 | fprintf(stderr, PROGRAM_NAME ": Unknown input type, please use -i option to specify one.\n" ); |
| 143 | parser.showHelp(exitCode: 1); |
| 144 | } |
| 145 | if (!(mode & (OutRep | OutSource | OutReplica))) { |
| 146 | fprintf(stderr, PROGRAM_NAME ": Unknown output type, please use -o option to specify one.\n" ); |
| 147 | parser.showHelp(exitCode: 1); |
| 148 | } |
| 149 | if (mode & InRep && mode & OutRep) { |
| 150 | fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are rep files.\n" ); |
| 151 | parser.showHelp(exitCode: 1); |
| 152 | } |
| 153 | if (mode & InJson && mode & OutSource) { |
| 154 | fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are source header files.\n" ); |
| 155 | parser.showHelp(exitCode: 1); |
| 156 | } |
| 157 | |
| 158 | QFile input; |
| 159 | if (inputFile.isEmpty()) { |
| 160 | inputFile = QStringLiteral("<stdin>" ); |
| 161 | input.open(stdin, ioFlags: QIODevice::ReadOnly); |
| 162 | } else { |
| 163 | input.setFileName(inputFile); |
| 164 | if (!input.open(flags: QIODevice::ReadOnly)) { |
| 165 | fprintf(stderr, PROGRAM_NAME ": %s: No such file.\n" , qPrintable(inputFile)); |
| 166 | return 1; |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | QFile output; |
| 171 | if (outputFile.isEmpty()) { |
| 172 | output.open(stdout, ioFlags: QIODevice::WriteOnly); |
| 173 | } else { |
| 174 | output.setFileName(outputFile); |
| 175 | if (!output.open(flags: QIODevice::WriteOnly)) { |
| 176 | fprintf(stderr, PROGRAM_NAME ": could not open output file '%s': %s.\n" , |
| 177 | qPrintable(outputFile), qPrintable(output.errorString())); |
| 178 | return 1; |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | if (mode & InJson) { |
| 183 | QJsonDocument doc(QJsonDocument::fromJson(json: input.readAll())); |
| 184 | input.close(); |
| 185 | if (!doc.isObject()) { |
| 186 | fprintf(stderr, PROGRAM_NAME ": Unable to read json input.\n" ); |
| 187 | return 0; |
| 188 | } |
| 189 | |
| 190 | QJsonObject json = doc.object(); |
| 191 | |
| 192 | if (!json.contains(key: QLatin1String("classes" )) || !json[QLatin1String("classes" )].isArray()) { |
| 193 | fprintf(stderr, PROGRAM_NAME ": No QObject classes found.\n" ); |
| 194 | return 0; |
| 195 | } |
| 196 | |
| 197 | QJsonArray classes = json[QLatin1String("classes" )].toArray(); |
| 198 | |
| 199 | if (mode & OutRep) { |
| 200 | CppCodeGenerator generator(&output); |
| 201 | generator.generate(classList: classes, alwaysGenerateClass: parser.isSet(option: alwaysClassOption)); |
| 202 | } else { |
| 203 | Q_ASSERT(mode & OutReplica); |
| 204 | RepCodeGenerator generator(&output, classList2AST(classes)); |
| 205 | generator.generate(mode: RepCodeGenerator::REPLICA, fileName: outputFile); |
| 206 | } |
| 207 | } else { |
| 208 | Q_ASSERT(!(mode & OutRep)); |
| 209 | RepParser repparser(input); |
| 210 | if (parser.isSet(option: debugOption)) |
| 211 | repparser.setDebug(); |
| 212 | if (!repparser.parse()) { |
| 213 | fprintf(stderr, PROGRAM_NAME ": %s:%d: error: %s\n" , qPrintable(inputFile), repparser.lineNumber(), qPrintable(repparser.errorString())); |
| 214 | // if everything is okay and only the input was malformed => remove the output file |
| 215 | // let's not create an empty file -- make sure the build system tries to run repc again |
| 216 | // this is the same behavior other code generators exhibit (e.g. flex) |
| 217 | output.remove(); |
| 218 | return 1; |
| 219 | } |
| 220 | |
| 221 | input.close(); |
| 222 | |
| 223 | RepCodeGenerator generator(&output, repparser.ast()); |
| 224 | if ((mode & OutMerged) == OutMerged) |
| 225 | generator.generate(mode: RepCodeGenerator::MERGED, fileName: outputFile); |
| 226 | else if (mode & OutReplica) |
| 227 | generator.generate(mode: RepCodeGenerator::REPLICA, fileName: outputFile); |
| 228 | else if (mode & OutSource) |
| 229 | generator.generate(mode: RepCodeGenerator::SOURCE, fileName: outputFile); |
| 230 | else { |
| 231 | fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n" ); |
| 232 | return 1; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | output.close(); |
| 237 | return 0; |
| 238 | } |
| 239 | |