diff --git a/gazprea/impl/errors.rst b/gazprea/impl/errors.rst index 349a040..bc4476b 100644 --- a/gazprea/impl/errors.rst +++ b/gazprea/impl/errors.rst @@ -177,6 +177,8 @@ Here is an example invalid program and a corresponding compile-time error: ReturnError on line 1: procedure "main" does not have a return statement reachable by all control flows +.. _ssec:errors_sizeErrors: + Run-time Errors --------------- diff --git a/gazprea/spec/built_in_functions.rst b/gazprea/spec/built_in_functions.rst index 3d1fdb0..90728a9 100644 --- a/gazprea/spec/built_in_functions.rst +++ b/gazprea/spec/built_in_functions.rst @@ -14,7 +14,11 @@ If a declaration or a definition with the same name as a built-in function is encountered in a *Gazprea* program, then the compiler should issue an error. Note that although the examples below all use arrays, all the built-ins work -on Vectors and Strings, since they are always compatible with arrays. +on vectors and strings as well, since a vector may be used as an array value of +its current length. When a built-in is applied to a vector it therefore reports +on, or operates over, the length that vector has *at the moment of the call* — +unlike an array, whose length has been fixed since +:ref:`elaboration `. .. _ssec:builtIn_length: @@ -30,6 +34,19 @@ representing the number of elements in the array. length(v) -> std_output; /* Prints 5 */ +Applied to an array, ``length`` is constant for the lifetime of that variable. +Applied to a :ref:`vector ` or :ref:`string ` it +returns the current length and may return different values at different points +in the program; it is equivalent to that vector's ``len`` method. + +:: + + var vector u = 1..5; + + length(u) -> std_output; /* Prints 5 */ + u.push(6); + length(u) -> std_output; /* Prints 6 */ + .. _ssec:builtIn_rows_cols: diff --git a/gazprea/spec/declarations.rst b/gazprea/spec/declarations.rst index a87865d..c51bbaf 100644 --- a/gazprea/spec/declarations.rst +++ b/gazprea/spec/declarations.rst @@ -26,7 +26,14 @@ In *Gazprea* all variables must be initialized in a well defined manner in order to ensure functional purity. If the variables are not initialized to a known value their initial value might change depending on when the program is run. Therefore, the second declaration style is equivalent to setting the value to -zero. +zero. For an :ref:`array ` this means every element of its +(already determined) length is set to the element type's zero; for a +:ref:`vector ` or :ref:`string `, which carries no +length in its type, it means the empty collection. + +Elaborating a declaration is also the moment at which an array's length is +fixed, permanently. See :ref:`sssec:array_sizing` for what that means, and +:ref:`sssec:array_vs_vector` for how it differs from a vector. For simplicity *Gazprea* assumes that declarations can only appear at the beginning of a block. For instance this would not be legal in @@ -60,7 +67,7 @@ illegal to refer to a variable within its own initialization statement. /* All of these declarations are illegal, they would result in garbage values. */ integer i = i; - integer[10] v = v[0] * 2; + integer[10] v = v[1] * 2; An error message should be raised about the use of undeclared variables in these cases. If a variable of the same name is declared in an diff --git a/gazprea/spec/expressions.rst b/gazprea/spec/expressions.rst index bde026d..f66ac8b 100644 --- a/gazprea/spec/expressions.rst +++ b/gazprea/spec/expressions.rst @@ -68,10 +68,18 @@ This additional expression is used to create the generated values. For example: The expression to the right of the bar (``|``), is used to generate the value at the given index. Let ``T`` be the type of the expression to the right of the bar (``|``). Then, -if the domain of the generator is an array of size ``N``, the result will be a -array of size ``N`` with element type ``T``. Otherwise, if the domain of the -generator is a matrix of size ``N`` x ``M``, the result will be a matrix of size -``N`` x ``M`` with element type ``T``. +if the generator has a single domain expression whose array has size ``N``, the +result is an array of size ``N`` with element type ``T``. If it has two domain +expressions whose arrays have sizes ``N`` and ``M``, the result is a matrix of +size ``N`` x ``M`` with element type ``T``. + +A generator always yields an *array* value, never a +:ref:`vector `, and the size of that value is settled when the +generator is evaluated — from the sizes of the domain arrays at that moment. +Using a generator to initialize an array with an inferred size is therefore one +of the ways an array's length is fixed at +:ref:`elaboration `. + Generators may be nested, and may be used within domain expressions. For instance, the generator below is perfectly legal: @@ -91,7 +99,11 @@ Domain Expressions ------------------ Domain expressions consist of an identifier denoting an iterator variable and -an expression that evaluates to **any** array type. +an expression that evaluates to **any** array type. A +:ref:`vector ` or :ref:`string ` may be used as the +domain expression, in which case its length at the moment the domain is +evaluated determines the number of iterations; growing the vector inside the +body does not add iterations, for the same reason given below. Domain expressions can only appear within iterator loops and generators. A domain expression is a way of declaring a variable that is local to the loop or generator, that takes on values from @@ -127,7 +139,7 @@ commas, such as in matrix generators. /* The "i"s both domain expressions are at the same scope, which is * the one enclosing the loop. Therefore the matrix is: [[0 0 0] [0 1 2] [0 2 4]] */ - integer[3,3] mat = [ i in 0..i, j in 0..i | i*j ]; + integer[3][3] mat = [ i in 0..i, j in 0..i | i*j ]; The domain for the domain expression is only evaluated once. For instance: diff --git a/gazprea/spec/functions.rst b/gazprea/spec/functions.rst index 5cc1403..0d9209b 100644 --- a/gazprea/spec/functions.rst +++ b/gazprea/spec/functions.rst @@ -164,41 +164,60 @@ do not have to match the argument names in the function definition. .. _ssec:function_vec_mat: -Array and Matrix Parameters and Returns ----------------------------------------- +Array, Vector and Matrix Parameters and Returns +----------------------------------------------- The arguments and return value of functions can have both explicit and inferred sizes. For example: :: - function to_real_vec(integer[*] x) returns real[*] { + function to_real_vec(integer[*] x) returns vector { /* Some code here */ } - function transpose3x3(real[3,3] x) returns real[3,3] { + function transpose3x3(real[3][3] x) returns real[3][3] { /* Some code here */ } +An array parameter written with an explicit size, such as ``real[3][3]``, is +part of the signature: the argument's length must match at the call, or a +``SizeError`` is raised. An array parameter written with an inferred size, such +as ``integer[*]``, is elaborated at the call — it takes the length of the +argument that is actually passed, and that length is then fixed for the +duration of the call. An inferred-size *return* type works the same way and is +elaborated at the ``return``, from the length of the value being returned. + +A ``vector`` parameter or return type carries no length at all, so no length +check applies to it in either direction. Like Rust, array *slices* may be passed as arguments: :: - function to_real_vec(integer[*] x) returns real[*] { - real[*] rvec = x; - return rvec; + function to_real_vec(integer[*] x) returns vector { + real[*] rvec = x; /* elaborated to the length of 'x' */ + return rvec; /* array value becomes the returned vector */ } function slicer() returns real[*] { integer[10] a = 1..10; - var vector two_halves = to_real_vec(a[1..5]); + var vector two_halves = to_real_vec(a[1..6]); two_halves.append(to_real_vec(a[6..])); + + /* 'two_halves' is a length-10 vector here, so the inferred-size + return type is elaborated to a length-10 array. */ return two_halves; } +Both directions of the array/vector conversion appear above, and neither one +changes the sizing discipline of the variables involved. ``rvec`` is an array +and its length is fixed the moment it is declared; ``two_halves`` is a vector +and its length grows with ``append``. Converting between them copies a value — +it does not make ``rvec`` growable or pin ``two_halves`` to a length. + Remember that all function parameters are ``const`` in *Gazprea*, so that all functions are pure. That means that while it is legal to pass arrays and slices -*be reference*, the array contents cannot be modified inside the function, +*by reference*, the array contents cannot be modified inside the function, because the change would be visible outside the function. You must check that the ``const`` requirement is honored. diff --git a/gazprea/spec/globals.rst b/gazprea/spec/globals.rst index ee294a4..11421bc 100644 --- a/gazprea/spec/globals.rst +++ b/gazprea/spec/globals.rst @@ -15,7 +15,7 @@ All global statements are considered declarations. Global statements may occur in any order, given respective symbols are defined before being referenced. Variable Declarations -===================== +--------------------- In *Gazprea* values can be assigned to a global identifier. All globals must be immutable (``const``). If a global identifier is declared with @@ -31,4 +31,18 @@ evaluate variables and functions at compile time. Global expression evaluation c be deferred to runtime, but that has the disadvantage of changing errors from compile time to run time. +For the same reason, a global :ref:`array ` must state its size +explicitly, and that size expression is subject to the same restriction as a +:ref:`typealias ` size: it must be composed exclusively of +arithmetic on scalar literals, so that the array's length is settled at compile +time rather than at some runtime elaboration point. An inferred size (``[*]``) +is not available at global scope, because the scalar literal on the RHS carries +no length to infer from. + +:: + + const integer[4] g = 0; /* legal -- [0, 0, 0, 0] */ + const integer[2 + 2] h = 1; /* legal -- constant folded to 4 */ + const integer[*] i = 0; /* illegal -- nothing to infer a length from */ + diff --git a/gazprea/spec/procedures.rst b/gazprea/spec/procedures.rst index af95cdb..6e0f05d 100644 --- a/gazprea/spec/procedures.rst +++ b/gazprea/spec/procedures.rst @@ -69,9 +69,9 @@ These procedures can be called as follows: :: - integer x = 12; - integer y = 21; - integer[5] v = 13; + var integer x = 12; + var integer y = 21; + var integer[5] v = 13; call change_first(v); /* v == [7, 13, 13, 13, 13] */ call increment(x); /* x == 13 */ @@ -151,11 +151,11 @@ call by reference, and are therefore *l-values* (pointers). :: - procedure byvalue(String x) returns integer { - return len(x); + procedure byvalue(string x) returns integer { + return length(x); } - procedure byreference(var String x) returns integer { - return len(x); + procedure byreference(var string x) returns integer { + return length(x); } procedure main() returns integer { const character[3] y = ['y', 'e', 's']; @@ -166,6 +166,13 @@ call by reference, and are therefore *l-values* (pointers). return 0; } +The call to ``byvalue`` promotes the ``character[3]`` array to a ``string``, +which is a :ref:`vector ` of ``character``. This is a conversion +of the argument's *value*: the parameter ``x`` is a runtime-sized string, while +``y`` remains a length-3 array in the caller. The call to ``byreference`` is +illegal because a ``var`` parameter is call by reference and so admits no +promotion — and separately because ``y`` is ``const``. + Aliasing -------- @@ -228,19 +235,39 @@ aliasing. .. _ssec:procedure_vec_mat: -Array Parameters and Returns ----------------------------------------- +Array and Vector Parameters and Returns +--------------------------------------- :ref:`As with functions `, the arguments and return -value of procedures can have both explicit and inferred sizes. +value of procedures can have both explicit and inferred sizes, with the same +rule: an explicit size must be matched by the argument, and an inferred size +(``[*]``) is elaborated at the call from the argument that is passed. Similarly, slices can be used whereever arrays are declared as parameters, and unlike functions, array parameters in procedures can be ``var``. -.. _ssec:function_namespacing: +A ``var`` array parameter is mutable in its *contents* only. Because an array +is :ref:`elaboration-time sized `, a procedure cannot +change the length of an array it was passed, no matter how the parameter is +qualified — assigning a longer value to it raises a ``SizeError`` just as it +would at the call site. A ``var vector`` parameter, by contrast, may be +grown with ``push`` and ``append``, and the caller observes the new length. + +:: + + procedure fill(var integer[*] a) { + a = 1; /* legal: every element becomes 1 */ + a = a || a; /* SizeError: 'a' cannot double in length */ + } + + procedure extend(var vector v) { + v.push(1); /* legal: caller's vector is now one longer */ + } + +.. _ssec:procedure_namespacing: Procedure Namespacing --------------------- +--------------------- In *Gazprea* procedure declarations occur in the global scope. This means that two procedures with the same name cannot coexist in the same diff --git a/gazprea/spec/statements.rst b/gazprea/spec/statements.rst index 9116a7d..6e34a3c 100644 --- a/gazprea/spec/statements.rst +++ b/gazprea/spec/statements.rst @@ -56,6 +56,31 @@ For instance, with single dimensional arrays: /* Change 'v' to [1, 0, 3] */ v[2] = 0; +Assigning a whole array value changes the array's *contents*, never its +*length*. An array is :ref:`elaboration-time sized `, so by +the time any assignment to it executes its length is already fixed. The RHS is +therefore fitted to the LHS using the same rules as initialization: a shorter +value is padded with the element type's zero, and a longer value raises a +``SizeError``. + +:: + + var integer[*] v = [0, 0, 0]; /* length fixed at 3 */ + + v = [1, 2]; /* v == [1, 2, 0] -- padded */ + v = [1, 2, 3, 4]; /* SizeError -- v cannot grow */ + +Assignment to a :ref:`vector ` behaves differently, because a +vector is runtime sized: the assignment replaces the vector's contents and its +length together, so neither padding nor a ``SizeError`` arises. + +:: + + var vector u = [0, 0, 0]; + + u = [1, 2]; /* u == [1, 2] -- length is now 2 */ + u = [1, 2, 3, 4]; /* u == [1, 2, 3, 4] -- length is now 4 */ + This applies to arrays of any dimension. :: @@ -332,7 +357,8 @@ semicolon. Iterator Loop ~~~~~~~~~~~~~ -Loops can be used to iterate over the elements of an array of any type. +Loops can be used to iterate over the elements of an array of any type, and +equally over a :ref:`vector ` or :ref:`string `. This is done by using domain expressions (for instance ``i in v``) in conjunction with a loop statement. @@ -374,6 +400,22 @@ defines a constant domain variable from it's respective index. For instance: i -> std_output; "\n" -> std_output; } +The same holds for a :ref:`vector ` or +:ref:`string ` domain: the domain is evaluated once, so the +iteration count is the vector's length at that moment. Growing the vector in +the loop body does not lengthen the loop. + +:: + + var vector v = [1, 2, 3]; + + /* Prints 1, 2, 3 -- three iterations, fixed when the domain was + evaluated, even though 'v' ends up with six elements. */ + loop i in v { + v.push(i); + i -> std_output; "\n" -> std_output; + } + Note that multiple domain expressions are *not* allowed: :: diff --git a/gazprea/spec/streams.rst b/gazprea/spec/streams.rst index 610ca35..7ce771c 100644 --- a/gazprea/spec/streams.rst +++ b/gazprea/spec/streams.rst @@ -51,6 +51,10 @@ prints the following: [1 2 3] +:ref:`Vectors ` print exactly as arrays do, using whatever length +they hold at the time of the output statement. ``string`` is the sole +exception, described next. + :ref:`strings ` print their contents as a contiguous sequence of characters. For example: diff --git a/gazprea/spec/type_casting.rst b/gazprea/spec/type_casting.rst index bddd522..3805ff9 100644 --- a/gazprea/spec/type_casting.rst +++ b/gazprea/spec/type_casting.rst @@ -84,9 +84,44 @@ size is assumed to be the old size. For example: // Truncate the array. real[2] y = as(v); +Note that a cast is the one place where a *shortening* is silent. Assigning a +too-long array value into an array raises a ``SizeError``; casting it to a +shorter array type truncates instead. The size named in the cast belongs to +the cast expression's result, and says nothing about the variable it is +eventually stored into — that variable's own length was already fixed at +:ref:`elaboration `, and the cast result still has to fit +it. + +:: + + real[3] v = [1.0, 2.0, 3.0]; + var real[3] w = 0.0; + + w = as(v); /* cast truncates to [1.0, 2.0], then pads back to + [1.0, 2.0, 0.0] to fit 'w' */ + Casting non-variable empty arrays ``[]`` is not allowed, because a literal empty array does not have a type. +.. _ssec:typeCasting_vec: + +Array and Vector +---------------- + +A ``vector`` may appear as the operand of an array cast and as the destination +type of a cast. When a vector is the operand, its current length is used. When +``vector`` is the destination type, no size may be given — vectors are +runtime sized and take the length of the value being cast, so there is nothing +to pad or truncate against. + +:: + + var vector v; + v.append([1, 2, 3]); + + real[2] a = as(v); // truncates to [1.0, 2.0] + var vector u = as>(v); // [1.0, 2.0, 3.0] + .. _ssec:typeCasting_mtom: Multi-dimensional Arrays diff --git a/gazprea/spec/type_promotion.rst b/gazprea/spec/type_promotion.rst index c5a6325..263a897 100644 --- a/gazprea/spec/type_promotion.rst +++ b/gazprea/spec/type_promotion.rst @@ -117,10 +117,50 @@ It is possible for a two sided promotion to occur with tuples. For example: boolean b = (1.0, 2) == (2, 3.0); +.. _ssec:typePromotion_avv: + +Array to/from Vector +-------------------- + +An array value may be implicitly converted to a ``vector`` of a compatible +element type, and a vector may be implicitly converted to an array value +(two-way type promotion). Element types are promoted according to +:ref:`ssec:typePromotion_scalar`. + +This promotion converts *values*; it never changes how either side is sized. +The two directions are consequently not symmetric: + +- **Vector to array.** The vector's *current* length is used to produce the + array value. Whether that value then fits depends on the array on the + receiving side, which was already :ref:`sized at elaboration + `: a shorter value is zero padded, a longer one raises + a ``SizeError``. When the receiving array's size is inferred (``[*]``) and + this promotion *is* its elaboration, the vector's current length becomes the + array's permanent length. + +- **Array to vector.** The array's fixed length is used to produce the vector + value, and the receiving vector simply takes that length on. No padding or + ``SizeError`` occurs, and the vector remains free to grow afterwards. + +:: + + var vector v; + v.append([1, 2, 3]); + + integer[*] a = v; /* elaborates 'a' to length 3, permanently */ + integer[5] b = v; /* b == [1, 2, 3, 0, 0] -- padded */ + integer[2] c = v; /* SizeError */ + + var vector u = a; /* u has length 3, and may still grow */ + u.push(4.0); /* u has length 4; 'a' is unaffected */ + Character Array to/from String ------------------------------- -A ``string`` can be implicitly converted to a vector of ``character``\ s and vice-versa (two-way type promotion). +Because a ``string`` is a ``vector`` of ``character``, this is the +:ref:`array/vector promotion ` above specialised to +``character``. +A ``string`` can be implicitly converted to an array of ``character``\ s and vice-versa (two-way type promotion). :: diff --git a/gazprea/spec/types/array.rst b/gazprea/spec/types/array.rst index a0ad9db..1b67da1 100644 --- a/gazprea/spec/types/array.rst +++ b/gazprea/spec/types/array.rst @@ -4,8 +4,41 @@ Arrays ------- Arrays are fixed size collections, where each element of the array has the -same type. Arrays can contain any of *Gazprea*'s base types (``boolean``, -``integer``, ``real``, and ``character``). +same type. The element type of an array may be any of *Gazprea*'s base types +(``boolean``, ``integer``, ``real``, and ``character``), or another array of a +base type, which yields a :ref:`matrix `. + +.. _sssec:array_sizing: + +Sizing +~~~~~~ + +Arrays are **elaboration-time sized**. The length of an array variable is +determined exactly *once*, when its declaration is elaborated — that is, on the +first runtime effect of that array's accessor or assignment — and from that +point on the length is frozen for the entire lifetime of the variable. + +Elaboration-time is not the same as compile-time. The size of an array may be +given by an arbitrary integer expression, so a length is not required to be a +compile-time constant; it is only required to be *settled* by the time the +array is first accessed or assigned, and to never change afterwards. Concretely: + +- A declaration such as ``integer[n] v;`` evaluates ``n`` once, at + elaboration. Later changes to ``n`` have no effect on the length of ``v``. + +- A declaration such as ``integer[*] v = ;`` takes its length from the + value of ```` at elaboration. The ``*`` means "infer this length once, + here"; it does **not** mean the array is resizable. + +- No subsequent operation can change the length of an array variable. + Assignment, concatenation, and casting all produce array *values*; storing + such a value into an array variable never resizes that variable. If the + value's length does not match, it is zero padded or a ``SizeError`` is + raised, as described in the sections below. + +If you need a collection whose length changes as the program runs, use a +:ref:`vector `, which is runtime sized. See +:ref:`sssec:array_vs_vector`. .. _sssec:array_decl: @@ -38,7 +71,15 @@ array instead of a ``real`` array. The size of the array is given by the integer expression between the - square brackets. + square brackets. That expression is evaluated exactly once, when the + declaration is elaborated, and its value becomes the permanent length of + the array: + + :: + + var integer n = 3; + var integer[n] v = 0; /* 'v' has length 3, now and forever */ + n = 100; /* 'v' still has length 3 */ If the array is given a scalar value (``type-expr``) of the same element type then the scalar value is duplicated for every single element of the array. @@ -51,6 +92,11 @@ array instead of a ``real`` array. compile-time or run-time. Check the :ref:`ssec:errors_sizeErrors` section to know when you should throw the error. + Note that the LHS length wins in both directions: the array is *not* + shortened to the RHS length when the RHS is smaller, and it is *not* grown + to the RHS length when the RHS is larger. This is what it means for the + length to be fixed at elaboration. + #. Inferred Size Declarations If an array is assigned an initial value when it is declared, then @@ -63,6 +109,17 @@ array instead of a ``real`` array. [*] = ; + The inferred length is still an elaboration-time length. ``[*]`` only moves + the choice of length from the declaration to the initializing expression; + once elaboration has happened the array is as fixed in length as one + declared with an explicit size: + + :: + + var integer[*] v = [1, 2, 3]; /* 'v' has length 3, now and forever */ + v = [4, 5]; /* 'v' == [4, 5, 0] -- padded, not shrunk */ + v = [4, 5, 6, 7]; /* SizeError -- 'v' cannot grow */ + #. Inferred Type and Size It is also possible to declare an array with an implied type and @@ -79,7 +136,9 @@ array instead of a ``real`` array. In this example the compiler can infer both the size and the type of ``w`` from ``v``. The size may not always be known at compile time, so this - may need to be handled during runtime. + may need to be handled during runtime; what matters is that the size is + resolved when the declaration of ``w`` is elaborated and does not change + thereafter. .. _sssec:array_constr: @@ -118,6 +177,47 @@ method of construction. real[*] v = []; /* Should create an empty array */ +Because the length of an array is fixed at elaboration, such an array has a +length of zero permanently; it is not an "empty, growable" array. A +:ref:`vector ` declared without an initializer also starts empty, +but *can* subsequently grow. + +.. _sssec:array_vs_vector: + +Arrays Versus Vectors +~~~~~~~~~~~~~~~~~~~~~ + +*Gazprea* has two collection types that share the same element-wise operations +but differ in exactly one respect — when their length is decided: + ++--------------------------+-------------------------------+-------------------------------+ +| | **Array** (``T[N]``, | **Vector** (``vector``, | +| | ``T[*]``) | ``string``) | ++==========================+===============================+===============================+ +| When is the length set? | Once, at elaboration | Continuously, at runtime | ++--------------------------+-------------------------------+-------------------------------+ +| Can the length change | No | Yes | +| after that? | | | ++--------------------------+-------------------------------+-------------------------------+ +| Written in the type? | Yes (``[N]``), or inferred | No | +| | once (``[*]``) | | ++--------------------------+-------------------------------+-------------------------------+ +| Grows via ``push`` / | No — ``SizeError`` | Yes | +| ``append``? | | | ++--------------------------+-------------------------------+-------------------------------+ +| Too-short value assigned | Zero padded to the array's | Vector takes the value's | +| into it | fixed length | length | ++--------------------------+-------------------------------+-------------------------------+ +| Too-long value assigned | ``SizeError`` | Vector takes the value's | +| into it | | length | ++--------------------------+-------------------------------+-------------------------------+ + +The two types interoperate, but only through *values*: a vector used in an +array context yields an array value of the vector's current length, and an +array value assigned into a vector sets that vector's length. Neither direction +ever makes an array variable resizable. See :ref:`ssec:vector` for the details +of that interoperation. + .. _sssec:array_ops: Operations @@ -137,7 +237,10 @@ Operations In this case ``numElements`` would be 3, since the array ``v`` - contains 3 elements. + contains 3 elements. For an array, ``length`` is invariant after + elaboration: it will report the same value for every call on the same + variable. For a :ref:`vector ` it reports the current + length, which may differ between calls. b. Concatenation @@ -183,15 +286,28 @@ Operations integer[3] v = 1 || 2 || 3; // produces [1, 2, 3] + Concatenation produces a new array *value* whose length is the sum of + the lengths of its operands. It does not extend either operand. Remember that arrays have a fixed length, which means you cannot grow an array by concatenating elements to the end: :: - var integer[*] growme = [0]; // length is now 1 + var integer[*] growme = [0]; // elaborated with length 1; fixed at 1 var integer i = 1; loop while (i < 10) { - growme = growme || i; // illegal: SizeError + growme = growme || i; // SizeError: a length-2 value into a length-1 array + i = i + 1; + } + + Use a :ref:`vector ` when the collection has to grow: + + :: + + var vector growme = 0; // [0] + var integer i = 1; + loop while (i < 10) { + growme.push(i); // fine: vectors are runtime sized i = i + 1; } @@ -251,6 +367,18 @@ Operations Therefore, it is *valid* to have bounds that will produce an empty array because the difference between them is negative. + A range yields an array *value*, whose length is settled when the range + expression is evaluated. Using a range to initialize an array variable + with an inferred size is therefore one of the ways an array's length + gets fixed at elaboration: + + :: + + var integer i = 3; + var integer[*] v = 1..i; /* 'v' is elaborated with length 3 */ + i = 10; + v = 1..i; /* SizeError: 'v' is still length 3 */ + d. Indexing An array may be indexed in order to retrieve the values stored in @@ -303,24 +431,30 @@ Operations For indexing purposes three additions are made to range syntax: +---------+---------------------------------+ - | | Interpretation | - +---------+---------------------------------+ - + `..` | all elements | + | Syntax | Interpretation | + +=========+=================================+ + | `..` | all elements | +---------+---------------------------------+ - + `i..` | ith to nth elements | + | `i..` | ith to nth elements | +---------+---------------------------------+ - + `..-i` | first to n-i-1th elements | + | `..-i` | first to n-i-1th elements | +---------+---------------------------------+ - + `i..j` | i to jth elements | + | `i..j` | i to jth elements | +---------+---------------------------------+ + Examples: :: integer[*] a = 0..10 by 2; /* a = [0, 2, 4, 6, 8, 10] */ - integer x = a[..4]; /* x == [0, 2, 4] */ - integer y = a[4..]; /* x == [6, 8, 10] */ - integer z = a[..-1]; /* x == [0, 2, 4, 6, 8] */ + integer[*] x = a[..4]; /* x == [0, 2, 4] */ + integer[*] y = a[4..]; /* y == [6, 8, 10] */ + integer[*] z = a[..-1]; /* z == [0, 2, 4, 6, 8] */ + + A slice is an array *value*, not a view that tracks the sliced array. + Its length is determined when the slice expression is evaluated. Slicing + a :ref:`vector ` is the standard way to obtain an array + value from a runtime-sized collection. #. Operations of the Element Type diff --git a/gazprea/spec/types/matrix.rst b/gazprea/spec/types/matrix.rst index 00ac607..7f82ce0 100644 --- a/gazprea/spec/types/matrix.rst +++ b/gazprea/spec/types/matrix.rst @@ -13,8 +13,11 @@ Declaration ~~~~~~~~~~~ Matrix declarations are similar to array declarations, the difference -being that matrices have two dimensions instead of one. The following are -valid matrix declarations: +being that matrices have two dimensions instead of one. Both dimensions are +:ref:`sized at elaboration ` and fixed thereafter, and +``[*]`` in either position means "infer this dimension once, here" — a matrix +is no more resizable than a one dimensional array. There is no two dimensional +counterpart to ``vector``. The following are valid matrix declarations: :: @@ -102,7 +105,7 @@ matrix respectively. For instance: Matrix indexing is done similarly to array indexing, however, two indices must be used. Because matrices are arrays of arrays the indexing is -coposite: +composite: :: @@ -117,7 +120,7 @@ and column. Both the row and column indices must be integers. integer[*][*] M = [[11, 12, 13], [21, 22, 23]]; - /* M[1, 2] == 12 */ + /* M[1][2] == 12 */ As with arrays, out of bounds indexing is an error on Matrices. diff --git a/gazprea/spec/types/string.rst b/gazprea/spec/types/string.rst index 664f9b7..8fddbef 100644 --- a/gazprea/spec/types/string.rst +++ b/gazprea/spec/types/string.rst @@ -5,8 +5,12 @@ String A ``string`` is another object within *Gazprea*. Fundamentally, a ``string`` is a ``vector`` of ``character``. -This means that, like a vector, a string behaves like a dynamically sized array, -but because it is an object *Gazprea* can provide type specific features. +This means a string is **runtime sized** in exactly the way a +:ref:`vector ` is — its length is never part of its type and +changes as it is assigned to, pushed to, or appended to — but because it is an +object *Gazprea* can provide type specific features. +A ``character[N]`` array is the fixed-length counterpart: its length is settled +at elaboration and stays there. String vectors behave a lot like character arrays, but there are several differences between the two types: @@ -21,7 +25,8 @@ Declaration A string may be declared with the keyword ``string``. The same rules of :ref:`vector declarations ` also apply to strings, which means -that all lenghts are inferred: +that no length may be written in the type and the length is always whatever the +string currently holds: :: @@ -48,7 +53,7 @@ differently by the compiler: character[*] carray = ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\n']; string vec = carray; - carry -> std_output; + carray -> std_output; vec -> std_output; prints: @@ -73,13 +78,15 @@ two types. You may also append a slice of characters to a string using the append method. As well, a scalar character may be concatenated onto a string in the same way as it would be concatenated onto an array of characters. -Note that because a ``string`` is a sub-type of ``vector``, concatenation may also -be accomplished with ``concat`` and ``push`` methods: +Note the difference between the two spellings. ``||`` is an operator: it +produces a new value and leaves its operands alone, which is why it is the only +option for arrays. Because a ``string`` is a sub-type of ``vector``, a string +can instead be *grown in place* with the ``append`` and ``push`` methods: :: var string letters = ['a', 'b'] || "cd"; - letters.concat("ef"); + letters.append("ef"); letters.push('g'); letters -> std_output; diff --git a/gazprea/spec/types/struct.rst b/gazprea/spec/types/struct.rst index 5a0a424..d570cc7 100644 --- a/gazprea/spec/types/struct.rst +++ b/gazprea/spec/types/struct.rst @@ -139,7 +139,7 @@ A struct itself cannot be cast or promoted. However, the fields within a struct can be individually cast/promoted, as described in sections :ref:`sec:typeCasting` and :ref:`sec:typePromotion`. -.. _ssec:function_namespacing: +.. _ssec:struct_namespacing: Struct Namespacing -------------------- diff --git a/gazprea/spec/types/vector.rst b/gazprea/spec/types/vector.rst index ff42f8a..0904bb6 100644 --- a/gazprea/spec/types/vector.rst +++ b/gazprea/spec/types/vector.rst @@ -3,11 +3,29 @@ Vectors ------- -Vectors are language supported objects that allow for dynamically sized arrays. -Once created, ``vectors`` in *Gazprea* behave exactly like arrays: they can be -intermixed with arrays in expressions; they can be used on the RHS of array -declarations and initializations; and they can be passed as array arguments to -subroutines and functions. +Vectors are language supported objects that provide a **runtime sized** +collection. Unlike an :ref:`array `, whose length is fixed once at +elaboration and never changes, the length of a vector is a property of its +current value: it is established when the vector is assigned to and it changes +whenever the vector is grown with ``push`` or ``append``. + +A vector participates in array expressions by *yielding an array value* of its +current length — this is what the spec means when it says a vector can be used +as, or sliced into, an array. Specifically, a vector may be: + +- used in expressions wherever an array of its current length and element type + would be legal; + +- used on the RHS of an array declaration, initialization, or assignment, + subject to the usual :ref:`padding and SizeError rules ` + for the fixed length of the array on the LHS; + +- passed as an array argument to a function or procedure. + +What a vector never does is make an array resizable, and what an array context +never does is fix a vector's length. The conversion is between *values*, in one +direction at a time; see :ref:`sssec:array_vs_vector` for the side-by-side +comparison. .. _sssec:vec_decl: @@ -25,10 +43,12 @@ the literals ``<`` and ``>`` are used in the declaration) vector<|type|> |identifier| = |type-array|; -Unlike the array type, *Gazprea* vectors do not have an explicit size -specifier, often called *capacity* in other languages. Below are some examples of -`vector` declarations. - +A vector type never carries a length: there is no ``vector[N]``, and +the ``[N]`` / ``[*]`` notation of arrays has no vector counterpart. The number +of elements a vector holds is not part of its type at all, which is precisely +why it is free to change at runtime. Below are some examples of ``vector`` +declarations. + :: const vector v1 = 3; // [[3, 3]] @@ -36,8 +56,21 @@ specifier, often called *capacity* in other languages. Below are some examples o const vector v3 = 42; // [42] const vector v4 = 1; // [1.0] +A vector declared without an initializer starts empty, rather than being +zero-filled to some length the way an array is. This is the vector analogue of +the rule in :ref:`sec:declaration` that an uninitialized variable takes its +zero value; for a runtime-sized collection the zero value is the empty vector. -Vectors of inferred sized arrays assume the size of the *first* array in the vector. + :: + + var vector v; // [] -- length 0, but free to grow + var integer[3] a; // [0, 0, 0] -- length 3, forever + +Note that the *element type* of a vector is still subject to array sizing +rules. Vectors of inferred sized arrays assume the size of the *first* array in +the vector, and that element size is then fixed for the lifetime of the vector +exactly as an array's own length would be — only the number of elements in the +vector remains free to change. Subsequent array elements of less than the inferred size are padded. Those greater raise a runtime ``SizeError``. @@ -45,7 +78,7 @@ Those greater raise a runtime ``SizeError``. const vector vec = ['a', 'b', 'c']; const vector ragged_right = [[1.0], [2.0, 2.0]]; // SizeError - const vector paddeded_right = [[1.0, 2.0], [1.0]]; // Padds second element + const vector padded_right = [[1.0, 2.0], [1.0]]; // Pads second element const vector const_vec = vec; @@ -53,21 +86,37 @@ Operations ~~~~~~~~~~~ Operations on vectors are identical syntactically and semantically to -operations on arrays. In particular, operand lengths must match for binary -expressions and dot product. Vectors can behave as arrays by using slices: +operations on arrays: in an expression, a vector is used as an array value of +its current length. In particular, operand lengths must match for binary +expressions and dot product, and the result of such an expression is an +*array*, not a vector — the vector-ness is not propagated through operators. +The lengths involved are the lengths the vectors happen to have when the +expression is evaluated: :: - var vector v1, v2; - var integer[3] a; - v1.append([1, 2, 3]); - a = v1; // slice of v yields array and can be used to initialize 'a' - v2 = v1 + a; // slice of vector plus array yields result type array - a = v1 + v2; // slice of v1 + slice of v2 still yields array type + var vector v1; + var vector v2; + var integer[3] a; // fixed at length 3 by this declaration + + v1.append([1, 2, 3]); // v1 currently has length 3 + a = v1; // legal: v1 yields a length-3 array value + v2 = v1 + a; // array result; v2 takes on length 3 + a = v1 + v2; // array result of length 3, matches 'a' + v1.push(4); // v1 now has length 4 -- vectors are runtime sized + a = v1; // SizeError: 'a' is still length 3 + v2 = v1; // fine: v2 takes on length 4 + +Assigning to an array and assigning to a vector are therefore *not* +symmetrical. Assigning into the array ``a`` must respect the length ``a`` was +elaborated with, and pads or raises a ``SizeError`` accordingly. Assigning into +the vector ``v2`` simply replaces its contents, length included. A vector or vector slice can be passed as a call argument that has been -declared as an array slice of the same size and type. When indexing a vector of arrays, +declared as an array of the same size and type; the check that the vector's +current length matches the parameter's declared length happens at the call. +When indexing a vector of arrays, the first index selects the array element within the vector, and the second index selects the element within the array: @@ -78,27 +127,41 @@ the element within the array: ragged_right[2][1] -> std_output; // prints 2.1 -As a language supported object, *Gazprea* provides several methods for ``vector``: +As a language supported object, *Gazprea* provides several methods for +``vector``. These are the operations that make a vector runtime sized; none of +them has an array counterpart, and applying the idea to an array — trying to +lengthen it — is always a ``SizeError``. -- ``push()`` - pushes a new element to the back of the vector +- ``push(T)`` - pushes a new element to the back of the vector, increasing its + length by one. ``T`` is the element type of the vector, or a type that can be + implicitly converted to it. -- ``len()`` - number of elements in the vector +- ``len()`` - the number of elements the vector currently holds. This is the + same value the :ref:`length ` built-in reports for the + same vector; ``len`` is the method spelling and ``length`` the built-in + spelling. -- ``append(T[*])`` - append another array slice to the vector where `T` is the type of the original vector or a type that can be implicitly cast to it. The following example tracks the elements inside `vec` through various appends. +- ``append(T[*])`` - append another array slice to the vector, increasing its + length by the length of that slice. ``T`` is the type of the original vector + or a type that can be implicitly cast to it. The following example tracks the + elements inside ``vec`` through various appends. Note that the *element* + size (``real[2]``) is fixed by the declaration and every appended element is + padded or rejected against it, while the *number* of elements changes freely. :: - const x = 1..10; + const x = 1..10; var vector vec; // [] - + // scalar to array promotion vec.append(1); // [[1.0, 1.0]] // array padding - vec.append(3..3); // [[1,0, 1.0], [3.0, 0.0]] - + vec.append(3..3); // [[1.0, 1.0], [3.0, 0.0]] + // slices - vec.append(x[5..7]); // [[1,0, 1.0], [3.0, 0.0], [5.0, 6.0]] + vec.append(x[5..7]); // [[1.0, 1.0], [3.0, 0.0], [5.0, 6.0]] - vec[tvec.len()] -> std_output; // prints 3 + vec.len() -> std_output; // prints 3 + vec[vec.len()] -> std_output; // prints [5 6]