syntax.pascal.pp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. Pascal syntax highlighter
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$MODE objfpc}
  12. {$H+}
  13. unit syntax.pascal;
  14. interface
  15. uses
  16. {$IFDEF FPC_DOTTEDUNITS}
  17. System.Types, System.SysUtils, syntax.highlighter;
  18. {$ELSE}
  19. types, sysutils, syntax.highlighter;
  20. {$ENDIF}
  21. type
  22. { TPascalSyntaxHighlighter }
  23. TPascalSyntaxHighlighter = class(TSyntaxHighlighter)
  24. private
  25. FSource: string;
  26. FPos: integer;
  27. protected
  28. procedure CheckCategories;
  29. procedure ProcessComment1(var endPos: integer; akind : TSyntaxHighlightKind);
  30. procedure ProcessComment2(var endPos: integer);
  31. function CheckForComment(var endPos: integer): boolean;
  32. procedure ProcessAsm(var endPos: integer);
  33. function CheckForKeyword(var endPos: integer): boolean;
  34. procedure ProcessSymbol(var endPos: integer);
  35. class function GetLanguages: TStringDynArray; override;
  36. public
  37. constructor Create; override;
  38. class var
  39. CategoryPascal,
  40. CategoryIdentifier : Integer;
  41. function Execute(const Source: string): TSyntaxTokenArray; override;
  42. end;
  43. function DoPascalHighlighting(const Source: string): TSyntaxTokenArray;
  44. implementation
  45. const
  46. MaxKeywordLength = 15;
  47. MaxKeyword = 60;
  48. KeywordTable: array[0..MaxKeyword] of string =
  49. ('AND', 'ARRAY', 'ASM', 'ASSEMBLER',
  50. 'BEGIN', 'BREAK',
  51. 'CASE', 'CONST', 'CONSTRUCTOR', 'CLASS',
  52. 'DEFAULT', 'DESTRUCTOR', 'DIV', 'DO', 'DOWNTO',
  53. 'ELSE', 'END', 'EXCEPT', 'EXIT',
  54. 'FINALIZATION', 'FINALLY', 'FOR', 'FUNCTION',
  55. 'GOTO',
  56. 'IF', 'IMPLEMENTATION', 'IN', 'INHERITED', 'INITIALIZATION', 'INTERFACE',
  57. 'NIL', 'NOT',
  58. 'OBJECT', 'OF', 'ON', 'OR', 'OVERRIDE',
  59. 'PACKED', 'PRIVATE', 'PROCEDURE', 'PROGRAM', 'PROPERTY', 'PROTECTED',
  60. 'PUBLIC', 'PUBLISHED',
  61. 'RAISE', 'RECORD', 'REPEAT', 'RESOURCESTRING',
  62. 'SET',
  63. 'THEN', 'TRY', 'TYPE',
  64. 'UNIT', 'UNTIL', 'USES',
  65. 'VAR', 'VIRTUAL',
  66. 'WHILE', 'WITH',
  67. 'XOR');
  68. KeywordAsmIndex = 2;
  69. { TPascalSyntaxHighlighter }
  70. procedure TPascalSyntaxHighlighter.CheckCategories;
  71. begin
  72. if CategoryPascal=0 then
  73. begin
  74. CategoryPascal:=RegisterCategory('pascal');
  75. CategoryIdentifier:=RegisterCategory('identifier');
  76. end;
  77. end;
  78. procedure TPascalSyntaxHighlighter.ProcessComment1(var endPos: integer; akind: TSyntaxHighlightKind);
  79. var
  80. startPos: integer;
  81. begin
  82. startPos := FPos;
  83. Inc(FPos); // Skip the opening '{'
  84. while (FPos <= Length(FSource)) and (FSource[FPos] <> '}') do
  85. Inc(FPos);
  86. if (FPos <= Length(FSource)) and (FSource[FPos] = '}') then
  87. Inc(FPos);
  88. endPos := FPos - 1;
  89. AddToken(Copy(FSource, startPos, endPos - startPos + 1), aKind);
  90. end;
  91. procedure TPascalSyntaxHighlighter.ProcessComment2(var endPos: integer);
  92. var
  93. startPos: integer;
  94. begin
  95. startPos := FPos;
  96. Inc(FPos, 2); // Skip the opening '(*'
  97. while (FPos < Length(FSource)) and not ((FSource[FPos] = '*') and (FSource[FPos + 1] = ')')) do
  98. Inc(FPos);
  99. if (FPos < Length(FSource)) and (FSource[FPos] = '*') and (FSource[FPos + 1] = ')') then
  100. begin
  101. Inc(FPos, 2);
  102. end
  103. else
  104. FPos := Length(FSource) + 1;
  105. endPos := FPos - 1;
  106. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shComment);
  107. end;
  108. function TPascalSyntaxHighlighter.CheckForComment(var endPos: integer): boolean;
  109. var
  110. startPos: integer;
  111. kind: TSyntaxHighlightKind;
  112. begin
  113. Result := True;
  114. startPos := FPos;
  115. if (FPos <= Length(FSource)) and (FSource[FPos] = '{') then
  116. begin
  117. if (FPos < Length(FSource)) and (FSource[FPos + 1] = '$') then
  118. kind := shDirective
  119. else
  120. kind := shComment;
  121. ProcessComment1(endPos,kind);
  122. end
  123. else if (FPos < Length(FSource)) and (FSource[FPos] = '(') and (FSource[FPos + 1] = '*') then
  124. begin
  125. ProcessComment2(endPos);
  126. end
  127. else if (FPos < Length(FSource)) and (FSource[FPos] = '/') and (FSource[FPos + 1] = '/') then
  128. begin
  129. while (FPos <= Length(FSource)) and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) do
  130. Inc(FPos);
  131. endPos := FPos - 1;
  132. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shComment);
  133. end
  134. else
  135. Result := False;
  136. end;
  137. procedure TPascalSyntaxHighlighter.ProcessAsm(var endPos: integer);
  138. var
  139. startPos: integer;
  140. lastChar: char;
  141. begin
  142. startPos := FPos;
  143. lastChar := ' ';
  144. while FPos <= Length(FSource) do
  145. begin
  146. if (lastChar in [' ', #9, #10, #13]) and
  147. (FPos + 2 <= Length(FSource)) and
  148. (UpCase(FSource[FPos]) = 'E') and (UpCase(FSource[FPos + 1]) = 'N') and
  149. (UpCase(FSource[FPos + 2]) = 'D') then
  150. begin
  151. endPos := FPos - 1;
  152. if endPos >= startPos then
  153. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shAssembler);
  154. AddToken('END', shKeyword);
  155. Inc(FPos, 3);
  156. Exit;
  157. end
  158. else
  159. begin
  160. if CheckForComment(endPos) then
  161. lastChar := ' '
  162. else
  163. begin
  164. lastChar := FSource[FPos];
  165. Inc(FPos);
  166. end;
  167. end;
  168. end;
  169. endPos := FPos - 1;
  170. if endPos >= startPos then
  171. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shAssembler);
  172. end;
  173. function TPascalSyntaxHighlighter.CheckForKeyword(var endPos: integer): boolean;
  174. const
  175. IdentifierChars = ['0'..'9', 'A'..'Z', 'a'..'z', '_'];
  176. var
  177. i, lIdx: integer;
  178. keyword, ukeyword: string;
  179. begin
  180. i := 0;
  181. while (FPos + i <= Length(FSource))
  182. and (i < MaxKeywordLength)
  183. and (FSource[FPos + i] in IdentifierChars) do
  184. Inc(i);
  185. keyword := Copy(FSource, FPos, i);
  186. ukeyword := UpperCase(keyword);
  187. Result := False;
  188. lIdx:=MaxKeyWord;
  189. While (Not Result) and (lIdx>=0) do
  190. begin
  191. Result:=KeywordTable[lIdx] = ukeyword;
  192. Dec(lIdx);
  193. end;
  194. if not Result then
  195. Exit;
  196. Inc(lIdx); // Index of actual keyword
  197. Inc(FPos,i);
  198. endPos:=FPos - 1;
  199. AddToken(keyword,shKeyword);
  200. if lIdx=KeywordAsmIndex then
  201. ProcessAsm(endPos);
  202. end;
  203. procedure TPascalSyntaxHighlighter.ProcessSymbol(var endPos: integer);
  204. var
  205. startPos: integer;
  206. begin
  207. startPos := FPos;
  208. if (FPos < Length(FSource)) and (FSource[FPos] = ':') and (FSource[FPos + 1] = '=') then
  209. Inc(FPos, 2)
  210. else
  211. Inc(FPos);
  212. endPos := FPos - 1;
  213. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shSymbol);
  214. end;
  215. class function TPascalSyntaxHighlighter.GetLanguages: TStringDynArray;
  216. begin
  217. Result:=['pascal','delphi','objectpascal']
  218. end;
  219. constructor TPascalSyntaxHighlighter.Create;
  220. begin
  221. inherited Create;
  222. CheckCategories;
  223. DefaultCategory:=CategoryPascal;
  224. end;
  225. function TPascalSyntaxHighlighter.Execute(const Source: string): TSyntaxTokenArray;
  226. var
  227. endPos: integer;
  228. StringLength: integer;
  229. lLen,startPos: integer;
  230. ch: char;
  231. begin
  232. Result:=Nil;
  233. CheckCategories;
  234. if Length(Source) = 0 then
  235. Exit;
  236. FSource:=Source;
  237. lLen:=Length(FSource);
  238. FTokens.Reset;
  239. FPos := 1;
  240. EndPos:=0;
  241. while FPos <= llen do
  242. begin
  243. ch := FSource[FPos];
  244. if CheckForComment(endPos) then
  245. begin
  246. FPos := endPos + 1;
  247. continue;
  248. end;
  249. case ch of
  250. ',', ';', ':', '.', '(', ')', '[', ']', '<', '>', '=',
  251. '*', '/', '+', '-', '^', '&', '@':
  252. ProcessSymbol(endPos);
  253. '#':
  254. begin
  255. startPos := FPos;
  256. Inc(FPos);
  257. if (FPos <= Length(FSource)) and (FSource[FPos] = '$') then
  258. Inc(FPos);
  259. while (FPos <= Length(FSource)) and (FSource[FPos] >= '0') and (FSource[FPos] <= '9') do
  260. Inc(FPos);
  261. endPos := FPos - 1;
  262. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shCharacters);
  263. end;
  264. '$':
  265. begin
  266. startPos := FPos;
  267. Inc(FPos);
  268. while (FPos <= Length(FSource)) and (FSource[FPos] in ['0'..'9', 'A'..'F', 'a'..'f']) do
  269. Inc(FPos);
  270. endPos := FPos - 1;
  271. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shNumbers);
  272. end;
  273. '0'..'9':
  274. begin
  275. startPos := FPos;
  276. Inc(FPos);
  277. while (FPos <= Length(FSource)) and (FSource[FPos] >= '0') and (FSource[FPos] <= '9') do
  278. Inc(FPos);
  279. endPos := FPos - 1;
  280. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shNumbers);
  281. end;
  282. '''':
  283. begin
  284. startPos := FPos;
  285. Inc(FPos);
  286. StringLength := 0;
  287. while (FPos <= Length(FSource)) do
  288. begin
  289. if FSource[FPos] = '''' then
  290. if (FPos < Length(FSource)) and (FSource[FPos + 1] = '''') then
  291. begin
  292. Inc(FPos, 2);
  293. Inc(StringLength);
  294. end
  295. else
  296. begin
  297. Inc(FPos);
  298. break;
  299. end
  300. else
  301. begin
  302. Inc(StringLength);
  303. Inc(FPos);
  304. end;
  305. end;
  306. endPos := FPos - 1;
  307. if StringLength = 1 then
  308. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shCharacters)
  309. else if (FPos > Length(FSource)) and (FSource[endPos] <> '''') then
  310. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shInvalid)
  311. else
  312. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shStrings);
  313. end;
  314. '_', 'A'..'Z', 'a'..'z':
  315. begin
  316. if not CheckForKeyword(endPos) then
  317. begin
  318. startPos := FPos;
  319. while (FPos <= Length(FSource)) and (FSource[FPos] in ['0'..'9', '_', 'A'..'Z', 'a'..'z']) do
  320. Inc(FPos);
  321. endPos := FPos - 1;
  322. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  323. end;
  324. end;
  325. ' ', #9, #10, #13:
  326. begin
  327. startPos := FPos;
  328. while (FPos <= Length(FSource)) and (FSource[FPos] in [' ', #9, #10, #13]) do
  329. Inc(FPos);
  330. endPos := FPos - 1;
  331. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  332. end;
  333. else
  334. AddToken(ch, shInvalid);
  335. Inc(FPos);
  336. end;
  337. if FPos = endPos then
  338. Inc(FPos);
  339. end;
  340. Result := FTokens.GetTokens;
  341. end;
  342. function DoPascalHighlighting(const Source: string): TSyntaxTokenArray;
  343. var
  344. highlighter: TPascalSyntaxHighlighter;
  345. begin
  346. highlighter := TPascalSyntaxHighlighter.Create;
  347. try
  348. Result := highlighter.Execute(Source);
  349. finally
  350. highlighter.Free;
  351. end;
  352. end;
  353. initialization
  354. TPascalSyntaxHighlighter.Register;
  355. end.