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
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. namespace CppTokeniser
  19. {
  20. static bool isIdentifierStart (const juce_wchar c) noexcept
  21. {
  22. return CharacterFunctions::isLetter (c)
  23. || c == '_' || c == '@';
  24. }
  25. static bool isIdentifierBody (const juce_wchar c) noexcept
  26. {
  27. return CharacterFunctions::isLetterOrDigit (c)
  28. || c == '_' || c == '@';
  29. }
  30. static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept
  31. {
  32. static const char* const keywords2Char[] =
  33. { "if", "do", "or", "id", 0 };
  34. static const char* const keywords3Char[] =
  35. { "for", "int", "new", "try", "xor", "and", "asm", "not", 0 };
  36. static const char* const keywords4Char[] =
  37. { "bool", "void", "this", "true", "long", "else", "char",
  38. "enum", "case", "goto", "auto", 0 };
  39. static const char* const keywords5Char[] =
  40. { "while", "bitor", "break", "catch", "class", "compl", "const", "false",
  41. "float", "short", "throw", "union", "using", "or_eq", 0 };
  42. static const char* const keywords6Char[] =
  43. { "return", "struct", "and_eq", "bitand", "delete", "double", "extern",
  44. "friend", "inline", "not_eq", "public", "sizeof", "static", "signed",
  45. "switch", "typeid", "wchar_t", "xor_eq", 0};
  46. static const char* const keywords7Char[] =
  47. { "default", "mutable", "private", "typedef", "nullptr", "virtual", 0 };
  48. static const char* const keywordsOther[] =
  49. { "noexcept", "const_cast", "continue", "explicit", "namespace",
  50. "operator", "protected", "register", "reinterpret_cast", "static_cast",
  51. "template", "typename", "unsigned", "volatile", "constexpr",
  52. "@implementation", "@interface", "@end", "@synthesize", "@dynamic", "@public",
  53. "@private", "@property", "@protected", "@class", 0 };
  54. const char* const* k;
  55. switch (tokenLength)
  56. {
  57. case 2: k = keywords2Char; break;
  58. case 3: k = keywords3Char; break;
  59. case 4: k = keywords4Char; break;
  60. case 5: k = keywords5Char; break;
  61. case 6: k = keywords6Char; break;
  62. case 7: k = keywords7Char; break;
  63. default:
  64. if (tokenLength < 2 || tokenLength > 16)
  65. return false;
  66. k = keywordsOther;
  67. break;
  68. }
  69. for (int i = 0; k[i] != 0; ++i)
  70. if (token.compare (CharPointer_ASCII (k[i])) == 0)
  71. return true;
  72. return false;
  73. }
  74. static int parseIdentifier (CodeDocument::Iterator& source) noexcept
  75. {
  76. int tokenLength = 0;
  77. String::CharPointerType::CharType possibleIdentifier [100];
  78. String::CharPointerType possible (possibleIdentifier);
  79. while (isIdentifierBody (source.peekNextChar()))
  80. {
  81. const juce_wchar c = source.nextChar();
  82. if (tokenLength < 20)
  83. possible.write (c);
  84. ++tokenLength;
  85. }
  86. if (tokenLength > 1 && tokenLength <= 16)
  87. {
  88. possible.writeNull();
  89. if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength))
  90. return CPlusPlusCodeTokeniser::tokenType_keyword;
  91. }
  92. return CPlusPlusCodeTokeniser::tokenType_identifier;
  93. }
  94. static bool skipNumberSuffix (CodeDocument::Iterator& source)
  95. {
  96. const juce_wchar c = source.peekNextChar();
  97. if (c == 'l' || c == 'L' || c == 'u' || c == 'U')
  98. source.skip();
  99. if (CharacterFunctions::isLetterOrDigit (source.peekNextChar()))
  100. return false;
  101. return true;
  102. }
  103. static bool isHexDigit (const juce_wchar c) noexcept
  104. {
  105. return (c >= '0' && c <= '9')
  106. || (c >= 'a' && c <= 'f')
  107. || (c >= 'A' && c <= 'F');
  108. }
  109. static bool parseHexLiteral (CodeDocument::Iterator& source) noexcept
  110. {
  111. if (source.nextChar() != '0')
  112. return false;
  113. juce_wchar c = source.nextChar();
  114. if (c != 'x' && c != 'X')
  115. return false;
  116. int numDigits = 0;
  117. while (isHexDigit (source.peekNextChar()))
  118. {
  119. ++numDigits;
  120. source.skip();
  121. }
  122. if (numDigits == 0)
  123. return false;
  124. return skipNumberSuffix (source);
  125. }
  126. static bool isOctalDigit (const juce_wchar c) noexcept
  127. {
  128. return c >= '0' && c <= '7';
  129. }
  130. static bool parseOctalLiteral (CodeDocument::Iterator& source) noexcept
  131. {
  132. if (source.nextChar() != '0')
  133. return false;
  134. if (! isOctalDigit (source.nextChar()))
  135. return false;
  136. while (isOctalDigit (source.peekNextChar()))
  137. source.skip();
  138. return skipNumberSuffix (source);
  139. }
  140. static bool isDecimalDigit (const juce_wchar c) noexcept
  141. {
  142. return c >= '0' && c <= '9';
  143. }
  144. static bool parseDecimalLiteral (CodeDocument::Iterator& source) noexcept
  145. {
  146. int numChars = 0;
  147. while (isDecimalDigit (source.peekNextChar()))
  148. {
  149. ++numChars;
  150. source.skip();
  151. }
  152. if (numChars == 0)
  153. return false;
  154. return skipNumberSuffix (source);
  155. }
  156. static bool parseFloatLiteral (CodeDocument::Iterator& source) noexcept
  157. {
  158. int numDigits = 0;
  159. while (isDecimalDigit (source.peekNextChar()))
  160. {
  161. source.skip();
  162. ++numDigits;
  163. }
  164. const bool hasPoint = (source.peekNextChar() == '.');
  165. if (hasPoint)
  166. {
  167. source.skip();
  168. while (isDecimalDigit (source.peekNextChar()))
  169. {
  170. source.skip();
  171. ++numDigits;
  172. }
  173. }
  174. if (numDigits == 0)
  175. return false;
  176. juce_wchar c = source.peekNextChar();
  177. const bool hasExponent = (c == 'e' || c == 'E');
  178. if (hasExponent)
  179. {
  180. source.skip();
  181. c = source.peekNextChar();
  182. if (c == '+' || c == '-')
  183. source.skip();
  184. int numExpDigits = 0;
  185. while (isDecimalDigit (source.peekNextChar()))
  186. {
  187. source.skip();
  188. ++numExpDigits;
  189. }
  190. if (numExpDigits == 0)
  191. return false;
  192. }
  193. c = source.peekNextChar();
  194. if (c == 'f' || c == 'F')
  195. source.skip();
  196. else if (! (hasExponent || hasPoint))
  197. return false;
  198. return true;
  199. }
  200. static int parseNumber (CodeDocument::Iterator& source)
  201. {
  202. const CodeDocument::Iterator original (source);
  203. if (parseFloatLiteral (source))
  204. return CPlusPlusCodeTokeniser::tokenType_float;
  205. source = original;
  206. if (parseHexLiteral (source))
  207. return CPlusPlusCodeTokeniser::tokenType_integer;
  208. source = original;
  209. if (parseOctalLiteral (source))
  210. return CPlusPlusCodeTokeniser::tokenType_integer;
  211. source = original;
  212. if (parseDecimalLiteral (source))
  213. return CPlusPlusCodeTokeniser::tokenType_integer;
  214. source = original;
  215. return CPlusPlusCodeTokeniser::tokenType_error;
  216. }
  217. static void skipQuotedString (CodeDocument::Iterator& source) noexcept
  218. {
  219. const juce_wchar quote = source.nextChar();
  220. for (;;)
  221. {
  222. const juce_wchar c = source.nextChar();
  223. if (c == quote || c == 0)
  224. break;
  225. if (c == '\\')
  226. source.skip();
  227. }
  228. }
  229. static void skipComment (CodeDocument::Iterator& source) noexcept
  230. {
  231. bool lastWasStar = false;
  232. for (;;)
  233. {
  234. const juce_wchar c = source.nextChar();
  235. if (c == 0 || (c == '/' && lastWasStar))
  236. break;
  237. lastWasStar = (c == '*');
  238. }
  239. }
  240. static void skipPreprocessorLine (CodeDocument::Iterator& source) noexcept
  241. {
  242. bool lastWasBackslash = false;
  243. for (;;)
  244. {
  245. const juce_wchar c = source.peekNextChar();
  246. if (c == '"')
  247. {
  248. skipQuotedString (source);
  249. continue;
  250. }
  251. if (c == '/')
  252. {
  253. CodeDocument::Iterator next (source);
  254. next.skip();
  255. const juce_wchar c2 = next.peekNextChar();
  256. if (c2 == '/' || c2 == '*')
  257. return;
  258. }
  259. if (c == 0)
  260. break;
  261. if (c == '\n' || c == '\r')
  262. {
  263. source.skipToEndOfLine();
  264. if (lastWasBackslash)
  265. skipPreprocessorLine (source);
  266. break;
  267. }
  268. lastWasBackslash = (c == '\\');
  269. source.skip();
  270. }
  271. }
  272. static void skipIfNextCharMatches (CodeDocument::Iterator& source, const juce_wchar c) noexcept
  273. {
  274. if (source.peekNextChar() == c)
  275. source.skip();
  276. }
  277. static void skipIfNextCharMatches (CodeDocument::Iterator& source,
  278. const juce_wchar c1, const juce_wchar c2) noexcept
  279. {
  280. const juce_wchar c = source.peekNextChar();
  281. if (c == c1 || c == c2)
  282. source.skip();
  283. }
  284. }
  285. //==============================================================================
  286. CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() {}
  287. CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() {}
  288. int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source)
  289. {
  290. using namespace CppTokeniser;
  291. int result = CPlusPlusCodeTokeniser::tokenType_error;
  292. source.skipWhitespace();
  293. const juce_wchar firstChar = source.peekNextChar();
  294. switch (firstChar)
  295. {
  296. case 0:
  297. source.skip();
  298. break;
  299. case '0':
  300. case '1':
  301. case '2':
  302. case '3':
  303. case '4':
  304. case '5':
  305. case '6':
  306. case '7':
  307. case '8':
  308. case '9':
  309. case '.':
  310. result = parseNumber (source);
  311. if (result == CPlusPlusCodeTokeniser::tokenType_error)
  312. {
  313. source.skip();
  314. if (firstChar == '.')
  315. result = CPlusPlusCodeTokeniser::tokenType_punctuation;
  316. }
  317. break;
  318. case ',':
  319. case ';':
  320. case ':':
  321. source.skip();
  322. result = CPlusPlusCodeTokeniser::tokenType_punctuation;
  323. break;
  324. case '(':
  325. case ')':
  326. case '{':
  327. case '}':
  328. case '[':
  329. case ']':
  330. source.skip();
  331. result = CPlusPlusCodeTokeniser::tokenType_bracket;
  332. break;
  333. case '"':
  334. case '\'':
  335. skipQuotedString (source);
  336. result = CPlusPlusCodeTokeniser::tokenType_string;
  337. break;
  338. case '+':
  339. result = CPlusPlusCodeTokeniser::tokenType_operator;
  340. source.skip();
  341. skipIfNextCharMatches (source, '+', '=');
  342. break;
  343. case '-':
  344. source.skip();
  345. result = parseNumber (source);
  346. if (result == CPlusPlusCodeTokeniser::tokenType_error)
  347. {
  348. result = CPlusPlusCodeTokeniser::tokenType_operator;
  349. skipIfNextCharMatches (source, '-', '=');
  350. }
  351. break;
  352. case '*':
  353. case '%':
  354. case '=':
  355. case '!':
  356. result = CPlusPlusCodeTokeniser::tokenType_operator;
  357. source.skip();
  358. skipIfNextCharMatches (source, '=');
  359. break;
  360. case '/':
  361. result = CPlusPlusCodeTokeniser::tokenType_operator;
  362. source.skip();
  363. if (source.peekNextChar() == '=')
  364. {
  365. source.skip();
  366. }
  367. else if (source.peekNextChar() == '/')
  368. {
  369. result = CPlusPlusCodeTokeniser::tokenType_comment;
  370. source.skipToEndOfLine();
  371. }
  372. else if (source.peekNextChar() == '*')
  373. {
  374. source.skip();
  375. result = CPlusPlusCodeTokeniser::tokenType_comment;
  376. skipComment (source);
  377. }
  378. break;
  379. case '?':
  380. case '~':
  381. source.skip();
  382. result = CPlusPlusCodeTokeniser::tokenType_operator;
  383. break;
  384. case '<':
  385. case '>':
  386. case '|':
  387. case '&':
  388. case '^':
  389. source.skip();
  390. result = CPlusPlusCodeTokeniser::tokenType_operator;
  391. skipIfNextCharMatches (source, firstChar);
  392. skipIfNextCharMatches (source, '=');
  393. break;
  394. case '#':
  395. result = CPlusPlusCodeTokeniser::tokenType_preprocessor;
  396. skipPreprocessorLine (source);
  397. break;
  398. default:
  399. if (isIdentifierStart (firstChar))
  400. result = parseIdentifier (source);
  401. else
  402. source.skip();
  403. break;
  404. }
  405. return result;
  406. }
  407. CodeEditorComponent::ColourScheme CPlusPlusCodeTokeniser::getDefaultColourScheme()
  408. {
  409. struct Type
  410. {
  411. const char* name;
  412. uint32 colour;
  413. };
  414. const Type types[] =
  415. {
  416. { "Error", 0xffcc0000 },
  417. { "Comment", 0xff00aa00 },
  418. { "Keyword", 0xff0000cc },
  419. { "Operator", 0xff225500 },
  420. { "Identifier", 0xff000000 },
  421. { "Integer", 0xff880000 },
  422. { "Float", 0xff885500 },
  423. { "String", 0xff990099 },
  424. { "Bracket", 0xff000055 },
  425. { "Punctuation", 0xff004400 },
  426. { "Preprocessor Text", 0xff660000 }
  427. };
  428. CodeEditorComponent::ColourScheme cs;
  429. for (int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2)
  430. cs.set (types[i].name, Colour (types[i].colour));
  431. return cs;
  432. }
  433. bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) noexcept
  434. {
  435. return CppTokeniser::isReservedKeyword (token.getCharPointer(), token.length());
  436. }