syntax.pascal.pp 10 KB

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