dexed/src/u_stringrange.pas

426 lines
10 KiB
Plaintext

unit u_stringrange;
{$I u_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).
*)
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}
// when {$DEBUG} is defined this helper assign yield to a local, which can then be inspected
function debug(): PStringRange; {$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}
// continue taking for N steps, i.e following any of the "take" family of functions
function takeMore(value: integer): TStringRange; overload; {$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 until the count of pair defined by the front() and closer is equal to 0, returns a copy.
function takePair(const closer: Char): TStringRange; {$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 count of pair defined by the front() and closer is equal to 0.
function popPair(const closer: Char): PStringRange; {$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
{$IFDEF DEBUG}
var
t1: string;
r1: TStringRange;
{$ENDIF}
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.debug(): PStringRange;
{$IFDEF DEBUG}
var
s: string;
{$ENDIF}
begin
result := @self;
{$IFDEF DEBUG}
s:= yield();
{$ENDIF}
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.takeMore(value: integer): TStringRange;
begin
Result.ptr := ptr;
Result.pos := pos;
Result.len := len + value;
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.takePair(const closer: Char): TStringRange;
var
opener: char;
c: integer = 0;
begin
Result.ptr := ptr + pos;
Result.pos := 0;
Result.len := 0;
opener := front();
while true do
begin
if empty() then
break;
c += Byte(front() = opener);
c -= Byte(front() = closer);
if c = 0 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.popPair(const closer: Char): PStringRange;
var
opener: char;
c: integer = 0;
begin
result := @self;
opener := front();
while true do
begin
if result^.empty() then
break;
c += Byte(result^.front() = opener);
c -= Byte(result^.front() = closer );
if c = 0 then
break;
result := result^.popFront();
end;
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;
{$IFDEF DEBUG}
begin
t1 := '~>0.6.0';
r1.init(t1);
assert(r1.takeUntil(['0'..'9']).yield = '~>');
assert(r1.takeUntil(#0).yield = '0.6.0');
t1 := '>=0.8.0-alpha.1';
r1.init(t1);
assert(r1.takeUntil(['0'..'9']).yield = '>=');
assert(r1.takeUntil(#0).yield = '0.8.0-alpha.1');
{$ENDIF}
end.