From 655846ffb749f88b530433339185df2a8cbb2299 Mon Sep 17 00:00:00 2001 From: Ayrton Chilibeck Date: Thu, 10 Sep 2026 12:10:18 -0400 Subject: [PATCH] feat(gazprea): compress builtins to `shape()` This branch implements a change to the gazprea specification eliminating the following functions: - `length()` - `rows()` - `columns()` And replaces them with a generic function that works for arbitrary ranks: `shape()`. The goal is to have a generic way to query any 'shaped' entity in gazprea without needing to case on the exact rank and type. The signature: ```gazprea function shape(T shaped) returns integer[*]; ``` The function takes as arguments an n-d array or vector and returns the longest contiguous static shape of the shaped element. For example: ```gazprea integer[1][2][3] a; shape(a) -> std_output; // prints '[1 2 3]' integer[*] b = [[1, 2], [3, 4]]; shape(b) -> std_output; // prints '[2 2]' vector c; c.append([[2, 3, 4], [4, 6, 8]]); shape(c) -> std_output; //prints '[1 2 3]' vector> d = [[[1, 2]]]; shape(d) -> std_output; // prints '[1]', vectors // generally cannot look inside other vectors to determine // shape, since multiple vectors could lead to a ragged // array, which is not efficiently expressible in this // format ``` docs(gazprea): clarify shape() semantics for nested collections Add explicit examples and documentation clarifying that shape() reports only the immediate collection's dimensions, not nested element types: - shape(vector) returns [length], not [length, 2, 3] - shape(vector[3][2]) returns [3, 2], the array extents - To query element shapes, index first then call shape: shape(a[0][0]) This simplifies the implementation and clarifies the contract: shape() returns the dimensions of what you pass it, nothing more. Nested dimensions are accessed by indexing into the collection. --- gazprea/spec/built_in_functions.rst | 122 ++++++++++++++++------------ gazprea/spec/procedures.rst | 8 +- gazprea/spec/types/matrix.rst | 17 ++-- 3 files changed, 78 insertions(+), 69 deletions(-) diff --git a/gazprea/spec/built_in_functions.rst b/gazprea/spec/built_in_functions.rst index 2b5aa4bf..b48d2eca 100644 --- a/gazprea/spec/built_in_functions.rst +++ b/gazprea/spec/built_in_functions.rst @@ -22,11 +22,10 @@ global identifier namespace. A user may freely declare, say, a ``function len()`` or a variable named ``push``. Note that although the examples below all use arrays, the array-shaped -built-ins (``length``, ``reverse``) also work on +built-ins (``shape``, ``reverse``) also work on :ref:`vectors ` and :ref:`strings `, using -whatever length that value currently holds. The shape-specific built-ins -keep the domains their own sections describe: ``rows`` and ``columns`` -require a two-dimensional matrix, and ``format`` takes only scalars. +whatever length that value currently holds. The ``format`` built-in +takes only scalars. Applying a built-in outside its defined domain is a compile-time error and the compiler must emit a @@ -45,9 +44,7 @@ generic over ``T``". This notation is **not** part of the language. :: - function length[T](T[*] arr) returns integer; // also accepts a vector / string - function rows[T](T[*][*] mat) returns integer; - function columns[T](T[*][*] mat) returns integer; + function shape[T](T shaped) returns integer[*]; // T is any array type, vector, or string function reverse[T](T[*] arr) returns T[*]; // also accepts a vector / string function format[T](T value) returns string; // T is a scalar type procedure stream_state(var input_stream) returns integer; // notional; see below @@ -64,84 +61,101 @@ In addition to these free-standing built-ins, ``vector`` and ``string`` values carry **methods** -- ``push``, ``append``, and ``len`` -- invoked with receiver syntax (``v.len()``). These are specified with the type, in :ref:`sssec:vec_methods`, not here. In particular, ``len`` (a method, on vectors -and strings only) and ``length`` (a built-in, accepting arrays, vectors, and -strings) answer the same question with different spellings and different domains: +and strings only) extracts the current length as a scalar, while ``shape`` (a +built-in, accepting arrays, vectors, and strings) returns the full shape as an +array: .. list-table:: :header-rows: 1 :widths: 30 35 35 * - Query on ``x`` - - ``length(x)`` (built-in) + - ``shape(x)`` (built-in) - ``x.len()`` (method) * - array ``T[n]`` - - the fixed length ``n`` + - ``[n]`` (array of extents) - ``TypeError`` -- arrays have no methods * - ``vector`` / ``string`` - - the current length - - the current length + - ``[current_length]`` (array of extents) + - the current length (scalar) -.. _ssec:builtIn_length: +.. _ssec:builtIn_shape: -Length ------- +Shape +----- -``length`` takes an rank-1 array of any element type, and -returns an integer representing the number of elements in the array. -``length`` is not defined for an array of rank greater than 1; use ``rows`` -and ``columns`` (see :ref:`ssec:builtIn_rows_cols`) for a two-dimensional -matrix instead. In future editions of the spec this may be extended to a -generic ``shape`` function, but that is left to future revisions of the -course. +The ``shape`` built-in takes an array of any rank, a :ref:`vector `, +or a :ref:`string `, and returns a rank-1 array of integers +representing the extent of each dimension, ordered from outermost to innermost +(left-to-right as declared). .. gazprea-example-wrap:: - :name: builtin_length + :name: builtin_shape_rank1 - integer[*] v = 1..5; - length(v) -> std_output; /* Prints 5 */ + integer[5] v = 1..5; + shape(v) -> std_output; /* Prints [5] */ --- output --- - 5 + [5] -Because an array is :term:`initialization`-time sized, ``length`` applied to -an array is invariant after :term:`initialization`: every call returns the -same number. Applied to a :ref:`vector ` (or a -:ref:`string `), ``length`` returns the value's *current* -length instead, so two calls may return different numbers if the vector grew -in between. In this role ``length`` is simply the built-in spelling of the -vector's :ref:`len ` method. +For a :ref:`matrix ` (rank-2 array), ``shape`` returns both +dimensions: -:: +.. gazprea-example-wrap:: + :name: builtin_shape_rank2 - var vector v = [1, 2, 3]; + integer[*][*] M = [[1, 2, 3], [4, 5, 6]]; + shape(M) -> std_output; /* Prints [2 3] */ - length(v) -> std_output; /* Prints 3 */ + --- output --- + [2 3] - call v.push(4); /* 'v' is now [1, 2, 3, 4] */ +Higher-rank arrays are fully supported: - length(v) -> std_output; /* Prints 4 */ +.. gazprea-example-wrap:: + :name: builtin_shape_rank3 + integer[2][3][4] A = ...; + shape(A) -> std_output; /* Prints [2 3 4] */ -.. _ssec:builtIn_rows_cols: + --- output --- + [2 3 4] -Rows and Columns ----------------- +For **vectors**, ``shape`` returns only the vector's runtime length, wrapped as a single-element +array. The dimensions of elements within the vector are not included; to inspect the shape of a vector +element, index the vector first and then query that element: -The built-ins ``rows`` and ``columns`` report the dimensions of a -rank-2 array (a :ref:`matrix `): ``rows`` returns the -number of rows and ``columns`` the number of columns. +:: -.. gazprea-example-wrap:: - :name: builtin_rows_columns + var vector v = ...; + shape(v) -> std_output; /* Prints current length, e.g., [5] */ + shape(v[1]) -> std_output; /* Prints [2 3] -- the shape of the array at v[1] */ - integer[*][*] M = [[1, 2, 3], [4, 5, 6]]; - rows(M) -> std_output; /* Prints 2 */ - '\n' -> std_output; - columns(M) -> std_output; /* Prints 3 */ +More generally, ``shape`` reports the dimensions of the **collection you pass it**, not of anything +contained within. An array of vectors has array dimensions: - --- output --- - 2 - 3 +:: + + vector[3][2] a = ...; + shape(a) -> std_output; /* Prints [3 2] -- the array extents only */ + shape(a[0][0]) -> std_output; /* Prints [n] -- the shape of the vector at a[0][0] */ + +Because an array is :term:`initialization`-time sized, ``shape`` applied to +an array is invariant after :term:`initialization`: every call returns the +same value. Applied to a :ref:`vector ` (or a +:ref:`string `), ``shape`` returns the value's *current* +length instead, so two calls may return different arrays if the vector grew +in between: + +:: + + var vector v = [1, 2, 3]; + + shape(v) -> std_output; /* Prints [3] */ + + call v.push(4); /* 'v' is now [1, 2, 3, 4] */ + + shape(v) -> std_output; /* Prints [4] */ .. _ssec:builtIn_reverse: diff --git a/gazprea/spec/procedures.rst b/gazprea/spec/procedures.rst index 04616961..4fdee437 100644 --- a/gazprea/spec/procedures.rst +++ b/gazprea/spec/procedures.rst @@ -239,11 +239,11 @@ implicit cast can be inserted. :name: procedure_var_no_implicit_cast :error: TypeError - procedure byvalue(string x) returns integer { - return length(x); + procedure byvalue(string x) returns integer[*] { + return shape(x); } - procedure byreference(var string x) returns integer { - return length(x); + procedure byreference(var string x) returns integer[*] { + return shape(x); } procedure main() returns integer { const character[3] y = ['y', 'e', 's']; diff --git a/gazprea/spec/types/matrix.rst b/gazprea/spec/types/matrix.rst index eb1e6672..b969e0be 100644 --- a/gazprea/spec/types/matrix.rst +++ b/gazprea/spec/types/matrix.rst @@ -8,13 +8,9 @@ Matrices any :ref:`storable type `. A *matrix* is the rank-2 case, and this section describes it in full; higher-rank arrays follow the same construction, indexing, and element-wise operation rules, generalized -to ``k`` index positions. The ``rows`` and ``columns`` built-ins discussed -below are defined on matrices (rank-2 arrays) specifically, and ``length`` on -rank-1 arrays; there is currently **no** size query for arrays of rank 3 or -more, so their extents are not observable at run time. This is a known -limitation: a general ``shape`` built-in reporting the extents of an array of -any rank is planned for a future revision of this specification. Matrix -multiplication (``**``), by contrast, is defined for +to ``k`` index positions. The extents of an array of any rank can be queried +at run time using the ``shape`` built-in (see :ref:`ssec:builtIn_shape`). +Matrix multiplication (``**``) is defined for arrays of **any** rank, as described in :ref:`sssec:matrix_ops`. .. _sssec:matrix_decl: @@ -101,7 +97,7 @@ literal ``[]`` is the empty rank-2 array, written ``[[]]``: Like an empty 1-D array, an empty matrix has its (zero) dimensions fixed at :term:`initialization` and is not growable. Both of its dimensions are zero: -``rows(m)`` and ``columns(m)`` are each ``0`` (a 0x0 matrix, notwithstanding the +``shape(m)`` is ``[0 0]`` (a 0x0 matrix, notwithstanding the ``[[]]`` notation). .. _sssec:matrix_ops: @@ -175,9 +171,8 @@ promotion, and the ``M || [r]`` row-append idiom). integer[*][*] B = [[5, 6]]; /* A || B == [[1, 2], [3, 4], [5, 6]] */ -The number of rows and columns in a matrix is given by the built-in -functions ``rows`` and ``columns``; see :ref:`ssec:builtIn_rows_cols` for -their full definition. +The number of rows and columns in a matrix is given by the ``shape`` built-in; +see :ref:`ssec:builtIn_shape` for its full definition. Matrix indexing is done similarly to array indexing. Because a matrix is an