mirror of https://gitlab.com/basile.b/dexed.git
426 lines
10 KiB
Plaintext
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.
|
|
|