Quite a few languages support negative offsets like that, but not Racket or Scheme (Not even in any of the string SRFIs, I believe). Easy enough to implement, though. Example module:
You could if you wanted to, of course, but I deliberately didn't do any checking for things like out of bounds indexes. Left that to the original substring, which is going to do it anyways.
I like the general idea, but I don't like the ambiguity of 0.
No ambiguity. 0 is not negative and points to the first char in string.
Python-style also allows second index to be very large
>>> "Hello"[0:1000]
'Hello'
So correct implementation should be
(define (normalize-ranges s start end)
(define l (string-length s))
(values
(if (< start 0) (+ l start) (min l start))
(if (< end 0) (+ l end) (min l end))))
$ racket
Welcome to Racket v8.8 [cs].
> (define (normalize-ranges s start end) ; copied from Discourse thread
(define l (string-length s))
(values
(if (< start 0) (+ l start) (min l start))
(if (< end 0) (+ l end) (min l end))))
> (define s "Hello")
> (normalize-ranges s -100 -90)
-95
-85
> (normalize-ranges s -100 3)
-95
3
> (normalize-ranges s 5 3)
substring: ending index is smaller than starting index
ending index: 3
starting index: 5
valid range: [0, 5]
string: "Hello"
[,bt for context]
Compare:
$ python
Python 3.11.4 (main, Jun 7 2023, 00:00:00) [GCC 13.1.1 20230511 (Red Hat 13.1.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> s = "Hello"
>>> s[-100:-90]
''
>>> s[-100:3]
'Hel'
>>> s[5:3]
''