SyntaxHighlighter.Mod 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088
  1. MODULE SyntaxHighlighter; (** AUTHOR "staubesv"; PURPOSE "Simple Syntax Highlighter"; *)
  2. IMPORT
  3. KernelLog,
  4. Streams, Commands, Strings, Files, Diagnostics, Texts, TextUtilities, XML, XMLScanner, XMLParser, XMLObjects;
  5. CONST
  6. DefineMask* = {0..5};
  7. FontMask* = {0..2};
  8. FontName* = 0;
  9. FontSize* = 1;
  10. FontStyle* = 2;
  11. Color* = 3;
  12. BgColor* = 4;
  13. Voff* = 5;
  14. DefaultBgColor = 0;
  15. DefaultVoff = 0;
  16. DefaultHighlighterFile = "SyntaxHighlighter.XML";
  17. XmlRootElementName = "SyntaxHighlighter";
  18. XmlHighlighters = "Highlighters";
  19. XmlHighlighter = "Highlighter";
  20. XmlWords = "Words";
  21. XmlTokens = "Tokens";
  22. XmlAttributeAllowCharacters = "allowCharacters";
  23. XmlStyles = "Styles";
  24. XmlStyle = "Style";
  25. XmlAttributeName = "name";
  26. XmlAttributeDefaultStyle = "defaultstyle";
  27. XmlAttributeNumberStyle = "numberStyle";
  28. XmlAttributeFontName = "fontname";
  29. XmlAttributeFontSize = "fontsize";
  30. XmlAttributeFontStyle = "fontstyle";
  31. XmlAttributeColor = "color";
  32. XmlAttributeBgColor = "bgcolor";
  33. XmlAttributeVoff = "voff";
  34. XmlAttributeStyle = "style";
  35. XmlAttributeStyleOpen = "styleOpen";
  36. XmlAttributeStyleClose = "styleClose";
  37. XmlAttributeStyleContent = "style";
  38. XmlGroup = "Group";
  39. XmlRegions = "Regions";
  40. XmlRegion = "Region";
  41. XmlAttributeOpen = "open";
  42. XmlAttributeClose = "close";
  43. XmlAttributeNesting = "nesting";
  44. XmlAttributeMultiLine = "multiline";
  45. XmlDontCare = "*";
  46. Trace_None = 0;
  47. Trace_1 = 1;
  48. Trace_Max = 2;
  49. Statistics = TRUE;
  50. NOTCLOSED = MAX(LONGINT) - 128; (* some safety distance to protect against overflow *)
  51. MaxOpenLength = 32;
  52. MaxCloseLength = 32;
  53. MaxWordLength = 32;
  54. Dim1Length = 128;
  55. MaxTokenLength = 64;
  56. Ok = 0;
  57. StringTooLong = 1;
  58. Outside = 0;
  59. OpenString = 1;
  60. Content = 2;
  61. CloseString = 3;
  62. NoMatch = 0;
  63. Matching = 1;
  64. OpenMatch = 2;
  65. CloseMatch = 3;
  66. (* token types and subtypes*)
  67. Type_Invalid* = 0;
  68. Type_Identifier* = 1;
  69. Type_Number* = 2;
  70. Type_Token* = 3;
  71. Subtype_Decimal* = 0;
  72. Subtype_Hex* = 1;
  73. Subtype_Float* = 2;
  74. Subtype_Char* = 3;
  75. TypeWords = 1;
  76. TypeTokens = 2;
  77. TYPE
  78. Identifier = ARRAY 64 OF CHAR;
  79. Style* = OBJECT
  80. VAR
  81. name- : Identifier;
  82. attributes- : Texts.Attributes;
  83. defined- : SET;
  84. next : Style;
  85. PROCEDURE &Init(CONST name : Identifier; color, bgcolor, voff : LONGINT; CONST fontname : ARRAY OF CHAR; fontsize : LONGINT; fontstyle : SET);
  86. BEGIN
  87. ASSERT(name # "");
  88. SELF.name := name;
  89. NEW(attributes);
  90. attributes.Set(color, bgcolor, voff, fontname, fontsize, fontstyle);
  91. defined := {};
  92. next := NIL;
  93. END Init;
  94. END Style;
  95. Styles = OBJECT
  96. VAR
  97. styles : Style; (* head of list *)
  98. PROCEDURE &Init;
  99. BEGIN
  100. styles := NIL;
  101. END Init;
  102. PROCEDURE Add(style : Style);
  103. BEGIN {EXCLUSIVE}
  104. ASSERT(FindIntern(style.name) = NIL);
  105. style.next := styles;
  106. styles := style;
  107. END Add;
  108. PROCEDURE Find(CONST name : ARRAY OF CHAR) : Style;
  109. BEGIN {EXCLUSIVE}
  110. RETURN FindIntern(name);
  111. END Find;
  112. PROCEDURE FindIntern(CONST name : ARRAY OF CHAR) : Style;
  113. VAR style : Style;
  114. BEGIN
  115. style := styles;
  116. WHILE (style # NIL) & (style.name # name) DO style := style.next; END;
  117. RETURN style;
  118. END FindIntern;
  119. END Styles;
  120. TYPE
  121. Word = POINTER TO RECORD
  122. name : ARRAY MaxWordLength OF CHAR;
  123. style : Style; (* { style # NIL } *)
  124. next : Word;
  125. END;
  126. DataEntry = RECORD
  127. open, close : LONGINT; (* region *)
  128. region : RegionMatcher; (* { region # NIL } *)
  129. eol : BOOLEAN;
  130. END;
  131. DataArray = POINTER TO ARRAY OF DataEntry;
  132. State* = OBJECT
  133. VAR
  134. matchers : RegionMatcher;
  135. data : DataArray; (* {data # NIL} *)
  136. nofData : LONGINT;
  137. PROCEDURE &Init;
  138. BEGIN
  139. matchers := NIL;
  140. NEW(data, 128);
  141. nofData := 0;
  142. END Init;
  143. PROCEDURE AddMatcher(matcher : RegionMatcher);
  144. VAR m : RegionMatcher;
  145. BEGIN
  146. ASSERT((matcher # NIL) & (matcher.next = NIL));
  147. IF (matchers = NIL) THEN
  148. matchers := matcher;
  149. ELSE
  150. m := matchers;
  151. WHILE (m.next # NIL) DO m := m.next; END;
  152. m.next := matcher;
  153. END;
  154. END AddMatcher;
  155. PROCEDURE ResetMatchers;
  156. VAR m : RegionMatcher;
  157. BEGIN
  158. m := matchers;
  159. WHILE (m # NIL) DO
  160. m.ResetMatching;
  161. m := m.next;
  162. END;
  163. END ResetMatchers;
  164. PROCEDURE GetStyle(position : LONGINT; VAR start, end : LONGINT) : Style;
  165. VAR style : Style; entry : DataEntry; location : LONGINT;
  166. BEGIN
  167. style := NIL;
  168. IF Find(position, entry) THEN
  169. location := GetLocation(position, entry);
  170. IF (location = OpenString) THEN
  171. style := entry.region.styleOpen;
  172. start := entry.open; end := entry.open + entry.region.openLength - 1;
  173. ELSIF (location = Content) THEN
  174. style := entry.region.styleContent;
  175. start := entry.open + entry.region.openLength; end := entry.close - entry.region.closeLength;
  176. ELSIF (location = CloseString) THEN
  177. style := entry.region.styleClose;
  178. start := entry.close - entry.region.closeLength + 1; end := entry.close;
  179. ELSE
  180. HALT(99);
  181. END;
  182. END;
  183. RETURN style;
  184. END GetStyle;
  185. PROCEDURE Find(CONST position : LONGINT; VAR entry : DataEntry) : BOOLEAN;
  186. VAR l, r, m : LONGINT;
  187. BEGIN
  188. (* binary search *)
  189. l := 0; r := nofData;
  190. WHILE l < r DO
  191. m := (r - l) DIV 2 + l;
  192. IF (position <= data[m].close) THEN r := m;
  193. ELSE l := m + 1;
  194. END;
  195. END;
  196. IF (r < nofData) & (data[r].open <= position) & (position <= data[r].close) THEN
  197. entry := data[r];
  198. RETURN TRUE;
  199. ELSE
  200. RETURN FALSE;
  201. END;
  202. END Find;
  203. PROCEDURE FindTriple(position : LONGINT; VAR hasLeft, hasMiddle, hasRight : BOOLEAN; VAR left, middle, right : DataEntry);
  204. VAR i : LONGINT;
  205. BEGIN
  206. hasLeft := FALSE; hasMiddle := FALSE; hasRight := FALSE;
  207. IF (nofData > 0) THEN
  208. i := 0;
  209. WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
  210. IF (i > 0) THEN
  211. hasLeft := TRUE; left := data[i-1];
  212. END;
  213. IF (i < nofData) & (data[i].open <= position) & (position <= data[i].close) THEN
  214. hasMiddle := TRUE; middle := data[i];
  215. END;
  216. IF (i < nofData - 1) THEN
  217. hasRight := TRUE; right := data[i + 1];
  218. END;
  219. END;
  220. END FindTriple;
  221. PROCEDURE Patch(fromPosition : LONGINT; length : LONGINT);
  222. VAR i : LONGINT;
  223. BEGIN
  224. IF (nofData > 0) THEN
  225. i := 0;
  226. WHILE (i < nofData) & (data[i].close < fromPosition) DO INC(i); END;
  227. WHILE (i < nofData) DO
  228. data[i].close := data[i].close + length;
  229. IF (data[i].open >= fromPosition) THEN
  230. data[i].open := data[i].open + length;
  231. END;
  232. INC(i);
  233. END;
  234. END;
  235. END Patch;
  236. PROCEDURE Add(CONST entry : DataEntry);
  237. VAR insertAt, i : LONGINT;
  238. BEGIN
  239. ASSERT(entry.region # NIL);
  240. insertAt := 0;
  241. WHILE (insertAt < nofData) & (entry.open > data[insertAt].close) DO INC(insertAt); END;
  242. INC(nofData); (* we will add one data element ... *)
  243. IF (nofData >= LEN(data)) THEN EnlargeDataArray; END;
  244. FOR i := nofData - 1 TO insertAt + 1 BY -1 DO
  245. data[i] := data[i-1];
  246. END;
  247. data[insertAt] := entry;
  248. END Add;
  249. PROCEDURE Remove(CONST entry : DataEntry);
  250. VAR removeIdx, i : LONGINT;
  251. BEGIN
  252. IF (nofData > 0) THEN
  253. removeIdx := 0;
  254. WHILE (removeIdx < nofData) & (data[removeIdx].open # entry.open) & (data[removeIdx].close # entry.close) DO
  255. INC(removeIdx);
  256. END;
  257. FOR i := removeIdx TO nofData - 2 DO
  258. data[i] := data[i + 1];
  259. END;
  260. DEC(nofData);
  261. END;
  262. END Remove;
  263. PROCEDURE RemoveFrom(position : LONGINT);
  264. VAR i : LONGINT;
  265. BEGIN
  266. IF (nofData > 0) THEN
  267. i := 0;
  268. WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
  269. nofData := i;
  270. END;
  271. END RemoveFrom;
  272. PROCEDURE RemoveFromTo(position, length : LONGINT) : BOOLEAN;
  273. VAR removedEntries : BOOLEAN; i : LONGINT;
  274. BEGIN
  275. removedEntries := FALSE;
  276. IF (nofData > 0) THEN
  277. i := 0;
  278. WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
  279. IF (i < nofData - 1) & (position + length - 1 >= data[i].open) THEN
  280. nofData := i;
  281. removedEntries := TRUE;
  282. END;
  283. END;
  284. RETURN removedEntries;
  285. END RemoveFromTo;
  286. PROCEDURE Clear;
  287. BEGIN
  288. nofData := 0;
  289. END Clear;
  290. PROCEDURE EnlargeDataArray;
  291. VAR newData : DataArray; i : LONGINT;
  292. BEGIN
  293. NEW(newData, 2 * LEN(data));
  294. FOR i := 0 TO LEN(data)-1 DO
  295. newData[i] := data[i];
  296. END;
  297. data := newData;
  298. END EnlargeDataArray;
  299. PROCEDURE ShowEntry(CONST entry : DataEntry; out : Streams.Writer);
  300. BEGIN
  301. ASSERT(out # NIL);
  302. out.String("From "); out.Int(entry.open, 0); out.String(" to "); out.Int(entry.close, 0);
  303. out.Ln;
  304. END ShowEntry;
  305. PROCEDURE Dump(out : Streams.Writer);
  306. VAR i : LONGINT;
  307. BEGIN
  308. ASSERT(out # NIL);
  309. out.String("Region dump : "); out.Int(nofData, 0); out.String(" entries"); out.Ln;
  310. IF (nofData > 0) THEN
  311. FOR i := 0 TO nofData - 1 DO
  312. ShowEntry(data[i], out);
  313. END;
  314. END;
  315. END Dump;
  316. END State;
  317. TYPE
  318. RegionDescriptor = OBJECT
  319. VAR
  320. open, close : Identifier;
  321. nesting, multiline : BOOLEAN;
  322. styleOpen, styleClose, styleContent : Style;
  323. openLength, closeLength : LONGINT;
  324. next : RegionDescriptor;
  325. PROCEDURE &Init(CONST open, close : ARRAY OF CHAR; nesting, multiline : BOOLEAN; styleOpen, styleClose, styleContent : Style);
  326. BEGIN
  327. Copy(open, SELF.open); openLength := Strings.Length(open); ASSERT((openLength > 0) & (openLength < MaxOpenLength));
  328. Copy(close, SELF.close); closeLength := Strings.Length(close); ASSERT((closeLength >= 0) & (closeLength < MaxCloseLength));
  329. SELF.nesting := nesting;
  330. SELF.multiline := multiline;
  331. SELF.styleOpen := styleOpen;
  332. SELF.styleClose := styleClose;
  333. SELF.styleContent := styleContent;
  334. next := NIL;
  335. END Init;
  336. END RegionDescriptor;
  337. TYPE
  338. RegionMatcher = OBJECT
  339. VAR
  340. open, close : Identifier;
  341. nesting, multiline : BOOLEAN;
  342. styleOpen, styleClose, styleContent : Style;
  343. openLength, closeLength : LONGINT;
  344. openChars : ARRAY MaxOpenLength OF CHAR;
  345. closeChars : ARRAY MaxCloseLength OF CHAR;
  346. firstOpenChar, nofOpenChars, firstCloseChar, nofCloseChars : LONGINT;
  347. lastChar : CHAR;
  348. entry : DataEntry;
  349. level : LONGINT;
  350. state : LONGINT;
  351. firstPosition : LONGINT;
  352. next : RegionMatcher;
  353. PROCEDURE &Init(descriptor : RegionDescriptor);
  354. BEGIN
  355. ASSERT(descriptor # NIL);
  356. Copy(descriptor.open, SELF.open); openLength := descriptor.openLength;
  357. Copy(descriptor.close, SELF.close); closeLength := descriptor.closeLength;
  358. SELF.nesting := descriptor.nesting;
  359. SELF.multiline := descriptor.multiline;
  360. SELF.styleOpen := descriptor.styleOpen;
  361. SELF.styleClose := descriptor.styleClose;
  362. SELF.styleContent := descriptor.styleContent;
  363. ResetMatching;
  364. next := NIL;
  365. END Init;
  366. PROCEDURE GetEntry() : DataEntry;
  367. BEGIN
  368. RETURN entry;
  369. END GetEntry;
  370. PROCEDURE ResetMatching;
  371. BEGIN
  372. nofOpenChars := 0; nofCloseChars := 0;
  373. lastChar := 0X;
  374. level := 0;
  375. state := NoMatch;
  376. firstPosition := MAX(LONGINT);
  377. END ResetMatching;
  378. PROCEDURE CheckOpen(reader : Texts.TextReader; position : LONGINT; VAR length : LONGINT) : BOOLEAN;
  379. VAR char32 : Texts.Char32; oldPosition : LONGINT;
  380. BEGIN
  381. ASSERT(reader # NIL);
  382. length := 0;
  383. oldPosition := reader.GetPosition();
  384. reader.SetPosition(position);
  385. reader.ReadCh(char32);
  386. WHILE (length < openLength) & (open[length] = CHR(char32)) & ~reader.eot DO reader.ReadCh(char32); INC(length); END;
  387. IF (length = openLength) THEN
  388. ResetMatching;
  389. entry.open := position;
  390. entry.close := NOTCLOSED;
  391. entry.region := SELF;
  392. entry.eol := FALSE;
  393. state := OpenMatch;
  394. level := 1;
  395. firstPosition := position;
  396. END;
  397. RETURN length = openLength;
  398. END CheckOpen;
  399. PROCEDURE FeedChar(char32 : Texts.Char32; position : LONGINT; VAR newState : LONGINT);
  400. VAR char : CHAR; openMatch, closeMatch : BOOLEAN;
  401. PROCEDURE AddToCircularBuffer(char : CHAR; VAR buffer : ARRAY OF CHAR; VAR first, length, maxLength : LONGINT);
  402. BEGIN
  403. buffer[(first + length) MOD LEN(buffer)] := char;
  404. IF (length = maxLength) THEN
  405. first := (first + 1) MOD LEN(buffer);
  406. ELSE
  407. ASSERT(length < maxLength);
  408. INC(length);
  409. END;
  410. END AddToCircularBuffer;
  411. PROCEDURE CheckBuffer(CONST buffer, compareTo : ARRAY OF CHAR; VAR first, length, maxLength : LONGINT) : BOOLEAN;
  412. VAR i : LONGINT;
  413. BEGIN
  414. ASSERT(length = maxLength);
  415. i := 0;
  416. WHILE (i < maxLength) & (buffer[(first + i) MOD LEN(buffer)] = compareTo[i]) DO INC(i); END;
  417. IF (i = maxLength) THEN
  418. length := 0; (* clear buffer *)
  419. RETURN TRUE;
  420. ELSE
  421. REPEAT
  422. first := (first + 1) MOD LEN(buffer);
  423. DEC(length);
  424. UNTIL (length = 0) OR (buffer[first] = compareTo[0]);
  425. RETURN FALSE;
  426. END;
  427. END CheckBuffer;
  428. BEGIN
  429. ASSERT(level >= 0);
  430. openMatch := FALSE; closeMatch := FALSE;
  431. char := CHR(char32);
  432. IF (level = 0) OR nesting THEN (* allow matching to open string *)
  433. IF (openLength = 1) THEN
  434. openMatch := (char = open[0]);
  435. ELSIF (openLength = 2) THEN
  436. openMatch := (char = open[1]) & (lastChar = open[0]);
  437. ELSIF (char = open[0]) OR (nofOpenChars > 0) THEN (* start OR continue to save characters *)
  438. AddToCircularBuffer(char, openChars, firstOpenChar, nofOpenChars, openLength);
  439. IF (nofOpenChars = openLength) THEN
  440. openMatch := CheckBuffer(openChars, open, firstOpenChar, nofOpenChars, openLength);
  441. END;
  442. END;
  443. IF openMatch THEN
  444. nofOpenChars := 0; lastChar := 0X;
  445. INC(level);
  446. IF (level = 1) THEN
  447. entry.open := position - openLength + 1;
  448. entry.close := NOTCLOSED;
  449. entry.region := SELF;
  450. entry.eol := FALSE;
  451. END;
  452. END;
  453. ELSE
  454. nofOpenChars := 0;
  455. END;
  456. IF ~openMatch & (level > 0) THEN (* allow matching to close string *)
  457. IF (closeLength = 1) THEN
  458. closeMatch := (char = close[0]);
  459. ELSIF (closeLength = 2) THEN
  460. closeMatch := (char = close[1]) & (lastChar = close[0]);
  461. ELSIF (closeLength > 0) & ((char = close[0]) OR (nofCloseChars > 0)) THEN
  462. AddToCircularBuffer(char, closeChars, firstCloseChar, nofCloseChars, closeLength);
  463. IF (nofCloseChars = closeLength) THEN
  464. closeMatch := CheckBuffer(closeChars, close, firstCloseChar, nofCloseChars, closeLength);
  465. END;
  466. END;
  467. IF ~multiline & (char = CHR(Texts.NewLineChar)) & (~closeMatch OR (level > 0)) THEN
  468. nofCloseChars := 0;
  469. level := 0;
  470. entry.close := position;
  471. entry.eol := TRUE;
  472. (* don't set closeMatch here since entry.end position may be different for EOL match *)
  473. ELSIF closeMatch THEN
  474. nofCloseChars := 0; lastChar := 0X;
  475. DEC(level);
  476. IF (level = 0) THEN
  477. entry.close := position;
  478. END;
  479. END;
  480. ELSE
  481. nofCloseChars := 0; (* clear buffer *)
  482. END;
  483. IF ~openMatch & ~closeMatch THEN lastChar := char; END;
  484. IF (state = NoMatch) THEN
  485. IF openMatch THEN state := OpenMatch; firstPosition := position;
  486. ELSIF (nofOpenChars > 0) THEN state := Matching; firstPosition := position;
  487. END;
  488. ELSIF (state = Matching) THEN
  489. IF openMatch THEN state := OpenMatch;
  490. ELSIF (nofOpenChars = 1) THEN state := Matching; firstPosition := position;
  491. ELSIF (nofOpenChars > 1) THEN state := Matching;
  492. ELSE state := NoMatch;
  493. END;
  494. ELSIF (state = OpenMatch) THEN
  495. IF (level = 0) THEN state := CloseMatch; END;
  496. ELSIF (state = CloseMatch) THEN
  497. (* no more state transitions until reset *)
  498. END;
  499. newState := state;
  500. END FeedChar;
  501. END RegionMatcher;
  502. TYPE
  503. Token* = RECORD
  504. type-, subtype- : SHORTINT;
  505. startPosition-, endPosition- : LONGINT;
  506. value- : ARRAY MaxTokenLength OF CHAR;
  507. length : LONGINT; (* if type = Type_Identifier: of string value *)
  508. style- : Style;
  509. END;
  510. TYPE
  511. Highlighter* = OBJECT
  512. VAR
  513. name : Identifier;
  514. defaultStyle, numberStyle : Style;
  515. words : ARRAY Dim1Length OF ARRAY MaxWordLength OF Word;
  516. wildcardWords : ARRAY MaxWordLength OF Word;
  517. wildcardsEnabled : BOOLEAN;
  518. tokens : ARRAY Dim1Length OF RECORD
  519. length : ARRAY MaxWordLength OF Word;
  520. maxLength : LONGINT;
  521. END;
  522. regions : RegionDescriptor;
  523. longestOpen, longestClose : LONGINT;
  524. regionChars, wordChars, isAllowedCharacter : ARRAY 256 OF BOOLEAN;
  525. next : Highlighter;
  526. PROCEDURE &Init(CONST name : ARRAY OF CHAR);
  527. VAR i, j : LONGINT;
  528. BEGIN
  529. ASSERT(name # "");
  530. Copy(name, SELF.name);
  531. defaultStyle := NIL; numberStyle := NIL;
  532. FOR i := 0 TO Dim1Length-1 DO
  533. FOR j := 0 TO MaxWordLength-1 DO
  534. words[i][j] := NIL;
  535. tokens[i].length[j] := NIL;
  536. tokens[i].maxLength := 0;
  537. END;
  538. END;
  539. FOR i := 0 TO LEN(wildcardWords)-1 DO wildcardWords[i] := NIL; END;
  540. wildcardsEnabled := FALSE;
  541. regions := NIL;
  542. longestOpen := 0; longestClose := 0;
  543. FOR i := 0 TO LEN(regionChars)-1 DO
  544. regionChars[i] := FALSE; wordChars[i] := FALSE;
  545. isAllowedCharacter[i] := FALSE;
  546. END;
  547. FOR i := ORD("a") TO ORD("z") DO isAllowedCharacter[i] := TRUE; END;
  548. FOR i := ORD("A") TO ORD("Z") DO isAllowedCharacter[i] := TRUE; END;
  549. FOR i := ORD("0") TO ORD("9") DO isAllowedCharacter[i] := TRUE; END;
  550. next := NIL;
  551. END Init;
  552. PROCEDURE IsAllowedCharacter*(character : Texts.Char32) : BOOLEAN;
  553. BEGIN
  554. RETURN (character < 256) & isAllowedCharacter[character MOD 256];
  555. END IsAllowedCharacter;
  556. PROCEDURE AllowCharacter(character : CHAR);
  557. BEGIN
  558. isAllowedCharacter[ORD(character)] := TRUE;
  559. END AllowCharacter;
  560. PROCEDURE Scan(reader : Texts.TextReader; from, to : LONGINT; CONST state : State; VAR match : BOOLEAN);
  561. VAR
  562. matcher, owner, oldOwner : RegionMatcher; char32 : Texts.Char32; continue : BOOLEAN; entry : DataEntry; oldPosition, position : LONGINT;
  563. mstate, tempState, nofMatching : LONGINT;
  564. PROCEDURE CheckLongestMatch(VAR owner : RegionMatcher);
  565. VAR matcher : RegionMatcher; length, maxLength : LONGINT;
  566. BEGIN
  567. ASSERT(owner # NIL);
  568. maxLength := owner.openLength;
  569. matcher := state.matchers;
  570. WHILE (matcher # NIL) DO
  571. IF (matcher.state = Matching) & (matcher.firstPosition <= owner.firstPosition) THEN
  572. IF matcher.CheckOpen(reader, matcher.firstPosition, length) & ((matcher.firstPosition < owner.firstPosition) OR (length > maxLength)) THEN
  573. maxLength := length;
  574. owner := matcher;
  575. END;
  576. END;
  577. matcher := matcher.next;
  578. END;
  579. ASSERT(owner # NIL);
  580. END CheckLongestMatch;
  581. BEGIN
  582. ASSERT((reader # NIL) & (state # NIL));
  583. IF (traceLevel >= Trace_1) THEN
  584. KernelLog.String("Scan from ");
  585. KernelLog.Int(from, 0); KernelLog.String(" to "); KernelLog.Int(to, 0);
  586. KernelLog.Ln;
  587. END;
  588. state.ResetMatchers;
  589. match := FALSE;
  590. owner := NIL; continue := FALSE;
  591. reader.SetPosition(from); position := reader.GetPosition();
  592. reader.SetDirection(1);
  593. reader.ReadCh(char32);
  594. WHILE ~reader.eot & (position <= to) DO
  595. IF (owner # NIL) THEN
  596. mstate := owner.state;
  597. ASSERT(mstate = OpenMatch);
  598. WHILE (mstate # CloseMatch) & ~reader.eot & (position <= to) DO
  599. owner.FeedChar(char32, position, mstate);
  600. reader.ReadCh(char32);
  601. INC(position);
  602. END;
  603. entry := owner.GetEntry();
  604. state.Add(entry);
  605. state.ResetMatchers;
  606. owner := NIL;
  607. ELSE
  608. owner := NIL; nofMatching := 0;
  609. mstate := NoMatch;
  610. matcher := state.matchers;
  611. WHILE (matcher # NIL) DO
  612. matcher.FeedChar(char32, position, tempState);
  613. IF (tempState = Matching) THEN
  614. INC(nofMatching);
  615. ELSIF (tempState = OpenMatch) THEN
  616. owner := matcher;
  617. END;
  618. matcher := matcher.next;
  619. END;
  620. match := match OR (owner # NIL);
  621. IF (owner # NIL) & (nofMatching > 1) THEN
  622. oldPosition := reader.GetPosition();
  623. oldOwner := owner;
  624. CheckLongestMatch(owner);
  625. IF (owner # oldOwner) THEN
  626. position := owner.firstPosition + owner.openLength;
  627. reader.SetPosition(position);
  628. reader.ReadCh(char32);
  629. ELSE
  630. reader.SetPosition(oldPosition);
  631. reader.ReadCh(char32);
  632. INC(position);
  633. END;
  634. ELSE
  635. reader.ReadCh(char32);
  636. INC(position);
  637. END;
  638. END;
  639. END;
  640. END Scan;
  641. PROCEDURE RebuildRegions*(reader : Texts.TextReader; CONST state : State);
  642. VAR ignore : BOOLEAN;
  643. BEGIN
  644. ASSERT((reader # NIL) & (state # NIL));
  645. IF Statistics THEN INC(NnofRebuildRegions); END;
  646. state.Clear;
  647. state.ResetMatchers;
  648. Scan(reader, 0, MAX(LONGINT), state, ignore);
  649. END RebuildRegions;
  650. PROCEDURE PatchRegions*(info : Texts.TextChangeInfo; reader : Texts.TextReader; state : State; VAR fullLayout : BOOLEAN);
  651. VAR
  652. char32 : Texts.Char32;
  653. PROCEDURE NeedRescan(position, length : LONGINT) : BOOLEAN;
  654. VAR rescan : BOOLEAN; i : LONGINT;
  655. BEGIN
  656. rescan := FALSE;
  657. reader.SetPosition(position);
  658. i := 0;
  659. WHILE (i < length) & ~rescan DO
  660. reader.ReadCh(char32);
  661. rescan := rescan OR regionChars[ORD(CHR(char32))];
  662. INC(i);
  663. END;
  664. RETURN rescan;
  665. END NeedRescan;
  666. PROCEDURE PatchInsert(position, length : LONGINT; VAR fullLayout : BOOLEAN);
  667. VAR
  668. hasLeft, hasMiddle, hasRight : BOOLEAN;
  669. left, middle, right : DataEntry;
  670. res : BOOLEAN;
  671. start, end, oldStart, oldEnd : LONGINT;
  672. ignore, match : BOOLEAN;
  673. location : LONGINT;
  674. BEGIN
  675. IF Statistics THEN INC(NnofPatchInsert); END;
  676. fullLayout := FALSE;
  677. state.FindTriple(position, hasLeft, hasMiddle, hasRight, left, middle, right);
  678. IF hasMiddle & (position > middle.open) THEN
  679. IF Statistics THEN INC(NnofPatchInsertHit); END;
  680. location := GetLocation(position, middle);
  681. IF (location = OpenString) OR ((location = CloseString) & (position > middle.close - middle.region.closeLength + 1)) THEN
  682. IF Statistics THEN INC(NnofPiOpenClose); END;
  683. state.RemoveFrom(position);
  684. Scan(reader, middle.open, MAX(LONGINT), state, ignore);
  685. fullLayout := TRUE;
  686. ELSIF middle.region.nesting THEN
  687. oldStart := middle.open;
  688. oldEnd := middle.close;
  689. state.Remove(middle);
  690. Scan(reader, oldStart, oldEnd + length, state, ignore);
  691. res := state.Find(position, middle);
  692. IF ~res OR
  693. (middle.open # oldStart) OR
  694. ((oldEnd # NOTCLOSED) & (middle.close # oldEnd + length)) OR
  695. ((oldEnd = NOTCLOSED) & (middle.close # NOTCLOSED)) THEN
  696. IF Statistics THEN INC(NnofPiNestedFull); END;
  697. state.RemoveFrom(position);
  698. Scan(reader, oldStart, MAX(LONGINT), state, ignore);
  699. fullLayout := TRUE;
  700. ELSE
  701. IF Statistics THEN INC(NnofPiNestedSimple); END;
  702. state.Patch(middle.close + 1, length); (* middle is already patched *)
  703. END;
  704. ELSIF NeedRescan(position, length) THEN
  705. IF Statistics THEN INC(NnofPiRescan); END;
  706. state.RemoveFrom(position); (* TBD optimize *)
  707. Scan(reader, middle.open, MAX(LONGINT), state, ignore);
  708. fullLayout := TRUE;
  709. ELSE
  710. IF Statistics THEN INC(NnofPiSimple); END;
  711. state.Patch(position, length);
  712. END;
  713. ELSE
  714. IF Statistics THEN INC(NnofPiNoHit); END;
  715. state.Patch(position, length);
  716. IF NeedRescan(position, length) THEN
  717. IF Statistics THEN INC(NnofPiNoHitRescan); END;
  718. start := position - longestOpen + 1;
  719. IF (longestClose > 0) THEN
  720. end := position + length + longestClose - 1;
  721. ELSE
  722. end := position + length;
  723. END;
  724. IF hasLeft & (left.close >= start) THEN start := left.close + 1; END;
  725. IF hasRight & (right.open + length <= end) THEN end := right.open + length - 1; END; (* manually patched since copy *)
  726. Scan(reader, start, end, state, match);
  727. IF match THEN
  728. IF Statistics THEN INC(NnofPiNoHitFull); END;
  729. state.RemoveFrom(start);
  730. Scan(reader, start, MAX(LONGINT), state, match);
  731. fullLayout := TRUE;
  732. END;
  733. END;
  734. END;
  735. END PatchInsert;
  736. PROCEDURE PatchDelete(position, length : LONGINT; VAR fullLayout : BOOLEAN);
  737. VAR
  738. hasLeft, hasMiddle, hasRight : BOOLEAN;
  739. left, middle, right : DataEntry;
  740. start, end, oldStart, oldEnd : LONGINT;
  741. match, ignore, res : BOOLEAN;
  742. location : LONGINT;
  743. BEGIN
  744. fullLayout := FALSE;
  745. state.FindTriple(position, hasLeft, hasMiddle, hasRight, left, middle, right);
  746. IF hasMiddle THEN
  747. location := GetLocation(position, middle);
  748. IF (middle.region.closeLength > 0) THEN end := middle.close - middle.region.closeLength + 1; ELSE end := middle.close; END;
  749. IF (location = Content) & (position + length - 1 < end) THEN
  750. oldStart := middle.open;
  751. oldEnd := middle.close;
  752. state.Remove(middle);
  753. Scan(reader, middle.open, middle.close, state, ignore);
  754. res := state.Find(position, middle);
  755. IF ~res OR
  756. (middle.open # oldStart) OR
  757. ((oldEnd # NOTCLOSED) & (middle.close # oldEnd - length)) OR
  758. ((oldEnd = NOTCLOSED) & (middle.close # NOTCLOSED)) THEN
  759. state.RemoveFrom(position);
  760. Scan(reader, oldStart, MAX(LONGINT), state, ignore);
  761. fullLayout := TRUE;
  762. ELSE
  763. state.Patch(middle.close + 1, -length);
  764. END;
  765. ELSE
  766. state.RemoveFrom(position);
  767. Scan(reader, middle.open, MAX(LONGINT), state, ignore);
  768. fullLayout := TRUE;
  769. END;
  770. ELSE
  771. start := position - longestOpen + 1;
  772. IF hasLeft & (left.close >= start) THEN start := left.close + 1; END;
  773. IF state.RemoveFromTo(position, length) THEN
  774. Scan(reader, start, MAX(LONGINT), state, ignore);
  775. fullLayout := TRUE;
  776. ELSE
  777. end := position - 1;
  778. state.Patch(position, -length);
  779. Scan(reader, start, end, state, match);
  780. IF match THEN
  781. state.RemoveFrom(start);
  782. Scan(reader, start, MAX(LONGINT), state, ignore);
  783. fullLayout := TRUE;
  784. END;
  785. END;
  786. END;
  787. END PatchDelete;
  788. BEGIN
  789. ASSERT((info.op = Texts.OpInsert) OR (info.op = Texts.OpDelete));
  790. ASSERT(reader # NIL);
  791. IF Statistics THEN INC(NnofPatchRegions); END;
  792. IF traceLevel >= Trace_1 THEN
  793. IF (info.op = Texts.OpInsert) THEN KernelLog.String("INS ");
  794. ELSE KernelLog.String("DEL ");
  795. END;
  796. KernelLog.Int(info.len, 0); KernelLog.String("@"); KernelLog.Int(info.pos, 0);
  797. KernelLog.Ln;
  798. END;
  799. IF (info.op = Texts.OpInsert) THEN
  800. PatchInsert(info.pos, info.len, fullLayout);
  801. ELSE
  802. PatchDelete(info.pos, info.len, fullLayout);
  803. END;
  804. END PatchRegions;
  805. PROCEDURE GetDefaultStyle*() : Style;
  806. BEGIN
  807. RETURN defaultStyle;
  808. END GetDefaultStyle;
  809. PROCEDURE GetState*() : State;
  810. VAR state : State; r : RegionDescriptor; m : RegionMatcher;
  811. BEGIN
  812. NEW(state);
  813. r := regions;
  814. WHILE (r # NIL) DO
  815. NEW(m, r); state.AddMatcher(m);
  816. r := r.next;
  817. END;
  818. RETURN state;
  819. END GetState;
  820. PROCEDURE MatchToken(char32 : Texts.Char32; reader : Texts.TextReader; VAR lookaheadIdx : LONGINT; VAR token : Token) : BOOLEAN;
  821. VAR tokensIdx, maxLength, i : LONGINT; t : Word;
  822. BEGIN
  823. ASSERT(reader # NIL);
  824. tokensIdx := char32 MOD Dim1Length;
  825. maxLength := tokens[tokensIdx].maxLength;
  826. IF (maxLength > 0) THEN
  827. (* copy look-ahead into local buffer *)
  828. WHILE (lookaheadIdx < maxLength) & (char32 > 32) & ~reader.eot DO
  829. reader.ReadCh(char32);
  830. IF (char32 > 32) & ~reader.eot THEN
  831. token.value[lookaheadIdx] := CHR(char32);
  832. INC(lookaheadIdx);
  833. END;
  834. END;
  835. token.value[lookaheadIdx] := 0X;
  836. (* compare look-ahead to token list. longest-match first *)
  837. i := lookaheadIdx;
  838. WHILE (i > 0) & (token.type = Type_Invalid) DO
  839. t := tokens[tokensIdx].length[i - 1];
  840. WHILE (t # NIL) & ~Equal(t.name, token.value, i) DO t := t.next; END;
  841. IF (t # NIL) THEN
  842. token.type := Type_Token;
  843. token.style := t.style;
  844. ASSERT(token.style # NIL);
  845. token.endPosition := token.startPosition + i - 1;
  846. token.value[i] := 0X;
  847. END;
  848. DEC(i);
  849. END;
  850. END;
  851. RETURN (token.type # Type_Invalid);
  852. END MatchToken;
  853. (* Scan reader at its current position *)
  854. PROCEDURE GetToken*(reader : Texts.TextReader; position : LONGINT; VAR token : Token);
  855. VAR char32 : Texts.Char32; idx, i : LONGINT;
  856. BEGIN
  857. ASSERT(reader # NIL);
  858. token.type := Type_Invalid;
  859. token.startPosition := position;
  860. token.endPosition := position - 1;
  861. token.value := "";
  862. token.style := NIL;
  863. reader.ReadCh(char32);
  864. IF (char32 > 32) THEN
  865. token.value[0] := CHR(char32);
  866. idx := 1;
  867. IF ~MatchToken(char32, reader, idx, token) THEN
  868. ASSERT(idx >= 1);
  869. (* check validity of lookahead buffer *)
  870. i := 0;
  871. WHILE (i < idx) & isAllowedCharacter[ORD(token.value[i])] DO INC(i); END;
  872. IF (i = idx) THEN
  873. reader.ReadCh(char32);
  874. WHILE (char32 > 32) & ~reader.eot & (i < LEN(token.value)) & IsAllowedCharacter(char32) DO
  875. token.value[i] := CHR(char32);
  876. INC(i);
  877. reader.ReadCh(char32);
  878. END;
  879. token.endPosition := token.startPosition + i - 1;
  880. IF (i < LEN(token.value)) THEN
  881. token.value[i] := 0X;
  882. token.length := i;
  883. GetTokenType(token);
  884. ELSE
  885. (* token too long .. .skip! *)
  886. token.type := Type_Invalid;
  887. WHILE (char32 > 32) & ~reader.eot & IsAllowedCharacter(char32) DO
  888. reader.ReadCh(char32);
  889. INC(token.endPosition);
  890. END;
  891. END;
  892. ELSE
  893. token.value[i] := 0X;
  894. IF (i > 0) THEN
  895. token.length := i;
  896. GetTokenType(token);
  897. ELSE
  898. token.type := Type_Invalid;
  899. END;
  900. END;
  901. END;
  902. ELSE
  903. (* whitespace or eot -> token.type = Type_Invalid *)
  904. END;
  905. END GetToken;
  906. PROCEDURE GetWordStyle*(reader : Texts.TextReader; position : LONGINT; VAR end : LONGINT) : Style;
  907. VAR style : Style; token : Token;
  908. BEGIN
  909. ASSERT(reader # NIL);
  910. reader.SetPosition(position);
  911. GetToken(reader, position, token);
  912. end := token.endPosition;
  913. IF (token.type # Type_Invalid) THEN
  914. IF (token.type # Type_Token) THEN
  915. style := GetStyle(token.value, token.length); (* keywords have higher priority than numbers *)
  916. IF (style = NIL) & (token.type = Type_Number) THEN
  917. style := numberStyle;
  918. END;
  919. ELSE
  920. (* style assigned in MatchToken *)
  921. style := token.style;
  922. END;
  923. ELSE
  924. style := NIL;
  925. END;
  926. RETURN style;
  927. END GetWordStyle;
  928. PROCEDURE GetRegionStyle*(position : LONGINT; state : State; VAR start, end : LONGINT) : Style;
  929. BEGIN
  930. ASSERT(state # NIL);
  931. RETURN state.GetStyle(position, start, end);
  932. END GetRegionStyle;
  933. PROCEDURE GetStyle*(CONST keyword : ARRAY OF CHAR; length : LONGINT) : Style;
  934. VAR style : Style; word : Word; i : LONGINT;
  935. BEGIN
  936. ASSERT(length > 0);
  937. style := NIL;
  938. IF wordChars[ORD(keyword[0])] THEN
  939. IF (length <= MaxWordLength) THEN
  940. word := words[ORD(keyword[0]) MOD Dim1Length][length - 1];
  941. WHILE (word # NIL) & (word.name < keyword) DO word := word.next; END;
  942. IF (word # NIL) & (word.name = keyword) THEN
  943. style := word.style;
  944. END;
  945. END;
  946. END;
  947. IF (style = NIL) & wildcardsEnabled THEN
  948. i := 0;
  949. WHILE (i < length) & (i < MaxWordLength) & (style = NIL) DO
  950. word := wildcardWords[i];
  951. WHILE (word # NIL) & ~Strings.Match(word.name, keyword) DO word := word.next; END;
  952. IF (word # NIL) THEN
  953. style := word.style;
  954. END;
  955. INC(i);
  956. END;
  957. END;
  958. RETURN style;
  959. END GetStyle;
  960. PROCEDURE AddToken(CONST tokenname : ARRAY OF CHAR; style : Style; VAR res : WORD);
  961. VAR token, t : Word; length, index1, index2 : LONGINT;
  962. BEGIN
  963. ASSERT((Strings.Length(tokenname) > 0) & (style # NIL) & (style.name # ""));
  964. length := Strings.Length(tokenname);
  965. IF (length <= MaxWordLength) THEN
  966. NEW(token);
  967. COPY(tokenname, token.name);
  968. token.style := style;
  969. index1 := ORD(token.name[0]) MOD Dim1Length;
  970. index2 := length - 1;
  971. IF (tokens[index1].length[index2] = NIL) OR (tokens[index1].length[index2].name > token.name) THEN
  972. token.next := tokens[index1].length[index2];
  973. tokens[index1].length[index2] := token;
  974. IF (length > tokens[index1].maxLength) THEN tokens[index1].maxLength := length; END;
  975. ELSE
  976. t := tokens[index1].length[index2];
  977. WHILE (t.next # NIL) & (t.next.name < token.name) DO t := t.next; END;
  978. token.next := t.next;
  979. t.next := token;
  980. END;
  981. res := Ok;
  982. ELSE
  983. res := StringTooLong;
  984. END;
  985. END AddToken;
  986. PROCEDURE AddWord(CONST keyword : ARRAY OF CHAR; style : Style; VAR res : WORD);
  987. VAR word, w : Word; nofWildcards, index1, index2, length : LONGINT;
  988. BEGIN
  989. ASSERT((Strings.Length(keyword) > 0) & (style # NIL) & (style.name # ""));
  990. length := Strings.Length(keyword);
  991. IF (length <= MaxWordLength) THEN
  992. NEW(word);
  993. Copy(keyword, word.name);
  994. word.style := style;
  995. nofWildcards := NofWildcards(word.name);
  996. IF (nofWildcards = 0) THEN
  997. index1 := ORD(word.name[0]) MOD Dim1Length;
  998. index2 := length - 1;
  999. IF (words[index1][index2] = NIL) OR (words[index1][index2].name > word.name) THEN
  1000. word.next := words[index1][index2];
  1001. words[index1][index2] := word;
  1002. ELSE
  1003. w := words[index1][index2];
  1004. WHILE (w.next # NIL) & (w.next.name < word.name) DO w := w.next; END;
  1005. word.next := w.next;
  1006. w.next := word;
  1007. END;
  1008. wordChars[ORD(word.name[0])] := TRUE;
  1009. ELSE
  1010. wildcardsEnabled := TRUE;
  1011. index1 := length - nofWildcards - 1;
  1012. word.next := wildcardWords[index1];
  1013. wildcardWords[index1] := word;
  1014. END;
  1015. res := Ok;
  1016. ELSE
  1017. res := StringTooLong;
  1018. END;
  1019. END AddWord;
  1020. PROCEDURE AddRegion(CONST open, close : ARRAY OF CHAR; nesting, multiline : BOOLEAN; styleOpen, styleClose, styleContent : Style);
  1021. VAR region, r : RegionDescriptor; length, i : LONGINT;
  1022. BEGIN
  1023. ASSERT((Strings.Length(open) > 0));
  1024. NEW(region, open, close, nesting, multiline, styleOpen, styleClose, styleContent);
  1025. (* append to list *)
  1026. IF (regions = NIL) THEN
  1027. regions := region;
  1028. ELSE
  1029. r := regions;
  1030. WHILE (r.next # NIL) DO r := r.next; END;
  1031. r.next := region;
  1032. END;
  1033. length := Strings.Length(open); IF (length > longestOpen) THEN longestOpen := length; END;
  1034. FOR i := 0 TO length-1 DO
  1035. regionChars[ORD(open[i])] := TRUE;
  1036. END;
  1037. length := Strings.Length(close); IF (length > longestClose) THEN longestClose := length; END;
  1038. FOR i := 0 TO length-1 DO
  1039. regionChars[ORD(close[i])] := TRUE;
  1040. END;
  1041. END AddRegion;
  1042. PROCEDURE DebugInterface*(code : LONGINT; state : State);
  1043. VAR out : Streams.Writer;
  1044. BEGIN
  1045. ASSERT(state # NIL);
  1046. IF (code = 0) THEN
  1047. NEW(out, KernelLog.Send, 256);
  1048. KernelLog.String("SyntaxHighlighter: Dump:"); KernelLog.Ln;
  1049. state.Dump(out);
  1050. ELSIF (code = 1) THEN
  1051. traceLevel := (traceLevel + 1) MOD (Trace_Max + 1);
  1052. KernelLog.String("SyntaxHighlighter: TraceLevel = ");
  1053. KernelLog.Int(traceLevel, 0); KernelLog.Ln;
  1054. END;
  1055. END DebugInterface;
  1056. PROCEDURE Dump(out : Streams.Writer);
  1057. PROCEDURE DumpWordList(out : Streams.Writer; word : Word);
  1058. BEGIN
  1059. ASSERT((out # NIL) & (word # NIL));
  1060. WHILE (word # NIL) DO out.String(word.name); out.String(" "); word := word.next; END;
  1061. END DumpWordList;
  1062. PROCEDURE DumpTokens(out : Streams.Writer; level : LONGINT);
  1063. VAR i, j : LONGINT;
  1064. BEGIN
  1065. ASSERT(out # NIL);
  1066. FOR i := 0 TO LEN(tokens)-1 DO
  1067. IF (tokens[i].maxLength > 0) THEN
  1068. Indent(out, level); out.Char(CHR(i)); out.String(": ");
  1069. FOR j := 0 TO LEN(tokens[i].length)-1 DO
  1070. IF (tokens[i].length[j] # NIL) THEN
  1071. out.Int(j + 1, 0); out.String(": ");
  1072. DumpWordList(out, tokens[i].length[j]);
  1073. END;
  1074. END;
  1075. out.Ln;
  1076. END;
  1077. END;
  1078. END DumpTokens;
  1079. BEGIN
  1080. ASSERT(out # NIL);
  1081. out.String("Highlighter: "); out.String(name); out.Ln;
  1082. out.String(" Tokens:"); out.Ln;
  1083. DumpTokens(out, 4);
  1084. END Dump;
  1085. END Highlighter;
  1086. Highlighters = OBJECT
  1087. VAR
  1088. list : Highlighter;
  1089. PROCEDURE &Init;
  1090. BEGIN
  1091. list := NIL;
  1092. END Init;
  1093. PROCEDURE Add(highlighter : Highlighter);
  1094. BEGIN {EXCLUSIVE}
  1095. ASSERT(highlighter # NIL);
  1096. highlighter.next := list;
  1097. list := highlighter;
  1098. END Add;
  1099. PROCEDURE Find(CONST name : ARRAY OF CHAR) : Highlighter;
  1100. VAR highlighter : Highlighter;
  1101. BEGIN {EXCLUSIVE}
  1102. highlighter := list;
  1103. WHILE (highlighter # NIL) & (highlighter.name # name) DO highlighter := highlighter.next; END;
  1104. RETURN highlighter;
  1105. END Find;
  1106. PROCEDURE Dump(out : Streams.Writer);
  1107. VAR h : Highlighter;
  1108. BEGIN {EXCLUSIVE}
  1109. ASSERT(out # NIL);
  1110. h := list;
  1111. WHILE (h # NIL) DO h.Dump(out); h := h.next; END;
  1112. END Dump;
  1113. END Highlighters;
  1114. VAR
  1115. source : Files.FileName;
  1116. diagnostics : Diagnostics.Diagnostics;
  1117. error, autoinit : BOOLEAN;
  1118. global_highlighters : Highlighters;
  1119. traceLevel : LONGINT;
  1120. (* Statistics (not thread-safe) *)
  1121. NnofRebuildRegions, NnofPatchRegions,
  1122. NnofPatchInsert, NnofPatchInsertHit, NnofPiOpenClose, NnofPiNestedFull, NnofPiNestedSimple,
  1123. NnofPiRescan, NnofPiSimple, NnofPiNoHit, NnofPiNoHitRescan, NnofPiNoHitFull
  1124. : LONGINT;
  1125. PROCEDURE GetHighlighter*(CONST name : ARRAY OF CHAR) : Highlighter;
  1126. VAR highlighter : Highlighter; diagnostics : Diagnostics.Diagnostics;
  1127. BEGIN {EXCLUSIVE}
  1128. IF (global_highlighters = NIL) & autoinit THEN
  1129. autoinit := FALSE; (* only try this once *)
  1130. NEW(diagnostics);
  1131. global_highlighters := Parse(DefaultHighlighterFile, diagnostics, error);
  1132. KernelLog.String("SyntaxHighlighter: Auto-loading "); KernelLog.String(DefaultHighlighterFile);
  1133. KernelLog.String(" ... ");
  1134. IF ~error THEN
  1135. KernelLog.String("done.");
  1136. ELSE
  1137. KernelLog.String("failed.");
  1138. global_highlighters := NIL;
  1139. END;
  1140. KernelLog.Ln;
  1141. END;
  1142. IF (global_highlighters # NIL) THEN
  1143. highlighter := global_highlighters.Find(name);
  1144. ELSE
  1145. highlighter := NIL;
  1146. END;
  1147. RETURN highlighter;
  1148. END GetHighlighter;
  1149. PROCEDURE GetTokenType(VAR token : Token);
  1150. VAR i : LONGINT; tokenDone : BOOLEAN;
  1151. BEGIN
  1152. token.type := Type_Identifier;
  1153. IF ('0' <= token.value[0]) & (token.value[0] <= '9') THEN
  1154. token.type := Type_Number;
  1155. i := 0; tokenDone := FALSE;
  1156. WHILE (token.value[i] # 0X) & (i < LEN(token.value)) DO
  1157. IF ~tokenDone & (token.type = Type_Number) THEN
  1158. CASE token.value[i] OF
  1159. |'0'..'9': (* do nothing here *)
  1160. |'A'..'F':
  1161. IF (token.subtype = Subtype_Decimal) THEN token.subtype := Subtype_Hex;
  1162. ELSIF (token.subtype = Subtype_Float) & (token.value[i] # "E") THEN token.type := Type_Identifier;
  1163. END;
  1164. |'X':
  1165. IF (token.subtype = Subtype_Decimal) OR (token.subtype = Subtype_Hex) THEN
  1166. token.subtype := Subtype_Char; tokenDone := TRUE;
  1167. ELSE
  1168. token.type := Type_Identifier;
  1169. END;
  1170. |'h', 'H':
  1171. IF (token.subtype = Subtype_Decimal) OR (token.subtype = Subtype_Hex) THEN
  1172. token.subtype := Subtype_Hex; tokenDone := TRUE;
  1173. ELSE
  1174. token.type := Type_Identifier;
  1175. END;
  1176. |'.':
  1177. IF (token.subtype = Subtype_Decimal) THEN token.subtype := Subtype_Float;
  1178. ELSE token.type := Type_Invalid;
  1179. END;
  1180. ELSE
  1181. token.type := Type_Identifier;
  1182. END;
  1183. ELSE
  1184. token.type := Type_Identifier;
  1185. END;
  1186. INC(i);
  1187. END;
  1188. END;
  1189. END GetTokenType;
  1190. (*? Actually, the XML framework should take care of unescaping characters *)
  1191. PROCEDURE Unescape(string : Strings.String);
  1192. VAR insertAt, i : LONGINT; ch : CHAR;
  1193. BEGIN
  1194. ASSERT(string # NIL);
  1195. i := 0; insertAt := 0;
  1196. WHILE (i < LEN(string)) DO
  1197. IF (string[i] = "&") THEN
  1198. IF (i + 3 < LEN(string)) & (string[i+2] = "t") & (string[i+3] = ";") THEN
  1199. IF (string[i+1] = "l") THEN
  1200. ch := "<"; i := i + 4;
  1201. ELSIF (string[i+1] = "g") THEN
  1202. ch := ">"; i := i + 4;
  1203. ELSE
  1204. ch := string[i]; INC(i);
  1205. END;
  1206. ELSIF (i + 4 < LEN(string)) & (string[i+1] = "a") & (string[i+2] = "m") & (string[i+3] = "p") & (string[i+4] = ";") THEN
  1207. ch := "&"; i := i + 5;
  1208. ELSIF (i + 5 < LEN(string)) & (string[i+1] = "q") & (string[i+2] = "u") & (string[i+3] = "o") & (string[i+4] = "t") & (string[i+5] = ";") THEN
  1209. ch := '"'; i := i + 6;
  1210. ELSIF (i + 5 < LEN(string)) & (string[i+1] = "a") & (string[i+2] = "p") & (string[i+3] = "o") & (string[i+4] = "s") & (string[i+5] = ";") THEN
  1211. ch := "'"; i := i + 6;
  1212. ELSE
  1213. ch := string[i]; INC(i);
  1214. END;
  1215. ELSE
  1216. ch := string[i]; INC(i);
  1217. END;
  1218. string[insertAt] := ch; INC(insertAt);
  1219. END;
  1220. IF (insertAt < LEN(string)) THEN string[insertAt] := 0X; END;
  1221. END Unescape;
  1222. PROCEDURE NofWildcards(CONST string : ARRAY OF CHAR) : LONGINT;
  1223. VAR nofWildcards, i : LONGINT;
  1224. BEGIN
  1225. nofWildcards := 0;
  1226. i := 0;
  1227. WHILE (i < LEN(string)) & (string[i] # 0X) DO
  1228. IF (string[i] = "?") OR (string[i] = "*") THEN INC(nofWildcards); END;
  1229. INC(i);
  1230. END;
  1231. RETURN nofWildcards;
  1232. END NofWildcards;
  1233. PROCEDURE Equal(CONST s1, s2 : ARRAY OF CHAR; length : LONGINT) : BOOLEAN;
  1234. VAR i : LONGINT;
  1235. BEGIN
  1236. i := 0;
  1237. WHILE (i < length) & (s1[i] = s2[i]) DO INC(i); END;
  1238. RETURN i = length;
  1239. END Equal;
  1240. PROCEDURE Indent(out : Streams.Writer; level : LONGINT);
  1241. VAR i : LONGINT;
  1242. BEGIN
  1243. ASSERT(out # NIL);
  1244. FOR i := 1 TO level DO out.Char(" "); END;
  1245. END Indent;
  1246. (**
  1247. Example:
  1248. open: <!-- entry.region.openLength = 4
  1249. close: --> entry.region.closeLength = 3
  1250. String: < ! - - B L A H - - >
  1251. 0 1 2 3 4 5 6 7 8 9 10
  1252. OpenString: [0, 3]
  1253. Content: [4, 7]
  1254. CloseString: [8, 10]
  1255. *)
  1256. PROCEDURE GetLocation(position : LONGINT; CONST entry : DataEntry) : LONGINT;
  1257. VAR location, closeLength : LONGINT;
  1258. BEGIN
  1259. IF entry.eol THEN closeLength := 0; ELSE closeLength := entry.region.closeLength; END;
  1260. IF (position >= entry.open) THEN
  1261. IF (position <= entry.open + entry.region.openLength - 1) THEN
  1262. location := OpenString;
  1263. ELSIF (position <= entry.close - closeLength) THEN
  1264. location := Content;
  1265. ELSIF (position <= entry.close) THEN
  1266. location := CloseString;
  1267. ELSE
  1268. location := Outside;
  1269. END;
  1270. ELSE
  1271. location := Outside;
  1272. END;
  1273. RETURN location;
  1274. END GetLocation;
  1275. PROCEDURE ParseStyle(
  1276. CONST element : XML.Element; CONST styles : Styles;
  1277. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1278. VAR
  1279. style : Style; string : Strings.String;
  1280. styleName : Identifier;
  1281. fontname : ARRAY 128 OF CHAR;
  1282. fontsize, color, bgcolor, voff : LONGINT;
  1283. fontstyle : SET;
  1284. defined : SET;
  1285. res : WORD;
  1286. BEGIN
  1287. ASSERT((element # NIL) & (styles # NIL) & (diagnostics # NIL));
  1288. string := element.GetName();
  1289. ASSERT((string # NIL) & (string^ = XmlStyle));
  1290. string := element.GetAttributeValue(XmlAttributeName);
  1291. IF (string # NIL) THEN
  1292. COPY(string^, styleName);
  1293. defined := {};
  1294. fontname := "";
  1295. string := element.GetAttributeValue(XmlAttributeFontName);
  1296. IF (string # NIL) THEN
  1297. Strings.TrimWS(string^);
  1298. IF (string^ # XmlDontCare) THEN
  1299. INCL(defined, FontName);
  1300. Copy(string^, fontname);
  1301. END;
  1302. ELSE
  1303. diagnostics.Warning(source, element.GetPos(), "Style font name missing");
  1304. END;
  1305. fontsize := 0;
  1306. string := element.GetAttributeValue(XmlAttributeFontSize);
  1307. IF (string # NIL) THEN
  1308. Strings.TrimWS(string^);
  1309. IF (string^ # XmlDontCare) THEN
  1310. INCL(defined, FontSize);
  1311. Strings.StrToInt(string^, fontsize);
  1312. END;
  1313. ELSE
  1314. diagnostics.Warning(source, element.GetPos(), "Style attribute font size missing");
  1315. END;
  1316. string := element.GetAttributeValue(XmlAttributeFontStyle);
  1317. IF (string # NIL) THEN
  1318. Strings.TrimWS(string^);
  1319. IF (string^ # XmlDontCare) THEN
  1320. INCL(defined, FontStyle);
  1321. Strings.StrToSet(string^, fontstyle);
  1322. END;
  1323. END;
  1324. color := 0;
  1325. string := element.GetAttributeValue(XmlAttributeColor);
  1326. IF (string # NIL) THEN
  1327. Strings.TrimWS(string^);
  1328. IF (string^ # XmlDontCare) THEN
  1329. Strings.HexStrToInt(string^, color, res);
  1330. IF (res = Strings.Ok) THEN
  1331. INCL(defined, Color);
  1332. ELSE
  1333. diagnostics.Warning(source, element.GetPos(), "Style attribute color: Invalid value");
  1334. END;
  1335. END;
  1336. ELSE diagnostics.Warning(source, element.GetPos(), "Style attribute color missing");
  1337. END;
  1338. bgcolor := 0;
  1339. string := element.GetAttributeValue(XmlAttributeBgColor);
  1340. IF (string # NIL) THEN
  1341. Strings.TrimWS(string^);
  1342. IF (string^ # XmlDontCare) THEN
  1343. Strings.HexStrToInt(string^, bgcolor, res);
  1344. IF (res = Strings.Ok) THEN
  1345. INCL(defined, BgColor);
  1346. ELSE
  1347. diagnostics.Warning(source, element.GetPos(), "Style attribute background color: Invalid value");
  1348. END;
  1349. END;
  1350. ELSE
  1351. INCL(defined, BgColor);
  1352. bgcolor := DefaultBgColor;
  1353. END;
  1354. voff := 0;
  1355. string := element.GetAttributeValue(XmlAttributeVoff);
  1356. IF (string # NIL) THEN
  1357. Strings.TrimWS(string^);
  1358. IF (string^ # XmlDontCare) THEN
  1359. INCL(defined, Voff);
  1360. Strings.StrToInt(string^, voff);
  1361. END;
  1362. ELSE
  1363. INCL(defined, Voff);
  1364. voff := DefaultVoff;
  1365. END;
  1366. NEW(style, styleName, color, bgcolor, voff, fontname, fontsize, fontstyle);
  1367. style.defined := defined;
  1368. styles.Add(style);
  1369. ELSE
  1370. error := TRUE;
  1371. diagnostics.Error(source, element.GetPos(), "Style name missing");
  1372. END;
  1373. END ParseStyle;
  1374. PROCEDURE ParseStyles(
  1375. CONST element : XML.Element; CONST styles : Styles;
  1376. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1377. VAR
  1378. string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
  1379. BEGIN
  1380. ASSERT((element # NIL) & (styles # NIL) & (diagnostics # NIL));
  1381. string := element.GetName();
  1382. ASSERT((string # NIL) & (string^ = XmlStyles));
  1383. enum := element.GetContents();
  1384. WHILE enum.HasMoreElements() DO
  1385. ptr := enum.GetNext();
  1386. IF (ptr IS XML.Element) THEN
  1387. string := ptr(XML.Element).GetName();
  1388. IF (string # NIL) & (string^ = XmlStyle) THEN
  1389. ParseStyle(ptr(XML.Element), styles, source, diagnostics, error);
  1390. ELSE
  1391. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected style element");
  1392. END;
  1393. END;
  1394. END;
  1395. END ParseStyles;
  1396. PROCEDURE ParseGroup(
  1397. CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
  1398. CONST type : LONGINT;
  1399. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1400. VAR
  1401. string : Strings.String; ptr : ANY;
  1402. reader : Streams.StringReader;
  1403. token : ARRAY 128 OF CHAR;
  1404. style : Style; res : WORD;
  1405. BEGIN
  1406. ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
  1407. ASSERT((type = TypeWords) OR (type = TypeTokens));
  1408. string := element.GetName();
  1409. ASSERT((string # NIL) & (string^ = XmlGroup));
  1410. string := element.GetAttributeValue(XmlAttributeStyle);
  1411. IF (string # NIL) THEN
  1412. style := styles.Find(string^);
  1413. IF (style # NIL) THEN
  1414. ptr := element.GetFirst();
  1415. IF (ptr # NIL) & (ptr IS XML.Chars) THEN
  1416. string := ptr(XML.Chars).GetStr();
  1417. IF (string # NIL) THEN
  1418. Unescape(string);
  1419. NEW(reader, LEN(string^));
  1420. reader.Set(string^);
  1421. reader.SkipWhitespace;
  1422. reader.Token(token);
  1423. WHILE (token # "") & (reader.res = Streams.Ok) DO
  1424. IF (type = TypeWords) THEN
  1425. highlighter.AddWord(token, style, res);
  1426. ELSE
  1427. highlighter.AddToken(token, style, res);
  1428. END;
  1429. IF (res # Ok) THEN
  1430. error := TRUE;
  1431. diagnostics.Error(source, element.GetPos(), "Token too long");
  1432. END;
  1433. reader.SkipWhitespace;
  1434. reader.Token(token);
  1435. END;
  1436. ELSE
  1437. diagnostics.Warning(source, element.GetPos(), "Empty group (string)");
  1438. END;
  1439. ELSE
  1440. diagnostics.Warning(source, element.GetPos(), "Empty group");
  1441. END;
  1442. ELSE
  1443. error := TRUE;
  1444. diagnostics.Error(source, element.GetPos(), "Could not find style for group...");
  1445. END;
  1446. ELSE
  1447. error := TRUE;
  1448. diagnostics.Error(source, element.GetPos(), "Group name missing");
  1449. END;
  1450. END ParseGroup;
  1451. PROCEDURE ParseTokens(
  1452. CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
  1453. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1454. VAR
  1455. string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
  1456. BEGIN
  1457. ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
  1458. string := element.GetName();
  1459. ASSERT((string # NIL) & (string^ = XmlTokens));
  1460. enum := element.GetContents();
  1461. WHILE enum.HasMoreElements() DO
  1462. ptr := enum.GetNext();
  1463. IF (ptr IS XML.Element) THEN
  1464. string := ptr(XML.Element).GetName();
  1465. IF (string # NIL) & (string^ = XmlGroup) THEN
  1466. ParseGroup(ptr(XML.Element), highlighter, styles, TypeTokens, source, diagnostics, error);
  1467. ELSE
  1468. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected group element");
  1469. END;
  1470. END;
  1471. END;
  1472. END ParseTokens;
  1473. PROCEDURE ParseWords(
  1474. CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
  1475. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1476. VAR
  1477. string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
  1478. i : LONGINT;
  1479. BEGIN
  1480. ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
  1481. string := element.GetName();
  1482. ASSERT((string # NIL) & (string^ = XmlWords));
  1483. enum := element.GetContents();
  1484. WHILE enum.HasMoreElements() DO
  1485. ptr := enum.GetNext();
  1486. IF (ptr IS XML.Element) THEN
  1487. string := ptr(XML.Element).GetName();
  1488. IF (string # NIL) & (string^ = XmlGroup) THEN
  1489. ParseGroup(ptr(XML.Element), highlighter, styles, TypeWords, source, diagnostics, error);
  1490. ELSE
  1491. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected group element");
  1492. END;
  1493. END;
  1494. END;
  1495. string := element.GetAttributeValue(XmlAttributeAllowCharacters);
  1496. IF (string # NIL) THEN
  1497. i := 0;
  1498. WHILE (i < LEN(string)) & (string[i] # 0X) DO
  1499. IF (string[i] > " ") THEN highlighter.AllowCharacter(string[i]); END;
  1500. INC(i);
  1501. END;
  1502. END;
  1503. string := element.GetAttributeValue(XmlAttributeNumberStyle);
  1504. IF (string # NIL) THEN
  1505. highlighter.numberStyle := styles.Find(string^);
  1506. IF (highlighter.numberStyle = NIL) THEN
  1507. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Number style not found");
  1508. END;
  1509. END;
  1510. END ParseWords;
  1511. PROCEDURE ParseRegion(
  1512. CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
  1513. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1514. VAR
  1515. string : Strings.String;
  1516. style : Style;
  1517. styleOpen, styleClose, styleContent : Style;
  1518. open, close : Identifier;
  1519. nesting, multiline : BOOLEAN;
  1520. BEGIN
  1521. ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
  1522. string := element.GetName();
  1523. ASSERT((string # NIL) & (string^ = XmlRegion));
  1524. styleOpen := NIL; styleClose := NIL; styleContent := NIL;
  1525. string := element.GetAttributeValue(XmlAttributeStyleOpen);
  1526. IF (string # NIL) THEN
  1527. style := styles.Find(string^);
  1528. IF (style # NIL) THEN
  1529. styleOpen := style;
  1530. ELSE
  1531. error := TRUE;
  1532. diagnostics.Error(source, element.GetPos(), "OpenStyle not found");
  1533. END;
  1534. END;
  1535. string := element.GetAttributeValue(XmlAttributeStyleClose);
  1536. IF (string # NIL) THEN
  1537. style := styles.Find(string^);
  1538. IF (style # NIL) THEN
  1539. styleClose := style;
  1540. ELSE
  1541. error := TRUE;
  1542. diagnostics.Error(source, element.GetPos(), "CloseStyle not found");
  1543. END;
  1544. END;
  1545. string := element.GetAttributeValue(XmlAttributeStyleContent);
  1546. IF (string # NIL) THEN
  1547. style := styles.Find(string^);
  1548. IF (style # NIL) THEN
  1549. styleContent := style;
  1550. ELSE
  1551. error := TRUE;
  1552. diagnostics.Error(source, element.GetPos(), "ContentStyle not found");
  1553. END;
  1554. END;
  1555. string := element.GetAttributeValue(XmlAttributeOpen);
  1556. IF (string # NIL) THEN
  1557. Copy(string^, open);
  1558. IF (open = "") THEN
  1559. error := TRUE;
  1560. diagnostics.Error(source, element.GetPos(), "Region attribute open is empty");
  1561. END;
  1562. ELSE
  1563. error := TRUE;
  1564. diagnostics.Error(source, element.GetPos(), "Region attribute open missing");
  1565. END;
  1566. string := element.GetAttributeValue(XmlAttributeClose);
  1567. IF (string # NIL) THEN
  1568. Copy(string^, close);
  1569. ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute close missing");
  1570. END;
  1571. nesting := FALSE;
  1572. string := element.GetAttributeValue(XmlAttributeNesting);
  1573. IF (string # NIL) THEN
  1574. Strings.TrimWS(string^);
  1575. Strings.StrToBool(string^, nesting);
  1576. ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute nesting missing");
  1577. END;
  1578. multiline := FALSE;
  1579. string := element.GetAttributeValue(XmlAttributeMultiLine);
  1580. IF (string # NIL) THEN
  1581. Strings.TrimWS(string^);
  1582. Strings.StrToBool(string^, multiline);
  1583. ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute multiline missing");
  1584. END;
  1585. IF ~error THEN
  1586. highlighter.AddRegion(open, close, nesting, multiline, styleOpen, styleClose, styleContent);
  1587. END;
  1588. END ParseRegion;
  1589. PROCEDURE ParseRegions(
  1590. CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
  1591. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1592. VAR
  1593. string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
  1594. BEGIN
  1595. ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
  1596. string := element.GetName();
  1597. ASSERT((string # NIL) & (string^ = XmlRegions));
  1598. enum := element.GetContents();
  1599. WHILE enum.HasMoreElements() DO
  1600. ptr := enum.GetNext();
  1601. IF (ptr IS XML.Element) THEN
  1602. string := ptr(XML.Element).GetName();
  1603. IF (string # NIL) & (string^ = XmlRegion) THEN
  1604. ParseRegion(ptr(XML.Element), highlighter, styles, source, diagnostics, error);
  1605. ELSE
  1606. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected region element");
  1607. END;
  1608. END;
  1609. END;
  1610. END ParseRegions;
  1611. PROCEDURE ParseHighlighter(
  1612. CONST element : XML.Element; CONST highlighters : Highlighters; CONST styles : Styles;
  1613. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1614. VAR
  1615. highlighter : Highlighter; string : Strings.String; tokens, words, regions : XML.Element;
  1616. BEGIN
  1617. ASSERT((element # NIL) & (highlighters # NIL) & (styles # NIL) & (diagnostics # NIL));
  1618. string := element.GetName();
  1619. ASSERT((string # NIL) & (string^ = XmlHighlighter));
  1620. string := element.GetAttributeValue(XmlAttributeName);
  1621. IF (string # NIL) THEN
  1622. NEW(highlighter, string^);
  1623. highlighters.Add(highlighter);
  1624. string := element.GetAttributeValue(XmlAttributeDefaultStyle);
  1625. IF (string # NIL) THEN
  1626. Strings.TrimWS(string^);
  1627. IF (string^ # "") & (string^ # XmlDontCare) THEN
  1628. highlighter.defaultStyle := styles.Find(string^);
  1629. IF (highlighter.defaultStyle = NIL) THEN
  1630. error := TRUE;
  1631. diagnostics.Error(source, element.GetPos(), "Default style not found");
  1632. END;
  1633. END;
  1634. END;
  1635. tokens := FindChild(element, XmlTokens);
  1636. IF (tokens # NIL) THEN
  1637. ParseTokens(tokens, highlighter, styles, source, diagnostics, error);
  1638. END;
  1639. words := FindChild(element, XmlWords);
  1640. IF (words # NIL) THEN
  1641. ParseWords(words, highlighter, styles, source, diagnostics, error);
  1642. END;
  1643. regions := FindChild(element, XmlRegions);
  1644. IF (regions # NIL) THEN
  1645. ParseRegions(regions, highlighter, styles, source, diagnostics, error);
  1646. END;
  1647. ELSE
  1648. error := TRUE;
  1649. diagnostics.Error(source, element.GetPos(), "Highlighter name missing");
  1650. END;
  1651. END ParseHighlighter;
  1652. PROCEDURE ParseHighlighters(
  1653. CONST element : XML.Element; CONST highlighters : Highlighters; CONST styles : Styles;
  1654. CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1655. VAR
  1656. string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
  1657. BEGIN
  1658. ASSERT((element # NIL) & (highlighters # NIL) & (styles # NIL) & (diagnostics # NIL));
  1659. string := element.GetName();
  1660. ASSERT((string # NIL) & (string^ = XmlHighlighters));
  1661. enum := element.GetContents();
  1662. WHILE enum.HasMoreElements() DO
  1663. ptr := enum.GetNext();
  1664. IF (ptr IS XML.Element) THEN
  1665. string := ptr(XML.Element).GetName();
  1666. IF (string # NIL) & (string^ = XmlHighlighter) THEN
  1667. ParseHighlighter(ptr(XML.Element), highlighters, styles, source, diagnostics, error);
  1668. ELSE
  1669. diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected highlighter element");
  1670. END;
  1671. END;
  1672. END;
  1673. END ParseHighlighters;
  1674. PROCEDURE ParseDocument(
  1675. CONST document : XML.Document;
  1676. CONST source : ARRAY OF CHAR;
  1677. VAR highlighters : Highlighters;
  1678. CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
  1679. VAR
  1680. root, element : XML.Element; string : Strings.String;
  1681. styles : Styles;
  1682. BEGIN
  1683. ASSERT((document # NIL) & (diagnostics # NIL));
  1684. root := document.GetRoot();
  1685. string := root.GetName();
  1686. IF (string # NIL) & (string^ = XmlRootElementName) THEN
  1687. NEW(styles);
  1688. element := FindChild(root, XmlStyles);
  1689. IF (element # NIL) THEN
  1690. ParseStyles(element, styles, source, diagnostics, error);
  1691. ELSE
  1692. error := TRUE;
  1693. diagnostics.Error(source, element.GetPos(), "Styles section missing");
  1694. END;
  1695. NEW(highlighters);
  1696. element := FindChild(root, XmlHighlighters);
  1697. IF (element # NIL) THEN
  1698. ParseHighlighters(element, highlighters, styles, source, diagnostics, error);
  1699. ELSE
  1700. error := TRUE;
  1701. diagnostics.Error(source, element.GetPos(), "Highlighters section missing");
  1702. END;
  1703. ELSE
  1704. error := TRUE;
  1705. diagnostics.Error(source, root.GetPos(), "XML root element name mismatch");
  1706. END;
  1707. END ParseDocument;
  1708. PROCEDURE Parse(CONST filename : ARRAY OF CHAR; diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN) : Highlighters;
  1709. VAR document : XML.Document; highlighters : Highlighters;
  1710. BEGIN
  1711. ASSERT(diagnostics # NIL);
  1712. document := LoadDocument(filename, diagnostics, error);
  1713. IF ~error THEN
  1714. NEW(highlighters);
  1715. ParseDocument(document, filename, highlighters, diagnostics, error);
  1716. IF error THEN highlighters := NIL; END;
  1717. ELSE
  1718. highlighters := NIL;
  1719. END;
  1720. RETURN highlighters;
  1721. END Parse;
  1722. PROCEDURE FindChild(parent : XML.Element; CONST childName : ARRAY OF CHAR) : XML.Element;
  1723. VAR child : XML.Element; enum : XMLObjects.Enumerator; ptr : ANY; string : Strings.String;
  1724. BEGIN
  1725. ASSERT(parent # NIL);
  1726. child := NIL;
  1727. enum := parent.GetContents();
  1728. WHILE (child = NIL) & enum.HasMoreElements() DO
  1729. ptr := enum.GetNext();
  1730. IF (ptr # NIL) THEN
  1731. string := ptr(XML.Element).GetName();
  1732. IF (string # NIL) & (string^ = childName) THEN
  1733. child := ptr(XML.Element);
  1734. END;
  1735. END;
  1736. END;
  1737. RETURN child;
  1738. END FindChild;
  1739. PROCEDURE TrapHandler(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
  1740. BEGIN
  1741. error := TRUE;
  1742. diagnostics.Error(source, pos, msg);
  1743. END TrapHandler;
  1744. PROCEDURE LoadDocument(CONST filename : ARRAY OF CHAR; CONST d: Diagnostics.Diagnostics; VAR e : BOOLEAN) : XML.Document;
  1745. VAR file : Files.File; reader : Files.Reader; scanner : XMLScanner.Scanner; parser : XMLParser.Parser; document : XML.Document;
  1746. BEGIN
  1747. ASSERT(d # NIL);
  1748. Copy(filename, source);
  1749. document := NIL;
  1750. file := Files.Old(filename);
  1751. IF (file # NIL) THEN
  1752. NEW(reader, file, 0);
  1753. NEW(scanner, reader);
  1754. NEW(parser, scanner);
  1755. parser.reportError := TrapHandler;
  1756. error := FALSE;
  1757. diagnostics := d;
  1758. document := parser.Parse();
  1759. e := error;
  1760. IF error THEN
  1761. document := NIL;
  1762. END;
  1763. ELSE
  1764. e := TRUE;
  1765. d.Error(filename, Streams.Invalid, "File not found");
  1766. END;
  1767. diagnostics := NIL; source := "";
  1768. ASSERT(error OR (document # NIL));
  1769. RETURN document;
  1770. END LoadDocument;
  1771. PROCEDURE Copy(CONST source : ARRAY OF CHAR; VAR target : ARRAY OF CHAR);
  1772. BEGIN
  1773. Strings.ConcatX(source, "", target);
  1774. END Copy;
  1775. PROCEDURE HighlightText*(text : Texts.Text; highlighter : Highlighter);
  1776. VAR
  1777. state : State; style : Style;
  1778. reader : Texts.TextReader; char32 : Texts.Char32; attributes : Texts.Attributes;
  1779. readerPosition, lastEnd, regionStart, regionEnd : LONGINT;
  1780. BEGIN
  1781. ASSERT((text # NIL) & (highlighter # NIL));
  1782. text.AcquireWrite;
  1783. style := highlighter.GetDefaultStyle();
  1784. IF (style # NIL) & (style.attributes # NIL) THEN
  1785. attributes := style.attributes;
  1786. ELSE
  1787. attributes := Texts.defaultAttributes.Clone();
  1788. END;
  1789. text.SetAttributes(0, text.GetLength(), attributes);
  1790. NEW(reader, text);
  1791. state := highlighter.GetState();
  1792. highlighter.RebuildRegions(reader, state);
  1793. reader.SetPosition(0);
  1794. lastEnd := -1;
  1795. WHILE ~reader.eot DO
  1796. style := NIL;
  1797. readerPosition := reader.GetPosition();
  1798. reader.ReadCh(char32);
  1799. IF (lastEnd < readerPosition) THEN
  1800. style := highlighter.GetRegionStyle(readerPosition, state, regionStart, regionEnd);
  1801. IF (style # NIL) THEN
  1802. lastEnd := regionEnd;
  1803. ELSE
  1804. IF highlighter.IsAllowedCharacter(char32) THEN
  1805. style := highlighter.GetWordStyle(reader, readerPosition, lastEnd);
  1806. END;
  1807. END;
  1808. END;
  1809. IF (style # NIL) THEN
  1810. text.SetAttributes(readerPosition, lastEnd - readerPosition + 1, style.attributes);
  1811. reader.SetPosition(lastEnd);
  1812. END;
  1813. END;
  1814. text.ReleaseWrite;
  1815. END HighlightText;
  1816. PROCEDURE Highlight*(context : Commands.Context); (** filename highlighterName ~ *)
  1817. VAR
  1818. file : Files.File; filename : Files.FileName; highlighterName : Identifier;
  1819. highlighter : Highlighter;
  1820. text : Texts.Text; format: LONGINT; res: WORD;
  1821. BEGIN
  1822. context.arg.SkipWhitespace; context.arg.String(filename);
  1823. context.arg.SkipWhitespace; context.arg.String(highlighterName);
  1824. highlighter := GetHighlighter(highlighterName);
  1825. IF (highlighter # NIL) THEN
  1826. NEW(text);
  1827. TextUtilities.LoadAuto(text, filename, format, res);
  1828. IF (res = 0) THEN
  1829. IF (format = 0) OR (format = 1) THEN (* Oberon rsp. Bluebottle text format *)
  1830. HighlightText(text, highlighter);
  1831. file := Files.Old(filename);
  1832. IF (file # NIL) THEN
  1833. file.GetName(filename);
  1834. CASE format OF
  1835. |0: TextUtilities.StoreOberonText(text, filename, res);
  1836. |1: TextUtilities.StoreText(text, filename, res);
  1837. |2: TextUtilities.ExportUTF8(text, filename, res);
  1838. ELSE
  1839. res := -99; (* file format not known *)
  1840. END;
  1841. IF (res = 0) THEN
  1842. context.out.String("Highlighted file "); context.out.String(filename); context.out.Ln;
  1843. ELSE
  1844. context.error.String("Could not store file "); context.error.String(filename);
  1845. context.error.String(" , res = "); context.error.Int(res, 0); context.error.Ln;
  1846. END;
  1847. ELSE
  1848. context.error.String(filename); context.error.String(": Could not resolve full filename.");
  1849. context.error.Ln;
  1850. END;
  1851. ELSE
  1852. context.error.String(filename); context.error.String(": Unsupported text format.");
  1853. context.error.Ln;
  1854. END;
  1855. ELSE
  1856. context.error.String("Could not open file "); context.error.String(filename);
  1857. context.error.String(", res = "); context.error.Int(res, 0); context.error.Ln;
  1858. END;
  1859. ELSE
  1860. context.error.String("Highligher "); context.error.String(highlighterName);
  1861. context.error.String(" not found."); context.error.Ln;
  1862. END;
  1863. END Highlight;
  1864. PROCEDURE ClearStats*(context : Commands.Context);
  1865. BEGIN
  1866. NnofRebuildRegions := 0; NnofPatchRegions := 0;
  1867. NnofPatchInsert := 0; NnofPatchInsertHit := 0; NnofPiOpenClose := 0; NnofPiNestedFull := 0; NnofPiNestedSimple := 0;
  1868. NnofPiRescan := 0; NnofPiSimple := 0; NnofPiNoHit := 0; NnofPiNoHitRescan := 0; NnofPiNoHitFull := 0;
  1869. context.out.String("SyntaxHighlighter: Statistics cleared."); context.out.Ln;
  1870. END ClearStats;
  1871. PROCEDURE Dump*(context : Commands.Context);
  1872. BEGIN {EXCLUSIVE}
  1873. IF (global_highlighters # NIL) THEN
  1874. global_highlighters.Dump(context.out);
  1875. ELSE
  1876. context.out.String("No highlighters available."); context.out.Ln;
  1877. END;
  1878. END Dump;
  1879. PROCEDURE Open*(context : Commands.Context); (** filename ~ *)
  1880. VAR filename : Files.FileName; diagnostics : Diagnostics.DiagnosticsList; newHighlighters : Highlighters;
  1881. BEGIN {EXCLUSIVE}
  1882. context.arg.SkipWhitespace; context.arg.String(filename);
  1883. NEW(diagnostics);
  1884. newHighlighters := Parse(filename, diagnostics, error);
  1885. IF ~error THEN
  1886. global_highlighters := newHighlighters;
  1887. context.out.String("SyntaxHighlighter: Loaded data from "); context.out.String(filename);
  1888. context.out.Ln;
  1889. END;
  1890. diagnostics.ToStream(context.out, Diagnostics.All);
  1891. END Open;
  1892. BEGIN
  1893. source := "";
  1894. diagnostics := NIL;
  1895. error := FALSE; autoinit := TRUE;
  1896. global_highlighters := NIL;
  1897. traceLevel := Trace_None;
  1898. END SyntaxHighlighter.
  1899. SyntaxHighlighter.Open SyntaxHighlighter.XML ~
  1900. SyntaxHighlighter.Dump ~
  1901. WMPerfMonPluginModVars.Install SyntaxHighlighter
  1902. SyntaxHighlighter.NnofRebuildRegions SyntaxHighlighter.NnofPatchRegions
  1903. SyntaxHighlighter.NnofPatchInsert SyntaxHighlighter.NnofPatchInsertHit SyntaxHighlighter.NnofPiOpenClose
  1904. SyntaxHighlighter.NnofPiNestedFull SyntaxHighlighter.NnofPiNestedSimple,
  1905. SyntaxHighlighter.NnofPiRescan SyntaxHighlighter.NnofPiSimple SyntaxHighlighter.NnofPiNoHit SyntaxHighlighter.NnofPiNoHitRescan
  1906. SyntaxHighlighter.NnofPiNoHitFull
  1907. ~