123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088 |
- MODULE SyntaxHighlighter; (** AUTHOR "staubesv"; PURPOSE "Simple Syntax Highlighter"; *)
- IMPORT
- KernelLog,
- Streams, Commands, Strings, Files, Diagnostics, Texts, TextUtilities, XML, XMLScanner, XMLParser, XMLObjects;
- CONST
- DefineMask* = {0..5};
- FontMask* = {0..2};
- FontName* = 0;
- FontSize* = 1;
- FontStyle* = 2;
- Color* = 3;
- BgColor* = 4;
- Voff* = 5;
- DefaultBgColor = 0;
- DefaultVoff = 0;
- DefaultHighlighterFile = "SyntaxHighlighter.XML";
- XmlRootElementName = "SyntaxHighlighter";
- XmlHighlighters = "Highlighters";
- XmlHighlighter = "Highlighter";
- XmlWords = "Words";
- XmlTokens = "Tokens";
- XmlAttributeAllowCharacters = "allowCharacters";
- XmlStyles = "Styles";
- XmlStyle = "Style";
- XmlAttributeName = "name";
- XmlAttributeDefaultStyle = "defaultstyle";
- XmlAttributeNumberStyle = "numberStyle";
- XmlAttributeFontName = "fontname";
- XmlAttributeFontSize = "fontsize";
- XmlAttributeFontStyle = "fontstyle";
- XmlAttributeColor = "color";
- XmlAttributeBgColor = "bgcolor";
- XmlAttributeVoff = "voff";
- XmlAttributeStyle = "style";
- XmlAttributeStyleOpen = "styleOpen";
- XmlAttributeStyleClose = "styleClose";
- XmlAttributeStyleContent = "style";
- XmlGroup = "Group";
- XmlRegions = "Regions";
- XmlRegion = "Region";
- XmlAttributeOpen = "open";
- XmlAttributeClose = "close";
- XmlAttributeNesting = "nesting";
- XmlAttributeMultiLine = "multiline";
- XmlDontCare = "*";
- Trace_None = 0;
- Trace_1 = 1;
- Trace_Max = 2;
- Statistics = TRUE;
- NOTCLOSED = MAX(LONGINT) - 128; (* some safety distance to protect against overflow *)
- MaxOpenLength = 32;
- MaxCloseLength = 32;
- MaxWordLength = 32;
- Dim1Length = 128;
- MaxTokenLength = 64;
- Ok = 0;
- StringTooLong = 1;
- Outside = 0;
- OpenString = 1;
- Content = 2;
- CloseString = 3;
- NoMatch = 0;
- Matching = 1;
- OpenMatch = 2;
- CloseMatch = 3;
- (* token types and subtypes*)
- Type_Invalid* = 0;
- Type_Identifier* = 1;
- Type_Number* = 2;
- Type_Token* = 3;
- Subtype_Decimal* = 0;
- Subtype_Hex* = 1;
- Subtype_Float* = 2;
- Subtype_Char* = 3;
- TypeWords = 1;
- TypeTokens = 2;
- TYPE
- Identifier = ARRAY 64 OF CHAR;
- Style* = OBJECT
- VAR
- name- : Identifier;
- attributes- : Texts.Attributes;
- defined- : SET;
- next : Style;
- PROCEDURE &Init(CONST name : Identifier; color, bgcolor, voff : LONGINT; CONST fontname : ARRAY OF CHAR; fontsize : LONGINT; fontstyle : SET);
- BEGIN
- ASSERT(name # "");
- SELF.name := name;
- NEW(attributes);
- attributes.Set(color, bgcolor, voff, fontname, fontsize, fontstyle);
- defined := {};
- next := NIL;
- END Init;
- END Style;
- Styles = OBJECT
- VAR
- styles : Style; (* head of list *)
- PROCEDURE &Init;
- BEGIN
- styles := NIL;
- END Init;
- PROCEDURE Add(style : Style);
- BEGIN {EXCLUSIVE}
- ASSERT(FindIntern(style.name) = NIL);
- style.next := styles;
- styles := style;
- END Add;
- PROCEDURE Find(CONST name : ARRAY OF CHAR) : Style;
- BEGIN {EXCLUSIVE}
- RETURN FindIntern(name);
- END Find;
- PROCEDURE FindIntern(CONST name : ARRAY OF CHAR) : Style;
- VAR style : Style;
- BEGIN
- style := styles;
- WHILE (style # NIL) & (style.name # name) DO style := style.next; END;
- RETURN style;
- END FindIntern;
- END Styles;
- TYPE
- Word = POINTER TO RECORD
- name : ARRAY MaxWordLength OF CHAR;
- style : Style; (* { style # NIL } *)
- next : Word;
- END;
- DataEntry = RECORD
- open, close : LONGINT; (* region *)
- region : RegionMatcher; (* { region # NIL } *)
- eol : BOOLEAN;
- END;
- DataArray = POINTER TO ARRAY OF DataEntry;
- State* = OBJECT
- VAR
- matchers : RegionMatcher;
- data : DataArray; (* {data # NIL} *)
- nofData : LONGINT;
- PROCEDURE &Init;
- BEGIN
- matchers := NIL;
- NEW(data, 128);
- nofData := 0;
- END Init;
- PROCEDURE AddMatcher(matcher : RegionMatcher);
- VAR m : RegionMatcher;
- BEGIN
- ASSERT((matcher # NIL) & (matcher.next = NIL));
- IF (matchers = NIL) THEN
- matchers := matcher;
- ELSE
- m := matchers;
- WHILE (m.next # NIL) DO m := m.next; END;
- m.next := matcher;
- END;
- END AddMatcher;
- PROCEDURE ResetMatchers;
- VAR m : RegionMatcher;
- BEGIN
- m := matchers;
- WHILE (m # NIL) DO
- m.ResetMatching;
- m := m.next;
- END;
- END ResetMatchers;
- PROCEDURE GetStyle(position : LONGINT; VAR start, end : LONGINT) : Style;
- VAR style : Style; entry : DataEntry; location : LONGINT;
- BEGIN
- style := NIL;
- IF Find(position, entry) THEN
- location := GetLocation(position, entry);
- IF (location = OpenString) THEN
- style := entry.region.styleOpen;
- start := entry.open; end := entry.open + entry.region.openLength - 1;
- ELSIF (location = Content) THEN
- style := entry.region.styleContent;
- start := entry.open + entry.region.openLength; end := entry.close - entry.region.closeLength;
- ELSIF (location = CloseString) THEN
- style := entry.region.styleClose;
- start := entry.close - entry.region.closeLength + 1; end := entry.close;
- ELSE
- HALT(99);
- END;
- END;
- RETURN style;
- END GetStyle;
- PROCEDURE Find(CONST position : LONGINT; VAR entry : DataEntry) : BOOLEAN;
- VAR l, r, m : LONGINT;
- BEGIN
- (* binary search *)
- l := 0; r := nofData;
- WHILE l < r DO
- m := (r - l) DIV 2 + l;
- IF (position <= data[m].close) THEN r := m;
- ELSE l := m + 1;
- END;
- END;
- IF (r < nofData) & (data[r].open <= position) & (position <= data[r].close) THEN
- entry := data[r];
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END;
- END Find;
- PROCEDURE FindTriple(position : LONGINT; VAR hasLeft, hasMiddle, hasRight : BOOLEAN; VAR left, middle, right : DataEntry);
- VAR i : LONGINT;
- BEGIN
- hasLeft := FALSE; hasMiddle := FALSE; hasRight := FALSE;
- IF (nofData > 0) THEN
- i := 0;
- WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
- IF (i > 0) THEN
- hasLeft := TRUE; left := data[i-1];
- END;
- IF (i < nofData) & (data[i].open <= position) & (position <= data[i].close) THEN
- hasMiddle := TRUE; middle := data[i];
- END;
- IF (i < nofData - 1) THEN
- hasRight := TRUE; right := data[i + 1];
- END;
- END;
- END FindTriple;
- PROCEDURE Patch(fromPosition : LONGINT; length : LONGINT);
- VAR i : LONGINT;
- BEGIN
- IF (nofData > 0) THEN
- i := 0;
- WHILE (i < nofData) & (data[i].close < fromPosition) DO INC(i); END;
- WHILE (i < nofData) DO
- data[i].close := data[i].close + length;
- IF (data[i].open >= fromPosition) THEN
- data[i].open := data[i].open + length;
- END;
- INC(i);
- END;
- END;
- END Patch;
- PROCEDURE Add(CONST entry : DataEntry);
- VAR insertAt, i : LONGINT;
- BEGIN
- ASSERT(entry.region # NIL);
- insertAt := 0;
- WHILE (insertAt < nofData) & (entry.open > data[insertAt].close) DO INC(insertAt); END;
- INC(nofData); (* we will add one data element ... *)
- IF (nofData >= LEN(data)) THEN EnlargeDataArray; END;
- FOR i := nofData - 1 TO insertAt + 1 BY -1 DO
- data[i] := data[i-1];
- END;
- data[insertAt] := entry;
- END Add;
- PROCEDURE Remove(CONST entry : DataEntry);
- VAR removeIdx, i : LONGINT;
- BEGIN
- IF (nofData > 0) THEN
- removeIdx := 0;
- WHILE (removeIdx < nofData) & (data[removeIdx].open # entry.open) & (data[removeIdx].close # entry.close) DO
- INC(removeIdx);
- END;
- FOR i := removeIdx TO nofData - 2 DO
- data[i] := data[i + 1];
- END;
- DEC(nofData);
- END;
- END Remove;
- PROCEDURE RemoveFrom(position : LONGINT);
- VAR i : LONGINT;
- BEGIN
- IF (nofData > 0) THEN
- i := 0;
- WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
- nofData := i;
- END;
- END RemoveFrom;
- PROCEDURE RemoveFromTo(position, length : LONGINT) : BOOLEAN;
- VAR removedEntries : BOOLEAN; i : LONGINT;
- BEGIN
- removedEntries := FALSE;
- IF (nofData > 0) THEN
- i := 0;
- WHILE (i < nofData) & (data[i].close < position) DO INC(i); END;
- IF (i < nofData - 1) & (position + length - 1 >= data[i].open) THEN
- nofData := i;
- removedEntries := TRUE;
- END;
- END;
- RETURN removedEntries;
- END RemoveFromTo;
- PROCEDURE Clear;
- BEGIN
- nofData := 0;
- END Clear;
- PROCEDURE EnlargeDataArray;
- VAR newData : DataArray; i : LONGINT;
- BEGIN
- NEW(newData, 2 * LEN(data));
- FOR i := 0 TO LEN(data)-1 DO
- newData[i] := data[i];
- END;
- data := newData;
- END EnlargeDataArray;
- PROCEDURE ShowEntry(CONST entry : DataEntry; out : Streams.Writer);
- BEGIN
- ASSERT(out # NIL);
- out.String("From "); out.Int(entry.open, 0); out.String(" to "); out.Int(entry.close, 0);
- out.Ln;
- END ShowEntry;
- PROCEDURE Dump(out : Streams.Writer);
- VAR i : LONGINT;
- BEGIN
- ASSERT(out # NIL);
- out.String("Region dump : "); out.Int(nofData, 0); out.String(" entries"); out.Ln;
- IF (nofData > 0) THEN
- FOR i := 0 TO nofData - 1 DO
- ShowEntry(data[i], out);
- END;
- END;
- END Dump;
- END State;
- TYPE
- RegionDescriptor = OBJECT
- VAR
- open, close : Identifier;
- nesting, multiline : BOOLEAN;
- styleOpen, styleClose, styleContent : Style;
- openLength, closeLength : LONGINT;
- next : RegionDescriptor;
- PROCEDURE &Init(CONST open, close : ARRAY OF CHAR; nesting, multiline : BOOLEAN; styleOpen, styleClose, styleContent : Style);
- BEGIN
- Copy(open, SELF.open); openLength := Strings.Length(open); ASSERT((openLength > 0) & (openLength < MaxOpenLength));
- Copy(close, SELF.close); closeLength := Strings.Length(close); ASSERT((closeLength >= 0) & (closeLength < MaxCloseLength));
- SELF.nesting := nesting;
- SELF.multiline := multiline;
- SELF.styleOpen := styleOpen;
- SELF.styleClose := styleClose;
- SELF.styleContent := styleContent;
- next := NIL;
- END Init;
- END RegionDescriptor;
- TYPE
- RegionMatcher = OBJECT
- VAR
- open, close : Identifier;
- nesting, multiline : BOOLEAN;
- styleOpen, styleClose, styleContent : Style;
- openLength, closeLength : LONGINT;
- openChars : ARRAY MaxOpenLength OF CHAR;
- closeChars : ARRAY MaxCloseLength OF CHAR;
- firstOpenChar, nofOpenChars, firstCloseChar, nofCloseChars : LONGINT;
- lastChar : CHAR;
- entry : DataEntry;
- level : LONGINT;
- state : LONGINT;
- firstPosition : LONGINT;
- next : RegionMatcher;
- PROCEDURE &Init(descriptor : RegionDescriptor);
- BEGIN
- ASSERT(descriptor # NIL);
- Copy(descriptor.open, SELF.open); openLength := descriptor.openLength;
- Copy(descriptor.close, SELF.close); closeLength := descriptor.closeLength;
- SELF.nesting := descriptor.nesting;
- SELF.multiline := descriptor.multiline;
- SELF.styleOpen := descriptor.styleOpen;
- SELF.styleClose := descriptor.styleClose;
- SELF.styleContent := descriptor.styleContent;
- ResetMatching;
- next := NIL;
- END Init;
- PROCEDURE GetEntry() : DataEntry;
- BEGIN
- RETURN entry;
- END GetEntry;
- PROCEDURE ResetMatching;
- BEGIN
- nofOpenChars := 0; nofCloseChars := 0;
- lastChar := 0X;
- level := 0;
- state := NoMatch;
- firstPosition := MAX(LONGINT);
- END ResetMatching;
- PROCEDURE CheckOpen(reader : Texts.TextReader; position : LONGINT; VAR length : LONGINT) : BOOLEAN;
- VAR char32 : Texts.Char32; oldPosition : LONGINT;
- BEGIN
- ASSERT(reader # NIL);
- length := 0;
- oldPosition := reader.GetPosition();
- reader.SetPosition(position);
- reader.ReadCh(char32);
- WHILE (length < openLength) & (open[length] = CHR(char32)) & ~reader.eot DO reader.ReadCh(char32); INC(length); END;
- IF (length = openLength) THEN
- ResetMatching;
- entry.open := position;
- entry.close := NOTCLOSED;
- entry.region := SELF;
- entry.eol := FALSE;
- state := OpenMatch;
- level := 1;
- firstPosition := position;
- END;
- RETURN length = openLength;
- END CheckOpen;
- PROCEDURE FeedChar(char32 : Texts.Char32; position : LONGINT; VAR newState : LONGINT);
- VAR char : CHAR; openMatch, closeMatch : BOOLEAN;
- PROCEDURE AddToCircularBuffer(char : CHAR; VAR buffer : ARRAY OF CHAR; VAR first, length, maxLength : LONGINT);
- BEGIN
- buffer[(first + length) MOD LEN(buffer)] := char;
- IF (length = maxLength) THEN
- first := (first + 1) MOD LEN(buffer);
- ELSE
- ASSERT(length < maxLength);
- INC(length);
- END;
- END AddToCircularBuffer;
- PROCEDURE CheckBuffer(CONST buffer, compareTo : ARRAY OF CHAR; VAR first, length, maxLength : LONGINT) : BOOLEAN;
- VAR i : LONGINT;
- BEGIN
- ASSERT(length = maxLength);
- i := 0;
- WHILE (i < maxLength) & (buffer[(first + i) MOD LEN(buffer)] = compareTo[i]) DO INC(i); END;
- IF (i = maxLength) THEN
- length := 0; (* clear buffer *)
- RETURN TRUE;
- ELSE
- REPEAT
- first := (first + 1) MOD LEN(buffer);
- DEC(length);
- UNTIL (length = 0) OR (buffer[first] = compareTo[0]);
- RETURN FALSE;
- END;
- END CheckBuffer;
- BEGIN
- ASSERT(level >= 0);
- openMatch := FALSE; closeMatch := FALSE;
- char := CHR(char32);
- IF (level = 0) OR nesting THEN (* allow matching to open string *)
- IF (openLength = 1) THEN
- openMatch := (char = open[0]);
- ELSIF (openLength = 2) THEN
- openMatch := (char = open[1]) & (lastChar = open[0]);
- ELSIF (char = open[0]) OR (nofOpenChars > 0) THEN (* start OR continue to save characters *)
- AddToCircularBuffer(char, openChars, firstOpenChar, nofOpenChars, openLength);
- IF (nofOpenChars = openLength) THEN
- openMatch := CheckBuffer(openChars, open, firstOpenChar, nofOpenChars, openLength);
- END;
- END;
- IF openMatch THEN
- nofOpenChars := 0; lastChar := 0X;
- INC(level);
- IF (level = 1) THEN
- entry.open := position - openLength + 1;
- entry.close := NOTCLOSED;
- entry.region := SELF;
- entry.eol := FALSE;
- END;
- END;
- ELSE
- nofOpenChars := 0;
- END;
- IF ~openMatch & (level > 0) THEN (* allow matching to close string *)
- IF (closeLength = 1) THEN
- closeMatch := (char = close[0]);
- ELSIF (closeLength = 2) THEN
- closeMatch := (char = close[1]) & (lastChar = close[0]);
- ELSIF (closeLength > 0) & ((char = close[0]) OR (nofCloseChars > 0)) THEN
- AddToCircularBuffer(char, closeChars, firstCloseChar, nofCloseChars, closeLength);
- IF (nofCloseChars = closeLength) THEN
- closeMatch := CheckBuffer(closeChars, close, firstCloseChar, nofCloseChars, closeLength);
- END;
- END;
- IF ~multiline & (char = CHR(Texts.NewLineChar)) & (~closeMatch OR (level > 0)) THEN
- nofCloseChars := 0;
- level := 0;
- entry.close := position;
- entry.eol := TRUE;
- (* don't set closeMatch here since entry.end position may be different for EOL match *)
- ELSIF closeMatch THEN
- nofCloseChars := 0; lastChar := 0X;
- DEC(level);
- IF (level = 0) THEN
- entry.close := position;
- END;
- END;
- ELSE
- nofCloseChars := 0; (* clear buffer *)
- END;
- IF ~openMatch & ~closeMatch THEN lastChar := char; END;
- IF (state = NoMatch) THEN
- IF openMatch THEN state := OpenMatch; firstPosition := position;
- ELSIF (nofOpenChars > 0) THEN state := Matching; firstPosition := position;
- END;
- ELSIF (state = Matching) THEN
- IF openMatch THEN state := OpenMatch;
- ELSIF (nofOpenChars = 1) THEN state := Matching; firstPosition := position;
- ELSIF (nofOpenChars > 1) THEN state := Matching;
- ELSE state := NoMatch;
- END;
- ELSIF (state = OpenMatch) THEN
- IF (level = 0) THEN state := CloseMatch; END;
- ELSIF (state = CloseMatch) THEN
- (* no more state transitions until reset *)
- END;
- newState := state;
- END FeedChar;
- END RegionMatcher;
- TYPE
- Token* = RECORD
- type-, subtype- : SHORTINT;
- startPosition-, endPosition- : LONGINT;
- value- : ARRAY MaxTokenLength OF CHAR;
- length : LONGINT; (* if type = Type_Identifier: of string value *)
- style- : Style;
- END;
- TYPE
- Highlighter* = OBJECT
- VAR
- name : Identifier;
- defaultStyle, numberStyle : Style;
- words : ARRAY Dim1Length OF ARRAY MaxWordLength OF Word;
- wildcardWords : ARRAY MaxWordLength OF Word;
- wildcardsEnabled : BOOLEAN;
- tokens : ARRAY Dim1Length OF RECORD
- length : ARRAY MaxWordLength OF Word;
- maxLength : LONGINT;
- END;
- regions : RegionDescriptor;
- longestOpen, longestClose : LONGINT;
- regionChars, wordChars, isAllowedCharacter : ARRAY 256 OF BOOLEAN;
- next : Highlighter;
- PROCEDURE &Init(CONST name : ARRAY OF CHAR);
- VAR i, j : LONGINT;
- BEGIN
- ASSERT(name # "");
- Copy(name, SELF.name);
- defaultStyle := NIL; numberStyle := NIL;
- FOR i := 0 TO Dim1Length-1 DO
- FOR j := 0 TO MaxWordLength-1 DO
- words[i][j] := NIL;
- tokens[i].length[j] := NIL;
- tokens[i].maxLength := 0;
- END;
- END;
- FOR i := 0 TO LEN(wildcardWords)-1 DO wildcardWords[i] := NIL; END;
- wildcardsEnabled := FALSE;
- regions := NIL;
- longestOpen := 0; longestClose := 0;
- FOR i := 0 TO LEN(regionChars)-1 DO
- regionChars[i] := FALSE; wordChars[i] := FALSE;
- isAllowedCharacter[i] := FALSE;
- END;
- FOR i := ORD("a") TO ORD("z") DO isAllowedCharacter[i] := TRUE; END;
- FOR i := ORD("A") TO ORD("Z") DO isAllowedCharacter[i] := TRUE; END;
- FOR i := ORD("0") TO ORD("9") DO isAllowedCharacter[i] := TRUE; END;
- next := NIL;
- END Init;
- PROCEDURE IsAllowedCharacter*(character : Texts.Char32) : BOOLEAN;
- BEGIN
- RETURN (character < 256) & isAllowedCharacter[character MOD 256];
- END IsAllowedCharacter;
- PROCEDURE AllowCharacter(character : CHAR);
- BEGIN
- isAllowedCharacter[ORD(character)] := TRUE;
- END AllowCharacter;
- PROCEDURE Scan(reader : Texts.TextReader; from, to : LONGINT; CONST state : State; VAR match : BOOLEAN);
- VAR
- matcher, owner, oldOwner : RegionMatcher; char32 : Texts.Char32; continue : BOOLEAN; entry : DataEntry; oldPosition, position : LONGINT;
- mstate, tempState, nofMatching : LONGINT;
- PROCEDURE CheckLongestMatch(VAR owner : RegionMatcher);
- VAR matcher : RegionMatcher; length, maxLength : LONGINT;
- BEGIN
- ASSERT(owner # NIL);
- maxLength := owner.openLength;
- matcher := state.matchers;
- WHILE (matcher # NIL) DO
- IF (matcher.state = Matching) & (matcher.firstPosition <= owner.firstPosition) THEN
- IF matcher.CheckOpen(reader, matcher.firstPosition, length) & ((matcher.firstPosition < owner.firstPosition) OR (length > maxLength)) THEN
- maxLength := length;
- owner := matcher;
- END;
- END;
- matcher := matcher.next;
- END;
- ASSERT(owner # NIL);
- END CheckLongestMatch;
- BEGIN
- ASSERT((reader # NIL) & (state # NIL));
- IF (traceLevel >= Trace_1) THEN
- KernelLog.String("Scan from ");
- KernelLog.Int(from, 0); KernelLog.String(" to "); KernelLog.Int(to, 0);
- KernelLog.Ln;
- END;
- state.ResetMatchers;
- match := FALSE;
- owner := NIL; continue := FALSE;
- reader.SetPosition(from); position := reader.GetPosition();
- reader.SetDirection(1);
- reader.ReadCh(char32);
- WHILE ~reader.eot & (position <= to) DO
- IF (owner # NIL) THEN
- mstate := owner.state;
- ASSERT(mstate = OpenMatch);
- WHILE (mstate # CloseMatch) & ~reader.eot & (position <= to) DO
- owner.FeedChar(char32, position, mstate);
- reader.ReadCh(char32);
- INC(position);
- END;
- entry := owner.GetEntry();
- state.Add(entry);
- state.ResetMatchers;
- owner := NIL;
- ELSE
- owner := NIL; nofMatching := 0;
- mstate := NoMatch;
- matcher := state.matchers;
- WHILE (matcher # NIL) DO
- matcher.FeedChar(char32, position, tempState);
- IF (tempState = Matching) THEN
- INC(nofMatching);
- ELSIF (tempState = OpenMatch) THEN
- owner := matcher;
- END;
- matcher := matcher.next;
- END;
- match := match OR (owner # NIL);
- IF (owner # NIL) & (nofMatching > 1) THEN
- oldPosition := reader.GetPosition();
- oldOwner := owner;
- CheckLongestMatch(owner);
- IF (owner # oldOwner) THEN
- position := owner.firstPosition + owner.openLength;
- reader.SetPosition(position);
- reader.ReadCh(char32);
- ELSE
- reader.SetPosition(oldPosition);
- reader.ReadCh(char32);
- INC(position);
- END;
- ELSE
- reader.ReadCh(char32);
- INC(position);
- END;
- END;
- END;
- END Scan;
- PROCEDURE RebuildRegions*(reader : Texts.TextReader; CONST state : State);
- VAR ignore : BOOLEAN;
- BEGIN
- ASSERT((reader # NIL) & (state # NIL));
- IF Statistics THEN INC(NnofRebuildRegions); END;
- state.Clear;
- state.ResetMatchers;
- Scan(reader, 0, MAX(LONGINT), state, ignore);
- END RebuildRegions;
- PROCEDURE PatchRegions*(info : Texts.TextChangeInfo; reader : Texts.TextReader; state : State; VAR fullLayout : BOOLEAN);
- VAR
- char32 : Texts.Char32;
- PROCEDURE NeedRescan(position, length : LONGINT) : BOOLEAN;
- VAR rescan : BOOLEAN; i : LONGINT;
- BEGIN
- rescan := FALSE;
- reader.SetPosition(position);
- i := 0;
- WHILE (i < length) & ~rescan DO
- reader.ReadCh(char32);
- rescan := rescan OR regionChars[ORD(CHR(char32))];
- INC(i);
- END;
- RETURN rescan;
- END NeedRescan;
- PROCEDURE PatchInsert(position, length : LONGINT; VAR fullLayout : BOOLEAN);
- VAR
- hasLeft, hasMiddle, hasRight : BOOLEAN;
- left, middle, right : DataEntry;
- res : BOOLEAN;
- start, end, oldStart, oldEnd : LONGINT;
- ignore, match : BOOLEAN;
- location : LONGINT;
- BEGIN
- IF Statistics THEN INC(NnofPatchInsert); END;
- fullLayout := FALSE;
- state.FindTriple(position, hasLeft, hasMiddle, hasRight, left, middle, right);
- IF hasMiddle & (position > middle.open) THEN
- IF Statistics THEN INC(NnofPatchInsertHit); END;
- location := GetLocation(position, middle);
- IF (location = OpenString) OR ((location = CloseString) & (position > middle.close - middle.region.closeLength + 1)) THEN
- IF Statistics THEN INC(NnofPiOpenClose); END;
- state.RemoveFrom(position);
- Scan(reader, middle.open, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- ELSIF middle.region.nesting THEN
- oldStart := middle.open;
- oldEnd := middle.close;
- state.Remove(middle);
- Scan(reader, oldStart, oldEnd + length, state, ignore);
- res := state.Find(position, middle);
- IF ~res OR
- (middle.open # oldStart) OR
- ((oldEnd # NOTCLOSED) & (middle.close # oldEnd + length)) OR
- ((oldEnd = NOTCLOSED) & (middle.close # NOTCLOSED)) THEN
- IF Statistics THEN INC(NnofPiNestedFull); END;
- state.RemoveFrom(position);
- Scan(reader, oldStart, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- ELSE
- IF Statistics THEN INC(NnofPiNestedSimple); END;
- state.Patch(middle.close + 1, length); (* middle is already patched *)
- END;
- ELSIF NeedRescan(position, length) THEN
- IF Statistics THEN INC(NnofPiRescan); END;
- state.RemoveFrom(position); (* TBD optimize *)
- Scan(reader, middle.open, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- ELSE
- IF Statistics THEN INC(NnofPiSimple); END;
- state.Patch(position, length);
- END;
- ELSE
- IF Statistics THEN INC(NnofPiNoHit); END;
- state.Patch(position, length);
- IF NeedRescan(position, length) THEN
- IF Statistics THEN INC(NnofPiNoHitRescan); END;
- start := position - longestOpen + 1;
- IF (longestClose > 0) THEN
- end := position + length + longestClose - 1;
- ELSE
- end := position + length;
- END;
- IF hasLeft & (left.close >= start) THEN start := left.close + 1; END;
- IF hasRight & (right.open + length <= end) THEN end := right.open + length - 1; END; (* manually patched since copy *)
- Scan(reader, start, end, state, match);
- IF match THEN
- IF Statistics THEN INC(NnofPiNoHitFull); END;
- state.RemoveFrom(start);
- Scan(reader, start, MAX(LONGINT), state, match);
- fullLayout := TRUE;
- END;
- END;
- END;
- END PatchInsert;
- PROCEDURE PatchDelete(position, length : LONGINT; VAR fullLayout : BOOLEAN);
- VAR
- hasLeft, hasMiddle, hasRight : BOOLEAN;
- left, middle, right : DataEntry;
- start, end, oldStart, oldEnd : LONGINT;
- match, ignore, res : BOOLEAN;
- location : LONGINT;
- BEGIN
- fullLayout := FALSE;
- state.FindTriple(position, hasLeft, hasMiddle, hasRight, left, middle, right);
- IF hasMiddle THEN
- location := GetLocation(position, middle);
- IF (middle.region.closeLength > 0) THEN end := middle.close - middle.region.closeLength + 1; ELSE end := middle.close; END;
- IF (location = Content) & (position + length - 1 < end) THEN
- oldStart := middle.open;
- oldEnd := middle.close;
- state.Remove(middle);
- Scan(reader, middle.open, middle.close, state, ignore);
- res := state.Find(position, middle);
- IF ~res OR
- (middle.open # oldStart) OR
- ((oldEnd # NOTCLOSED) & (middle.close # oldEnd - length)) OR
- ((oldEnd = NOTCLOSED) & (middle.close # NOTCLOSED)) THEN
- state.RemoveFrom(position);
- Scan(reader, oldStart, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- ELSE
- state.Patch(middle.close + 1, -length);
- END;
- ELSE
- state.RemoveFrom(position);
- Scan(reader, middle.open, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- END;
- ELSE
- start := position - longestOpen + 1;
- IF hasLeft & (left.close >= start) THEN start := left.close + 1; END;
- IF state.RemoveFromTo(position, length) THEN
- Scan(reader, start, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- ELSE
- end := position - 1;
- state.Patch(position, -length);
- Scan(reader, start, end, state, match);
- IF match THEN
- state.RemoveFrom(start);
- Scan(reader, start, MAX(LONGINT), state, ignore);
- fullLayout := TRUE;
- END;
- END;
- END;
- END PatchDelete;
- BEGIN
- ASSERT((info.op = Texts.OpInsert) OR (info.op = Texts.OpDelete));
- ASSERT(reader # NIL);
- IF Statistics THEN INC(NnofPatchRegions); END;
- IF traceLevel >= Trace_1 THEN
- IF (info.op = Texts.OpInsert) THEN KernelLog.String("INS ");
- ELSE KernelLog.String("DEL ");
- END;
- KernelLog.Int(info.len, 0); KernelLog.String("@"); KernelLog.Int(info.pos, 0);
- KernelLog.Ln;
- END;
- IF (info.op = Texts.OpInsert) THEN
- PatchInsert(info.pos, info.len, fullLayout);
- ELSE
- PatchDelete(info.pos, info.len, fullLayout);
- END;
- END PatchRegions;
- PROCEDURE GetDefaultStyle*() : Style;
- BEGIN
- RETURN defaultStyle;
- END GetDefaultStyle;
- PROCEDURE GetState*() : State;
- VAR state : State; r : RegionDescriptor; m : RegionMatcher;
- BEGIN
- NEW(state);
- r := regions;
- WHILE (r # NIL) DO
- NEW(m, r); state.AddMatcher(m);
- r := r.next;
- END;
- RETURN state;
- END GetState;
- PROCEDURE MatchToken(char32 : Texts.Char32; reader : Texts.TextReader; VAR lookaheadIdx : LONGINT; VAR token : Token) : BOOLEAN;
- VAR tokensIdx, maxLength, i : LONGINT; t : Word;
- BEGIN
- ASSERT(reader # NIL);
- tokensIdx := char32 MOD Dim1Length;
- maxLength := tokens[tokensIdx].maxLength;
- IF (maxLength > 0) THEN
- (* copy look-ahead into local buffer *)
- WHILE (lookaheadIdx < maxLength) & (char32 > 32) & ~reader.eot DO
- reader.ReadCh(char32);
- IF (char32 > 32) & ~reader.eot THEN
- token.value[lookaheadIdx] := CHR(char32);
- INC(lookaheadIdx);
- END;
- END;
- token.value[lookaheadIdx] := 0X;
- (* compare look-ahead to token list. longest-match first *)
- i := lookaheadIdx;
- WHILE (i > 0) & (token.type = Type_Invalid) DO
- t := tokens[tokensIdx].length[i - 1];
- WHILE (t # NIL) & ~Equal(t.name, token.value, i) DO t := t.next; END;
- IF (t # NIL) THEN
- token.type := Type_Token;
- token.style := t.style;
- ASSERT(token.style # NIL);
- token.endPosition := token.startPosition + i - 1;
- token.value[i] := 0X;
- END;
- DEC(i);
- END;
- END;
- RETURN (token.type # Type_Invalid);
- END MatchToken;
- (* Scan reader at its current position *)
- PROCEDURE GetToken*(reader : Texts.TextReader; position : LONGINT; VAR token : Token);
- VAR char32 : Texts.Char32; idx, i : LONGINT;
- BEGIN
- ASSERT(reader # NIL);
- token.type := Type_Invalid;
- token.startPosition := position;
- token.endPosition := position - 1;
- token.value := "";
- token.style := NIL;
- reader.ReadCh(char32);
- IF (char32 > 32) THEN
- token.value[0] := CHR(char32);
- idx := 1;
- IF ~MatchToken(char32, reader, idx, token) THEN
- ASSERT(idx >= 1);
- (* check validity of lookahead buffer *)
- i := 0;
- WHILE (i < idx) & isAllowedCharacter[ORD(token.value[i])] DO INC(i); END;
- IF (i = idx) THEN
- reader.ReadCh(char32);
- WHILE (char32 > 32) & ~reader.eot & (i < LEN(token.value)) & IsAllowedCharacter(char32) DO
- token.value[i] := CHR(char32);
- INC(i);
- reader.ReadCh(char32);
- END;
- token.endPosition := token.startPosition + i - 1;
- IF (i < LEN(token.value)) THEN
- token.value[i] := 0X;
- token.length := i;
- GetTokenType(token);
- ELSE
- (* token too long .. .skip! *)
- token.type := Type_Invalid;
- WHILE (char32 > 32) & ~reader.eot & IsAllowedCharacter(char32) DO
- reader.ReadCh(char32);
- INC(token.endPosition);
- END;
- END;
- ELSE
- token.value[i] := 0X;
- IF (i > 0) THEN
- token.length := i;
- GetTokenType(token);
- ELSE
- token.type := Type_Invalid;
- END;
- END;
- END;
- ELSE
- (* whitespace or eot -> token.type = Type_Invalid *)
- END;
- END GetToken;
- PROCEDURE GetWordStyle*(reader : Texts.TextReader; position : LONGINT; VAR end : LONGINT) : Style;
- VAR style : Style; token : Token;
- BEGIN
- ASSERT(reader # NIL);
- reader.SetPosition(position);
- GetToken(reader, position, token);
- end := token.endPosition;
- IF (token.type # Type_Invalid) THEN
- IF (token.type # Type_Token) THEN
- style := GetStyle(token.value, token.length); (* keywords have higher priority than numbers *)
- IF (style = NIL) & (token.type = Type_Number) THEN
- style := numberStyle;
- END;
- ELSE
- (* style assigned in MatchToken *)
- style := token.style;
- END;
- ELSE
- style := NIL;
- END;
- RETURN style;
- END GetWordStyle;
- PROCEDURE GetRegionStyle*(position : LONGINT; state : State; VAR start, end : LONGINT) : Style;
- BEGIN
- ASSERT(state # NIL);
- RETURN state.GetStyle(position, start, end);
- END GetRegionStyle;
- PROCEDURE GetStyle*(CONST keyword : ARRAY OF CHAR; length : LONGINT) : Style;
- VAR style : Style; word : Word; i : LONGINT;
- BEGIN
- ASSERT(length > 0);
- style := NIL;
- IF wordChars[ORD(keyword[0])] THEN
- IF (length <= MaxWordLength) THEN
- word := words[ORD(keyword[0]) MOD Dim1Length][length - 1];
- WHILE (word # NIL) & (word.name < keyword) DO word := word.next; END;
- IF (word # NIL) & (word.name = keyword) THEN
- style := word.style;
- END;
- END;
- END;
- IF (style = NIL) & wildcardsEnabled THEN
- i := 0;
- WHILE (i < length) & (i < MaxWordLength) & (style = NIL) DO
- word := wildcardWords[i];
- WHILE (word # NIL) & ~Strings.Match(word.name, keyword) DO word := word.next; END;
- IF (word # NIL) THEN
- style := word.style;
- END;
- INC(i);
- END;
- END;
- RETURN style;
- END GetStyle;
- PROCEDURE AddToken(CONST tokenname : ARRAY OF CHAR; style : Style; VAR res : WORD);
- VAR token, t : Word; length, index1, index2 : LONGINT;
- BEGIN
- ASSERT((Strings.Length(tokenname) > 0) & (style # NIL) & (style.name # ""));
- length := Strings.Length(tokenname);
- IF (length <= MaxWordLength) THEN
- NEW(token);
- COPY(tokenname, token.name);
- token.style := style;
- index1 := ORD(token.name[0]) MOD Dim1Length;
- index2 := length - 1;
- IF (tokens[index1].length[index2] = NIL) OR (tokens[index1].length[index2].name > token.name) THEN
- token.next := tokens[index1].length[index2];
- tokens[index1].length[index2] := token;
- IF (length > tokens[index1].maxLength) THEN tokens[index1].maxLength := length; END;
- ELSE
- t := tokens[index1].length[index2];
- WHILE (t.next # NIL) & (t.next.name < token.name) DO t := t.next; END;
- token.next := t.next;
- t.next := token;
- END;
- res := Ok;
- ELSE
- res := StringTooLong;
- END;
- END AddToken;
- PROCEDURE AddWord(CONST keyword : ARRAY OF CHAR; style : Style; VAR res : WORD);
- VAR word, w : Word; nofWildcards, index1, index2, length : LONGINT;
- BEGIN
- ASSERT((Strings.Length(keyword) > 0) & (style # NIL) & (style.name # ""));
- length := Strings.Length(keyword);
- IF (length <= MaxWordLength) THEN
- NEW(word);
- Copy(keyword, word.name);
- word.style := style;
- nofWildcards := NofWildcards(word.name);
- IF (nofWildcards = 0) THEN
- index1 := ORD(word.name[0]) MOD Dim1Length;
- index2 := length - 1;
- IF (words[index1][index2] = NIL) OR (words[index1][index2].name > word.name) THEN
- word.next := words[index1][index2];
- words[index1][index2] := word;
- ELSE
- w := words[index1][index2];
- WHILE (w.next # NIL) & (w.next.name < word.name) DO w := w.next; END;
- word.next := w.next;
- w.next := word;
- END;
- wordChars[ORD(word.name[0])] := TRUE;
- ELSE
- wildcardsEnabled := TRUE;
- index1 := length - nofWildcards - 1;
- word.next := wildcardWords[index1];
- wildcardWords[index1] := word;
- END;
- res := Ok;
- ELSE
- res := StringTooLong;
- END;
- END AddWord;
- PROCEDURE AddRegion(CONST open, close : ARRAY OF CHAR; nesting, multiline : BOOLEAN; styleOpen, styleClose, styleContent : Style);
- VAR region, r : RegionDescriptor; length, i : LONGINT;
- BEGIN
- ASSERT((Strings.Length(open) > 0));
- NEW(region, open, close, nesting, multiline, styleOpen, styleClose, styleContent);
- (* append to list *)
- IF (regions = NIL) THEN
- regions := region;
- ELSE
- r := regions;
- WHILE (r.next # NIL) DO r := r.next; END;
- r.next := region;
- END;
- length := Strings.Length(open); IF (length > longestOpen) THEN longestOpen := length; END;
- FOR i := 0 TO length-1 DO
- regionChars[ORD(open[i])] := TRUE;
- END;
- length := Strings.Length(close); IF (length > longestClose) THEN longestClose := length; END;
- FOR i := 0 TO length-1 DO
- regionChars[ORD(close[i])] := TRUE;
- END;
- END AddRegion;
- PROCEDURE DebugInterface*(code : LONGINT; state : State);
- VAR out : Streams.Writer;
- BEGIN
- ASSERT(state # NIL);
- IF (code = 0) THEN
- NEW(out, KernelLog.Send, 256);
- KernelLog.String("SyntaxHighlighter: Dump:"); KernelLog.Ln;
- state.Dump(out);
- ELSIF (code = 1) THEN
- traceLevel := (traceLevel + 1) MOD (Trace_Max + 1);
- KernelLog.String("SyntaxHighlighter: TraceLevel = ");
- KernelLog.Int(traceLevel, 0); KernelLog.Ln;
- END;
- END DebugInterface;
- PROCEDURE Dump(out : Streams.Writer);
- PROCEDURE DumpWordList(out : Streams.Writer; word : Word);
- BEGIN
- ASSERT((out # NIL) & (word # NIL));
- WHILE (word # NIL) DO out.String(word.name); out.String(" "); word := word.next; END;
- END DumpWordList;
- PROCEDURE DumpTokens(out : Streams.Writer; level : LONGINT);
- VAR i, j : LONGINT;
- BEGIN
- ASSERT(out # NIL);
- FOR i := 0 TO LEN(tokens)-1 DO
- IF (tokens[i].maxLength > 0) THEN
- Indent(out, level); out.Char(CHR(i)); out.String(": ");
- FOR j := 0 TO LEN(tokens[i].length)-1 DO
- IF (tokens[i].length[j] # NIL) THEN
- out.Int(j + 1, 0); out.String(": ");
- DumpWordList(out, tokens[i].length[j]);
- END;
- END;
- out.Ln;
- END;
- END;
- END DumpTokens;
- BEGIN
- ASSERT(out # NIL);
- out.String("Highlighter: "); out.String(name); out.Ln;
- out.String(" Tokens:"); out.Ln;
- DumpTokens(out, 4);
- END Dump;
- END Highlighter;
- Highlighters = OBJECT
- VAR
- list : Highlighter;
- PROCEDURE &Init;
- BEGIN
- list := NIL;
- END Init;
- PROCEDURE Add(highlighter : Highlighter);
- BEGIN {EXCLUSIVE}
- ASSERT(highlighter # NIL);
- highlighter.next := list;
- list := highlighter;
- END Add;
- PROCEDURE Find(CONST name : ARRAY OF CHAR) : Highlighter;
- VAR highlighter : Highlighter;
- BEGIN {EXCLUSIVE}
- highlighter := list;
- WHILE (highlighter # NIL) & (highlighter.name # name) DO highlighter := highlighter.next; END;
- RETURN highlighter;
- END Find;
- PROCEDURE Dump(out : Streams.Writer);
- VAR h : Highlighter;
- BEGIN {EXCLUSIVE}
- ASSERT(out # NIL);
- h := list;
- WHILE (h # NIL) DO h.Dump(out); h := h.next; END;
- END Dump;
- END Highlighters;
- VAR
- source : Files.FileName;
- diagnostics : Diagnostics.Diagnostics;
- error, autoinit : BOOLEAN;
- global_highlighters : Highlighters;
- traceLevel : LONGINT;
- (* Statistics (not thread-safe) *)
- NnofRebuildRegions, NnofPatchRegions,
- NnofPatchInsert, NnofPatchInsertHit, NnofPiOpenClose, NnofPiNestedFull, NnofPiNestedSimple,
- NnofPiRescan, NnofPiSimple, NnofPiNoHit, NnofPiNoHitRescan, NnofPiNoHitFull
- : LONGINT;
- PROCEDURE GetHighlighter*(CONST name : ARRAY OF CHAR) : Highlighter;
- VAR highlighter : Highlighter; diagnostics : Diagnostics.Diagnostics;
- BEGIN {EXCLUSIVE}
- IF (global_highlighters = NIL) & autoinit THEN
- autoinit := FALSE; (* only try this once *)
- NEW(diagnostics);
- global_highlighters := Parse(DefaultHighlighterFile, diagnostics, error);
- KernelLog.String("SyntaxHighlighter: Auto-loading "); KernelLog.String(DefaultHighlighterFile);
- KernelLog.String(" ... ");
- IF ~error THEN
- KernelLog.String("done.");
- ELSE
- KernelLog.String("failed.");
- global_highlighters := NIL;
- END;
- KernelLog.Ln;
- END;
- IF (global_highlighters # NIL) THEN
- highlighter := global_highlighters.Find(name);
- ELSE
- highlighter := NIL;
- END;
- RETURN highlighter;
- END GetHighlighter;
- PROCEDURE GetTokenType(VAR token : Token);
- VAR i : LONGINT; tokenDone : BOOLEAN;
- BEGIN
- token.type := Type_Identifier;
- IF ('0' <= token.value[0]) & (token.value[0] <= '9') THEN
- token.type := Type_Number;
- i := 0; tokenDone := FALSE;
- WHILE (token.value[i] # 0X) & (i < LEN(token.value)) DO
- IF ~tokenDone & (token.type = Type_Number) THEN
- CASE token.value[i] OF
- |'0'..'9': (* do nothing here *)
- |'A'..'F':
- IF (token.subtype = Subtype_Decimal) THEN token.subtype := Subtype_Hex;
- ELSIF (token.subtype = Subtype_Float) & (token.value[i] # "E") THEN token.type := Type_Identifier;
- END;
- |'X':
- IF (token.subtype = Subtype_Decimal) OR (token.subtype = Subtype_Hex) THEN
- token.subtype := Subtype_Char; tokenDone := TRUE;
- ELSE
- token.type := Type_Identifier;
- END;
- |'h', 'H':
- IF (token.subtype = Subtype_Decimal) OR (token.subtype = Subtype_Hex) THEN
- token.subtype := Subtype_Hex; tokenDone := TRUE;
- ELSE
- token.type := Type_Identifier;
- END;
- |'.':
- IF (token.subtype = Subtype_Decimal) THEN token.subtype := Subtype_Float;
- ELSE token.type := Type_Invalid;
- END;
- ELSE
- token.type := Type_Identifier;
- END;
- ELSE
- token.type := Type_Identifier;
- END;
- INC(i);
- END;
- END;
- END GetTokenType;
- (*? Actually, the XML framework should take care of unescaping characters *)
- PROCEDURE Unescape(string : Strings.String);
- VAR insertAt, i : LONGINT; ch : CHAR;
- BEGIN
- ASSERT(string # NIL);
- i := 0; insertAt := 0;
- WHILE (i < LEN(string)) DO
- IF (string[i] = "&") THEN
- IF (i + 3 < LEN(string)) & (string[i+2] = "t") & (string[i+3] = ";") THEN
- IF (string[i+1] = "l") THEN
- ch := "<"; i := i + 4;
- ELSIF (string[i+1] = "g") THEN
- ch := ">"; i := i + 4;
- ELSE
- ch := string[i]; INC(i);
- END;
- ELSIF (i + 4 < LEN(string)) & (string[i+1] = "a") & (string[i+2] = "m") & (string[i+3] = "p") & (string[i+4] = ";") THEN
- ch := "&"; i := i + 5;
- 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
- ch := '"'; i := i + 6;
- 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
- ch := "'"; i := i + 6;
- ELSE
- ch := string[i]; INC(i);
- END;
- ELSE
- ch := string[i]; INC(i);
- END;
- string[insertAt] := ch; INC(insertAt);
- END;
- IF (insertAt < LEN(string)) THEN string[insertAt] := 0X; END;
- END Unescape;
- PROCEDURE NofWildcards(CONST string : ARRAY OF CHAR) : LONGINT;
- VAR nofWildcards, i : LONGINT;
- BEGIN
- nofWildcards := 0;
- i := 0;
- WHILE (i < LEN(string)) & (string[i] # 0X) DO
- IF (string[i] = "?") OR (string[i] = "*") THEN INC(nofWildcards); END;
- INC(i);
- END;
- RETURN nofWildcards;
- END NofWildcards;
- PROCEDURE Equal(CONST s1, s2 : ARRAY OF CHAR; length : LONGINT) : BOOLEAN;
- VAR i : LONGINT;
- BEGIN
- i := 0;
- WHILE (i < length) & (s1[i] = s2[i]) DO INC(i); END;
- RETURN i = length;
- END Equal;
- PROCEDURE Indent(out : Streams.Writer; level : LONGINT);
- VAR i : LONGINT;
- BEGIN
- ASSERT(out # NIL);
- FOR i := 1 TO level DO out.Char(" "); END;
- END Indent;
- (**
- Example:
- open: <!-- entry.region.openLength = 4
- close: --> entry.region.closeLength = 3
- String: < ! - - B L A H - - >
- 0 1 2 3 4 5 6 7 8 9 10
- OpenString: [0, 3]
- Content: [4, 7]
- CloseString: [8, 10]
- *)
- PROCEDURE GetLocation(position : LONGINT; CONST entry : DataEntry) : LONGINT;
- VAR location, closeLength : LONGINT;
- BEGIN
- IF entry.eol THEN closeLength := 0; ELSE closeLength := entry.region.closeLength; END;
- IF (position >= entry.open) THEN
- IF (position <= entry.open + entry.region.openLength - 1) THEN
- location := OpenString;
- ELSIF (position <= entry.close - closeLength) THEN
- location := Content;
- ELSIF (position <= entry.close) THEN
- location := CloseString;
- ELSE
- location := Outside;
- END;
- ELSE
- location := Outside;
- END;
- RETURN location;
- END GetLocation;
- PROCEDURE ParseStyle(
- CONST element : XML.Element; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- style : Style; string : Strings.String;
- styleName : Identifier;
- fontname : ARRAY 128 OF CHAR;
- fontsize, color, bgcolor, voff : LONGINT;
- fontstyle : SET;
- defined : SET;
- res : WORD;
- BEGIN
- ASSERT((element # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlStyle));
- string := element.GetAttributeValue(XmlAttributeName);
- IF (string # NIL) THEN
- COPY(string^, styleName);
- defined := {};
- fontname := "";
- string := element.GetAttributeValue(XmlAttributeFontName);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- INCL(defined, FontName);
- Copy(string^, fontname);
- END;
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Style font name missing");
- END;
- fontsize := 0;
- string := element.GetAttributeValue(XmlAttributeFontSize);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- INCL(defined, FontSize);
- Strings.StrToInt(string^, fontsize);
- END;
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Style attribute font size missing");
- END;
- string := element.GetAttributeValue(XmlAttributeFontStyle);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- INCL(defined, FontStyle);
- Strings.StrToSet(string^, fontstyle);
- END;
- END;
- color := 0;
- string := element.GetAttributeValue(XmlAttributeColor);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- Strings.HexStrToInt(string^, color, res);
- IF (res = Strings.Ok) THEN
- INCL(defined, Color);
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Style attribute color: Invalid value");
- END;
- END;
- ELSE diagnostics.Warning(source, element.GetPos(), "Style attribute color missing");
- END;
- bgcolor := 0;
- string := element.GetAttributeValue(XmlAttributeBgColor);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- Strings.HexStrToInt(string^, bgcolor, res);
- IF (res = Strings.Ok) THEN
- INCL(defined, BgColor);
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Style attribute background color: Invalid value");
- END;
- END;
- ELSE
- INCL(defined, BgColor);
- bgcolor := DefaultBgColor;
- END;
- voff := 0;
- string := element.GetAttributeValue(XmlAttributeVoff);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # XmlDontCare) THEN
- INCL(defined, Voff);
- Strings.StrToInt(string^, voff);
- END;
- ELSE
- INCL(defined, Voff);
- voff := DefaultVoff;
- END;
- NEW(style, styleName, color, bgcolor, voff, fontname, fontsize, fontstyle);
- style.defined := defined;
- styles.Add(style);
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Style name missing");
- END;
- END ParseStyle;
- PROCEDURE ParseStyles(
- CONST element : XML.Element; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
- BEGIN
- ASSERT((element # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlStyles));
- enum := element.GetContents();
- WHILE enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr IS XML.Element) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = XmlStyle) THEN
- ParseStyle(ptr(XML.Element), styles, source, diagnostics, error);
- ELSE
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected style element");
- END;
- END;
- END;
- END ParseStyles;
- PROCEDURE ParseGroup(
- CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
- CONST type : LONGINT;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; ptr : ANY;
- reader : Streams.StringReader;
- token : ARRAY 128 OF CHAR;
- style : Style; res : WORD;
- BEGIN
- ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
- ASSERT((type = TypeWords) OR (type = TypeTokens));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlGroup));
- string := element.GetAttributeValue(XmlAttributeStyle);
- IF (string # NIL) THEN
- style := styles.Find(string^);
- IF (style # NIL) THEN
- ptr := element.GetFirst();
- IF (ptr # NIL) & (ptr IS XML.Chars) THEN
- string := ptr(XML.Chars).GetStr();
- IF (string # NIL) THEN
- Unescape(string);
- NEW(reader, LEN(string^));
- reader.Set(string^);
- reader.SkipWhitespace;
- reader.Token(token);
- WHILE (token # "") & (reader.res = Streams.Ok) DO
- IF (type = TypeWords) THEN
- highlighter.AddWord(token, style, res);
- ELSE
- highlighter.AddToken(token, style, res);
- END;
- IF (res # Ok) THEN
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Token too long");
- END;
- reader.SkipWhitespace;
- reader.Token(token);
- END;
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Empty group (string)");
- END;
- ELSE
- diagnostics.Warning(source, element.GetPos(), "Empty group");
- END;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Could not find style for group...");
- END;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Group name missing");
- END;
- END ParseGroup;
- PROCEDURE ParseTokens(
- CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
- BEGIN
- ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlTokens));
- enum := element.GetContents();
- WHILE enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr IS XML.Element) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = XmlGroup) THEN
- ParseGroup(ptr(XML.Element), highlighter, styles, TypeTokens, source, diagnostics, error);
- ELSE
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected group element");
- END;
- END;
- END;
- END ParseTokens;
- PROCEDURE ParseWords(
- CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
- i : LONGINT;
- BEGIN
- ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlWords));
- enum := element.GetContents();
- WHILE enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr IS XML.Element) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = XmlGroup) THEN
- ParseGroup(ptr(XML.Element), highlighter, styles, TypeWords, source, diagnostics, error);
- ELSE
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected group element");
- END;
- END;
- END;
- string := element.GetAttributeValue(XmlAttributeAllowCharacters);
- IF (string # NIL) THEN
- i := 0;
- WHILE (i < LEN(string)) & (string[i] # 0X) DO
- IF (string[i] > " ") THEN highlighter.AllowCharacter(string[i]); END;
- INC(i);
- END;
- END;
- string := element.GetAttributeValue(XmlAttributeNumberStyle);
- IF (string # NIL) THEN
- highlighter.numberStyle := styles.Find(string^);
- IF (highlighter.numberStyle = NIL) THEN
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Number style not found");
- END;
- END;
- END ParseWords;
- PROCEDURE ParseRegion(
- CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String;
- style : Style;
- styleOpen, styleClose, styleContent : Style;
- open, close : Identifier;
- nesting, multiline : BOOLEAN;
- BEGIN
- ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlRegion));
- styleOpen := NIL; styleClose := NIL; styleContent := NIL;
- string := element.GetAttributeValue(XmlAttributeStyleOpen);
- IF (string # NIL) THEN
- style := styles.Find(string^);
- IF (style # NIL) THEN
- styleOpen := style;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "OpenStyle not found");
- END;
- END;
- string := element.GetAttributeValue(XmlAttributeStyleClose);
- IF (string # NIL) THEN
- style := styles.Find(string^);
- IF (style # NIL) THEN
- styleClose := style;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "CloseStyle not found");
- END;
- END;
- string := element.GetAttributeValue(XmlAttributeStyleContent);
- IF (string # NIL) THEN
- style := styles.Find(string^);
- IF (style # NIL) THEN
- styleContent := style;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "ContentStyle not found");
- END;
- END;
- string := element.GetAttributeValue(XmlAttributeOpen);
- IF (string # NIL) THEN
- Copy(string^, open);
- IF (open = "") THEN
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Region attribute open is empty");
- END;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Region attribute open missing");
- END;
- string := element.GetAttributeValue(XmlAttributeClose);
- IF (string # NIL) THEN
- Copy(string^, close);
- ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute close missing");
- END;
- nesting := FALSE;
- string := element.GetAttributeValue(XmlAttributeNesting);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- Strings.StrToBool(string^, nesting);
- ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute nesting missing");
- END;
- multiline := FALSE;
- string := element.GetAttributeValue(XmlAttributeMultiLine);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- Strings.StrToBool(string^, multiline);
- ELSE diagnostics.Warning(source, element.GetPos(), "Region attribute multiline missing");
- END;
- IF ~error THEN
- highlighter.AddRegion(open, close, nesting, multiline, styleOpen, styleClose, styleContent);
- END;
- END ParseRegion;
- PROCEDURE ParseRegions(
- CONST element : XML.Element; CONST highlighter : Highlighter; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
- BEGIN
- ASSERT((element # NIL) & (highlighter # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlRegions));
- enum := element.GetContents();
- WHILE enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr IS XML.Element) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = XmlRegion) THEN
- ParseRegion(ptr(XML.Element), highlighter, styles, source, diagnostics, error);
- ELSE
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected region element");
- END;
- END;
- END;
- END ParseRegions;
- PROCEDURE ParseHighlighter(
- CONST element : XML.Element; CONST highlighters : Highlighters; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- highlighter : Highlighter; string : Strings.String; tokens, words, regions : XML.Element;
- BEGIN
- ASSERT((element # NIL) & (highlighters # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlHighlighter));
- string := element.GetAttributeValue(XmlAttributeName);
- IF (string # NIL) THEN
- NEW(highlighter, string^);
- highlighters.Add(highlighter);
- string := element.GetAttributeValue(XmlAttributeDefaultStyle);
- IF (string # NIL) THEN
- Strings.TrimWS(string^);
- IF (string^ # "") & (string^ # XmlDontCare) THEN
- highlighter.defaultStyle := styles.Find(string^);
- IF (highlighter.defaultStyle = NIL) THEN
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Default style not found");
- END;
- END;
- END;
- tokens := FindChild(element, XmlTokens);
- IF (tokens # NIL) THEN
- ParseTokens(tokens, highlighter, styles, source, diagnostics, error);
- END;
- words := FindChild(element, XmlWords);
- IF (words # NIL) THEN
- ParseWords(words, highlighter, styles, source, diagnostics, error);
- END;
- regions := FindChild(element, XmlRegions);
- IF (regions # NIL) THEN
- ParseRegions(regions, highlighter, styles, source, diagnostics, error);
- END;
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Highlighter name missing");
- END;
- END ParseHighlighter;
- PROCEDURE ParseHighlighters(
- CONST element : XML.Element; CONST highlighters : Highlighters; CONST styles : Styles;
- CONST source : ARRAY OF CHAR; CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- string : Strings.String; enum : XMLObjects.Enumerator; ptr : ANY;
- BEGIN
- ASSERT((element # NIL) & (highlighters # NIL) & (styles # NIL) & (diagnostics # NIL));
- string := element.GetName();
- ASSERT((string # NIL) & (string^ = XmlHighlighters));
- enum := element.GetContents();
- WHILE enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr IS XML.Element) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = XmlHighlighter) THEN
- ParseHighlighter(ptr(XML.Element), highlighters, styles, source, diagnostics, error);
- ELSE
- diagnostics.Warning(source, ptr(XML.Element).GetPos(), "Expected highlighter element");
- END;
- END;
- END;
- END ParseHighlighters;
- PROCEDURE ParseDocument(
- CONST document : XML.Document;
- CONST source : ARRAY OF CHAR;
- VAR highlighters : Highlighters;
- CONST diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN);
- VAR
- root, element : XML.Element; string : Strings.String;
- styles : Styles;
- BEGIN
- ASSERT((document # NIL) & (diagnostics # NIL));
- root := document.GetRoot();
- string := root.GetName();
- IF (string # NIL) & (string^ = XmlRootElementName) THEN
- NEW(styles);
- element := FindChild(root, XmlStyles);
- IF (element # NIL) THEN
- ParseStyles(element, styles, source, diagnostics, error);
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Styles section missing");
- END;
- NEW(highlighters);
- element := FindChild(root, XmlHighlighters);
- IF (element # NIL) THEN
- ParseHighlighters(element, highlighters, styles, source, diagnostics, error);
- ELSE
- error := TRUE;
- diagnostics.Error(source, element.GetPos(), "Highlighters section missing");
- END;
- ELSE
- error := TRUE;
- diagnostics.Error(source, root.GetPos(), "XML root element name mismatch");
- END;
- END ParseDocument;
- PROCEDURE Parse(CONST filename : ARRAY OF CHAR; diagnostics : Diagnostics.Diagnostics; VAR error : BOOLEAN) : Highlighters;
- VAR document : XML.Document; highlighters : Highlighters;
- BEGIN
- ASSERT(diagnostics # NIL);
- document := LoadDocument(filename, diagnostics, error);
- IF ~error THEN
- NEW(highlighters);
- ParseDocument(document, filename, highlighters, diagnostics, error);
- IF error THEN highlighters := NIL; END;
- ELSE
- highlighters := NIL;
- END;
- RETURN highlighters;
- END Parse;
- PROCEDURE FindChild(parent : XML.Element; CONST childName : ARRAY OF CHAR) : XML.Element;
- VAR child : XML.Element; enum : XMLObjects.Enumerator; ptr : ANY; string : Strings.String;
- BEGIN
- ASSERT(parent # NIL);
- child := NIL;
- enum := parent.GetContents();
- WHILE (child = NIL) & enum.HasMoreElements() DO
- ptr := enum.GetNext();
- IF (ptr # NIL) THEN
- string := ptr(XML.Element).GetName();
- IF (string # NIL) & (string^ = childName) THEN
- child := ptr(XML.Element);
- END;
- END;
- END;
- RETURN child;
- END FindChild;
- PROCEDURE TrapHandler(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
- BEGIN
- error := TRUE;
- diagnostics.Error(source, pos, msg);
- END TrapHandler;
- PROCEDURE LoadDocument(CONST filename : ARRAY OF CHAR; CONST d: Diagnostics.Diagnostics; VAR e : BOOLEAN) : XML.Document;
- VAR file : Files.File; reader : Files.Reader; scanner : XMLScanner.Scanner; parser : XMLParser.Parser; document : XML.Document;
- BEGIN
- ASSERT(d # NIL);
- Copy(filename, source);
- document := NIL;
- file := Files.Old(filename);
- IF (file # NIL) THEN
- NEW(reader, file, 0);
- NEW(scanner, reader);
- NEW(parser, scanner);
- parser.reportError := TrapHandler;
- error := FALSE;
- diagnostics := d;
- document := parser.Parse();
- e := error;
- IF error THEN
- document := NIL;
- END;
- ELSE
- e := TRUE;
- d.Error(filename, Streams.Invalid, "File not found");
- END;
- diagnostics := NIL; source := "";
- ASSERT(error OR (document # NIL));
- RETURN document;
- END LoadDocument;
- PROCEDURE Copy(CONST source : ARRAY OF CHAR; VAR target : ARRAY OF CHAR);
- BEGIN
- Strings.ConcatX(source, "", target);
- END Copy;
- PROCEDURE HighlightText*(text : Texts.Text; highlighter : Highlighter);
- VAR
- state : State; style : Style;
- reader : Texts.TextReader; char32 : Texts.Char32; attributes : Texts.Attributes;
- readerPosition, lastEnd, regionStart, regionEnd : LONGINT;
- BEGIN
- ASSERT((text # NIL) & (highlighter # NIL));
- text.AcquireWrite;
- style := highlighter.GetDefaultStyle();
- IF (style # NIL) & (style.attributes # NIL) THEN
- attributes := style.attributes;
- ELSE
- attributes := Texts.defaultAttributes.Clone();
- END;
- text.SetAttributes(0, text.GetLength(), attributes);
- NEW(reader, text);
- state := highlighter.GetState();
- highlighter.RebuildRegions(reader, state);
- reader.SetPosition(0);
- lastEnd := -1;
- WHILE ~reader.eot DO
- style := NIL;
- readerPosition := reader.GetPosition();
- reader.ReadCh(char32);
- IF (lastEnd < readerPosition) THEN
- style := highlighter.GetRegionStyle(readerPosition, state, regionStart, regionEnd);
- IF (style # NIL) THEN
- lastEnd := regionEnd;
- ELSE
- IF highlighter.IsAllowedCharacter(char32) THEN
- style := highlighter.GetWordStyle(reader, readerPosition, lastEnd);
- END;
- END;
- END;
- IF (style # NIL) THEN
- text.SetAttributes(readerPosition, lastEnd - readerPosition + 1, style.attributes);
- reader.SetPosition(lastEnd);
- END;
- END;
- text.ReleaseWrite;
- END HighlightText;
- PROCEDURE Highlight*(context : Commands.Context); (** filename highlighterName ~ *)
- VAR
- file : Files.File; filename : Files.FileName; highlighterName : Identifier;
- highlighter : Highlighter;
- text : Texts.Text; format: LONGINT; res: WORD;
- BEGIN
- context.arg.SkipWhitespace; context.arg.String(filename);
- context.arg.SkipWhitespace; context.arg.String(highlighterName);
- highlighter := GetHighlighter(highlighterName);
- IF (highlighter # NIL) THEN
- NEW(text);
- TextUtilities.LoadAuto(text, filename, format, res);
- IF (res = 0) THEN
- IF (format = 0) OR (format = 1) THEN (* Oberon rsp. Bluebottle text format *)
- HighlightText(text, highlighter);
- file := Files.Old(filename);
- IF (file # NIL) THEN
- file.GetName(filename);
- CASE format OF
- |0: TextUtilities.StoreOberonText(text, filename, res);
- |1: TextUtilities.StoreText(text, filename, res);
- |2: TextUtilities.ExportUTF8(text, filename, res);
- ELSE
- res := -99; (* file format not known *)
- END;
- IF (res = 0) THEN
- context.out.String("Highlighted file "); context.out.String(filename); context.out.Ln;
- ELSE
- context.error.String("Could not store file "); context.error.String(filename);
- context.error.String(" , res = "); context.error.Int(res, 0); context.error.Ln;
- END;
- ELSE
- context.error.String(filename); context.error.String(": Could not resolve full filename.");
- context.error.Ln;
- END;
- ELSE
- context.error.String(filename); context.error.String(": Unsupported text format.");
- context.error.Ln;
- END;
- ELSE
- context.error.String("Could not open file "); context.error.String(filename);
- context.error.String(", res = "); context.error.Int(res, 0); context.error.Ln;
- END;
- ELSE
- context.error.String("Highligher "); context.error.String(highlighterName);
- context.error.String(" not found."); context.error.Ln;
- END;
- END Highlight;
- PROCEDURE ClearStats*(context : Commands.Context);
- BEGIN
- NnofRebuildRegions := 0; NnofPatchRegions := 0;
- NnofPatchInsert := 0; NnofPatchInsertHit := 0; NnofPiOpenClose := 0; NnofPiNestedFull := 0; NnofPiNestedSimple := 0;
- NnofPiRescan := 0; NnofPiSimple := 0; NnofPiNoHit := 0; NnofPiNoHitRescan := 0; NnofPiNoHitFull := 0;
- context.out.String("SyntaxHighlighter: Statistics cleared."); context.out.Ln;
- END ClearStats;
- PROCEDURE Dump*(context : Commands.Context);
- BEGIN {EXCLUSIVE}
- IF (global_highlighters # NIL) THEN
- global_highlighters.Dump(context.out);
- ELSE
- context.out.String("No highlighters available."); context.out.Ln;
- END;
- END Dump;
- PROCEDURE Open*(context : Commands.Context); (** filename ~ *)
- VAR filename : Files.FileName; diagnostics : Diagnostics.DiagnosticsList; newHighlighters : Highlighters;
- BEGIN {EXCLUSIVE}
- context.arg.SkipWhitespace; context.arg.String(filename);
- NEW(diagnostics);
- newHighlighters := Parse(filename, diagnostics, error);
- IF ~error THEN
- global_highlighters := newHighlighters;
- context.out.String("SyntaxHighlighter: Loaded data from "); context.out.String(filename);
- context.out.Ln;
- END;
- diagnostics.ToStream(context.out, Diagnostics.All);
- END Open;
- BEGIN
- source := "";
- diagnostics := NIL;
- error := FALSE; autoinit := TRUE;
- global_highlighters := NIL;
- traceLevel := Trace_None;
- END SyntaxHighlighter.
- SyntaxHighlighter.Open SyntaxHighlighter.XML ~
- SyntaxHighlighter.Dump ~
- WMPerfMonPluginModVars.Install SyntaxHighlighter
- SyntaxHighlighter.NnofRebuildRegions SyntaxHighlighter.NnofPatchRegions
- SyntaxHighlighter.NnofPatchInsert SyntaxHighlighter.NnofPatchInsertHit SyntaxHighlighter.NnofPiOpenClose
- SyntaxHighlighter.NnofPiNestedFull SyntaxHighlighter.NnofPiNestedSimple,
- SyntaxHighlighter.NnofPiRescan SyntaxHighlighter.NnofPiSimple SyntaxHighlighter.NnofPiNoHit SyntaxHighlighter.NnofPiNoHitRescan
- SyntaxHighlighter.NnofPiNoHitFull
- ~
|