I should say that a slice is an explcit user-declated type in my language.
Regular arrays can be passed, nominally by value (it's normally done
with pointers) but they are fixed size so caller and callee both know
the length.
The language doesn't have a built-in way of representing dynamic arrays;
slices were one alternative to passing separate pointers and lengths.
(The dynamic language handles those of course, but by fairly heavyweight
descriptors.)
>> It's a feature I haven't really used, so it's partly fallen into
>> disuse, but the following example works (s as a 'char* instead of
>> char[] doesn't work):
>>
>> proc testfn(slice[]char s)=
>> println s, s.len, ref void(s.sliceptr)
>> end
>>
>> proc start=
>> static []char s=z"one two three"
>>
>> testfn("hello")
>> testfn(s)
>> testfn(s[5..7])
>> end
>
> Do you allow the callee to modify s.len or even s.sliceptr and can a
> callee /return/ a slice?
I don't think I allow that. But you can extract those components, modify
them and construct a new slice. To change the caller's copy of slice, it
needs to be a reference parameter to an actual slice object (not a
constructed one).
> I think you said somewhere that you return a 2-tuple in two registers
> but I cannot find that comment ATM.
They use the same handling as 128-bit numbers. A 64-bit value is
returned in register D0 (rax), and 128-bit ones in registers D1:D0
(don't know what D1 is in Intel terms, but it'll be one of the volatile
ones).
This program:
function fn(string s,t)string u= # string defined below
return u
end
proc start=
string a,b,c
c:=fn(a,b)
end
Generates this ASM (note that my register optimiser will only put or
keep 64-bit variables in registers; 128-bit ones are a little too
rarified for that):
t.fn:
fn.s = 16
fn.t = 32
fn.u = -16
push Dframe
mov Dframe, Dstack
sub Dstack, 16
mov [Dframe+16], D10
mov [Dframe+24], D11
mov [Dframe+32], D12
mov [Dframe+40], D13
;-------------------------------------------------
mov D0, [Dframe+fn.u]
mov D1, [Dframe+fn.u+8]
;-------------------------------------------------
add Dstack, 16
pop Dframe
ret
t.start:
....
mov D10, [Dframe+start.a]
mov D11, [Dframe+start.a+8]
mov D12, [Dframe+start.b]
mov D13, [Dframe+start.b+8]
call t.fn
mov [Dframe+start.c], D0
mov [Dframe+start.c+8], D1
....
> If slices can be returned should
> they be new objects or should they be restricted to referring to parts
> of existing objects?
Since slices are an explicit type of fixed size (16 bytes) they are
passed and returned by value:
type string = slice[]char # save some typing
function widen(string s)string t=
t:=(s.sliceptr, s.len+1)
return t
end
proc start=
string a:=("ABCDEFGH",3)
println a
println widen(a)
end
This displays ABC then ABCD. It's up to your code to ensure the string
they point to still exists.
I could have used:
string a:="ABC"
too (it will treat that as a:=("ABC", 3)) but my widening example
wouldn't have worked too well! Here's another way of returning a slice:
function left3(string s)string t=
return s[1..3]
end
This could be called as left3("ABCDEFGHI"), and returns "ABC" as a
slice. A bit too trivial though, as you can just do "ABCDEFGHI"[1..3].
A returned slice value could refer to an existing string or slice or
slice of slice etc, but it can refer to a newly allocated string.
At level I implement this, user-code needs to keep track of that.
(My 2-element slices can be used for allocated arrays that do not change
in size. Arrays that grow are more challenging. It could be done by
splitting the 64-bit length into a 32-bit length, and 32-capacity, but
then it also needs language support to check accesses are within bounds,
and to grow the arrays as needed.
A bit like the C++ vector type, but that uses a 196-bit descriptor,
which really needs handling by reference. But I decided that was a
little beyond the remit of my static language.)
>>
>> The first two calls to testfn automatically turn the array (which has
>> a known size, probably why char* didn't work) into a slice object.
>
> I guess char* does not and can not know the length.
It could assume a terminated string (when there are separate char and u8
types), and call strlen to determine the length for the slice.
> Thinking out loud, as it were, are there logically at least two string
> types in common use in programming: one dynamic and of variable-length,
> the other an array of char where for some operations the array size is
> fixed at or by run time?
Yes, there's three (at least):
* Fixed at compile-time
* Set at runtime, but fixed size, once allocated
* Set at runtime, and the size can change
Fixed ones including declarations like this:
[100]int A
[50]int B
Here, either A or B can be passed to a function taking a slice[]int
parameter.
Fixed ones also include short strings forming their own type; I mainly
used these for struct member types.
> A callee which expected a read-only argument to be an array of char
> could probably accept either type of string and it might be clearer how
> a function was going to use a string for it to be declared as array of
> char rather than a string of char.
>
> Many options!
Yeah, too many.