unit highlighterDFAs; interface uses DFAs, classes, lexer_interfaces; { colors: "green" "cyan" "bold" "blue" "red" "magenta" } // TODO brace-matching. type THighlighterDFAState = class(TDFAState) private fColorSymbol : string; published property ColorSymbol : string read fColorSymbol write fColorSymbol; end; function LoadJOESyntaxDefintionFile(aSourceFileName : String) : IDFAAutomaton; implementation uses sysutils; type TStringArray = array of String; function Pos(const source : String; SplitAt : Char; startPosition : Integer) : Integer; inline; var i : Integer; begin Result := -1; for i := startPosition to Length(source) do if source[i] = SplitAt then begin Result := i; end; end; // TODO limit. function GreedySplit(const source : String; const SplitAt : Char) : TStringArray; var i : Integer; previous : Integer; item : string; begin SetLength(Result, 0); previous := 0; i := 0; repeat previous := i; i := Pos(source, splitAt, i + 1); if (i = -1) then i := Length(source); if previous = i + 1 then Continue; item := Copy(source, previous + 1, i - previous); SetLength(Result, Length(Result) + 1); Result[High(Result)] := item; until i = Length(source); end; function LoadJOESyntaxDefintionFile(aSourceFileName : String) : IDFAAutomaton; var colorDefinitions : TStringList; // space-separated list of color names ("blue", "bold" etc) dfaState : THighlighterDFAState; // FIXME free (sometimes). stateNames : TStringList; // FIXME free. first : Char; // TODO speed up? function LookupState(const aName : string) : Cardinal; var i : Cardinal; begin Result := StateInvalid; if stateNames.Count > 0 then for i := 0 to stateNames.Count - 1 do if stateNames[i] = aName then begin Result := i; Break; end; end; procedure AddTransition(forInputSpec : string; transition : TDFATransition); var inputChar : Char; endChar : Char; i : Integer; rangeP : Boolean; begin i := 1; while i < Length(forInputSpec) do begin inputChar := forInputSpec[i]; Inc(i); rangeP := (i + 1 < Length(forInputSpec)); if rangeP then rangeP := forInputSpec[i] = '-'; if not rangeP then begin dfaState.Add(inputChar, transition); Inc(i); end else begin // range endChar := forInputSpec[i + 1]; while inputChar <= endChar do begin dfaState.Add(inputChar, transition); Inc(inputChar); end; Inc(i, 2); end; end; end; var items : array of string; key : string; transition : TDFATransition; sourceFile : TStringList; // TODO Free. line : String; lineNumber : Integer; stateIndex : Cardinal; begin dfaState := nil; sourceFile := TStringList.Create; sourceFile.LoadFromFile(aSourceFileName); colorDefinitions := TStringList.Create; stateNames := TStringList.Create; Result := TDFAAutomaton.Create; Result.Add(TDFAState.Create); // "invalid". for lineNumber := 0 to sourceFile.Count - 1 do begin line := sourceFile[lineNumber]; if (Length(line) >= 1) and (line[1] = '#') then // comment. Continue; line := Trim(line); if Length(line) = 0 then // Empty line. Continue; assert(Length(line) > 0); first := line[1]; if first = '=' then begin // color definition. '=Comment bold green', '=Idle' items := GreedySplit(line, ' '); key := items[0]; colorDefinitions.Values[key] := Trim(Copy(line, Length(key) + 1, Length(line))); end; if first = ':' then begin // start new state definition, ':idle Idle' (': ') // finish old state. items := GreedySplit(line, ' '); //TODO if Length(items) < 1 then // FormatError; if Assigned(dfaState) then begin Result.Add(dfaState); end; // start new state. stateIndex := LookupState(items[0]); if stateIndex <> StateInvalid then begin dfaState := THighlighterDFAState(Result.States[stateIndex]); // check whether the state is still unused, if not, throw "duplicate state name" exception. if dfaState.ColorSymbol <> '' then begin // FIXME throw exception "duplicate state name". end; end else begin dfaState := THighlighterDFAState.Create; stateNames.Add(items[0]); // TODO set label IN instance? end; dfaState.ColorSymbol := items[1]; // TODO check bounds. end; if (first = '*') or (first = '"') then begin // TODO remember this fill-up default. // TODO handle recolor: "comment_1 recolor=-1". // TODO handle ident: "\"a-z\" ident buffer" items := GreedySplit(line, ' '); key := items[0]; // TODO check limits. transition := TDFATransition.Create; // TODO delay this until all the state's objects are known. stateIndex := LookupState(key); if stateIndex = StateInvalid then transition.NewState := THighlighterDFAState.Create // this is a workaround since we can't just put a symbol, we use a symbol-like DFA state that will be updated by the actual definition later. else transition.NewState := Result.States[stateIndex]; if first = '*' then dfaState.DefaultTransition := transition else begin assert(items[0][1] = '"'); // TODO bounds check. //assert(items[0][High(items[0])] = '"'); // TODO bounds check. AddTransition(Copy(items[0], 1, Length(items[0]) - 2), transition); end; end; end; if Assigned(dfaState) then begin // XXX always adds an useless state at the end. Result.Add(dfaState); end; end; end.