dexed/src/ce_stringrange.pas

344 lines
8.3 KiB
Plaintext

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}
// indicates wether the range ends with value.
function endsWith(const value: string): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
// read-only position
function position: integer;
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.position: integer;
begin
Result := pos;
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;
function TStringRange.endsWith(const value: string): boolean;
begin
if empty then
Result := false
else
Result := ptr[pos .. length(value)-1] = value;
end;
end.