The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 lines
19KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../jucer_Headers.h"
  18. #include "jucer_CodeHelpers.h"
  19. //==============================================================================
  20. namespace CodeHelpers
  21. {
  22. String indent (const String& code, const int numSpaces, bool indentFirstLine)
  23. {
  24. if (numSpaces == 0)
  25. return code;
  26. const String space (String::repeatedString (" ", numSpaces));
  27. StringArray lines;
  28. lines.addLines (code);
  29. for (int i = (indentFirstLine ? 0 : 1); i < lines.size(); ++i)
  30. {
  31. String s (lines[i].trimEnd());
  32. if (s.isNotEmpty())
  33. s = space + s;
  34. lines.set (i, s);
  35. }
  36. return lines.joinIntoString (newLine);
  37. }
  38. String makeValidIdentifier (String s, bool capitalise, bool removeColons, bool allowTemplates)
  39. {
  40. if (s.isEmpty())
  41. return "unknown";
  42. if (removeColons)
  43. s = s.replaceCharacters (".,;:/@", "______");
  44. else
  45. s = s.replaceCharacters (".,;/@", "_____");
  46. for (int i = s.length(); --i > 0;)
  47. if (CharacterFunctions::isLetter (s[i])
  48. && CharacterFunctions::isLetter (s[i - 1])
  49. && CharacterFunctions::isUpperCase (s[i])
  50. && ! CharacterFunctions::isUpperCase (s[i - 1]))
  51. s = s.substring (0, i) + " " + s.substring (i);
  52. String allowedChars ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_ 0123456789");
  53. if (allowTemplates)
  54. allowedChars += "<>";
  55. if (! removeColons)
  56. allowedChars += ":";
  57. StringArray words;
  58. words.addTokens (s.retainCharacters (allowedChars), false);
  59. words.trim();
  60. String n (words[0]);
  61. if (capitalise)
  62. n = n.toLowerCase();
  63. for (int i = 1; i < words.size(); ++i)
  64. {
  65. if (capitalise && words[i].length() > 1)
  66. n << words[i].substring (0, 1).toUpperCase()
  67. << words[i].substring (1).toLowerCase();
  68. else
  69. n << words[i];
  70. }
  71. if (CharacterFunctions::isDigit (n[0]))
  72. n = "_" + n;
  73. if (CPlusPlusCodeTokeniser::isReservedKeyword (n))
  74. n << '_';
  75. return n;
  76. }
  77. static void writeEscapeChars (OutputStream& out, const char* utf8, const int numBytes,
  78. const int maxCharsOnLine, const bool breakAtNewLines,
  79. const bool replaceSingleQuotes, const bool allowStringBreaks)
  80. {
  81. int charsOnLine = 0;
  82. bool lastWasHexEscapeCode = false;
  83. for (int i = 0; i < numBytes || numBytes < 0; ++i)
  84. {
  85. const unsigned char c = (unsigned char) utf8[i];
  86. bool startNewLine = false;
  87. switch (c)
  88. {
  89. case '\t': out << "\\t"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
  90. case '\r': out << "\\r"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
  91. case '\n': out << "\\n"; lastWasHexEscapeCode = false; charsOnLine += 2; startNewLine = breakAtNewLines; break;
  92. case '\\': out << "\\\\"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
  93. case '\"': out << "\\\""; lastWasHexEscapeCode = false; charsOnLine += 2; break;
  94. case 0:
  95. if (numBytes < 0)
  96. return;
  97. out << "\\0";
  98. lastWasHexEscapeCode = true;
  99. charsOnLine += 2;
  100. break;
  101. case '\'':
  102. if (replaceSingleQuotes)
  103. {
  104. out << "\\\'";
  105. lastWasHexEscapeCode = false;
  106. charsOnLine += 2;
  107. break;
  108. }
  109. // deliberate fall-through...
  110. default:
  111. if (c >= 32 && c < 127 && ! (lastWasHexEscapeCode // (have to avoid following a hex escape sequence with a valid hex digit)
  112. && CharacterFunctions::getHexDigitValue (c) >= 0))
  113. {
  114. out << (char) c;
  115. lastWasHexEscapeCode = false;
  116. ++charsOnLine;
  117. }
  118. else if (allowStringBreaks && lastWasHexEscapeCode && c >= 32 && c < 127)
  119. {
  120. out << "\"\"" << (char) c;
  121. lastWasHexEscapeCode = false;
  122. charsOnLine += 3;
  123. }
  124. else
  125. {
  126. out << (c < 16 ? "\\x0" : "\\x") << String::toHexString ((int) c);
  127. lastWasHexEscapeCode = true;
  128. charsOnLine += 4;
  129. }
  130. break;
  131. }
  132. if ((startNewLine || (maxCharsOnLine > 0 && charsOnLine >= maxCharsOnLine))
  133. && (numBytes < 0 || i < numBytes - 1))
  134. {
  135. charsOnLine = 0;
  136. out << "\"" << newLine << "\"";
  137. lastWasHexEscapeCode = false;
  138. }
  139. }
  140. }
  141. String addEscapeChars (const String& s)
  142. {
  143. MemoryOutputStream out;
  144. writeEscapeChars (out, s.toRawUTF8(), -1, -1, false, true, true);
  145. return out.toUTF8();
  146. }
  147. String createIncludeStatement (const File& includeFile, const File& targetFile)
  148. {
  149. return createIncludeStatement (FileHelpers::unixStylePath (FileHelpers::getRelativePathFrom (includeFile, targetFile.getParentDirectory())));
  150. }
  151. String createIncludeStatement (const String& includePath)
  152. {
  153. if (includePath.startsWithChar ('<') || includePath.startsWithChar ('"'))
  154. return "#include " + includePath;
  155. return "#include \"" + includePath + "\"";
  156. }
  157. String makeHeaderGuardName (const File& file)
  158. {
  159. return file.getFileName().toUpperCase()
  160. .replaceCharacters (" .", "__")
  161. .retainCharacters ("_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  162. + "_INCLUDED";
  163. }
  164. String makeBinaryDataIdentifierName (const File& file)
  165. {
  166. return makeValidIdentifier (file.getFileName()
  167. .replaceCharacters (" .", "__")
  168. .retainCharacters ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789"),
  169. false, true, false);
  170. }
  171. String stringLiteral (const String& text, int maxLineLength)
  172. {
  173. if (text.isEmpty())
  174. return "String::empty";
  175. StringArray lines;
  176. {
  177. String::CharPointerType t (text.getCharPointer());
  178. bool finished = t.isEmpty();
  179. while (! finished)
  180. {
  181. for (String::CharPointerType startOfLine (t);;)
  182. {
  183. switch (t.getAndAdvance())
  184. {
  185. case 0: finished = true; break;
  186. case '\n': break;
  187. case '\r': if (*t == '\n') ++t; break;
  188. default: continue;
  189. }
  190. lines.add (String (startOfLine, t));
  191. break;
  192. }
  193. }
  194. }
  195. if (maxLineLength > 0)
  196. {
  197. for (int i = 0; i < lines.size(); ++i)
  198. {
  199. String& line = lines.getReference (i);
  200. if (line.length() > maxLineLength)
  201. {
  202. const String start (line.substring (0, maxLineLength));
  203. const String end (line.substring (maxLineLength));
  204. line = start;
  205. lines.insert (i + 1, end);
  206. }
  207. }
  208. }
  209. for (int i = 0; i < lines.size(); ++i)
  210. lines.getReference(i) = addEscapeChars (lines.getReference(i));
  211. lines.removeEmptyStrings();
  212. for (int i = 0; i < lines.size(); ++i)
  213. lines.getReference(i) = "\"" + lines.getReference(i) + "\"";
  214. String result (lines.joinIntoString (newLine));
  215. if (! CharPointer_ASCII::isValidString (text.toUTF8(), std::numeric_limits<int>::max()))
  216. result = "CharPointer_UTF8 (" + result + ")";
  217. return result;
  218. }
  219. String alignFunctionCallParams (const String& call, const StringArray& parameters, const int maxLineLength)
  220. {
  221. String result, currentLine (call);
  222. for (int i = 0; i < parameters.size(); ++i)
  223. {
  224. if (currentLine.length() >= maxLineLength)
  225. {
  226. result += currentLine.trimEnd() + newLine;
  227. currentLine = String::repeatedString (" ", call.length()) + parameters[i];
  228. }
  229. else
  230. {
  231. currentLine += parameters[i];
  232. }
  233. if (i < parameters.size() - 1)
  234. currentLine << ", ";
  235. }
  236. return result + currentLine.trimEnd() + ")";
  237. }
  238. String floatLiteral (double value, int numDecPlaces)
  239. {
  240. String s (value, numDecPlaces);
  241. if (s.containsChar ('.'))
  242. s << 'f';
  243. else
  244. s << ".0f";
  245. return s;
  246. }
  247. String boolLiteral (bool value)
  248. {
  249. return value ? "true" : "false";
  250. }
  251. String colourToCode (Colour col)
  252. {
  253. const Colour colours[] =
  254. {
  255. #define COL(col) Colours::col,
  256. #include "jucer_Colours.h"
  257. #undef COL
  258. Colours::transparentBlack
  259. };
  260. static const char* colourNames[] =
  261. {
  262. #define COL(col) #col,
  263. #include "jucer_Colours.h"
  264. #undef COL
  265. 0
  266. };
  267. for (int i = 0; i < numElementsInArray (colourNames) - 1; ++i)
  268. if (col == colours[i])
  269. return "Colours::" + String (colourNames[i]);
  270. return "Colour (0x" + hexString8Digits ((int) col.getARGB()) + ')';
  271. }
  272. String justificationToCode (Justification justification)
  273. {
  274. switch (justification.getFlags())
  275. {
  276. case Justification::centred: return "Justification::centred";
  277. case Justification::centredLeft: return "Justification::centredLeft";
  278. case Justification::centredRight: return "Justification::centredRight";
  279. case Justification::centredTop: return "Justification::centredTop";
  280. case Justification::centredBottom: return "Justification::centredBottom";
  281. case Justification::topLeft: return "Justification::topLeft";
  282. case Justification::topRight: return "Justification::topRight";
  283. case Justification::bottomLeft: return "Justification::bottomLeft";
  284. case Justification::bottomRight: return "Justification::bottomRight";
  285. case Justification::left: return "Justification::left";
  286. case Justification::right: return "Justification::right";
  287. case Justification::horizontallyCentred: return "Justification::horizontallyCentred";
  288. case Justification::top: return "Justification::top";
  289. case Justification::bottom: return "Justification::bottom";
  290. case Justification::verticallyCentred: return "Justification::verticallyCentred";
  291. case Justification::horizontallyJustified: return "Justification::horizontallyJustified";
  292. default: break;
  293. }
  294. jassertfalse;
  295. return "Justification (" + String (justification.getFlags()) + ")";
  296. }
  297. void writeDataAsCppLiteral (const MemoryBlock& mb, OutputStream& out,
  298. bool breakAtNewLines, bool allowStringBreaks)
  299. {
  300. const int maxCharsOnLine = 250;
  301. const unsigned char* data = (const unsigned char*) mb.getData();
  302. int charsOnLine = 0;
  303. bool canUseStringLiteral = mb.getSize() < 32768; // MS compilers can't handle big string literals..
  304. if (canUseStringLiteral)
  305. {
  306. unsigned int numEscaped = 0;
  307. for (size_t i = 0; i < mb.getSize(); ++i)
  308. {
  309. const unsigned int num = (unsigned int) data[i];
  310. if (! ((num >= 32 && num < 127) || num == '\t' || num == '\r' || num == '\n'))
  311. {
  312. if (++numEscaped > mb.getSize() / 4)
  313. {
  314. canUseStringLiteral = false;
  315. break;
  316. }
  317. }
  318. }
  319. }
  320. if (! canUseStringLiteral)
  321. {
  322. out << "{ ";
  323. for (size_t i = 0; i < mb.getSize(); ++i)
  324. {
  325. const int num = (int) (unsigned int) data[i];
  326. out << num << ',';
  327. charsOnLine += 2;
  328. if (num >= 10)
  329. {
  330. ++charsOnLine;
  331. if (num >= 100)
  332. ++charsOnLine;
  333. }
  334. if (charsOnLine >= maxCharsOnLine)
  335. {
  336. charsOnLine = 0;
  337. out << newLine;
  338. }
  339. }
  340. out << "0,0 };";
  341. }
  342. else
  343. {
  344. out << "\"";
  345. writeEscapeChars (out, (const char*) data, (int) mb.getSize(),
  346. maxCharsOnLine, breakAtNewLines, false, allowStringBreaks);
  347. out << "\";";
  348. }
  349. }
  350. //==============================================================================
  351. static unsigned int calculateHash (const String& s, const unsigned int hashMultiplier)
  352. {
  353. const char* t = s.toUTF8();
  354. unsigned int hash = 0;
  355. while (*t != 0)
  356. hash = hashMultiplier * hash + (unsigned int) *t++;
  357. return hash;
  358. }
  359. static unsigned int findBestHashMultiplier (const StringArray& strings)
  360. {
  361. unsigned int v = 31;
  362. for (;;)
  363. {
  364. SortedSet <unsigned int> hashes;
  365. bool collision = false;
  366. for (int i = strings.size(); --i >= 0;)
  367. {
  368. const unsigned int hash = calculateHash (strings[i], v);
  369. if (hashes.contains (hash))
  370. {
  371. collision = true;
  372. break;
  373. }
  374. hashes.add (hash);
  375. }
  376. if (! collision)
  377. break;
  378. v += 2;
  379. }
  380. return v;
  381. }
  382. void createStringMatcher (OutputStream& out, const String& utf8PointerVariable,
  383. const StringArray& strings, const StringArray& codeToExecute, const int indentLevel)
  384. {
  385. jassert (strings.size() == codeToExecute.size());
  386. const String indent (String::repeatedString (" ", indentLevel));
  387. const unsigned int hashMultiplier = findBestHashMultiplier (strings);
  388. out << indent << "unsigned int hash = 0;" << newLine
  389. << indent << "if (" << utf8PointerVariable << " != 0)" << newLine
  390. << indent << " while (*" << utf8PointerVariable << " != 0)" << newLine
  391. << indent << " hash = " << (int) hashMultiplier << " * hash + (unsigned int) *" << utf8PointerVariable << "++;" << newLine
  392. << newLine
  393. << indent << "switch (hash)" << newLine
  394. << indent << "{" << newLine;
  395. for (int i = 0; i < strings.size(); ++i)
  396. {
  397. out << indent << " case 0x" << hexString8Digits ((int) calculateHash (strings[i], hashMultiplier))
  398. << ": " << codeToExecute[i] << newLine;
  399. }
  400. out << indent << " default: break;" << newLine
  401. << indent << "}" << newLine << newLine;
  402. }
  403. String getLeadingWhitespace (String line)
  404. {
  405. line = line.removeCharacters ("\r\n");
  406. const String::CharPointerType endOfLeadingWS (line.getCharPointer().findEndOfWhitespace());
  407. return String (line.getCharPointer(), endOfLeadingWS);
  408. }
  409. int getBraceCount (String::CharPointerType line)
  410. {
  411. int braces = 0;
  412. for (;;)
  413. {
  414. const juce_wchar c = line.getAndAdvance();
  415. if (c == 0) break;
  416. else if (c == '{') ++braces;
  417. else if (c == '}') --braces;
  418. else if (c == '/') { if (*line == '/') break; }
  419. else if (c == '"' || c == '\'') { while (! (line.isEmpty() || line.getAndAdvance() == c)) {} }
  420. }
  421. return braces;
  422. }
  423. bool getIndentForCurrentBlock (CodeDocument::Position pos, const String& tab,
  424. String& blockIndent, String& lastLineIndent)
  425. {
  426. int braceCount = 0;
  427. bool indentFound = false;
  428. while (pos.getLineNumber() > 0)
  429. {
  430. pos = pos.movedByLines (-1);
  431. const String line (pos.getLineText());
  432. const String trimmedLine (line.trimStart());
  433. braceCount += getBraceCount (trimmedLine.getCharPointer());
  434. if (braceCount > 0)
  435. {
  436. blockIndent = getLeadingWhitespace (line);
  437. if (! indentFound)
  438. lastLineIndent = blockIndent + tab;
  439. return true;
  440. }
  441. if ((! indentFound) && trimmedLine.isNotEmpty())
  442. {
  443. indentFound = true;
  444. lastLineIndent = getLeadingWhitespace (line);
  445. }
  446. }
  447. return false;
  448. }
  449. }