unit ce_stringrange; {$I ce_defines.inc} interface uses SysUtils; type PStringRange = ^TStringRange; (** * Iterator specialized for strings. * * This structure allows to easily scan strings. * Most of the operations can be chained because the functions * return either a pointer to a TStringRange (in this case this is always * the "Self") or a new TStringRange (in this case this is always a copy). * * This is based on a more generic work which tries to implement some kind * of "D" ranges in Object Pascal (see https://github.com/BBasile/ArrayOps). * Even if Object Pascal doesn't provide the expressivness required to mimic * D ranges, a few good stuff are still possible. *) TStringRange = record private ptr: PChar; pos: integer; len: integer; public // returns a new range initialized with a string. class function create(const str: string): TStringRange; static; // returns a new range initialized from a pointer. class function create(const pchr: PChar; length: integer): TStringRange; static; // initializes the range with a string. function init(const str: string): PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // initialized the range from a pointer. function init(const pchr: PChar; length: integer): PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // advances. function popFront: PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // advances N times function popFrontN(n: integer): PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // returns the current element. function front: char; {$IFNDEF DEBUG}inline;{$ENDIF} // indicates wether the range is consumed. function empty: boolean; {$IFNDEF DEBUG}inline;{$ENDIF} // yields the state of the range to a string. function yield: string; {$IFNDEF DEBUG}inline;{$ENDIF} // returns a copy. function save: TStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // resets the range. function reset: PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range while the front is in value, returns a copy. function takeWhile(value: TSysCharSet): TStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range while the front is equal to value, returns a copy. function takeWhile(value: Char): TStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range until the front is in value, returns a copy. function takeUntil(value: TSysCharSet): TStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range until the front is equal to value, returns a copy. function takeUntil(value: Char): TStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range while the front is in value. function popWhile(value: TSysCharSet): PStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range while the front is equal to value. function popWhile(value: Char): PStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range until the front is in value. function popUntil(value: TSysCharSet): PStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range until the front is equal to value. function popUntil(value: Char): PStringRange; overload; {$IFNDEF DEBUG}inline;{$ENDIF} // advances the range until the beginning of the next line. function popLine: PStringRange; {$IFNDEF DEBUG}inline;{$ENDIF} // returns the next word. function nextWord: string; {$IFNDEF DEBUG}inline;{$ENDIF} // returns the next line. function nextLine: string; {$IFNDEF DEBUG}inline;{$ENDIF} // indicates wether the range starts with value. function startsWith(const value: string): boolean; {$IFNDEF DEBUG}inline;{$ENDIF} // indicates wether the range starts with value. function startsWith(var value: TStringRange): boolean; {$IFNDEF DEBUG}inline;{$ENDIF} end; implementation class function TStringRange.create(const str: string): TStringRange; begin result.ptr := nil; result.pos := 0; result.len := 0; if str = '' then exit; result.ptr := @str[1]; result.pos := 0; result.len := length(str); end; class function TStringRange.create(const pchr: PChar; length: integer): TStringRange; begin result.ptr := pchr; result.pos := 0; result.len := length; end; function TStringRange.init(const str: string): PStringRange; begin Result := @self; ptr := nil; pos := 0; len := 0; if str = '' then exit; ptr := @str[1]; pos := 0; len := length(str); end; function TStringRange.init(const pchr: PChar; length: integer): PStringRange; begin ptr := pchr; pos := 0; len := length; Result := @self; end; function TStringRange.popFront: PStringRange; begin pos += 1; Result := @self; end; function TStringRange.popFrontN(n: integer): PStringRange; begin pos += n; Result := @self; end; function TStringRange.front: char; begin result := (ptr + pos)^; end; function TStringRange.empty: boolean; begin result := pos >= len; end; function TStringRange.yield: string; begin Result := ptr[pos .. len-1]; end; function TStringRange.save: TStringRange; begin Result.len:= len; Result.pos:= pos; Result.ptr:= ptr; end; function TStringRange.reset: PStringRange; begin pos := 0; Result := @Self; end; function TStringRange.takeWhile(value: TSysCharSet): TStringRange; begin Result.ptr := ptr + pos; Result.pos := 0; Result.len := 0; while true do begin if empty or not (front in value) then break; Result.len += 1; popFront; end; end; function TStringRange.takeWhile(value: Char): TStringRange; begin Result.ptr := ptr + pos; Result.pos := 0; Result.len := 0; while true do begin if empty or not (front = value) then break; Result.len += 1; popFront; end; end; function TStringRange.takeUntil(value: TSysCharSet): TStringRange; begin Result.ptr := ptr + pos; Result.pos := 0; Result.len := 0; while true do begin if empty or (front in value) then break; Result.len += 1; popFront; end; end; function TStringRange.takeUntil(value: Char): TStringRange; begin Result.ptr := ptr + pos; Result.pos := 0; Result.len := 0; while true do begin if empty or (front = value) then break; Result.len += 1; popFront; end; end; function TStringRange.popWhile(value: TSysCharSet): PStringRange; begin while true do begin if empty or not (front in value) then break; popFront; end; Result := @self; end; function TStringRange.popWhile(value: Char): PStringRange; begin while true do begin if empty or not (front = value) then break; popFront; end; Result := @self; end; function TStringRange.popUntil(value: TSysCharSet): PStringRange; begin while true do begin if empty or (front in value) then break; popFront; end; Result := @self; end; function TStringRange.popUntil(value: Char): PStringRange; begin while true do begin if empty or (front = value) then break; popFront; end; Result := @self; end; function TStringRange.popLine: PStringRange; begin popUntil(#10); if not empty then popFront; Result := @self; end; function TStringRange.nextWord: string; const blk = [#0 .. #32]; begin Result := popWhile(blk)^.takeUntil(blk).yield; end; function TStringRange.nextLine: string; const lsp = [#10, #13]; begin Result := popWhile(lsp)^.takeUntil(lsp).yield; end; function TStringRange.startsWith(const value: string): boolean; begin Result := false; if len - pos >= length(value) then Result := ptr[pos .. pos + length(value)-1] = value; end; function TStringRange.startsWith(var value: TStringRange): boolean; var p0, p1: integer; begin p0 := pos; p1 := value.pos; Result := true; while not empty and not value.empty do begin if front <> value.front then begin Result := false; break; end; popFront; value.popFront; end; pos := p0; value.pos := p1; end; end.