Ranges and Slices

This language is starting to take shape very fast and the "base language" looks like it will be done sooner than later.

Today's questions are mainly syntax but also semantics.

Ranges

A ranged for loop looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
for val in start..<end { // Half-Closed Range
}
// Similar to
for val := start; val < end; val++ {
}

for val in start...end { // Open Range
}
// Similar to
for val := start; val <= end; val++ {
}


These are very useful constructs however, I have very little need for the "open range" and the explicit c-style for loop may be clearer.
My question for this part is, should I allow both (like Swift) or just have the "half-closed range" (like Rust)?

If the only range is half-closed, maybe the operator should just be a double dot `..` (and that would also act as the "ellipsis"), or a colon `:` for ranges (like slice operations currently) (see the Cobra language).


Slicing Operations

Currently, if you want to slice something, the syntax is similar to Go, Python, Cobra, etc:

1
2
3
4
thing[a:b] // a ..< b
thing[a:]  // a ..< thing.count
thing[:b]  // 0 ..< b
thing[:]   // 0 ..< thing.count


This means that all slice operations are half-closed which is syntactically inconsistency with for ranges. If I allow both range types, the syntax may look a little weird to get the same results.

1
2
3
4
5
6
7
8
9
thing[a..<b] // a ..< b
thing[a..<]  // a ..< thing.count
thing[..<b]  // 0 ..< b
thing[..<]   // 0 ..< thing.count

thing[a...b] // a ... b
thing[a...]  // a ... thing.count-1
thing[...b]  // 0 ... b
thing[...]   // 0 ... thing.count-1



Using the `..` range proposed in the previous section, would make it look a bit nicer:

1
2
3
4
thing[a..b] // a ..< b
thing[a..]  // a ..< thing.count
thing[..b]  // 0 ..< b
thing[..]   // 0 ..< thing.count


My question for this part is (dependent on the previous section), what should the slicing syntax be?

Slice Internals

The colon syntax was originally used as slices stored a capacity as well as a count (e.g. `a[low:high:max]`) which meant that slices could be used as a buffer-like type. This was removed when dynamic arrays but this may not be the designed result. I'm starting to think that allowing a slice to have a capacity would be a better ideas as it would follow a "pattern":

1
2
3
4
5
6
Pointer       - data
(Fixed) Array - data + count* 
Slice         - data + count + capacity
Dynamic Array - data + count + capacity + allocator

* count is implicit as it's not stored due to array counts being known at compile time.


And finally the questions for this part: are buffer-like slices are a better construct to have? And what would the slicing syntax be to allow for an optional capacity "field"?

Edited by Ginger Bill on
After a lot of thought:

* I've removed `...` and `..<` and replaced it with `..` which is equivalent to the previous `..<`.
* Slicing uses `..` instead of `:`
* Slices store capacity as well as count and thus slicing can take an optional capacity arugment
* `append` now supports slices now

I think solving the "90% case" is what I need to do and the rest will need specialized stuff.
if you allow floating point literals like "2." or ".0" then the ".." may pose an issue when lexing because "2..5" will lex like "2. .5" instead of "2 .. 5"

That's an issue D had which they fixed by special casing that construct.
I do not allow those type of floating point literals so that's not a problem in this language. This is mainly for consistency and to stop people doing `.2` and `4.` as I personally hate it and it's only one more character to write `0`. It also allows for something like `x.0` if I need it eventually.