spec(gazprea): consolidated spec-review stack — nested types + audit fixes - #139
spec(gazprea): consolidated spec-review stack — nested types + audit fixes#139Sir-NoChill wants to merge 84 commits into
Conversation
Manifest-driven apt/check/healthcheck scripts plus the session's
ephemeral agent signing identity, per the agent-bootstrap protocol.
Volatile paths (key material, baselines, agent-env.sh) are gitignored.
The scaffold is vendor-neutral: the directory is `.agents/`, the default
identity is generic ("Agent"), and the skills-directory path is a
manifest variable rather than hardcoded to any one agent tool's
convention.
Assisted-by: Agent <ai@blobfish.icu>
Applies review feedback from PR #110. Removes the committed rendered scripts (bootstrap.sh, check.sh, healthcheck.sh) and the committed public key (agent-pubkey.asc); each session regenerates the scripts from the .tmpl files via render.py before running bootstrap. Identity is now opt-in: the manifest's `agent:` block is blank by default, and bootstrap only mints a GPG signing key (and writes agent-env.sh) when a user fills it in locally. Users choose their own signing identity; the repo prescribes none. Python dependencies move to pyproject.toml under uv: bootstrap installs uv from astral.sh if missing, then runs `uv sync` to provision a venv pinning sphinx==6.2.1 alongside jinja2 and PyYAML (both needed by render.py, which now uses stock Jinja2 with a `{## ##}` comment tag to avoid colliding with bash's `${#arr[@]}` array-length syntax). Adds an empty `.agents/skills/` directory as the location for bundled review skills; `manifest.yaml`'s `skills:` list names entries under it, and healthcheck asserts each listed skill has a non-empty SKILL.md. BREAKING CHANGE: `.agents/bootstrap.sh`, `.agents/check.sh`, and `.agents/healthcheck.sh` are no longer tracked. Existing checkouts must render them once (`for t in .agents/*.tmpl; do uv run .agents/render.py .agents/manifest.yaml "$t" > "${t%.tmpl}"; done`) before running bootstrap. Users who had a signing identity configured must re-populate the `agent:` block in their local copy of manifest.yaml. Assisted-by: Agent (claude) <ai@blobfish.icu>
Populates .agents/skills/ with the two skills the repo will actually use during specification review, and lists them in manifest.yaml so the healthcheck notices if either goes missing. spec-review is the editorial/structural checklist a human maintainer runs before a spec chapter merges: build integrity (`sphinx-build -W -n`), heading hierarchy, `:term:`/`:ref:`/`:doc:` cross-reference integrity, admonition placement, gazc-backed sanity check on `.. code-block:: gazprea` examples, TODO/FIXME residue, and targeted sibling-file consistency spot-checks. grammar-consistency catches the cross-file syntactic-surface divergences that PRs like #116 (vector-vs-array) and #118 (precedence single-home) exist to fix. It compares the same grammar element (keyword, operator, named rule, type-form) as it appears in different chapters and reports disagreements without picking a winner -- consistency is orthogonal to correctness. Assisted-by: Agent (claude) <ai@blobfish.icu>
Adds .agents/skills/spec-review/check-ci.sh, which runs the same two
checks CI does before a spec change hits the remote:
* Sphinx build across every doc subdir in the top-level Makefile
(setup, generator, lolcode, vcalc, gazprea, info), with -W -n so
warnings and unresolved cross-references become errors -- stricter
than the CI deploy step's own `make html`.
* lychee over the exact file globs and args CI's linkcheck.yml uses,
self-installing the binary from lycheeverse's installer or cargo
when it is missing rather than silently skipping the check.
Sub-modes `sphinx` and `links` scope the run to one workflow's worth
of checks. The SKILL.md checklist's "build integrity" step now points
at the script instead of open-coding the sphinx-build invocation.
Assisted-by: Agent (claude) <ai@blobfish.icu>
Address the review on #110: - manifest.yaml now names ghcr.io/cmput415/docs-dev as the preferred environment; native apt/uv path stays as a fallback. - README.md documents the docker-first flow, keeps the "regenerate on session start" note, and explicitly frames signing as opt-in and non-prescriptive. - bootstrap.sh.tmpl trims the block-level commentary the reviewer called out; identity handling stays gated on a populated agent block. - skills/grammar-consistency/SKILL.md is rewritten to audit English prose (spelling, passive voice, subject/tense, terminology, technical-writing anti-patterns). Deriving the Gazprea grammar from the informal spec is a student exercise and is now out of scope. - skills/spec-review/SKILL.md swaps the hand-rolled check-ci.sh for act-based workflow replay (act ships in the DocsDev image) and points at grammar-consistency for prose review. - Removes .agents/skills/spec-review/check-ci.sh (subsumed by act). Assisted-by: Agent (claude) <ai@blobfish.icu>
The /tree/main/DocsDev path does not exist on cmput415/ci-utils and tripped linkcheck. The repo root already documents the image.
pyproject.toml was committed without its lockfile, so `uv sync` resolved freely on every checkout and the session environment was not actually reproducible. Pin it. Assisted-by: Agent (claude) <ai@blobfish.icu>
String and free len() do not exist (string, length(), or the .len() method do), char is not a type (character is), and comma indexing M[1, 2] is defined nowhere (composite M[1][2] is). Each example now uses the spelling the spec defines. Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "replace undefined syntax in examples" so the
patch's own examples read cleanly:
* ``procedures.rst`` byvalue/byreference now call ``x.len()`` on the
``string`` argument instead of the free ``length(x)``. ``string`` is
a sub-type of ``vector<character>`` (types/string.rst:76) and
``vector`` defines ``.len()`` (types/vector.rst:65); using the method
keeps the string-vs-vector surface consistent, which is the shape
the rest of the spec assumes.
* ``types/struct.rst`` renames the ``character char`` and ``real float``
fields on the ``Another`` example to ``character c`` and ``real r``.
Naming a field after a type from another language reads like a
keyword clash even though Gazprea's grammar allows it; the new
names follow the ``s1`` example's ``i``/``r``/``iv`` convention.
Also fixes the paragraph that referred to the struct by the wrong
name (``Struct type "s"`` -> ``s1``) and updates the field-name
list to match.
Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up. The previous rename (fcf3ad8) landed on `character c, real r, string[256] str, s1 struct_field`, which put Another's `r` right next to the sibling `s1` example's `r` field two lines above. Scoping is fine (each struct owns its field namespace), but the collision reads as an accidental repeat in a two-line example whose whole point is to show that field identifiers are chosen freely. Rename to `character ch, real f, string[256] str, s1 struct_field`, and update the prose that enumerates them. Distinct short names, no shadow of the neighbouring example, and still tracks the `s1` `i`/`r`/`iv` pattern of using initials-of-the-type. Assisted-by: Agent (claude) <ai@blobfish.icu>
vector<T> never said which T are legal, leaving vector<vector<T>>, vector<tuple(...)>, and vector<string> undecidable. Now: base types and 1-D arrays of base types, matching every existing example. Assisted-by: Agent <ai@blobfish.icu>
Human-review follow-up. The new `vector<T>` element-type rule enumerated the base types as `(boolean, character, integer, real)`, which is inconsistent with the two other places the same set is enumerated: `types/array.rst:6` and `constexpr.rst:22` both use `(boolean, integer, real, character)`. Any drift between these three enumerations makes a reader wonder if the set itself changed at one of the sites. Reorder the vector rule to match. Same set, no rot; if the set ever does grow (e.g. `string` promoted to a base type), all three files will need to change in lockstep and having them start identical makes that easier. Assisted-by: Agent (claude) <ai@blobfish.icu>
'Vectors behave exactly like arrays' papered over normative differences the same file relies on: methods, array-valued binary results, and a pad-to-first ragged policy that contradicts matrix pad-to-longest for the same literal. The claim is now an interop list plus an explicit enumeration of the differences, with a cross-reference at the padding rule. Also replaces the undefined term 'subroutines'. Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "scope the vector-array equivalence claim" (ab6eeed). Two lingering over-broad claims in ``types/vector.rst``: * The intro's differences list said "binary operations *involving a vector* produce array results", but the body (the "Operations" subsection just below) only supports that for *mixed* vector+array operations. The vector+vector case is not stated anywhere. Narrow the intro to match the body: "a mixed binary operation between a vector and an array". * The Operations subsection opened with "Operations on vectors are identical syntactically **and semantically** to operations on arrays" -- the exact over-broad equivalence the patch is scoping out of the intro. Left in place, it re-introduces the paperover a few lines down. Reword to "use the same syntax as ... and, except for the differences enumerated above, share their semantics" so the enumeration in the intro remains authoritative. Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up. Two related over-commits in the intro
paragraph now that the six spec-review PRs are viewed as a group:
* "interoperate freely with arrays" — overstates the case once the
vector element-type restriction (companion PR #113) lands. A
`vector<S>` where `S` is a struct never exists, so interop is
"free" only over the element types both sides support. Add the
qualifier: "interoperate with arrays for the element types they
both support".
* "the differences are normative: [three-item list]" — presented as
exhaustive. Once PR #113 merges, a fourth normative difference
(element-type set is narrower on vectors) is documented directly
below, and a reader who trusts this enumeration as complete will
miss it. Soften to "differences include (non-exhaustively)" so
the paragraph remains true after the neighbouring PR merges and
tolerates future additions without re-editing.
Assisted-by: Agent (claude) <ai@blobfish.icu>
'Arrays cannot be indexed with array expressions' collided with ranges being arrays and with slicing being defined as indexing by a range. The rule now names the distinction: array values (including ranges bound to variables) are illegal indices; literal range syntax in an index position is the slice form. Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "distinguish range slices from array-valued indices" (ebe5f8d). The patch scoped the general rule in statements.rst but left `types/array.rst`'s indexing subsection saying only "An array may be indexed using integers", which continued to contradict the very next subsection defining slices as indexing by a range. Extend the rule here to match: an integer index yields an element, a range at the index position yields a slice (with a cross-reference to the slices subsection), and an array *value* (including a range bound to a variable) is not a legal index. Same distinction the statements.rst hunk introduced, phrased for the type chapter's local vocabulary. Assisted-by: Agent (claude) <ai@blobfish.icu>
…pression Human-review follow-up. The original rule at statements.rst:46 said ``v[w]`` is illegal "when ``w`` is an array variable", but the companion rule at types/array.rst:260 (added on the same branch) correctly says "an array *value* … is not a legal index". Different scopes for the same rule: an expression, a parenthesized value, or a function call that returns an integer array is caught by the array.rst wording and slips past the statements.rst one. Widen statements.rst to "whenever ``w`` evaluates to an array value", and spell out the class of expressions this covers. Same rule stated consistently in both chapters, no more gap on function-returned ranges. Assisted-by: Agent (claude) <ai@blobfish.icu>
The universal claim was false for string/character[*] (no as<> form exists) and glossed the size requirement on scalar-to-array casts. The vague 'higher dimension' exception is replaced with the concrete rule (no 1-D to 2-D promotion; scalars broadcast), the square-matrix note is scoped to ** operands, and the string promotion section now names the array/vector distinction precisely. Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "scope the promotion-implies-cast claim" (c087971). * The scalar-to-matrix promotion note used "a matrix of any shape". ``shape`` is a stdlib extension in this project, not part of the formal spec (a companion patch consciously scopes it out), and reusing the word here as a common noun invites conflation. Swap to "of any dimensions". * The Character-Array/String section's added tail clause ("a ``string`` used where a character array is expected, or vice-versa, converts silently") duplicated "implicitly converted" from the sentence just above it. Drop the redundant clause; the "of note is between ``string`` and character *arrays*" pointer still carries the intended emphasis. Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up. The first commit introduced a positional
forward reference ("see the final section of this chapter") from the
intro's second caveat down to the Character-Array/String section.
Positional references silently break when a later section is added
to the chapter — the "final section" is no longer the intended
target.
Add an `ssec:typePromotion_string` label on the section heading and
swap the intro's forward reference to `:ref:` against that label.
Same target today, robust against reordering, and readable inline
(Sphinx renders the section title).
Assisted-by: Agent (claude) <ai@blobfish.icu>
The precedence relation existed in three copies (expressions.rst, integer.rst, boolean.rst); the per-type copies are the divergence trap since any operator change must land in all three. The per-type pages now reference the normative table in expressions.rst. Assisted-by: Agent <ai@blobfish.icu>
… parens note
Two human-review follow-ups on the operator-precedence single-home
refactor:
* `types/real.rst:55` still routed readers to the integer chapter's
Operations subsection for "operation and precedence". Now that
integer.rst delegates upward instead of hosting the table, this
is a two-hop indirection where the first hop no longer contains
what the sentence promises. Split the sentence: operations
(semantics — IEEE-754-style behavior, unary rules, C99 remainder)
still point at `sssec:integer_ops`; precedence and associativity
point directly at the normative table.
* The integer chapter's removed precedence paragraph carried a
useful note that parentheses are absent from the list because
they override precedence rather than participate in it. The note
is normative guidance about the table, not integer-specific, so
lift it into `expressions.rst` alongside the table itself.
Assisted-by: Agent (claude) <ai@blobfish.icu>
The procedure was fully specified twice: only built_in_functions.rst had the initial state, only streams.rst had the null-value/position rule, and the two used different wording. streams.rst error handling is now normative (codes, initial state, per-type table); built-ins keeps the signature, marked as notional since input_stream is not a language type. Also fixes the F 1.0 output that violated the %g rule, the 'characters have no error state' claim contradicted by the state table, and the undefined 'null value' term. Assisted-by: Agent <ai@blobfish.icu>
The rule was restated in three files; type_qualifiers.rst is now the normative home and the misleading 'essentially a no-op' wording is replaced. The other two sites cross-reference it. Assisted-by: Agent <ai@blobfish.icu>
…e the last duplicate
Two human-review follow-ups on the const-by-default single-home
refactor:
* The normative paragraph in `type_qualifiers.rst` now names the
default-is-const rule explicitly, but the "both spellings are
legal" corollary was only implicit ("writing ``const`` is
therefore redundant"). A reader is left to infer that
``T x`` and ``const T x`` are exchangeable. Add a one-sentence
explicit statement of that equivalence so the normative section
doesn't rely on the reader's inference.
* `procedures.rst:9` is the last remaining restatement: "By default
arguments are ``const`` just like functions." Its scope
(parameter-passing) is narrower than the variable-declaration
rule and it is worth keeping in place for readers landing on the
procedures chapter, but it should point at the normative home so
it does not become a fourth divergent copy. Add a
`see :ref:sec:typeQualifiers` cross-reference.
Assisted-by: Agent (claude) <ai@blobfish.icu>
The impl chapter (sec:errors) defines the full error taxonomy, but a dozen spec rules said only 'an error', leaving class and phase to guess. Each site now names the class from the taxonomy and the first mention in each file cross-references sec:errors. Also normalizes 'should raise' to 'must raise' at these sites. Assisted-by: Agent <ai@blobfish.icu>
Two error-class mentions predate the errors-chapter patch but never had
the (see :ref:`sec:errors`) cross-reference the patch introduced at the
first mention in each file. Add the reference and normalize the site's
wording to the "must raise" phrasing used by the patch:
* typedef.rst: SymbolError for duplicate alias names. Also fixes a
literal-role typo (single-backticks would render as an unresolved
default role) and the missing trailing period.
* types/array.rst: SizeError for RHS-too-large in an array
initializer. This is the first error-class mention in the file
(line 53); the patch's cross-ref at line 280 was on the second
mention.
Assisted-by: Agent <ai@blobfish.icu>
Follow-up to the errors-chapter patch. Three coordinated cleanups, kept
in one commit so the wording change stays local to the sites it touches:
* Fill omitted error classifications the initial patch missed. Adds a
ReturnError classification to the "return reachable by all control
flows" rule (functions.rst), a GlobalError classification covering
the non-constexpr / vector-global / non-global-statement bullets
(globals.rst), a StatementError for a declaration outside the
leading declaration block of a block statement (declarations.rst),
a SyntaxError for iterator loops with more than one domain
(statements.rst iterator loop), and a StatementError for a
``continue`` outside a loop (statements.rst continue, mirroring the
``break`` rule).
* Normalize two "should raise" sites in files the patch already
touched but did not reword: SizeError for matmul dimension mismatch
(types/matrix.rst) and SizeError for elementwise binop size
mismatch (types/array.rst). Also normalizes typedef.rst's inline
"Should raise a ``SizeError``" callout on the size-mismatch example.
* Converge every patch insertion (and the two residuals above) on
"must emit a ``X``" (or the passive "must be emitted") for the
error-raising rule. The initial patch used six different verbs
(issue / raise / yield / cause / is / is to be produced) at
otherwise identical sites; one verb reads more consistently and
matches how the errors chapter itself describes the requirement.
Also drops the redundant ``(see :ref:`sec:errors`)`` on
types/array.rst:279 (out-of-bounds indexing) since types/array.rst:53
now carries the first-mention cross-reference for that file.
Assisted-by: Agent (claude) <ai@blobfish.icu>
Four follow-ups on the errors-chapter branch flagged by a human-review
pass across the six spec-review PRs open on this stack:
* `procedures.rst`: the two "*Gazprea* must emit a ``CallError``"
sites drifted from the "the compiler must emit …" subject used
everywhere else the pass touched. Normalize both to
"the compiler must emit".
* `globals.rst`: the summary paragraph re-enumerated the three
restrictions from the bullet list right above it, and the
re-enumeration silently rots when the bullets are edited.
Collapse to one sentence: "Violations of any of the above must
be reported as a ``GlobalError``." — no drifting second copy.
* `functions.rst`: the ReturnError classification I added on the
return-reachable-by-all-paths rule ended with a sentence
("Control-flow constructs are assumed to be undecidable, so
both branches of every conditional are considered reachable.")
that `impl/errors.rst:104-106` already states as part of the
normative ``ReturnError`` definition. Drop it here; the cross-ref
carries the rule.
* `declarations.rst`: the StatementError classification used the
undefined term "leading declaration block". Nothing else in the
spec introduces it, and the paragraph immediately above uses "at
the start of the block". Reword to "the declaration prefix at
the start of its enclosing block statement", and lead with the
compiler as the subject to match the surrounding rules.
Assisted-by: Agent (claude) <ai@blobfish.icu>
Add spec/flags.rst: the precise semantics of -ffast-math (the integer math faults of integer.rst become undefined behavior; no effect on real, which stays IEEE 754) plus the testing policy -- student tests never exercise UB, the flag is reserved for perf stress-tests of already-validated linear algebra, and every test is run against the non-fast-math compiler first to confirm it is UB-free. Make the type pages normative for their own math errors: errors.rst now defers its MathError conditions to integer.rst (integer faults) and notes that real never raises a MathError (real.rst). The glossary states normatively that Gazprea has no undefined behavior under standard operation, with -ffast-math the single exception, and points at flags.rst. integer.rst and real.rst link to flags.rst. Assisted-by: Agent (claude) <ai@blobfish.icu>
A global must always be initialized. Unlike a local, a global is never implicitly zero-initialized, so a global declared without an initializer is ill-formed and the compiler must emit a GlobalError; an intended zero value must be written explicitly. Fix the const-vector wording accordingly: an empty global vector now needs an explicit [] initializer rather than being declared without one. Assisted-by: Agent (claude) <ai@blobfish.icu>
| receiver is an array or a scalar -- the result is an array, exactly as in | ||
| the examples above. Nothing else about concatenation changes: at least one | ||
| operand must still be composite, and the operands must share a common | ||
| element type through implicit casts. This is what keeps a string |
There was a problem hiding this comment.
I thought the thing that would make x a string is that it is a string variable and the assignment will implicit cast. I think with that in mind, this receiver formulation is unnecessary.
There was a problem hiding this comment.
The way I have it specified in vectors is that vector-ness is never propagated, since the vector is always used as an array in arithmetic expressions. The reciever formulation allows for strings to print normally if the reciever is a string. I have string specified as a true compiler-provided typealias for a char array and that printing is the only difference.
So under my vector rules, if we wanted to assign a string to a variable, it would require a type definition.
Before this rule
var a = "ab" || ['c', 'd'] || "de";
a -> std_output; // prints "[a b c d e]"
"ab" || ['c', 'd'] || "de" -> std_output; // prints "[a b c d e]"
After type resolution, I'm thinking that RHS is treated like a char array for the sake of assignment and expression resolution since a vector is never part of an expression and implicitly two-way castable to a the equivalent array with size fixed at the vector's current length.
Under this rule it becomes unwieldly to print raw characters/strings to the terminal.
I want to do it this way because the vector is not a true 'primitive type' and so the language can have an escape hatch here.
After this rule
var a = "ab" || ['c', 'd'] || "de";
a -> std_output; // prints "[a b c d e]"
"ab" || ['c', 'd'] || "de" -> std_output; // prints "abcde"
Since the reciever dictates the output formatting, then the string prints nicely.
Alternatives
Vector-ness is Propagated Everywhere
We could change it so that vector-ness is propagated through, but then do vectors downcast to arrays in expressions or do arrays upcast and what becomes the rules on size errors via expressions?
I think that answering the size error questions in the context of vectors becomes significantly harder than answering them via arrays like we currently do.
Assignment propagates the Widest Type
So vectors cross the boundary to the assignment rather than being elided to their underlying array.
I am open to suggestions here, but having any single element being a vector and that being propagated changes the implications of expressions always using their underlying array as the arithmetic type; or at least results in a bit of an arbitrary contradiction.
| so is an lvalue only when that array is mutable (declared ``var``): | ||
| - **In value position** (an :term:`rvalue`) -- as an initializer, on the right | ||
| of an assignment, as an argument bound to a ``const`` parameter, or anywhere | ||
| an array value is expected -- a slice produces a **fresh, independent array** |
There was a problem hiding this comment.
This is wrong according to Ron’s intention for slices
There was a problem hiding this comment.
Yes, it is, but slices on the LHS are not really slices, they are a 'splat'.
Building subarrays from indexes I think is a natural extension of using slices to begin with. I have defined the definition of new variables from splats as being a truly 'new' variable, so a copy. If you hate this, I can revert, but I have a further defense in my subsequent comment.
There was a problem hiding this comment.
I think our understanding of the language semantics is aligned, but we disagree on how it is presented. Assigning the result of a slice to a variable produces a copy because assignment always copies.
I think of it like an extension of indexing or Tuple element selection
var arr = [1, 2, 3];
arr[1..2] = [10, 20]; // LHS is a lvalue, the is an rvalue
const x = arr[2..3]; // LHS is an lvalue, the slice is an lvalue
// semantically the same as if I do
const y = arr; // arr is an lvalue, assignment copies the array
the way you explain it also works with this example, but it is more complex to reason about.
Remove the restriction that declarations may appear only at the start of a block; a declaration may now be interleaved freely with the statements around it, which is what constexpr.rst already assumed. Drop the corresponding StatementError wording here and in statements.rst. Globals remain the exception: their dependency order is now stated as a hard requirement -- a global may reference only globals defined earlier in the file, and a forward reference to a not-yet-defined global is a SymbolError (the name is not yet in scope). declarations.rst cross-references this. Assisted-by: Agent (claude) <ai@blobfish.icu>
| assignment, or bound to a ``var`` reference parameter -- a slice is a **view** | ||
| that writes *through* to its backing array. This is the only situation in | ||
| which a slice aliases storage, and it requires the backing array to be mutable | ||
| (declared ``var``); a slice of a ``const`` array is never an lvalue. The |
There was a problem hiding this comment.
Should be the same error as assignment to const—AssignError, I believe
There was a problem hiding this comment.
This is one that I should have flagged, I have been considering mutability part of the type.
I think my argument is grounded in gazprea, since const and var parameters have different semantics and historically the compiler implementations decide mutability at type check.
I think that I would rather remove the assign error in favour of having this and other examples fail type check instead, but I can revert this if that is the vibe.
There was a problem hiding this comment.
As long as it is consistent, either option is good for me.
|
|
||
| Because a subscript chain names successive axes rather than re-indexing an | ||
| intermediate result, writing more index positions than the array has axes is | ||
| *not* how one indexes into a slice's result; for that, bind the slice to a |
There was a problem hiding this comment.
The same operator now has two different meanings
There was a problem hiding this comment.
consequence of n-d arrays I think. We could change it so that n-d arrays are instead written and indexed in the form <array>[axis, axis, axis][index], which I think is cleaner, but requires a big change. Could also go the numpy way and just keep the indexing as positional.
There was a problem hiding this comment.
The root of the problem I think is that indexing is a rank reducing operation, while slicing is a rank preserving operation. Therefore, indexing chains naturally to specify a position in the nd array as a series of indices, while composing slices just repeatedly slices.
If we were to add multidimensional slices we should go the numpy way, but I think we should keep them out of the spec until we see how students handle/implement slices this year.
Prototypes (forward declarations) remain legal for functions and procedures, but a prototype must be matched by a definition. State in functions.rst and procedures.rst that a function or procedure that is prototyped but never defined is ill-formed and the compiler must emit a DefinitionError -- the taxonomy's existing error for 'declared but not defined' (impl/errors.rst). Assisted-by: Agent (claude) <ai@blobfish.icu>
Make explicit that a scalar operand of ** broadcasts to a rank-1 array's length, since a rank-1 array has a single (unambiguous) dimension: [1, 2, 3] ** 4 is the dot product [1, 2, 3] ** [4, 4, 4] == 24. Generalize matrix.rst's scalar-broadcast rule from 'square matrices and equal-extent hypercubes' to 'operands whose extents are all equal', which a rank-1 array trivially satisfies. Assisted-by: Agent (claude) <ai@blobfish.icu>
| :ref:`procedure <sec:procedure>`. In an argument position it follows the same | ||
| copy-or-view rule as everywhere else, decided by the *parameter* it binds to: | ||
|
|
||
| - A slice bound to a ``const`` parameter is passed **by value** -- the callee |
There was a problem hiding this comment.
As discussed, saying it is passed by value is misleading—in most implementations it would be passed by reference as usual, but the compiler assures that it is never written to
There was a problem hiding this comment.
I believe I clarify this in the next sentence or two, that being 'passed by value' is more of a conceptual way to think about const arrays. Also in MLIR this abstraction works, we do actually pass the full 'tensor' or whatever we are using as an array, rather than as a reference. The bufferization pass handles the reference/value semantics if done correctly. Likewise for vars, in mlir everything is pass by value and functionally returned without side effects and the students can abuse this feature to help with some of their analysis, especially since globals are all immutable.
There was a problem hiding this comment.
I agree, your following explanation is good. I think though that the surrounding lvalue/rvalue view/copy language should be removed.
|
The slice changes are a step back in my opinion. They were good as they were in the last meeting with the exception of the fact that slices cannot be assigned to a variable and thus are never const or var in themselves. That has been correctly removed, but most of the added stuff is not good. Multidimensional slices breaks a property of array and vector representation. Previously, arrays, vectors, and slices are all represented by {ptr, len} and all operations (indexing in particular) compose in the obvious way. Now, indexing by ranges gives non-obvious behaviour and slices cannot be represented as {ptr, len}. The copy vs read distinction is not needed. If we assume a slice is always a view that lasts for the duration of an expression, then all properties fall out naturally. A slice ends up copied as an r value not because the slice copies, but because assignment always copies—uniform with other array assignment semantics. I would recommend reverting the slices commit and just deleting the Const vs var paragraph that implies slices can be assigned to a variable as views. |
|
Rather than having both slices and ranges be half open, they should both be closed—this is the way most existing 1-indexed languages treat ranges, as this formulation is more natural for 1-indexing. Push back if you disagree. |
| A declaration may appear at **any** point within a block; *Gazprea* does not | ||
| require the declarations of a block to be grouped at its start, so a declaration | ||
| may be interleaved freely with the statements around it. For instance, this is | ||
| legal even though a declaration follows an ordinary statement: |
There was a problem hiding this comment.
This language should not assume so strongly that students would assume declarations must be grouped. This is ordinary behaviour for a programming language.
A vector declaration vector<T> v = E is resolved into exactly one of two cases by the rank of E, which are mutually exclusive so no tie-break is needed. Single-element: E is a scalar or a same-rank array cast/broadcast to T (a fixed-size element is padded to T's size -- vector<integer[3]> v = [4,5] is [[4,5,0]]); the vector has one element. Multi-element: E has the rank of T[] (one higher), and each element must be implicitly castable to T. Any other rank is a TypeError. This removes the flat-literal ambiguity: [1,2] for vector<integer[*]> is one element ([[1,2]]) while [[1,2]] is a one-element multi declaration ([[1,2]]). append/push now use the identical single-vs-multi test. Assisted-by: Agent (claude) <ai@blobfish.icu>
State that structs are nominal -- a struct's identity is the declaration that introduced it, not its field layout -- so two definitions with identical fields (or the same name in different scopes) are distinct types, while a typealias introduces no new type and transparently carries its target's identity. Type names now live in a lexically scoped type namespace: a struct or typealias defined in a function/procedure is local and shadows any outer name of the same kind, and does not leak to global scope. Allow typealias (including the typealias struct form) in local scopes, reversing the previous global-only rule; a duplicate in the same scope is still a SymbolError, while an inner redefinition shadows. Add a worked example showing which struct comparisons are legal across a global/local S and its alias Pair. Updates typealias.rst, struct.rst, and namespaces.rst. Assisted-by: Agent (claude) <ai@blobfish.icu>
|
I withdraw my claim of having a well-reasoned argument for extending slice semantics, but if that's the case then are we allowed to slice an n-d array? Or should we remove all n-d array capabilities and go back to rank-2 maximum? |
|
I think slicing an nd array is fine, but it only slices the outermost extent, in the natural way assuming slices are 1d. in the future I think we can extend to numpy like syntax for nd slicing. |
This resolves the slices being assignable, fundamentally a different operation, more like a 'splat' than a slice. Of course we could add a new 'splat' operator for this use case.
So we disallow slicing on any non-terminal rank, does that mean we can also not slice vectors containing arrays? My argument for this was that we can slice them, we can even assign to n-d arrays, and it is all still computable via index operations from the (ptr, len) tuple. It actually works nicely if you implement slicing as an indexing modifier rather than an 'operation' in itself, then it just becomes a mapping. We do not have the issue of this being a reference to the underlying array since slices are not themselves storable, so I'm not sure I see the issue with slicing the n-d arrays here.
If we remove the splatting, then yes. I like splats because it makes it easy to build an array out of a different, potentially modified array or vector where we do not necessarily know the element we want, but if removing them seems like the better option then I will cede.
This should already be deleted, I have it specified that slices can be assigned via the splat which just coincidentally shares view syntax but on the LHS. Could be confusing, so could make a new operator, but also could wholesale delete any mention of slices on the LHS or in expressions, which is the implication. Under deletion slices can only be an R-value and can never be used in an expression context. |
|
Would you like to call to discuss the splat stuff? |
|
I didn't think we ever allowed multi-dimensional slicing, so sorry if I missed that whole discussion. The prime reason I wanted slicing is to allow Vectors to interoperate with arrays. We have concluded that n-d vectors can never interoperate with n-d arrays, so I think there's not much point in trying to figure out n-d slices. The only thing that comes to mind is that an n-d array is still <pointer, len=N1N2...Nn>, so a slice could still be mapped onto the array. In that sense a slice is range indexing, and I can't remember if we still have that or not. |
|
WRT splatting, I'm not at all interested in extending the language at this point. |
|
consider it dead |
statements.rst: reset the column counter in the 3x3-square example so it actually prints a square; note that the single-statement post-predicated loop needs unbounded lookahead; make arrays the normative home for the array-valued-index prohibition and cross-reference it here. array.rst: state the in-bounds range for negative indices (-n..-1) and that a negative *left* slice bound is an IndexError while a negative right bound resolves from the end (including the two-sided i..-j form); change 'most binary operations' to 'every' with ==/!= called out as the sole collapsing exception; add two missing semicolons. matrix.rst: reorder the scalar-** broadcast paragraph so it no longer splits the m x n dimension rule; state that an empty matrix is 0x0 (rows and columns both 0). Assisted-by: Agent (claude) <ai@blobfish.icu>
expressions: note that >=3-iterator generators are a future addition and that the SyntaxError is a legitimate post-parse syntactic check. integer: reword 'mandatory -ffast-math' as required-to-be-supported-but-off-by-default. real: use an IEEE-754-exact % example (5.5 % 2.0 == 1.5) and note that real == is bit-exact. character: a \x escape with no hex digit is a LiteralError. boolean: add the non-short-circuit divide-by-zero trap example. vector: replace the confusing |type| metasyntax with named placeholders; make push(x)/append(x) notation uniform. tuple/struct: a type with fewer than two members is a TypeError; note the t1.1 real-literal lexer pitfall and that tuple index errors are compile-time. string: note that growth needs a var receiver (const string is fixed). comments: unterminated block comment is a SyntaxError. type_casting: integer->character uses the non-negative mod (as<character>(-1) is 0xFF), and an empty-literal cast is a TypeError. typealias: add the missing return 0 to the example. Assisted-by: Agent (claude) <ai@blobfish.icu>
procedures: clarify that a procedure call is a single-target assignment/declaration RHS and cannot be the source of a tuple-unpacking assignment (bind to one variable, then unpack); name the SyntaxError for the procedure = <stmt> form; state that AliasingError is always compile-time using the conservative same-backing-array rule; fix the indentation of the 'Legal' call block. declarations: make the self-initializer rule crisp -- a reference in a declaration's own initializer resolves to an enclosing binding if one exists, and is a SymbolError only when none does. constexpr: name GlobalError for a bad global initializer and note that a non-constexpr typealias size is not strictly exercised by the test battery. Assisted-by: Agent (claude) <ai@blobfish.icu>
… input State that an end-of-stream character read yields 0xFF (255), not -1, since characters are unsigned bytes, and that a real 0xFF byte is distinguishable from EOF only via stream_state (the reason it exists); make the two remaining -1 mentions and the state table consistent. Add an array-element lvalue example for input (v[2] <- std_input) and cross-reference expressions. Reword the real-input whitespace rule to mean the sign and digits must be contiguous. Assisted-by: Agent (claude) <ai@blobfish.icu>
Add spec/errors.rst as the normative Errors chapter (the set of error classes and when each must be emitted) and move the sec:errors anchor there, so the ~110 cross-references throughout the spec now resolve within the specification part rather than into the implementation book. impl/errors.rst is retitled 'Errors (Implementation)', re-anchored to sec:errors_impl, and back-references the taxonomy; it keeps the reporting mechanics (CompileTimeExceptions.h, the ANTLR listener, run_time_errors.h, examples, tester rules). The new page also blesses raising SyntaxError from a post-parse syntactic-validation pass (for >=3-iterator generators, multi-domain iterator loops, and function-argument qualifiers), and widens IndexError to cover tuple field indices (always compile-time, since a tuple index is a literal). Assisted-by: Agent (claude) <ai@blobfish.icu>
Rename the chapter to 'Built-in Functions, Procedures and Methods' (it also documents the stream_state procedure). Add a Signatures section giving each built-in an equivalent Gazprea signature using an exposition-only [T] type-parameter notation, with a note that type parameters are not part of the language and may be added later. Add a Vector and String Methods section that cross-references the vector method spec and a small table contrasting length(x) (built-in; arrays/vectors/strings) with x.len() (method; vectors/strings only, TypeError on arrays). Assisted-by: Agent (claude) <ai@blobfish.icu>
The disclaimer said glossary entries are terminology 'not statements of Gazprea semantics', which invited readers to skip entries that actually carry normative rules. Amend it to call out the load-bearing entries (zero value, initialization, re-initialization, domain, value type) as normative and cross-referenced to the chapter that states them in full, and add the missing cross-reference from the zero-value entry to the declarations chapter. Assisted-by: Agent (claude) <ai@blobfish.icu>
…rity State explicitly that arrays of rank 3 or more have no size query (length is rank-1, rows/columns rank-2), so their extents are currently unobservable -- a known limitation pending a future 'shape' built-in. Also note that pad-to-longest-row is a property of the nested literal, applying identically to a matrix, an array variable, or a vector of arrays, and that only incremental push/append pads to the first element instead (worked contrast in the vector chapter). Assisted-by: Agent (claude) <ai@blobfish.icu>
Consolidated spec-review stack — 20 PRs in one
This collapses the stacked-PR chain (
gh stack #121, formerly[stack 1/20]–[stack 20/20], PRs #110–#138) into a single PR againstmaster. The 20 individual PRs are closed; their branches and comment threads stay readable at the links in the manifest below. All 40 commits are preserved (nothing squashed) — use the Files changed tab for the cumulative diff, or the commit list for the logical steps.Footprint: 35 files, +1576 / −229. Spec: 24 files under
gazprea/spec/. Infra: the.agents/reproducibility scaffold +pyproject.toml/uv.lock(from #110).Provenance: grew out of the vector-semantics discussion (#106) and the Aug-17 rubric-meeting action item (#132). The final nested-types change closes #106, #82, #71, #101, #86.
The stack intentionally states a rule and later revises it. Review the final tree, not the intermediate commits:
vector<T>is a base type or 1-D array only; no vectors/tuples/structs/strings." [stack 20/20] spec(gazprea): permit nested aggregates and n-d arrays #138 reverses this to "any storable type, nested to any depth." Net: permissive.s1 struct_fieldexample to comply with the old "no struct-in-struct" rule; [stack 20/20] spec(gazprea): permit nested aggregates and n-d arrays #138 lifts that rule (struct.rst:10,tuple.rst:6). Net: nesting legal; the example simply no longer demonstrates it.T[n1]...[nk], any element type.Open questions flagged for reviewer attention (per former PR)
These are the ambiguities each PR flagged as needing a language-owner decision (verify against
gazc/ the reference compiler where noted).#122 — combined struct decl form: does
struct S (...) x;accept a qualifier (var struct S (...) x;)? The "combined form is alwaysconst" claim is inferred from examples; ifgazcacceptsvar, it's wrong.#123 — mixed concatenation result: is
char || chareven legal? (patch's "(or characters)" assertschar||char = char[].) Is the rule "at least one operand isstring" or "either operand"?char[] || char[] = char[]is unexemplified.#124 — tuple member conversions: members "convert by their kind" lists only scalar + array. Nested
tuple,string,matrixmembers are unaddressed — now relevant post-#138. Confirm array-in-tuple casts pad/truncate like standalone array casts.#125 — uninit const & stride:
byis restricted to>= 1(rules outby -1reverse iteration — intended?). Uninitconstis defined as "holds the zero value permanently" — confirm that vs. treating uninit-const as a type error.#126 — slice bounds: confirm
..-iselects1..n-i; confirm the 1-indexed assertion doesn't contradict another chapter; confirm grammar acceptsa[i..j] by 2(vsa[i..j by 2]).#127 — struct fixed-length field: patch chose
character[256]overstring[256]. If sized/bounded strings are intended, the fix differs. (See cross-PR note — the nesting ban it obeyed is now reversed.)#128 — procedure call sites: are casts legal on a procedure-call result? (patch adds "and casts" to the operator whitelist.) Confirm the restatement's disallowed-site list is the exact inverse of the chapter-top allowed list.
#129 — method-call surface (largest
feat:): (a) receivers are variables only — doesgazcacceptx.len()on a literal/expression result? (b) mutation limited to function-local vars vs. "no global/captured mutation" — which framing? (c)appendtie-break = single-element wins — matches reference? (d) "method call is the only expression usable as a statement" — reconcile withcallstatements. (e)concat→appendrename is breaking — does the reference still exposeconcatas an alias?#114 — range vs array-index: is matrix slicing intended? (
matrix.rst:103still "indices must be integers"). No formal def of "range syntax" vs "range expression";by-on-slice rule lives in a different chapter and may drift.#115 — promotion vs cast:
type_casting.rst:48still says "promoted" for an explicitas<>cast;type_casting.rst:95allows array→array casts across dimensions — confirm 1-D↔2-D explicit cast is intended.#138 — nested aggregates & n-d arrays (deferred, not in scope): a rank-agnostic
shapeinterface (vsrows/columns, per #82), n-d matrix-multiply, and broadcasting are left to a follow-up. The ragged (vector<vector<T>>) vs rectangular (vector<T[*]>,T[*][*]) split from #101 is implied but not yet spelled out operationally.Lower-priority / follow-ups noted in-PR: #116 (
string.rst:8loose equivalence); #118 (integer.rstAssociativity column duplication); #111 (expressions.rst:54generator-error class; repo-wide "must emit" sweep); #112 (proseString/Vectorcapitalization left per maintainer decision). #119, #120, #130 flagged no open questions.Infra (
.agents/scaffold, from #110) — open feedback digestPR #110 drew 25 inline comments (@Sir-NoChill). Themes (several already addressed by later commits on the same branch —
223c45ddrop identity prescription & generated artifacts,6e2bbbapoint .agents/ at the DocsDoc image + rewrite skills,66ae45fuv lockfile):agent-pubkey.asc. (→ addressed by223c45d.)bootstrap.sh/check.sh/healthcheck.share rendered from templates; gitignore them and regenerate on setup. (→ addressed by223c45d.)cmput415/ci-utils(optionally a nix flake); point the manifest/README at the container instead of a bash env harness. (→ partly addressed by6e2bbba.)uv—pyproject.toml+uv syncon a uv-managed venv. (→ addressed by66ae45f+ pyproject.)jinja2— use the jinja2 package rather than a custom renderer (render.pywas later approved: "This is perfect").grammar-consistencyskill — focus on English prose quality (spelling, passive voice, subject consistency, technical writing), not the Gazprea EBNF (that's the students' exercise); thespec-reviewskill should consume it after the rewrite. (→ addressed by6e2bbba.)actrather than a hand-rolledcheck-ci.shbash harness; add a.agents/scratchgitignore entry.Full thread: #110.
Manifest — the 20 collapsed PRs
chore/agents-scaffoldfix/undefined-syntax-examplesfix/vector-element-typesfix/vector-array-divergencefix/range-index-semanticsfix/promotion-cast-claimrefactor/precedence-single-homerefactor/stream-state-homerefactor/const-default-homefeat/errors-chapterfix/struct-decl-formfix/string-concat-resultfix/tuple-conversion-membersfix/uninit-const-and-stridefix/slice-boundsfix/struct-nestingfix/procedure-call-sitesfeat/method-callsfix/example-correctionsfeat/nested-composite-typesConsolidated from
gh stack #121. Individual PRs closed in favour of this one; commit history preserved.