diff --git a/.agents/bootstrap.sh.tmpl b/.agents/bootstrap.sh.tmpl new file mode 100644 index 0000000..490fa1f --- /dev/null +++ b/.agents/bootstrap.sh.tmpl @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# {{ project }} session bootstrap -- rendered from bootstrap.sh.tmpl. +# Edit .agents/manifest.yaml and re-render; do not edit this file directly. +set -euo pipefail + +AGENTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STATE_DIR="${AGENTS_DIR}/state" +mkdir -p "${STATE_DIR}" +cd "${AGENTS_DIR}/.." + +log() { printf '[bootstrap] %s\n' "$*"; } +fail() { printf '[bootstrap] ERROR: %s\n' "$*" >&2; exit 1; } + +# ---------------------------------------------------------------- apt packages +export DEBIAN_FRONTEND=noninteractive + +APT_PACKAGES=( +{% for pkg in apt_packages %} + "{{ pkg.name }}{% if pkg.version %}={{ pkg.version }}{% endif %}" +{% endfor %} +) + +SUDO="" +if [[ "$(id -u)" -ne 0 ]]; then + command -v sudo >/dev/null || fail "not root and sudo unavailable" + SUDO="sudo" +fi + +missing=() +for spec in "${APT_PACKAGES[@]}"; do + name="${spec%%=*}" + if ! dpkg-query -W -f='${Status}' "${name}" 2>/dev/null | grep -q 'install ok installed'; then + missing+=("${spec}") + fi +done + +if ((${#missing[@]})); then + log "installing: ${missing[*]}" + ${SUDO} apt-get update -q || log "apt-get update warning; attempting install anyway" + ${SUDO} apt-get install -q -y --no-install-recommends "${missing[@]}" +else + log "apt dependencies already satisfied" +fi + +# ------------------------------------------------------------------------ uv +if ! command -v uv >/dev/null; then + log "installing uv" + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="${HOME}/.local/bin:${PATH}" + command -v uv >/dev/null || fail "uv install completed but binary not on PATH" +fi +log "uv $(uv --version | awk '{print $2}')" + +(cd "${AGENTS_DIR}/.." && uv sync) + +# ------------------------------------------------------- optional signing key +# Only mints a key when the manifest names an identity. Otherwise signing is +# left to whatever the user has configured in git. +AGENT_GIT_NAME="{{ agent.name or '' }}" +AGENT_GIT_EMAIL="{{ agent.email or '' }}" + +if [[ -n "${AGENT_GIT_NAME}" && -n "${AGENT_GIT_EMAIL}" ]]; then + AGENT_GNUPGHOME="${AGENTS_DIR}/gnupg" + AGENT_UID="${AGENT_GIT_NAME} <${AGENT_GIT_EMAIL}>" + + live_fpr() { + gpg --homedir "${AGENT_GNUPGHOME}" --list-secret-keys --with-colons "${AGENT_GIT_EMAIL}" 2>/dev/null \ + | awk -F: '$1=="sec" && $2!="e" && $2!="r" {take=1; next} + take && $1=="fpr" {print $10; exit}' + } + + fpr="$(live_fpr || true)" + if [[ -z "${fpr}" ]]; then + log "generating agent key for ${AGENT_UID}" + rm -rf "${AGENT_GNUPGHOME}" + mkdir -p "${AGENT_GNUPGHOME}" && chmod 700 "${AGENT_GNUPGHOME}" + gpg --homedir "${AGENT_GNUPGHOME}" --batch --pinentry-mode loopback --passphrase '' \ + --quick-generate-key "${AGENT_UID}" ed25519 sign "{{ agent.key_expiry or '1w' }}" + fpr="$(live_fpr)" + [[ -n "${fpr}" ]] || fail "key generation produced no usable secret key" + else + log "reusing agent key ${fpr}" + fi + + cat > "${AGENTS_DIR}/agent-env.sh" < "${STATE_DIR}/manifest.sha256" + +: > "${STATE_DIR}/dpkg-versions.txt" +for spec in "${APT_PACKAGES[@]}"; do + dpkg-query -W -f='${Package} ${Version}\n' "${spec%%=*}" >> "${STATE_DIR}/dpkg-versions.txt" +done + +{ +{% for s in sentinels %} + sha256sum "{{ s }}" +{% endfor %} +} > "${STATE_DIR}/sentinels.sha256" + +log "bootstrap complete" diff --git a/.agents/check.sh.tmpl b/.agents/check.sh.tmpl new file mode 100644 index 0000000..a567046 --- /dev/null +++ b/.agents/check.sh.tmpl @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# {{ project }} dependency validation -- rendered from check.sh.tmpl. +# Edit .agents/manifest.yaml and re-render; do not edit this file directly. +set -euo pipefail + +AGENTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +failures=0 +flag() { printf '[check] FAIL: %s\n' "$*" >&2; failures=$((failures + 1)); } +ok() { printf '[check] ok: %s\n' "$*"; } + +# dpkg state proves the package installed; a runnable command on PATH proves +# it usable -- either alone gives false confidence [arslan2019] +{% for pkg in apt_packages %} +if dpkg-query -W -f='${Status}' "{{ pkg.name }}" 2>/dev/null | grep -q 'install ok installed'; then + ok "package {{ pkg.name }}" +else + flag "package {{ pkg.name }} not installed" +fi +{% endfor %} + +{% for c in commands %} +if command -v "{{ c.cmd }}" >/dev/null; then + ok "command {{ c.cmd }} ($("{{ c.cmd }}" {{ c.version_flag }} 2>&1 | head -n 1))" +else + flag "command {{ c.cmd }} missing from PATH (expected via {{ c.package }})" +fi +{% endfor %} + +# Signing identity is optional. Only enforce agent-env.sh when the manifest +# named an agent identity for this repo. +{% if agent.name and agent.email %} +if [[ -r "${AGENTS_DIR}/agent-env.sh" ]]; then + # shellcheck source=/dev/null + source "${AGENTS_DIR}/agent-env.sh" + if gpg --homedir "${AGENT_GNUPGHOME}" --list-secret-keys --with-colons "${AGENT_SIGNING_KEY}" 2>/dev/null \ + | awk -F: '$1=="sec" && $2!="e" && $2!="r" {found=1} END {exit !found}'; then + ok "agent signing key ${AGENT_SIGNING_KEY}" + else + flag "agent signing key ${AGENT_SIGNING_KEY} missing, expired, or revoked" + fi +else + flag "agent-env.sh missing; run .agents/bootstrap.sh" +fi +{% else %} +ok "no agent identity configured; signing check skipped" +{% endif %} + +if ((failures)); then + printf '[check] %d failure(s); run .agents/bootstrap.sh or fix the manifest\n' "${failures}" >&2 + exit 1 +fi +printf '[check] all dependencies validated\n' diff --git a/.agents/healthcheck.sh.tmpl b/.agents/healthcheck.sh.tmpl new file mode 100644 index 0000000..2211be4 --- /dev/null +++ b/.agents/healthcheck.sh.tmpl @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# {{ project }} environment healthcheck -- rendered from healthcheck.sh.tmpl. +# Edit .agents/manifest.yaml and re-render; do not edit this file directly. +# +# Compares the live environment against the baselines bootstrap.sh recorded, +# the IaC drift-detection pattern: desired state is code, drift is divergence +# from it [hashicorp-drift]. Verdict contract (machine-readable last line): +# HEALTHCHECK: ok exit 0 environment matches baselines +# HEALTHCHECK: drift exit 1 recoverable divergence; re-render/re-bootstrap +# HEALTHCHECK: corrupt exit 2 integrity violation; stop and tell the user +set -uo pipefail + +AGENTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STATE_DIR="${AGENTS_DIR}/state" +cd "${AGENTS_DIR}/.." || { echo "HEALTHCHECK: corrupt"; exit 2; } + +verdict=0 +note() { printf '[healthcheck] %s\n' "$*"; } +drift() { note "DRIFT: $*"; ((verdict < 1)) && verdict=1; } +corrupt() { note "CORRUPT: $*"; verdict=2; } + +if [[ ! -d "${STATE_DIR}" ]]; then + corrupt "no recorded baselines; .agents/bootstrap.sh has never completed here" +fi + +# Manifest changed since the scripts were rendered => scripts are stale +if [[ -f "${STATE_DIR}/manifest.sha256" ]]; then + current="$(sha256sum "${AGENTS_DIR}/manifest.yaml" | awk '{print $1}')" + recorded="$(cat "${STATE_DIR}/manifest.sha256")" + [[ "${current}" == "${recorded}" ]] \ + || drift "manifest.yaml changed after last bootstrap; re-render *.tmpl and re-run bootstrap" +fi + +# Package set drift: removed packages are corruption, version churn is drift +{% for pkg in apt_packages %} +if ! dpkg-query -W -f='${Status}' "{{ pkg.name }}" 2>/dev/null | grep -q 'install ok installed'; then + corrupt "required package {{ pkg.name }} is no longer installed" +fi +{% endfor %} +if [[ -f "${STATE_DIR}/dpkg-versions.txt" ]]; then + while read -r name recorded_ver; do + live_ver="$(dpkg-query -W -f='${Version}' "${name}" 2>/dev/null || true)" + [[ "${live_ver}" == "${recorded_ver}" ]] \ + || drift "package ${name} moved ${recorded_ver} -> ${live_ver:-} since bootstrap" + done < "${STATE_DIR}/dpkg-versions.txt" +fi + +# Sentinel files: silent mutation of the environment's own machinery +if [[ -f "${STATE_DIR}/sentinels.sha256" ]]; then + if ! sha256sum --check --quiet "${STATE_DIR}/sentinels.sha256" 2>/dev/null; then + corrupt "sentinel file checksum mismatch (see: sha256sum --check ${STATE_DIR}/sentinels.sha256)" + fi +fi + +# Bundled skills: each listed name must exist under .agents/skills/ with a +# non-empty SKILL.md. These are checked-in review helpers, not workspace +# copies of external skill libraries. +{% for s in skills %} +if [[ ! -s "${AGENTS_DIR}/skills/{{ s }}/SKILL.md" ]]; then + drift "bundled skill '{{ s }}' missing or empty at .agents/skills/{{ s }}/SKILL.md" +fi +{% endfor %} + +# Agent key: only enforced when the manifest configured an identity +{% if agent.name and agent.email %} +if [[ -r "${AGENTS_DIR}/agent-env.sh" ]]; then + # shellcheck source=/dev/null + source "${AGENTS_DIR}/agent-env.sh" + gpg --homedir "${AGENT_GNUPGHOME}" --list-secret-keys --with-colons "${AGENT_SIGNING_KEY}" 2>/dev/null \ + | awk -F: '$1=="sec" && $2!="e" && $2!="r" {found=1} END {exit !found}' \ + || drift "agent signing key unusable (likely expired); re-run bootstrap to mint a fresh one" +else + drift "agent-env.sh missing; re-run bootstrap" +fi +{% endif %} + +# Project-specific indicators from the manifest +{% for c in extra_checks %} +if ! bash -c '{{ c.cmd }}' >/dev/null 2>&1; then + drift "extra check failed: {{ c.name }}" +fi +{% endfor %} + +case "${verdict}" in + 0) echo "HEALTHCHECK: ok" ;; + 1) echo "HEALTHCHECK: drift" ;; + *) echo "HEALTHCHECK: corrupt" ;; +esac +exit "${verdict}" diff --git a/.agents/manifest.yaml b/.agents/manifest.yaml new file mode 100644 index 0000000..d638ab8 --- /dev/null +++ b/.agents/manifest.yaml @@ -0,0 +1,66 @@ +# Render context for .agents/*.tmpl and desired state for healthcheck.sh. +# Committed with a blank agent identity; users fill it in locally (or leave +# it blank to skip signing entirely). Identity is not prescribed by this +# repo. + +project: 415-docs + +# Preferred way to run a review session: pull the DocsDev image maintained +# in cmput415/ci-utils. It bundles sphinx, latexmk, texlive, lychee, act, +# uv, graphviz, and gnupg, so the apt_packages/commands blocks below are +# only exercised when running natively without the image. +docker: + image: ghcr.io/cmput415/docs-dev:latest + source: https://github.com/cmput415/ci-utils/tree/main/DocsDev + +# apt packages the bootstrap installs when running natively. Python +# packages are managed separately by uv (see `pyproject.toml`). +apt_packages: + - name: gnupg + version: null + - name: graphviz + version: null + - name: curl + version: null + +# Runnable-on-PATH checks. `uv` is bootstrapped by the install script when +# missing, so it belongs in commands rather than apt_packages. +commands: + - cmd: gpg + package: gnupg + version_flag: --version + - cmd: dot + package: graphviz + version_flag: -V + - cmd: uv + package: uv + version_flag: --version + +# Signing identity for `git agent-commit`. Blank by default -- fill in +# locally to opt in; leave blank to skip GPG entirely. Not prescribed. +agent: + name: null + email: null + key_expiry: 1w + +# Skills bundled with this repo for review/consistency work over the spec. +# Each entry names a directory under `.agents/skills/` that contains a +# SKILL.md file. The healthcheck asserts these directories still exist. +skills: + - spec-review + - grammar-consistency + +sentinels: + - .agents/bootstrap.sh.tmpl + - .agents/check.sh.tmpl + - .agents/healthcheck.sh.tmpl + - .agents/render.py + - .agents/manifest.yaml + - pyproject.toml + +# Project-specific readiness probes evaluated by healthcheck. +extra_checks: + - name: inside the 415-docs repo + cmd: git rev-parse --is-inside-work-tree + - name: sphinx importable from the uv-managed venv + cmd: uv run python -c "import sphinx, sys; sys.exit(0 if sphinx.__version__.startswith(\"6.2\") else 1)" diff --git a/.agents/render.py b/.agents/render.py new file mode 100644 index 0000000..408b9a7 --- /dev/null +++ b/.agents/render.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Render a Jinja2 template against a YAML manifest. + +Usage: render.py MANIFEST.yaml TEMPLATE.tmpl > OUTPUT + +Dependencies (`jinja2`, `PyYAML`) are declared in the repo's pyproject.toml +and provisioned by `uv sync`; run this script under `uv run` from a fresh +checkout so those are guaranteed available. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import jinja2 +import yaml + + +def render(manifest_path: Path, template_path: Path) -> str: + scope = yaml.safe_load(manifest_path.read_text()) + if not isinstance(scope, dict): + raise ValueError("manifest must be a mapping at the top level") + # Comment delimiter is remapped away from `{# #}` because the default + # collides with bash array-length syntax `${#name[@]}` in shell templates. + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(str(template_path.parent)), + undefined=jinja2.StrictUndefined, + keep_trailing_newline=True, + comment_start_string="{##", + comment_end_string="##}", + ) + return env.get_template(template_path.name).render(scope) + + +def main(argv: list[str]) -> int: + if len(argv) != 3: + print(__doc__, file=sys.stderr) + return 2 + sys.stdout.write(render(Path(argv[1]), Path(argv[2]))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/.agents/skills/grammar-consistency/SKILL.md b/.agents/skills/grammar-consistency/SKILL.md new file mode 100644 index 0000000..80fe3e6 --- /dev/null +++ b/.agents/skills/grammar-consistency/SKILL.md @@ -0,0 +1,143 @@ +--- +name: grammar-consistency +description: English-prose consistency check over the spec. Use whenever a change touches Sphinx RST under `gazprea/spec/` (or a sibling doc directory) and you want to catch the writing-quality issues a careful copy-editor would: spelling and typos, unjustified passive voice, subject/tense drift within a paragraph, inconsistent terminology, agreement errors, and technical-writing anti-patterns (weasel words, wandering pronouns, unmotivated jargon). This skill audits English usage only -- it does NOT critique the Gazprea language grammar or its EBNF surface. Deriving the Gazprea grammar from the informal spec examples is part of the assignment for CMPUT 415 students, so keep grammar-of-Gazprea observations out of the report. Pair with [[spec-review]] for structural/build/reference review of the same files. +--- + +# Grammar consistency (English prose) + +The Gazprea spec is written for students who then implement a compiler +from it. Ambiguity in the English prose costs student time and produces +divergent implementations; this skill catches that ambiguity before it +ships. Scope is strictly English usage in the RST sources. The Gazprea +language's own grammar is intentionally out of scope -- the exercise for +students is to derive it from the examples and clarify with the reference +compiler where needed. + +## 1. What to look for + +Apply the checks below to every paragraph of prose the change touches +(chapter body, admonition body, list items, table cells). Skip fenced +code samples (``.. code-block::``) and directive arguments. + +### 1.1 Spelling and typography + +- Real typos and misspellings (`recieve`, `seperate`, `occured`). +- Locale drift within a single file: pick either US (`initialize`, + `behavior`) or UK (`initialise`, `behaviour`) and hold it. The spec's + established convention is US spelling; flag UK spellings as changes to + align, not stylistic preferences. +- Straight vs. curly quotes: RST source uses straight quotes; a curly + quote copied in from a word processor is a build-time hazard. +- Doubled words (`the the`, `to to`) and stray whitespace inside + sentences. +- Product/library/tool names spelled inconsistently (`Sphinx` vs. + `sphinx`, `GitHub` vs. `Github`, `Gazprea` vs. `gazprea` when used as a + proper noun in prose rather than a code identifier). + +### 1.2 Passive voice + +Passive voice is not forbidden, but it should be justified. Flag a +passive construction when: + +- The agent is important and the sentence hides it ("the value is + promoted" -- by what?), especially in normative statements. +- The passive is being used to duck a shall/must claim ("errors are + raised" instead of "the implementation shall raise an error"). +- Two consecutive sentences are both passive and could be flipped to + active without loss. + +Leave passives alone when the agent is genuinely irrelevant, when the +patient is the topic of the paragraph, or when the active form would +require inventing a subject the spec does not otherwise name. + +### 1.3 Subject and tense consistency + +- Subject drift inside a paragraph: `you` -> `the programmer` -> `one` -> + `we` across three sentences forces the reader to re-resolve reference. + Pick one and hold it for the paragraph (the spec's default is `the + program` / `the implementation` for normative claims and `you` for + tutorial-style prose). +- Tense drift: normative statements should stay in the present indicative + (`the type is`, `the operator returns`), not slip into future + (`the type will be`) or subjunctive (`the type would be`) except when + the surrounding logic genuinely requires it. +- Number agreement: `each of the operators return` -> `returns`; + `a list of expressions are` -> `is`. + +### 1.4 Terminology consistency + +- The same concept named two ways in the same file: `element type` vs. + `component type`, `bounds check` vs. `range check`, `identity value` + vs. `zero value`. Pick one per file (ideally per chapter) and note the + divergence. +- Glossary terms used without `:term:` on first mention within a section. + Cross-check against `gazprea/spec/glossary.rst`. +- Editorial synonyms creeping in ("a.k.a.", "or, equivalently", "in + other words") that redefine a term already introduced elsewhere. + +### 1.5 Technical-writing anti-patterns + +- Weasel words in normative prose: `may`, `might`, `could`, `probably`, + `should` (when the RFC 2119 meaning is intended, use `MUST`/`SHALL` + explicitly in a `.. note::`). +- Ambiguous pronouns: `this`, `that`, `it` without an unambiguous + antecedent in the previous sentence. +- Unmotivated jargon: a term introduced without definition on first use. +- Overloaded phrasing: `the type of the type` type constructions where a + rewrite would flatten the sentence. +- Long sentences (>~40 words) that could be split without losing the + logical connective; especially in normative claims. + +## 2. Method + +1. **Extract the prose surface** from the diff (or full-file scan). RST + sources contain both prose and directives; strip directive bodies + before running text checks. +2. **Run mechanical checks first** (spelling, doubled words, quote style, + locale). These are cheap and their output frames what a human editor + would then look at. +3. **Read the prose sequentially** to catch subject/tense/terminology + drift; these require paragraph-level context and are not reliably + caught by tooling. +4. **Cross-reference terminology** against `glossary.rst` and the file's + own first-use conventions. +5. **Compose with [[spec-review]]** for anything that is structural + rather than prose (cross-references, heading hierarchy, code-block + correctness). Do not duplicate its findings. + +## 3. Report structure + +Group findings by category (spelling, passive voice, subject/tense, +terminology, anti-patterns). Within each category list `file:line`, +a one-sentence claim, and a concrete rewrite where the fix is +mechanical. Do not list what passed. Close with the machine-readable +verdict: + + GRAMMAR-CONSISTENCY: clean | advisory | blocking + +`blocking` when a defect changes the meaning of a normative statement or +would confuse a student implementing from the spec; `advisory` for +readability improvements that do not change meaning; `clean` when the +prose surface the change touched has no findings. + +## 4. What this skill does NOT do + +- It does not comment on the Gazprea language's own grammar, EBNF, or + syntax rules. That is the students' exercise; the spec's informal + examples are the intended interface. +- It does not rewrite prose beyond mechanical fixes; the author decides + substantive rewrites. +- It does not lint the code inside `.. code-block::` blocks -- that is + [[spec-review]] 2.5 (parse/typecheck via the reference compiler). +- It does not enforce a house style guide that is not documented in this + file. If the repo adds a `STYLE.md`, port the checks here rather than + inventing them ad hoc. + +## 5. Composition + +- Run [[spec-review]] first for structural/build issues, then this skill + for prose quality. A file that is structurally broken (build fails, refs + unresolved) is not worth a prose pass yet. +- If the change touches only prose, this skill runs first and + [[spec-review]] runs as a lighter follow-up (skip the parse-block + step). diff --git a/.agents/skills/spec-review/SKILL.md b/.agents/skills/spec-review/SKILL.md new file mode 100644 index 0000000..5b981ac --- /dev/null +++ b/.agents/skills/spec-review/SKILL.md @@ -0,0 +1,154 @@ +--- +name: spec-review +description: Systematic editorial and structural review of one or more Sphinx source files in `gazprea/spec/`. Use this skill whenever you are asked to review, audit, sanity-check, or "read through" spec content in this repository -- including PR review over spec changes, pre-merge checks on a feature branch, or a fresh pass over an existing chapter. It defines the checklist a careful maintainer applies: heading hierarchy, cross-reference (`:ref:`, `:term:`, `:doc:`) integrity, glossary term coverage, admonition usage, RST directive correctness, unresolved TODO/FIXME/XXX markers, and a `gazc`-backed sanity check on inline Gazprea code blocks. Compose with [[grammar-consistency]] for English-prose quality on the same files (spelling, passive voice, subject/tense drift, terminology); this skill deliberately does not cover those. Glossary entry sourcing lives in the memory `gazprea-glossary-source-audit`. +--- + +# Spec review + +You are reviewing Sphinx-format specification source in `gazprea/spec/`. The +goal is the review a human editor performs before a chapter merges: catch +structural problems, broken cross-references, missing glossary links, and +code examples that no longer parse -- without rewriting the author's prose. + +Scope this skill to spec content only (`gazprea/spec/**/*.rst`, +`gazprea/index.rst`). Non-spec RST (`base/`, `template/`, `info/`, other +languages under `scalc/`, `vcalc/`, `generator/`) is out of scope; if the +change touches those, note it and stop. + +## 1. Report structure + +Report findings in one grouped list, most-severe first. For each finding +give: file:line, category, one-sentence claim, and one concrete example of +how it manifests (what the reader sees, what breaks). Do not list what +passed -- silence means "checked, fine". End with a single-line verdict: + + SPEC-REVIEW: clean | advisory | blocking + +`blocking` when any finding would break the Sphinx build or a normative +claim; `advisory` for everything else; `clean` only when the checklist +below ran end-to-end with no findings. + +## 2. The checklist + +Apply these in order. Skip a section only when it does not apply (e.g. no +code blocks in the file), and say so in the report. + +### 2.1 Build integrity + CI parity + +Replay the real CI workflows locally with [`act`](https://github.com/nektos/act) +rather than a hand-rolled shell harness. `act` is preinstalled in the +DocsDev image (`ghcr.io/cmput415/docs-dev`); if you are running natively, +install it once and re-use across sessions. + + act -j build # replays .github/workflows/deploySite.yml + act -j linkcheck # replays .github/workflows/linkcheck.yml (if present) + +The two workflows cover: + +- `deploySite.yml` -- Sphinx html + latexpdf over every doc subdirectory + listed in the top-level Makefile (`setup generator lolcode vcalc + gazprea info`). +- `linkcheck.yml` -- `lychee` over the file globs and args CI uses. + +Any Sphinx warning that becomes an error, any RST parse failure, any +unresolved cross-reference, and any `lychee`-reported broken link is +`blocking`. + +For a stricter local pass than CI's own Sphinx step, invoke the build +directly with warnings-as-errors and nit-picky mode after (or instead of) +the workflow replay: + + uv run sphinx-build -W -n -q -b html gazprea gazprea/_build/html + +Passing this stricter form is a stronger guarantee than passing CI alone. + +### 2.2 Heading hierarchy + +- Underline characters must form a consistent hierarchy within a file. + Sphinx accepts any set of characters, but re-using a character at a + different level in the same file collapses the TOC. +- Chapter files (top of `gazprea/spec/`) use `=` for the title, `-` for + sections, `~` for subsections, `^` for subsubsections. Nested files + under `types/` inherit from their parent -- do not restart at `=`. +- A single `=` title per file; skip a level (title, then `~` + subsubsection) is `blocking` because Sphinx silently promotes. + +### 2.3 Cross-references + +- Every normative term the file uses -- "L-value", "R-value", + "promotion", "constant expression", "identity value", etc. -- should + be a `:term:` link to `glossary.rst` on its first meaningful mention + in the file. Repeat mentions in the same section do not need to + re-link. +- Chapter-to-chapter references use `:doc:`, not raw text. Section + references use `:ref:` against an explicit label + (`.. _section-label:`) placed immediately above the heading. +- A `:term:` reference whose target does not exist in `glossary.rst` is + `blocking`. A missing `:term:` on a first mention of a defined term + is `advisory`. + +### 2.4 Admonitions and directives + +- Normative statements ("must", "shall") that are hidden in prose + should be lifted into `.. note::`, `.. warning::`, or `.. important::` + where the surrounding paragraphs make the emphasis worthwhile. Do + not over-lift; over-use of admonitions dilutes their signal. +- `.. code-block:: gazprea` is the correct language tag for inline + Gazprea samples (not `gz`, not `gazp`). Fenced examples without a + language tag lose syntax highlighting and are `advisory`. + +### 2.5 Code examples parse + +- Extract every ` .. code-block:: gazprea ` block in the file to a temp + directory, one file per block, and run `gazc --parse-only` (or + `--typecheck` when the block is a full program) on each. A block that + fails to parse or typecheck is `blocking` unless the surrounding + prose explicitly marks it as intentionally invalid ("this program is + rejected because..."). Store the parser output next to the extracted + file for the report. +- Do not attempt to lower or execute; parse/typecheck is enough for + spec review. Execution semantics belong in the test corpus. + +### 2.6 TODO / FIXME / XXX / editorial residue + +- `TODO`, `FIXME`, `XXX`, `NOTE:`, or bracketed `[ ... ]` placeholders + in shipping spec text are `blocking`. Comments-out placeholders in + RST (`.. TODO:`) are `advisory` -- flag but do not block. +- Trailing whitespace, tab characters (RST wants spaces), and mixed + indent within a directive body are `advisory`. + +### 2.7 Consistency with sibling files + +- If the file describes behavior that another file also describes + (e.g. `types/array.rst` and `types/vector.rst` on element-type + rules), spot-check that the two do not contradict. This is a + targeted check, not an exhaustive cross-file diff; that is + [[spec-lattice-consistency]] territory. +- Prose-quality review (spelling, passive voice, subject/tense drift, + terminology consistency) is out of scope for this skill; run + [[grammar-consistency]] over the same files and note that the + delegation happened. The two skills are designed to compose: run this + one first for structural/build issues, then grammar-consistency for + the English pass. + +## 3. Invocation contract + +The skill is invoked with a set of one or more files (a chapter, a +subsection, or a diff). Default to reviewing all files under +`gazprea/spec/` if no scope is given. + +For a PR review, restrict the code-example parse check to blocks that +were added or modified in the diff -- rerunning `gazc` over unchanged +blocks recomputes existing state and rarely surfaces new findings. + +## 4. What this skill does NOT do + +- It does not rewrite prose. Findings describe the problem; the author + fixes it. If a fix is trivial and mechanical (a broken `:term:` + target, a wrong language tag), propose the exact edit in the finding. +- It does not check normative correctness against the reference + implementation. That is a separate `spec-example-check` skill (not + bundled). +- It does not enforce style guide preferences that are not in this + checklist. If the repository grows a `STYLE.md`, add the checks + here; do not invent them ad hoc. diff --git a/.gitignore b/.gitignore index d6b4d8c..8d96651 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,17 @@ tmp/ # Ignore Python bytecode caches. __pycache__/ *.pyc + +# uv-managed virtualenv for the pyproject +.venv/ + +# agent session machinery (volatile) +.agents/gnupg/ +.agents/state/ +.agents/agent-env.sh +.agents/agent-pubkey.asc +.agents/scratch/ +# rendered outputs of .agents/*.tmpl -- regenerated each session by render.py +.agents/bootstrap.sh +.agents/check.sh +.agents/healthcheck.sh diff --git a/README.md b/README.md index 5161871..40101b3 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,57 @@ The site is automatically updated using a Github Action when For more details on the Github Action workflow, see `.github/workflows/deploySite.yml` + +## Agent sessions + +The `.agents/` scaffold is opt-in tooling for reproducible agent-run review +sessions over the spec. + +### Docker (preferred) + +The [`ghcr.io/cmput415/docs-dev`](https://github.com/cmput415/ci-utils) +image bundles the toolchain the review session needs (Sphinx + latexmk + +texlive, `lychee`, `uv`, [`act`](https://github.com/nektos/act) for +replaying the repo's GitHub Actions locally, and `gnupg` + `graphviz`). +Bump the tag reference in `.agents/manifest.yaml` when the ci-utils image +is rebuilt; keep this README in sync. + + docker run --rm -it -v "$PWD":/workspace \ + ghcr.io/cmput415/docs-dev:latest bash + +Inside the container you can go straight to `uv sync && make -C gazprea +html` or `act -j build` without any host apt work. + +### Native (fallback) + +If you cannot use the image, render the three shell helpers from their +templates and run bootstrap. The rendered scripts are **not committed** -- +regenerate them at the start of each session: + + for t in .agents/*.tmpl; do + uv run .agents/render.py .agents/manifest.yaml "$t" > "${t%.tmpl}" + chmod +x "${t%.tmpl}" + done + .agents/bootstrap.sh + [[ -r .agents/agent-env.sh ]] && source .agents/agent-env.sh + +`bootstrap.sh` installs system packages, installs `uv` if missing, runs +`uv sync` to provision the Python venv from `pyproject.toml`, and records +baselines that `healthcheck.sh` compares the live environment against. + +### Identity + +Commit signing is opt-in and not prescribed by this repo. The `agent:` +block in `manifest.yaml` is blank; bootstrap only mints a GPG signing key +when you fill in a name and email. Whether you sign, and under what +identity, is your call -- treat the blank template as the shared committed +state and keep any populated copy local. If you do configure signing, +register the exported public key +(`gpg --homedir .agents/gnupg --armor --export `) on the forge before +your first push. + +### Skills + +Bundled skills for spec review/consistency work live under +`.agents/skills/` and are listed in `manifest.yaml`'s `skills:`. The +healthcheck confirms each listed skill still has a non-empty `SKILL.md`. diff --git a/gazprea/impl/errors.rst b/gazprea/impl/errors.rst index 6da69bf..237ecc9 100644 --- a/gazprea/impl/errors.rst +++ b/gazprea/impl/errors.rst @@ -1,7 +1,13 @@ -.. _sec:errors: +.. _sec:errors_impl: -Errors -====== +Errors (Implementation) +======================= + +The **normative error taxonomy** -- the set of error classes and the condition +under which each must be emitted -- lives in the specification part, at +:ref:`sec:errors`. This chapter covers only the *mechanics* of reporting those +errors; the per-class notes below are implementation reminders that defer to +that taxonomy. Your implementation is required to report both :term:`compile-time ` and :term:`run-time ` errors. You must use the exceptions defined in ``include/CompileTimeExceptions.h`` and @@ -65,7 +71,9 @@ keyword. throw MainError(1, "program does not have a main procedure"); -Here are the compile-time errors your compiler must throw: +The compiler must throw the following exceptions. Each corresponds to an error +class defined normatively in :ref:`sec:errors`; the notes here add +implementation-specific reminders (line numbers, tester leniency): * ``SyntaxError`` @@ -103,16 +111,18 @@ Here are the compile-time errors your compiler must throw: Raised during compilation if the program detects a function or procedure with a return value that does not have a return statement reachable by all control flows. Control flow constructs may be assumed to always be undecidable, - meaning they may branch in either direction. - - If the subroutine has a ``return`` statement with a type that does not - match the owning subroutine's type, the line number of the ``return`` - statement should be reported, along with the name and (correct) type of the - enclosing routine. - - Note also that, strictly speaking, this is a type error, not a return error. - If the procedure/function is missing a ``return`` statement, then the line - number of the subroutine declaration should be printed instead. + meaning they may branch in either direction. When the subroutine is missing + a reachable ``return`` statement, the line number of the subroutine + declaration should be printed. + + A ``return`` statement whose value's type does not match, and cannot be + implicitly cast to, the owning subroutine's return type is normalized as a + ``TypeError`` (see the ``TypeError`` entry above and :ref:`sec:statements`), + **not** a ``ReturnError``; the line number of the ``return`` statement + should be reported, along with the name and (correct) type of the enclosing + routine. (The tester is lenient about the exact error name here -- it + checks only for the substring "Error" and the line -- as noted at the end + of this chapter.) * ``GlobalError`` @@ -145,8 +155,13 @@ Here are the compile-time errors your compiler must throw: * ``MathError`` - May be raised during compile time expression evaluation when division by zero occurs. - Conditions for raising are equivalent to a :term:`runtime ` ``MathError``. + Raised for the integer math faults defined normatively in :ref:`ssec:integer` + -- signed 32-bit overflow, division or ``%`` by ``0``, and exponentiation of + base ``0`` with a non-positive exponent. ``real`` arithmetic never raises a + ``MathError`` (it follows IEEE 754; see :ref:`ssec:real`). This error may be + raised at compile time when the faulting expression is evaluated during + constant folding; the conditions are identical to the :term:`runtime ` ``MathError``. * ``IndexError`` @@ -159,11 +174,6 @@ Here are the compile-time errors your compiler must throw: is applied to or between arrays with invalid or incompatible sizes. -* ``StrideError`` - - May be raised during compilation if the ``by`` operation is used with a stride value - ``<=0``. - Here is an example invalid program and a corresponding compile-time error: :: @@ -203,13 +213,12 @@ at compile time or at runtime and the tester will accommodate different implemen * ``MathError`` - Raised at runtime if either zero to the power of N, where N is <= 0, or a - division by zero is evaluated. - -* ``StrideError`` - - Raised at runtime if the ``by`` operation is used with a stride value - ``<=0``. + Raised at runtime for the integer math faults defined normatively in + :ref:`ssec:integer` (signed 32-bit overflow, division or ``%`` by ``0``, and + exponentiation of base ``0`` with a non-positive exponent). ``real`` + arithmetic never raises a ``MathError``; see :ref:`ssec:real`. Under the + ``-ffast-math`` flag these integer faults are :term:`undefined behavior` + instead (see :ref:`sec:flags`). Here is an example :term:`ill-formed` program. If your compiler is smart, you may raise the later error, if you prefer not to implement static analysis, the former error can be emitted at runtime. @@ -218,7 +227,7 @@ prefer not to implement static analysis, the former error can be emitted at runt 1 procedure main() returns integer { 2 integer[3] x = [2, 4, 6]; - 3 return integer[4]; + 3 return x[4]; 4 } :: @@ -236,21 +245,30 @@ More Examples :: /* Indexes */ - character[3] v = ['a', 'b', 'c']; // Indexing is harder than it looks! + var character[3] v = ['a', 'b', 'c']; // Indexing is harder than it looks! integer i = 10; - v(3) = 'X'; // SyntaxError + v(3) = 'X'; // SyntaxError: a call expression cannot be an assignment target v[i] = '?'; // Runtime error v['a'] = '!'; // TypeError - i[1] = 1; // SymbolError + i[1] = 1; // TypeError /* Tuples */ tuple (integer, integer) a = (9, 5); - integer b; - integer c; - integer d; + var integer b; + var integer c; + var integer d; b, c, d = a; // AssignError tuple(integer, integer, integer) z = a; // TypeError +``v(3) = 'X'`` is a ``SyntaxError`` because ``v(3)`` parses as a *call* +expression, and a call expression cannot appear on the left-hand side of an +assignment; the malformed assignment target is rejected at parse time, before +any type checking. (Indexing uses square brackets, ``v[3]``.) The ``b, c, d`` +are declared ``var`` so that ``b, c, d = a;`` is purely the intended arity +mismatch (three lvalues, a two-field tuple) rather than also an assignment to +``const`` values -- both are ``AssignError``\ s, but the example is meant to +isolate the arity case. + How to Write an Error Test Case ------------------------------- @@ -293,13 +311,14 @@ example of a run-time error test case and the corresponding expected output file :: procedure main() returns integer { - 1..1 by 0 -> std_output; + integer x = 0; + 5 / x -> std_output; return 0; } :: - StrideError + MathError How to make the Tester Happy ------------------------------------------ diff --git a/gazprea/impl/part_1.rst b/gazprea/impl/part_1.rst index db2509e..2fc0486 100644 --- a/gazprea/impl/part_1.rst +++ b/gazprea/impl/part_1.rst @@ -27,7 +27,7 @@ recommended strategy. * :ref:`ssec:typeQualifiers_var` * :ref:`ssec:typeQualifiers_const` - * :ref:`sec:typePromotion` + * :ref:`sec:implicitCasts` * :ref:`sec:typeCasting` * :ref:`sec:typeInference` * :ref:`sec:typealias` diff --git a/gazprea/index.rst b/gazprea/index.rst index 904a226..f5709b9 100644 --- a/gazprea/index.rst +++ b/gazprea/index.rst @@ -26,8 +26,8 @@ Hardware Acceleration Laboratory in Markham, ON. spec/types spec/type_inference spec/type_casting - spec/type_promotion - spec/typedef + spec/implicit_casts + spec/typealias spec/streams spec/expressions spec/statements @@ -35,6 +35,8 @@ Hardware Acceleration Laboratory in Markham, ON. spec/procedures spec/globals spec/built_in_functions + spec/flags + spec/errors spec/glossary .. toctree:: diff --git a/gazprea/spec/built_in_functions.rst b/gazprea/spec/built_in_functions.rst index 5a4c28d..853bdf5 100644 --- a/gazprea/spec/built_in_functions.rst +++ b/gazprea/spec/built_in_functions.rst @@ -1,62 +1,156 @@ .. _sec:builtIn: -Built-In Functions -================== +Built-in Functions, Procedures and Methods +=========================================== -*Gazprea* has some built-in functions. These built in functions may have -some special behaviour that normal functions can not have, for instance +*Gazprea* has some built-in functions. These built-in functions may have +some special behavior that normal functions cannot have, for instance many of them will work on arrays of any element type. Normally a function must specify the element type of an array argument. -The name of built in functions are reserved and a user program cannot -define a function or a procedure with the same name as a built-in function. -If a :term:`declaration` or a :term:`definition` with the same name as a -built-in function is encountered in a *Gazprea* program, then the compiler -should issue an error. +The names of the built-in functions are reserved. A user program may not +declare *any* identifier -- a variable, function, procedure, ``struct``, or +otherwise -- with the same name as a built-in function; doing so would shadow +the built-in, and the compiler must emit a ``SymbolError`` (see +:ref:`sec:errors`). These names are reserved semantically rather than being +syntactic :ref:`keywords `. + +The :ref:`vector/string method ` names (``push``, +``append``, ``len``), by contrast, are **not** reserved. They live in a method +namespace associated with the compiler-defined ``vector`` object -- reachable +only after a ``.`` on a ``vector`` receiver -- and so do not collide with the +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 +: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 a scalar. + +Applying a built-in outside its defined domain -- ``reverse``/``length`` on +a non-1-D value, ``rows``/``columns`` on a non-2-D value, or ``format`` on a +non-scalar -- is a compile-time error; the compiler must emit a +``TypeError`` (see :ref:`sec:errors`). + +.. _ssec:builtIn_signatures: + +Signatures +---------- + +*Gazprea* has no user-facing type parameters -- they may be added in a future +revision -- but the built-ins are generic over element and scalar types. Their +signatures are therefore written below with a ``[T]`` type-parameter notation +purely for exposition: ``function id[T](T obj) returns T;`` reads as "``id`` is +generic over ``T``". This notation is **not** part of the language. -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. +:: + + 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 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 + +The per-built-in sections below give each domain and its error conditions in +full. + +.. _ssec:builtIn_methods: + +Vector and String Methods +------------------------- + +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: + +.. list-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - Query on ``x`` + - ``length(x)`` (built-in) + - ``x.len()`` (method) + * - array ``T[n]`` + - the fixed length ``n`` + - ``TypeError`` -- arrays have no methods + * - ``vector`` / ``string`` + - the current length + - the current length .. _ssec:builtIn_length: Length ------ -``length`` takes an array of any element type, and returns an integer -representing the number of elements in the array. +``length`` takes a single-dimensional 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. :: - integer[*] v = 1..5; + integer[*] v = 1..6; length(v) -> std_output; /* Prints 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. + +:: + + var vector v = [1, 2, 3]; + + length(v) -> std_output; /* Prints 3 */ + + call v.push(4); /* 'v' is now [1, 2, 3, 4] */ + + length(v) -> std_output; /* Prints 4 */ + .. _ssec:builtIn_rows_cols: -Shape ------ +Rows and Columns +---------------- -The built-in ``shape`` operates on arrays of any dimension, and returns an -array listing the size of each dimension. +The built-ins ``rows`` and ``columns`` report the dimensions of a +two-dimensional array (a :ref:`matrix `): ``rows`` returns the +number of rows and ``columns`` the number of columns. (There is no +rank-agnostic ``shape`` built-in in this version of the language.) :: integer[*][*] M = [[1, 2, 3], [4, 5, 6]]; - shape(M) -> std_output; /* Prints [2, 3] */ + rows(M) -> std_output; /* Prints 2 */ + columns(M) -> std_output; /* Prints 3 */ .. _ssec:builtIn_reverse: Reverse ------- -The reverse built-in takes any single dimensional array, Vector, or String, and returns a -reversed version of it. +The reverse built-in takes any single-dimensional array, vector, or string, and +returns a reversed *array*. Even when the argument is a vector or string, the +result is an array value -- vector-ness (string-ness) is not preserved, just as +for the element-wise operators (see :ref:`sssec:vec_ops`). The resulting array +may of course be implicitly cast back to a vector or string when stored into +one. :: - integer[*] v = 1..5; + integer[*] v = 1..6; integer[*] w = reverse(v); v -> std_output; /* Prints [1, 2, 3, 4, 5] */ @@ -68,7 +162,10 @@ Format ------- The ``format`` built-in takes any :term:`scalar ` as input and -returns a ``string`` containing the formatted value of the scalar. +returns a ``string`` containing the formatted value of the scalar. The result +uses the same representation the scalar's type has when sent to an output +stream (see :ref:`sssec:output_format`); a type with no defined output format +(a ``tuple`` or ``struct``) cannot be formatted. :: @@ -78,9 +175,8 @@ returns a ``string`` containing the formatted value of the scalar. "i = " || format(i) || ", r = " || format(r) || '\n' -> std_output; // Prints: "i = 24, r = 2.4\n" -Note that ``format`` will have to allocate space to hold the return string. -You will have to figure out how to manage the memory so it is reclaimed -eventually. +Note that ``format`` allocates space to hold the return string; the +implementation is responsible for reclaiming it. .. _ssec:builtIn_stream_state: @@ -89,26 +185,22 @@ Stream State When reading values of certain types from ``std_input`` it is possible that an error is encountered, or that the end of the stream has been encountered. In -order to handle these situations *Gazprea* provides a built in procedure that is +order to handle these situations *Gazprea* provides a built-in procedure that is implicitly defined in every file: :: procedure stream_state(var input_stream) returns integer; -This procedure can only be called with the ``std_input`` as a parameter, but it’s -general enough that it could be used if the language were expanded to include -multiple input streams. - -When called, ``stream_state`` will return an integer value. The return value is -an error code defined as follows: - - - ``0``: Last read from the stream was successful. - - ``1``: Last read from the stream encountered an error. - - ``2``: Last read from the stream encountered the end of the stream. +The signature is notional: ``input_stream`` is not a *Gazprea* type, and +the only valid argument is ``std_input``. The form is general enough that +it could be reused if the language were expanded to include multiple input +streams. -``stream_state`` is initialized to ``0``, which is the value return if no -read has been issued. +The returned state codes, the initial state, and the per-type behavior of +reads are specified in :ref:`sssec:stream_error`. In brief: ``0`` means the +last read succeeded, ``1`` that it encountered an error, and ``2`` that it +encountered the end of the stream. :: @@ -125,4 +217,4 @@ read has been issued. The input stream is described in more detail in the -:ref:`input stream ` section. +:ref:`input stream ` section. diff --git a/gazprea/spec/comments.rst b/gazprea/spec/comments.rst index f6fa782..e43c89d 100644 --- a/gazprea/spec/comments.rst +++ b/gazprea/spec/comments.rst @@ -10,7 +10,7 @@ the two adjacent forward slashes is ignored. For example: :: - integer x = 2 * 3; // This is ignored + integer x = 2 * 3; // This is ignored Multi-line block comments are made using **/\*** and **\*/**. The start of a block comment is marked using **/\***, and the end of the block @@ -26,7 +26,8 @@ comment is the **first** occurrence of the sequence of characters Block comments cannot be nested because the comment finishes when it reaches the first closing sequence. For example, the following is -:term:`ill-formed` (the second ``*/`` has no matching ``/*``): +:term:`ill-formed` (the second ``*/`` has no matching ``/*``); the compiler must +emit a ``SyntaxError`` (see :ref:`sec:errors`): :: diff --git a/gazprea/spec/constexpr.rst b/gazprea/spec/constexpr.rst index 673bdb2..d129343 100644 --- a/gazprea/spec/constexpr.rst +++ b/gazprea/spec/constexpr.rst @@ -22,12 +22,19 @@ An expression is a valid ``constexpr`` if it is composed exclusively of: 1. :term:`Literals ` of :term:`primitive types ` (``boolean``, ``integer``, ``real``, ``character``). -2. The operators ``+``, ``-``, ``*``, ``/``, ``not``, ``and``, ``or``, - between two or more ``constexpr``\ s. +2. The unary operators ``+``, ``-``, ``not`` applied to a single + ``constexpr``, and the binary operators ``+``, ``-``, ``*``, ``/``, + ``%``, ``^``, ``<``, ``>``, ``<=``, ``>=``, ``==``, ``!=``, ``and``, + ``or``, ``xor`` applied between two ``constexpr``\ s. 3. Constructors for :term:`aggregate types `, provided that the aggregate is const and all members are ``constexpr``\ s. 4. Index or field access on ``constexpr`` aggregate types. 5. Other variables that are themselves valid ``constexpr``\ s. +6. The implicit :term:`zero value` of a ``const`` declared with no + initializer (e.g. ``const integer i;`` is the constexpr ``0``). +7. An aggregate-level operator (element-wise arithmetic, ``**``, ``||``) + applied between ``constexpr`` aggregates, or a slice of a ``constexpr`` + array; each is itself a ``constexpr`` under these same rules. An expression is **not** a ``constexpr`` if it contains: @@ -40,11 +47,19 @@ variable is a ``constexpr``, the compiler must trace its entire dependency chain. If the chain ever depends on a :term:`run time` value, the check fails. -The only expressions that *must* be ``constexpr`` are global constants. Other -constexprs arising from constants inside function :term:`scope` may also be -constexprs +A context that requires a ``constexpr`` -- a global initializer (see +:ref:`sec:global`) or a ``typealias`` size (see :ref:`sec:typealias`) -- reports +that context's own error when this check fails: a ``GlobalError`` for a global. +For a ``typealias`` size the specification does not mandate a specific error, and +the test battery is permissive here -- an implementation that accepts a +non-``constexpr`` size, or diagnoses it late, is not penalized. + +The only expressions that *must* be ``constexpr`` are global constants and +the size expressions used to parameterize a ``typealias`` (see +:ref:`sec:typealias`). Other constexprs arising from constants inside +function :term:`scope` may also be constexprs but the implementation does not need to enforce or necessarily identify this. -Students should also note that MLIR has a constant propagation pass built in, +Students should also note that MLIR has a constant propagation pass built-in, so doing constant folding yourself may not be necessary depending on your implementation. @@ -65,9 +80,6 @@ implementation. const C = B + 5; // C is 25 // Illegal Global Constant Expressions - var x = 10; - const Y = x + 5; // Not a constexpr: depends on a 'var' - function get_val() returns integer { return 100; } const Z = get_val(); // Not a constexpr: depends on a function call @@ -86,12 +98,22 @@ allowing them to be used to define other constants. 1. Its size is a valid ``constexpr``. 2. All of its element initializers are valid ``constexpr``\ s. - A ``vector`` (the dynamically-sized type) can never be a ``constexpr`` - aggregate, since its size is determined at run time. An inferred-size - array - such as ``integer[*] X = [1, 2, 3]`` must be a ``constexpr``, meaning its - initializer is itself a ``constexpr``: ``[*]`` denotes an inferred size, not - a dynamic one. + A ``var`` ``vector`` can never be a ``constexpr`` aggregate, since its + length can change at run time. A ``const`` vector, however, cannot grow -- + its mutating methods (``push``/``append``) require a ``var`` receiver -- so + a ``const`` vector whose initializer is itself a ``constexpr`` *is* a + ``constexpr``, equivalent to a ``const`` array whose length is that of its + initializer (or the empty array, if the ``const`` vector is declared without + an initializer). Because + ``string`` is a strong-equivalence alias for ``vector`` (see + :ref:`ssec:string`), the same holds for ``string``: for example + ``const vector v = [1, 2, 3];`` and ``const string s = "hi";`` are + ``constexpr``\ s. An inferred-size array such + as ``integer[*] X = [1, 2, 3]`` is a ``constexpr`` when its initializer + is; ``[*]`` denotes a size inferred once, at :term:`initialization`, not + a resizable one (see :ref:`sssec:array_sizing`). An array whose size or + initializer is only known at run time is still a perfectly legal array + -- it is simply not a ``constexpr``. :: @@ -106,9 +128,14 @@ allowing them to be used to define other constants. integer[ELEMENT] my_array = 0; // Legal: static array of size 30, zero-filled const integer[2] BAD_TABLE = [10, get_val()]; // Illegal: initializer is not a constexpr - // also illegal if a procedure since - // procedure calls are not allowed - // within declarations + + This would remain illegal even if ``get_val()`` were a procedure: a call + may appear only as the direct right-hand side of a declaration or + assignment, or as the callee of a ``call`` statement, and its result may + not be used in the direct construction of a differently-typed aggregate + (see :ref:`procedure call positions `); + here the call is nested inside the array literal, not the declaration's + direct right-hand side. A ``constexpr`` can appear anywhere a ``const`` declaration is legal, including inside functions, procedures, and control-flow blocks. However, @@ -126,10 +153,12 @@ allowing them to be used to define other constants. x <- std_input; const integer y = x; // Legal: y is immutable, but NOT a constexpr // because its value depends on runtime input. - integer[y] arr; // Illegal: an explicit array size must be a - // constexpr, and y is not a constexpr. - vector v; // Legal: a vector is the dynamically-sized type; - // use it when the size is only known at runtime. + integer[y] arr; // Legal: the runtime size y is evaluated once, + // at initialization, and fixes arr's length for + // good; arr is an ordinary (non-constexpr) array + // and can never be resized. + vector v; // Legal: use a vector when the collection must + // grow or shrink after it is created. The compiler propagates the constexpr property through local scopes normally; there is no restriction on where in a block the declaration diff --git a/gazprea/spec/declarations.rst b/gazprea/spec/declarations.rst index f4aa6fd..bc30019 100644 --- a/gazprea/spec/declarations.rst +++ b/gazprea/spec/declarations.rst @@ -4,77 +4,94 @@ Declarations ============ Variables must be declared before they are used. Aside from -a few :ref:`special cases `, declarations have the +a few :ref:`special cases `, declarations have the following formats: :: - [] [= ]; + [] [] [= ]; A declaration creates a variable with an :ref:`identifier ` of -````, with :ref:`type ` ````, and optionally a :ref:`type qualifier ` of ````. -The two qualifiers are ``var`` and ``const``, which qualify the identifier as -*mutable* or *immutable*, respectively. -In *Gazprea* it is important to remember that if the optional qualifier is -omitted the default is ``const``, i.e. variables are immutable by default. +````, with :ref:`type ` ````, and optionally a +:ref:`type qualifier ` of ````. The two +qualifiers are ``var`` and ``const``, which qualify the identifier as *mutable* +or *immutable*, respectively. In *Gazprea* it is important to remember that if +the optional qualifier is omitted the default is ``const``, i.e. variables are +immutable by default (normative statement in :ref:`sec:typeQualifiers`). + +Both ```` and ```` are optional, but **at least one must be +present** so that the declaration can be told apart from an assignment. When +```` is elided it is inferred from ````, which must therefore +be present and have an inferable type; if the type cannot be inferred the +compiler must emit a ``TypeError`` (see :ref:`sec:typeInference` and +:ref:`sec:errors`). When ```` is elided it defaults to ``const`` as +described above. Optionally, a declaration may explicitly initialize the value of the new variable with the value of ````. -In *Gazprea* all variables must be initialized in a well defined manner in +In *Gazprea* all variables must be initialized in a well-defined manner in order to ensure :term:`functional purity`. If the variables are not initialized to a known value their initial value might change depending on when the program is run. *Gazprea* therefore follows a strict RAII-style discipline: every -declaration is also an :term:`initialization `, and no +declaration is also an :term:`initialization`, and no variable is ever observable in an uninitialized state. When the programmer omits the explicit initializer, the compiler implicitly -initializes the variable to the *default value* of its type. -The default value is ``0`` for ``integer`` and ``real``, -``false`` for ``boolean``, ``' '`` for ``character``, the empty -string ``""`` for ``string``, and the element-wise default for -:term:`aggregate types ` (arrays, vectors, tuples, -structs). *Gazprea* has no ``null`` value. - -For simplicity *Gazprea* assumes that declarations can only appear at -the beginning of a block. For instance this would not be legal in -*Gazprea*: +initializes the variable to the :term:`zero value` of its type. +The zero value is ``0`` for ``integer``, ``0.0`` for ``real``, +``false`` for ``boolean``, ``'\0'`` (the null character) for ``character``, the +empty collection (e.g. the empty string ``""``) for a ``vector`` or +``string``, and, for a fixed-size :term:`aggregate type ` +(array, matrix, tuple, or struct), each element or field set to its own +zero value. +*Gazprea* has no ``null`` value. +An array's length is likewise settled at :term:`initialization` and is +then fixed for the remainder of the variable's lifetime (see +:ref:`sssec:array_sizing`): an uninitialized array holds its declared +number of elements, each set to the element type's zero value. +This applies to ``const`` declarations as well: a ``const`` variable +declared without an initializer is legal and holds the zero value of +its type permanently. + +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: :: var integer i = 10; if (blah) { - i = i + 1; - real i = 0; // Illegal placement of a declaration. + i = i + 1; // an ordinary statement + var real r = 2.0; // a declaration after a statement -- legal + r = r + i; } -because the declaration of the real version of ``i`` does not occur at -the start of the block. +The one exception is :ref:`global scope `, where declarations are +**not** free to appear in any order: because every global is initialized before +the program runs, a global may reference only globals defined *earlier* in the +file, so globals must be written in :term:`initialization` order (see +:ref:`sec:global`). -The following declaration placement is legal: +A variable's name enters :term:`scope` only after its initializer has +been evaluated. A program that refers to a variable within its own +initialization statement is therefore :term:`ill-formed`. :: - var integer i = 10; - if (blah) { - var real i = 0; // At the start of the block. All good. - i = i + 1; - } - -The declaration of a variable happens after initialization. A program -that refers to a variable within its own initialization statement is -therefore :term:`ill-formed`. - -:: - - /* All of these declarations are illegal, they would result in garbage values. */ + /* All of these declarations are illegal: the right-hand-side identifier + is not yet in scope during its own initializer. */ integer i = i; - integer[10] v = v[0] * 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 -enclosing :term:`scope`, then it is legal to use that in the initialization -of a variable with the same name. For instance: + integer[10] v = v[1] * 2; + +Since the name being declared is not yet in scope during its own initializer, +the reference resolves as usual to the nearest *enclosing*-scope binding of that +name, if one exists; only when there is no such outer binding is this a reference +to an undeclared variable, for which the compiler must emit a ``SymbolError`` +(see :ref:`sec:errors`). So ``integer i = i;`` at the outermost scope is a +``SymbolError``, whereas the same text nested inside a scope that already binds +``i`` legally reads the outer ``i``. For instance: :: diff --git a/gazprea/spec/errors.rst b/gazprea/spec/errors.rst new file mode 100644 index 0000000..8407f52 --- /dev/null +++ b/gazprea/spec/errors.rst @@ -0,0 +1,94 @@ +.. _sec:errors: + +Errors +====== + +Every :term:`ill-formed` *Gazprea* program is rejected with an error drawn from +the fixed taxonomy below; a :term:`well-formed` program produces none. This page +is the **normative** source of that taxonomy -- the set of error classes and the +condition under which each must be emitted. The *mechanics* of reporting them +(which C++ exception class to throw, the ANTLR error listener, the run-time error +functions, and how the test harness reads ``stderr``) are described in the +:ref:`implementation chapter `. + +Each class is *either* a compile-time or a run-time error, but for several of +them the exact moment of detection is left to the implementation: some +conditions (an out-of-bounds index, a division by zero) are undecidable in +general, so an implementation may catch them at :term:`compile time` when it can +prove them and otherwise at :term:`run time`. The prose throughout this +specification therefore says only that the compiler "must emit" a given error, +naming the *class* rather than the phase. + +Compile-time errors +------------------- + +* ``SyntaxError`` -- the program is not syntactically valid. This covers both + errors the parser reports directly and *syntactic* restrictions enforced by a + validation pass **after** parsing (for example a generator with three or more + iterator variables, an iterator loop with more than one domain, or a qualifier + on a function argument). Raising a ``SyntaxError`` from a post-parse pass is a + legitimate strategy; the grammar itself need not reject these constructs. + +* ``SymbolError`` -- an undefined symbol is referenced, or a symbol is re-defined + in the same :term:`scope`. + +* ``TypeError`` -- an operation or statement is applied to, or between, + expressions of invalid or incompatible types. A ``return`` whose value does not + match, and cannot be implicitly cast to, the routine's return type is a + ``TypeError`` (not a ``ReturnError``). + +* ``AliasingError`` -- two arguments that may name the same mutable memory are + passed to a procedure with at least one bound to a ``var`` parameter (see + :ref:`sec:procedure`). This is always a compile-time diagnosis, using the + conservative same-backing-array rule. + +* ``AssignError`` -- an assignment to a ``const`` value, or a tuple-unpacking + assignment whose number of :term:`lvalues ` differs from the number of + fields in the tuple :term:`rvalue`. + +* ``MainError`` -- the program has no ``main`` procedure, or ``main`` has an + :term:`ill-formed` signature (see :ref:`ssec:procedure_main`). + +* ``ReturnError`` -- a function or procedure with a return type has a control + path that reaches its end without a ``return``. + +* ``GlobalError`` -- an illegal global: a ``var`` global, a global with no + initializer or a non-``constexpr`` initializer, a global referencing a name not + yet defined, or a non-declaration statement at global scope (see + :ref:`sec:global`). + +* ``StatementError`` -- the program is syntactically valid but a statement is + used in an invalid context (for example ``break`` or ``continue`` outside a + loop). + +* ``CallError`` -- ``call`` is applied to a function, a procedure is called in an + invalid position, or a procedure method is written without ``call``. + +* ``DefinitionError`` -- a function or procedure is declared (prototyped) but + never defined. + +* ``LiteralError`` -- a literal does not fit its type (for example an integer + literal outside the ``i32`` range, or a ``\x`` escape with no hex digit). + +Run-time errors +--------------- + +The following are classified as run-time errors, but an implementation may +instead detect and report them at :term:`compile time` whenever it can prove them +(for instance when the operands are literals); the test harness accepts either +phase. + +* ``MathError`` -- an integer math fault: signed 32-bit overflow, division or + ``%`` by ``0``, or exponentiation of base ``0`` with a non-positive exponent + (see :ref:`ssec:integer`). ``real`` arithmetic never raises a ``MathError`` + (see :ref:`ssec:real`). Under ``-ffast-math`` these integer faults are + :term:`undefined behavior` instead (see :ref:`sec:flags`). + +* ``IndexError`` -- an index is out of bounds. For an array this is an integer + index outside ``1..n`` or ``-n..-1`` (see :ref:`sssec:array_ops`); for a + :ref:`tuple ` it is a field index outside ``1..k`` which -- because + a tuple index is always a literal -- is necessarily caught at + :term:`compile time`. + +* ``SizeError`` -- an operation or assignment is applied to or between arrays + whose sizes are invalid or incompatible (see :ref:`sssec:array_sizing`). diff --git a/gazprea/spec/expressions.rst b/gazprea/spec/expressions.rst index 6d85201..138fdb6 100644 --- a/gazprea/spec/expressions.rst +++ b/gazprea/spec/expressions.rst @@ -8,11 +8,13 @@ or another expression. .. _ssec:expressions_toop: -Table of Operator precedence +Table of Operator Precedence ---------------------------- The following is a table containing all of the precedences and -associativities of the operators in *Gazprea*. +associativities of the operators in *Gazprea*. Parentheses are not +listed: they do not participate in the precedence relation and instead +override it by grouping their contents into a new atom. +----------------+------------------------------------+-------------------+ | **Precedence** | **Operators** | **Associativity** | @@ -21,46 +23,87 @@ associativities of the operators in *Gazprea*. +----------------+------------------------------------+-------------------+ | 2 | ``[]`` (indexing) | left | +----------------+------------------------------------+-------------------+ -| 3 | ``..`` | N/A | +| 3 | ``^`` | right | +----------------+------------------------------------+-------------------+ | 4 | unary ``+``, unary ``-``, ``not`` | right | +----------------+------------------------------------+-------------------+ -| 5 | ``^`` | right | +| 5 | ``*``\ , ``/``\ , ``%``, ``**`` | left | +----------------+------------------------------------+-------------------+ -| 6 | ``*``\ , ``/``\ , ``%``, ``**`` | left | +| 6 | ``+``\ , ``-`` | left | +----------------+------------------------------------+-------------------+ -| 7 | ``+``\ , ``-`` | left | +| 7 | ``..`` | N/A | +----------------+------------------------------------+-------------------+ -| 8 | ``by`` | left | +| 8 | ``<``\ , ``>``\ , ``<=``\ , ``>=`` | left | +----------------+------------------------------------+-------------------+ -| 9 | ``<``\ , ``>``\ , ``<=``\ , ``>=`` | left | +| 9 | ``==``\ , ``!=`` | left | +----------------+------------------------------------+-------------------+ -| 10 | ``==``\ , ``!=`` | left | +| 10 | ``and`` | left | +----------------+------------------------------------+-------------------+ -| 11 | ``and`` | left | +| 11 | ``or``\ , ``xor`` | left | +----------------+------------------------------------+-------------------+ -| 12 | ``or``\ , ``xor`` | left | -+----------------+------------------------------------+-------------------+ -| (Lowest) 13 | ``||`` | right | +| (Lowest) 12 | ``||`` | right | +----------------+------------------------------------+-------------------+ +The stream operators ``->`` and ``<-`` are statement-level operators, not +expression operators, so they do not appear in the table above. They bind more +loosely than every operator listed -- effectively the very bottom of the +precedence relation -- so an entire expression is evaluated before it is sent +to or read from a stream (see :ref:`sec:streams`). + +Two consequences of this table are worth calling out, because both changed +how computed ranges parse: + +- Unary ``+``/``-``/``not`` (precedence 4) bind *looser* than exponentiation + ``^`` (precedence 3), so ``-2^2`` parses as ``-(2^2) = -4`` (as in ordinary + mathematics), not ``(-2)^2``. + +- The range operator ``..`` (precedence 7) binds *looser* than every unary and + arithmetic operator, so ``-4..5`` parses as ``(-4)..5`` and ``1..n-1`` parses + as ``1..(n-1)`` -- the bounds are computed first, then the range is formed. + +The indexing operator ``[]`` (precedence 2) is a *postfix, multi-axis* operator: +a maximal run of subscripts written directly against an array operand -- +``a[s1][s2]...[sk]`` -- is a single :ref:`positional index ` +on that operand, with ``sm`` selecting along axis ``m`` (see :ref:`ssec:matrix`). +Its left-associativity only fixes the order in which the axes are read (left to +right, outermost axis first); it does **not** re-index an intermediate result. +Because the axes are counted against the operand, parentheses matter: +``M[1..3][2]`` indexes ``M`` positionally and selects column 2 of rows 1--2, +whereas ``(M[1..3])[2]`` first evaluates ``M[1..3]`` to an array value and then +indexes *that* value on its own first axis (selecting a row). Parenthesizing an +inner slice -- or binding it to a variable -- is therefore how one indexes into +a slice's result. + .. _ssec:expressions_generators: Generators ---------- A generator may be used to construct either a one or two dimensional array. +A generator always yields an :ref:`array ` value -- never a +:ref:`vector ` -- whose size is settled at the moment the +generator is evaluated and is fixed thereafter. Using a generator (or a +range) to initialize an inferred-size array such as an ``integer[*]`` is +therefore one of the ways an array's length becomes fixed at +:term:`initialization` (see :ref:`sssec:array_sizing`). A generator creates a value of a 1D array type when one :term:`iterator variable` is used, and a 2D array type when two iterator variables are used. -Any other number of iterator variables will yield an error. -In particular, *Gazprea* does not currently support generators over -three or more iterator variables (no direct construction of arrays -with three or more dimensions). +Supplying any other number of iterator variables is :term:`ill-formed`: the +compiler must emit a ``SyntaxError`` (see :ref:`sec:errors`). This is a +*syntactic* rejection even though a natural grammar would accept it -- the +grammar need not encode the "one or two iterator variables" restriction; it may +instead be enforced during syntactic validation after parsing, which is a +legitimate place to raise a ``SyntaxError`` (see :ref:`sec:errors`). In +particular, *Gazprea* does not currently support generators over three or more +iterator variables (no direct construction of arrays with three or more +dimensions); higher-dimensional generators are a planned addition to a future +revision of this specification. The :term:`domain` in a domain expression is any array-typed value: -static arrays, dynamically-sized :ref:`vectors `, and -:ref:`ranges ` all count. The generator +static arrays, dynamically-sized :ref:`vectors `, +:ref:`strings `, and :ref:`ranges ` +all count. The generator dimension is determined solely by how many iterator variables the generator introduces (one or two), not by the shape of the domain value. @@ -72,19 +115,20 @@ This additional expression is used to create the generated values. For example: :: - integer[10] v = [i in 1..10 | i * i]; + integer[10] v = [i in 1..11 | i * i]; /* v[i] == i * i */ - integer[2][3] M = [i in 1..2, j in 1..3 | i * j]; + integer[2][3] M = [i in 1..3, j in 1..4 | i * j]; /* M[i][j] == i * j */ -The expression to the right of the bar (``|``), is used to generate the +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``. +Let ``T`` be the type of the expression to the right of the bar (``|``). The +rank of the result is fixed by the number of iterator variables, not by the +shape of any domain. With one iterator variable ranging over a domain of size +``N``, the result is a 1D array of size ``N`` with element type ``T``. With two +iterator variables ranging over domains of size ``N`` and ``M`` respectively, +the result is a 2D array of size ``N`` x ``M`` with element type ``T``. Generators may be nested, and may be used within domain expressions. For instance, the generator below is perfectly legal: @@ -93,8 +137,8 @@ is perfectly legal: integer i = 7; - /* The domain expression should use the previously defined i \*/ - integer[*] v = [i in [i in 1..i | i] | [i in 1..10 | i * i][i]]; + /* The domain expression should use the previously defined i */ + integer[*] v = [i in [i in 1..i+1 | i] | [i in 1..11 | i * i][i]]; /* v should contain the first 7 squares. */ @@ -110,8 +154,8 @@ Domain expressions can only appear within :ref:`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 the domain in order. -The domain must evaluate to a type, which means empty literal arrays -yield a ``TypeError``. +The domain's element type must be inferable, so an empty array literal -- +which has no inferable element type -- yields a ``TypeError``. The :term:`scope` of the iterator variable (the left hand side of the declaration) is within the body of the generator or loop. The domain (the right hand side) is evaluated before any of the @@ -125,39 +169,49 @@ For instance: integer i = 7; /* This will print 1234567 */ - loop i in 1..i { + loop i in 1..i+1 { i -> std_output; } Iterator variables are not initialized when they are declared. In loops, :term:`re-initialization` happens at the start of each -execution of the loop's body statement. We may chain iterator -variables using commas, such as in matrix generators. +execution of the loop's body statement. A generator -- but not an +iterator loop, which permits only a single domain expression (see +:ref:`sssec:statements_iter_loop`) -- may chain iterator variables +using commas, such as in matrix generators. :: integer i = 2; /* 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]] + * the one enclosing the generator. 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+1, j in 0..i+1 | i*j ]; The domain of a domain expression is only evaluated once. For instance: :: - integer x = 1; + integer x = 2; /* 1..x is only evaluated the first time the loop executes, so it is - simply 1..1, and not an infinite loop. */ + simply 1..2 -- the one-element range [1] -- and not an infinite + loop. */ loop i in 1..x { x = x + 1; } This is true for domain expressions within generators as well. +Because the domain is captured by evaluating it once, a runtime-sized +domain fixes its iteration count at :term:`initialization`. A :ref:`vector +` or :ref:`string ` may serve as the domain, +and the length it holds when the domain is evaluated sets the number of +iterations; growing the vector or string inside the loop body does not +add iterations. + Iterator variables can be assigned to and :term:`re-declared ` within the enclosed iterator loop. Neither carries information into the next iteration: the next iteration performs @@ -167,6 +221,6 @@ iterator variable is bound fresh. :: - loop i in 1..6 { + loop i in 1..7 { integer i = 5; - } + } diff --git a/gazprea/spec/flags.rst b/gazprea/spec/flags.rst new file mode 100644 index 0000000..367e0b5 --- /dev/null +++ b/gazprea/spec/flags.rst @@ -0,0 +1,62 @@ +.. _sec:flags: + +Flags +===== + +*Gazprea* programs are compiled with a fixed set of compiler flags. Almost all +of them leave the meaning of a program unchanged. This page documents the one +flag that changes program semantics -- ``-ffast-math`` -- and states precisely +what it affects and how it may be used. + +.. _ssec:flags_ffastmath: + +The -ffast-math Flag +-------------------- + +``-ffast-math`` is a mandatory compiler flag provided **solely for performance +testing**. It is the *single* place in *Gazprea* where a program may exhibit +:term:`undefined behavior`: under standard compilation -- that is, without +``-ffast-math`` -- the language has **no undefined behavior** at all. + +Effect on integer arithmetic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Without ``-ffast-math``, the integer operations enumerated in +:ref:`ssec:integer` raise a ``MathError`` (see :ref:`sec:errors`) on a math +fault; :ref:`ssec:integer` is normative for exactly which operations these are. +Under ``-ffast-math`` every one of those faults instead becomes +:term:`undefined behavior` -- no ``MathError`` is raised, and this specification +imposes no requirement whatsoever on the result. The affected faults are: + +- signed 32-bit integer overflow of ``+``, ``-``, ``*``, ``/``, ``^``, and unary + ``-`` (including ``INT_MIN / -1`` and ``-INT_MIN``); +- integer division or remainder ``%`` by ``0``; +- integer exponentiation ``^`` with base ``0`` and a non-positive exponent. + +Effect on real arithmetic +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +None. ``real`` arithmetic always follows IEEE 754 -- overflow yields a signed +``Infinity``, division or ``%`` by ``0.0`` yields ``Infinity`` or ``NaN``, and +operations on ``Infinity`` and ``NaN`` propagate as usual -- and ``-ffast-math`` +never changes this. A ``real`` operation therefore never raises a ``MathError`` +and never has undefined behavior, with or without the flag; :ref:`ssec:real` is +normative for real semantics. + +.. _ssec:flags_testing: + +Testing Policy +-------------- + +Because ``-ffast-math`` is the only source of undefined behavior, its use in +testing is tightly constrained: + +- **Student tests must never exercise undefined behavior.** No student test may + rely on, or trigger, any of the integer faults listed above. +- ``-ffast-math`` is **reserved for performance testing** -- specifically, + stress-tests of linear algebra that have already been validated to contain no + undefined behavior. +- ``-ffast-math`` will **never** exercise undefined behavior in testing. Every + test is first run against the non-``-ffast-math`` compiler to confirm that it + contains no undefined behavior *before* it is ever used for performance + testing under ``-ffast-math``. diff --git a/gazprea/spec/functions.rst b/gazprea/spec/functions.rst index 37067e2..5b6f625 100644 --- a/gazprea/spec/functions.rst +++ b/gazprea/spec/functions.rst @@ -5,17 +5,23 @@ Functions A function in *Gazprea* has several requirements: -1. All of the arguments are implicitly ``const``, and can not be mutable. +1. All of the arguments are implicitly ``const``, and cannot be mutable. -2. Function arguments cannot contain type qualifiers. Including a type qualifier with a function argument should result in a ``SyntaxError``. +2. Function arguments cannot contain type qualifiers. Including a type + qualifier with a function argument must emit a ``SyntaxError`` (see + :ref:`sec:errors`). -3. Argument types must be explicit. Inferred size arrays are allowed +3. Argument types must be explicit. Inferred size arrays are allowed. -4. Functions can not perform any I/O. +4. Functions cannot perform any I/O; performing I/O in a function body must + emit a ``StatementError`` (see :ref:`sec:errors`). -5. Functions can not rely upon any mutable state outside of the function. +5. Functions cannot rely upon any mutable state outside of the function. -6. Functions can not call any procedures. +6. Functions cannot call any procedures, with one exception: a mutating + vector/string method (``push``, ``append``) may be called on a variable + local to the function (see :ref:`sssec:vec_methods`); any other procedure + call inside a function must emit a ``CallError`` (see :ref:`sec:errors`). 7. Functions must be declared in the global scope. @@ -73,14 +79,15 @@ These can be called as follows: :: integer x = f(); /* x == 1 */ - real c = pythag(3, 4); /* Type promotion to real arguments. c == 5.0 */ - real value = get([i in 1..10 | i], 3); /* value == 3 */ + real c = pythag(3, 4); /* 3 and 4 are implicitly cast to real. c == 5.0 */ + real value = get([i in 1..11 | i], 3); /* value == 3 */ -A function’s body can also be given by a block statement instead of a +A function's body can also be given by a block statement instead of a single expression. In this case the return value of the function is given with the return statement. A return statement must be reached by all possible control flows in the function before the end of the -function is encountered. +function is encountered; if this cannot be established the compiler must +emit a ``ReturnError`` (see :ref:`sec:errors`). :: @@ -104,7 +111,7 @@ function is encountered. ``f`` is :term:`ill-formed` since if ``b == false``, then we reach the end of the function without a return statement, so we do not know what value ``f(false)`` should take on. A conforming implementation must -reject this program with a ``ReturnError`` such as:: +emit a ``ReturnError`` (see :ref:`sec:errors`) rejecting this program, such as:: ReturnError on line 1: function "f" does not have a return statement reachable by all control flows @@ -112,7 +119,7 @@ reject this program with a ``ReturnError`` such as:: /* This is invalid because if the loop ever finished executing the function would end before a return statement is encountered. In - general the compiler can not tell when a loop would execute + general the compiler cannot tell when a loop would execute forever, so we make the assumption that all branches in the control flow could be followed. */ function f() returns integer { @@ -165,13 +172,19 @@ function and the definition must be identical. That means the argument names in the prototype are *optional*. If the prototype arguments are given names they do not have to match the argument names in the function definition. +A prototype is only a forward *declaration*, not a definition: it must be +matched by a definition elsewhere in the program. A function that is prototyped +but never defined is :term:`ill-formed`, and the compiler must emit a +``DefinitionError`` (see :ref:`sec:errors`). + .. _ssec:function_vec_mat: Array and Matrix Parameters and Returns ---------------------------------------- -The arguments and return value of functions can have both explicit and inferred sizes. For example: +The arguments and return value of functions can have both explicit and inferred +sizes. For example: :: @@ -184,7 +197,30 @@ The arguments and return value of functions can have both explicit and inferred } -Like Rust, array *slices* may be passed as arguments: +The size written in a parameter or return type is part of how each call is +checked: + +- An **explicitly sized** array parameter such as ``real[3][3]`` makes that + size part of the function's signature. The corresponding argument must have + exactly that length in every dimension, or the compiler must emit a + ``SizeError`` (see :ref:`sec:errors`). + +- An **inferred-size** array parameter such as ``integer[*]`` imposes no size + requirement of its own. It is :term:`initialized ` at the + call from the argument that is passed, taking on that argument's length, + which is then fixed for the duration of the call (see + :ref:`sssec:array_sizing`). + +- An **inferred-size return type** such as ``real[*]`` is likewise + :term:`initialized ` at the ``return`` statement, from the + value being returned. + +- A :ref:`vector ` parameter or return type (for example + ``vector``, or the :ref:`string ` alias) carries no + length in its type, so no length check applies in either direction; the + parameter simply takes on the length of the value passed or returned. + +Array *slices* may also be passed as arguments: :: @@ -194,26 +230,33 @@ Like Rust, array *slices* may be passed as arguments: } function slicer() returns real[*] { - integer[10] a = 1..10; - var vector two_halves = to_real_vec(a[1..5]); - two_halves.append(to_real_vec(a[6..])); + integer[10] a = 1..11; + var vector two_halves = to_real_vec(a[1..6]); + call two_halves.append(to_real_vec(a[6..])); return two_halves; } 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 -*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. +functions are pure. That means that arrays, vectors, and strings, like every +other function argument, are passed *by value* at the call (see +:ref:`ssec:procedure_implicit_casts`), not by reference; a function can change +neither the contents nor the length of an array, vector, or string it +receives, since a ``const`` parameter cannot be assigned to at all. A function +that assigns to one of its parameters must emit an ``AssignError`` (see +:ref:`sec:errors`). + +Because every function parameter is ``const``, an array :ref:`slice +` passed to a function is received **by value** -- a copy of +the selected elements -- and so can never observe or cause a change to the +slice's backing storage. The view-versus-copy distinction that matters for a +``var`` parameter therefore does not arise for functions: every slice a function +receives is a copy. .. _ssec:function_namespacing: Function Namespacing -------------------- -In *Gazprea* function declarations occur in the global scope. -This means that two functions with the same name cannot coexist in the same -gazprea program, nor can you forward declare the same function twice. - -Additionally, functions and procedures share the same namespace; you cannot -declare a function and procedure with the same name +Function identifiers share the global variable/function/procedure namespace +with every other global identifier; see :ref:`sec:namespaces` for the full +namespacing rules, including the ``SymbolError`` raised on a collision. diff --git a/gazprea/spec/globals.rst b/gazprea/spec/globals.rst index 4f6cead..2fd461c 100644 --- a/gazprea/spec/globals.rst +++ b/gazprea/spec/globals.rst @@ -12,33 +12,61 @@ Valid global :term:`scope` :term:`statements ` include: * Typealias All global statements are considered :term:`declarations `. -Global statements may occur in any order, given respective symbols are -defined before being referenced. +Global statements must be written in **dependency order**, and this is a hard +requirement: a global may reference only symbols already defined *earlier* in +the file, so globals are initialized in the order they are written. A global +whose initializer references a global not yet defined at that point is +:term:`ill-formed` -- the referenced name is not yet in :term:`scope` -- so the +compiler must emit a ``SymbolError`` (see :ref:`sec:errors`). The one exception +is calls to functions and procedures, for which a forward +:ref:`prototype ` lets a later definition be +referenced before it textually appears. + +A statement other than a declaration at global scope -- an assignment, an +``if``, a loop, or a bare expression -- must emit a ``GlobalError`` (see +:ref:`sec:errors`). Variable Declarations --------------------- In *Gazprea* values can be assigned to a global :term:`identifier`. All globals must be immutable (``const``). If a global identifier is declared -with the ``var`` specifier, then an error should be raised. This restriction -is in place since mutable global variables would ruin -:term:`functional purity`. If functions have access to mutable global -state then we can not guarantee their purity. - -Globals must be initialized with a valid -:ref:`constant expression `. A global :term:`initializer` -may therefore reference other globals and use arithmetic and constexpr +with the ``var`` specifier, then the compiler must emit a ``GlobalError`` +(see :ref:`sec:errors`). This restriction is in place since mutable global +variables would ruin :term:`functional purity`. If functions have access to +mutable global state then the compiler can no longer guarantee their purity. + +Globals must always be initialized with a valid +:ref:`constant expression `. Unlike a local variable, a global is +never implicitly :term:`zero-initialized `: a global declared +without an initializer is :term:`ill-formed`, and the compiler must emit a +``GlobalError`` (see :ref:`sec:errors`). A zero value is never assumed for a +global -- if one is intended it must be written explicitly (for example +``const integer i = 0;`` or ``const integer[3] a = 0;``). A global +:term:`initializer` may reference other globals and use arithmetic and constexpr aggregates, but it must be fully evaluable by the compiler before the program runs. This preserves functional purity and enables :term:`compile-time ` optimizations. As a consequence: * Functions, procedures, and I/O operations may not appear in a global's initializer. -* A global may not have a ``vector`` type (the dynamically-sized type), - because a vector's size is determined at :term:`run time`. An - inferred-size array such as ``const integer[*] X = [1, 2, 3]`` *is* - permitted: ``[*]`` denotes an inferred size that is fixed by its - ``constexpr`` initializer at compile time. +* A global ``vector`` or ``string`` is permitted only when it is ``const`` + with a ``constexpr`` initializer -- which, since every global is already + ``const`` (see above), is the same requirement placed on every other + global. Because a ``const`` vector cannot grow (its mutating methods + ``push``/``append`` require a ``var`` receiver), its length is fixed at + compile time, so a ``const`` vector is equivalent to an array the size of + its initializer (or the empty array, when that initializer is the empty + literal ``[]``). Consequently ``const string s = "hi";`` and + ``const vector v = [1, 2, 3];`` are legal globals. (A ``var`` + vector global is still rejected, but for the independent reason that no + global may be ``var``.) An inferred-size array such as + ``const integer[*] X = [1, 2, 3]`` is likewise permitted: ``[*]`` denotes + an inferred size that is fixed by its ``constexpr`` initializer at compile + time (see :ref:`sssec:array_sizing`). * All globals are implicitly ``constexpr``. +The compiler must emit a ``GlobalError`` (see :ref:`sec:errors`) for any +violation of the above. + diff --git a/gazprea/spec/glossary.rst b/gazprea/spec/glossary.rst index 2c3a9bc..0dde51f 100644 --- a/gazprea/spec/glossary.rst +++ b/gazprea/spec/glossary.rst @@ -15,7 +15,7 @@ an ISO/IEC or IEEE standard, the documentation of an ongoing industrial open-source project (LLVM, GCC, GNU Binutils), or a peer-reviewed publication in a respected venue. Where a term has a widely-used *effective* reference (e.g. cppreference for the C++ value categories), that reference -appears alongside the authoritative citation and is explicitly labelled as +appears alongside the authoritative citation and is explicitly labeled as non-normative. Every glossary entry is a Sphinx ``:term:`` target and can be @@ -25,13 +25,18 @@ sentence in another chapter can read .. note:: - The entries here are *definitions of terminology*, not statements of - *Gazprea* semantics. Where *Gazprea* re-uses a word from another - language (for instance ``type qualifier``, which C reserves for - ``const``/``volatile``/``restrict``/``_Atomic`` but *Gazprea* uses for - the mutability distinction ``const``/``var``), the glossary explains - the source of the word and points to the *Gazprea* chapter that gives - its language-specific meaning. + Most entries here are *definitions of terminology*. Where *Gazprea* + re-uses a word from another language (for instance ``type qualifier``, + which C reserves for ``const``/``volatile``/``restrict``/``_Atomic`` but + *Gazprea* uses for the mutability distinction ``const``/``var``), the + glossary explains the source of the word and points to the *Gazprea* + chapter that gives its language-specific meaning. + + A few entries, however, *do* state normative *Gazprea* rules -- notably + :term:`zero value`, :term:`initialization`, :term:`re-initialization`, + :term:`domain`, and :term:`value type`. These are load-bearing: their + content is normative wherever it appears, and each is cross-referenced to + the chapter that states it in full. Do not skip them. .. contents:: On this page :local: @@ -45,11 +50,45 @@ Terms .. glossary:: :sorted: + initialization + The :term:`run time` instant at which a variable's declaration first + executes. A declaration is a single *program point*; at run time + control reaches that point along some *control-flow path*, and may reach + it more than once -- a declaration in a loop body, or one on a branch of + a conditional, is reached once per time control flows through it. A + variable is *initialized* on the first execution of its declaration + point along the path taken; each subsequent execution of the same + declaration begins a fresh :term:`lifetime` rather than mutating the + previous one (see :term:`re-initialization`). A variable's array and + matrix dimensions are settled *exactly once*, at initialization, and are + then fixed for the remainder of that variable's lifetime: an array is + sized once and can never be resized. A size may be any integer + expression -- it need not be a :term:`compile time` constant -- but it is + evaluated a single time, at this instant, and later changes to that + expression's inputs do not affect the array. (Ada draws the same + once-only distinction with a separate *elaboration* step; *Gazprea* + keeps a single definition of a variable's size and calls the instant it + happens *initialization*.) + + zero value + The value a variable of a given type holds when it is declared + without an :term:`initializer`. It is ``0`` for ``integer``, + ``0.0`` for ``real``, ``false`` for ``boolean``, and ``'\0'`` (the + null character) for ``character``. For a fixed-size array or + matrix it is that shape filled with the element type's zero value; + for a ``tuple`` or ``struct``, each member set to its own zero + value; for a ``vector`` or ``string``, the empty collection. A + ``const`` variable declared without an initializer keeps its zero + value for its entire lifetime; a shorter array value stored into a + longer array is padded with the element type's zero value. This rule + is stated normatively, in the context of *Gazprea*'s RAII-style + initialization, in :ref:`sec:declaration`. + aggregate type A type composed of subordinate members of possibly-different types. In ISO C the term denotes array and structure types collectively - [#iso-c11]_. In *Gazprea* the aggregate types are arrays, vectors, - tuples, strings, and structs; see :ref:`sec:types`. + [#iso-c11]_. In *Gazprea* the aggregate types are arrays, matrices, + vectors, tuples, and structs; see :ref:`sec:types`. *Terminology note.* Ada calls this umbrella category *composite type* rather than *aggregate type* [#ada-rm]_. ISO C @@ -67,10 +106,10 @@ Terms compile time The interval during which the source program is being translated - by the :term:`compiler`, before program execution begins. Ada - states the contrast explicitly: "At compile time, the declaration - of an entity declares the entity. At run time, the elaboration of - the declaration creates the entity" [#ada-rm]_. The C + by the :term:`compiler`, before program execution begins. The + contrast with :term:`run time` matters for sizing in *Gazprea*: an + array's size need not be fixed at compile time, only at + :term:`initialization`, which is a run-time instant. The C standard specifies the phases of translation in ISO/IEC 9899 §5.1.1.2 [#iso-c11]_. @@ -84,7 +123,7 @@ Terms *Compiler subtypes.* The unmarked base case -- a compiler whose target is machine code executable by a CPU -- has no distinct term of art; it is simply *compiler*. Two marked variants are - recognised: + recognized: * A :term:`source-to-source translator` compiles from one high-level language to another high-level language [#dragon]_. @@ -172,7 +211,7 @@ Terms in a defined order. The concept is standard across modern language families: Python defines it operationally through the iterator protocol (``__iter__`` / ``__next__``) [#pep-234]_; - C++ defines *iterators* as generalised pointers into a range, + C++ defines *iterators* as generalized pointers into a range, with the requirements collected in [iterator.requirements] [#cpp-draft]_. In *Gazprea* the word is used informally for the mechanism that a @@ -219,7 +258,7 @@ Terms An informal property of a language, expression, or function: the absence of observable :term:`side effects `. There is no ISO definition; the standard academic reference is - Strachey's characterisation of :term:`referential transparency` + Strachey's characterization of :term:`referential transparency` [#strachey-2000]_. *Gazprea* invokes functional purity as the motivation for forbidding mutable :ref:`globals ` and for the input-only nature of function arguments. @@ -252,14 +291,43 @@ Terms *Gazprea policy.* A conforming *Gazprea* implementation must not have any user-distinguishable implementation-defined - behavior, unspecified behavior, or undefined behavior: every - program is either :term:`well-formed` and produces the output - required by this specification, or it is :term:`ill-formed` and - the implementation emits an error. The reason these + behavior or unspecified behavior, and has **no undefined behavior + under standard operation**. The single, deliberate exception is + the ``-ffast-math`` compiler flag (see :ref:`sec:flags`), under + which the integer math faults of :ref:`ssec:integer` become + undefined behavior; it is provided solely for performance testing. + Outside that one case, every program is either :term:`well-formed` + and produces the output required by this specification, or it is + :term:`ill-formed` and the implementation emits an error. The reason these C/C++ terms appear in this glossary is definitional -- the *Gazprea* prose uses them to say what the language does *not* allow, not to reserve latitude for implementers. + implicit cast + A conversion the compiler performs automatically, with no syntax + in the program text. In *Gazprea*, "cast" is the umbrella term + for both implicit casts and the *explicit casts* written + ``as(value)``; an implicit cast is simply the automatic + counterpart (e.g. ``integer`` -> ``real`` when arithmetic mixes + them). The mechanism is specified in + :ref:`sec:implicitCasts`. Most implicit casts can also be written + explicitly as an ``as<>`` cast; a scalar-to-array explicit cast must + then state the destination size (see :ref:`ssec:typeCasting_stovm`). + + explicit cast + A conversion the programmer writes out explicitly in the program + text with the ``as(value)`` syntax, as opposed to an + :term:`implicit cast`, which the compiler inserts automatically. + Both are *casts*; the explicit form is specified in + :ref:`sec:typeCasting`. + + value type + A type whose values are stored inline, by value, rather than + through indirection. In *Gazprea* every :term:`aggregate type` + except ``vector`` is a value type; nesting must be acyclic through + value types, so a ``struct`` or ``tuple`` may refer to its own type + only through a ``vector`` (see :ref:`ssec:storable_types`). + implicit conversion An automatic conversion inserted by the language, without a cast, to make an operand's type match a required target type. ISO C @@ -267,13 +335,8 @@ Terms *Gazprea* uses this general term only in the glossary. In the *Gazprea* specification proper the analogous mechanism is called - :ref:`type promotion `, and it is - deliberately distinct from :term:`type casting`: type promotion - is the *implicit* mechanism (e.g. ``integer`` -> ``real`` when - arithmetic mixes them), while type casting is the *explicit* - mechanism invoked via ``as(value)``. The two are not - interchangeable in *Gazprea*: some casts have no corresponding - implicit promotion. + an :term:`implicit cast`, described in + :ref:`sec:implicitCasts`. initializer The syntactic element that supplies an initial value to a newly @@ -315,7 +378,7 @@ Terms The interval during which the :term:`linker` runs, after :term:`translation` of each translation unit and before program execution. ISO C describes this as translation phase 8 - [#iso-c11]_. Link-time optimisation (LTO), performed at this + [#iso-c11]_. Link-time optimization (LTO), performed at this point, is documented for the LLVM toolchain in [#llvm-lto]_. literal @@ -352,7 +415,7 @@ Terms *Gazprea note.* *Gazprea* is not an object-oriented language -- it has no user-defined classes, no inheritance, and no virtual dispatch. The one place the *Gazprea* prose reaches for - OO-flavoured wording is the :term:`aggregate ` + OO-flavored wording is the :term:`aggregate ` :ref:`vector ` type, which exposes methods (``push``, ``len``, ``append``) via dot syntax. Those are built-in operations on the vector's storage-region object, not @@ -366,7 +429,8 @@ Terms *primitive* types (``i32``, floating-point types, ``void``, etc.) from *derived* and *aggregate* types [#llvm-langref]_. In *Gazprea* the primitive types are ``boolean``, ``integer``, - ``real``, and ``character``. + ``real``, and ``character``. See also :term:`scalar type`, + ISO C's term for the same four *Gazprea* types. prvalue A "pure r-value": "an expression whose evaluation initializes an @@ -395,7 +459,9 @@ Terms therefore change between calls with the same arguments; nor for any expression that transitively depends on a procedure call. This is why *Gazprea* forbids calling procedures inside - functions, forbids mutable globals, and restricts the operators + functions (aside from a mutating ``vector``/``string`` method such as + ``push``/``append`` on a function-local variable; see + :ref:`sec:function`), forbids mutable globals, and restricts the operators that may combine a procedure call's return value (see :ref:`sec:procedure`). @@ -419,7 +485,9 @@ Terms types are collectively called scalar types" [#iso-c11]_. Ada groups enumeration, integer, and real types as scalar [#ada-rm]_. In *Gazprea* the scalar types are ``boolean``, - ``integer``, ``real``, and ``character``. + ``integer``, ``real``, and ``character``. See also + :term:`primitive type`, the LLVM-derived term for the same four + *Gazprea* types. scope "The region of program text within which [an] identifier is @@ -481,7 +549,7 @@ Terms converter are all translators [#dragon]_. type - A characterisation of a set of values together with a set of + A characterization of a set of values together with a set of operations on those values [#ada-rm]_ [#pierce-tapl]_. *Gazprea*'s types are enumerated in :ref:`sec:types`. @@ -498,21 +566,6 @@ Terms academic reference [#milner-1978]_. *Gazprea*'s type inference is described in :ref:`sec:typeInference`. - type promotion - In general PL usage, an :term:`implicit conversion` in which an - operand of one type is converted to a "wider" or "richer" type - before an operation; ISO C specifies *integer promotions* - (§6.3.1.1) and the *usual arithmetic conversions* (§6.3.1.8) - [#iso-c11]_. - - In *Gazprea*, *type promotion* is the specific implicit-conversion - mechanism defined in :ref:`sec:typePromotion` (integer to real, - scalar to array, tuple to tuple, string to character-vector and - back). It is deliberately separate from :term:`type casting`, - which is the explicit mechanism invoked via ``as(value)``. - Every promotion is available as an explicit cast, but not every - cast is available as an implicit promotion. - type qualifier In ISO C the term refers to the *cv*-qualifiers ``const``, ``restrict``, ``volatile``, and ``_Atomic``, defined in §6.7.3 @@ -532,7 +585,7 @@ Terms *Further reading (for the curious student).* The deep connection between type systems and formal logic -- types correspond to propositions, programs to proofs, program - reduction to proof normalisation -- is the *Curry-Howard + reduction to proof normalization -- is the *Curry-Howard correspondence*. Wadler's ACM lecture "Propositions as Types" [#wadler-2015]_ is a short, entry-level survey; Sørensen and Urzyczyn's book-length *Lectures on the Curry-Howard @@ -546,7 +599,9 @@ Terms [#iso-c11]_. A program exhibiting undefined behavior at run time is not obliged to signal an error, terminate, or produce any particular output. Contrast :term:`unspecified behavior` and - :term:`implementation-defined behavior`. + :term:`implementation-defined behavior`. In *Gazprea*, undefined + behavior arises only under the ``-ffast-math`` flag (see + :ref:`sec:flags`); a program compiled without it has none. unspecified behavior "Use of an unspecified value, or other behavior where this @@ -565,7 +620,7 @@ Terms "C++ program constructed according to the syntax rules, diagnosable semantic rules, and the one-definition rule" [#cpp-defns]_. *Gazprea* uses "well-formed" throughout in - this generalised sense: a *Gazprea* program is well-formed if it + this generalized sense: a *Gazprea* program is well-formed if it satisfies every diagnosable rule stated in this specification. xvalue @@ -636,7 +691,7 @@ The primary citations for the entries above are listed here. defines the range-based ``for`` statement's *for-range-declaration* and *for-range-initializer*. * [iterator.requirements] §25.3 -- entry :term:`iterator`; defines - iterators as generalised pointers into a range and enumerates + iterators as generalized pointers into a range and enumerates the iterator category requirements. .. [#cpp-defns] ISO/IEC 14882:2020 (C++20), the "defns" definitions @@ -773,11 +828,11 @@ The Diataxis framework classifies a glossary as *reference* documentation, whose job is to describe -- accurately, austerely, and without narrative -- the technical vocabulary of a system [#diataxis]_. The Write the Docs community guide reiterates the -constraint that reference material should be optimised for lookup +constraint that reference material should be optimized for lookup rather than for narrative reading [#wtd-reference]_. Guidance on the craft of glossary-writing itself -- one entry per concept, plain language, definitions that do not re-use the word being defined, and -concrete examples where possible -- is summarised by Lester at The +concrete examples where possible -- is summarized by Lester at The Word Factory [#wordfactory-glossary]_. ISO/IEC/IEEE 26514:2022 gives the formal standards-track requirements for user documentation, including terminology sections [#iso-26514]_. diff --git a/gazprea/spec/identifiers.rst b/gazprea/spec/identifiers.rst index 96a55df..d72f87f 100644 --- a/gazprea/spec/identifiers.rst +++ b/gazprea/spec/identifiers.rst @@ -4,30 +4,34 @@ Identifiers =========== :term:`Identifiers ` in *Gazprea* must start with either an -underscore or a letter (upper or lower cased). Subsequent characters can +underscore or a letter (upper or lower case). Subsequent characters can be an underscore, letter (upper or lower case), or number. An identifier may not be any of *Gazprea*\ 's keywords. Here are some valid identifiers in *Gazprea*: :: - hello - h3ll0 - _h3LL0 - _Hi - Hi - _3 + hello + h3ll0 + _h3LL0 + _Hi + Hi + _3 The following are some examples of :term:`ill-formed` identifiers. They begin with a number, contain invalid characters, or are a keyword: :: - 3d - in - a-bad-variable-name - no@twitter - we.don't.like.punctuation + 3d + in + a-bad-variable-name + no@twitter + we.don't.like.punctuation *Gazprea* imposes no restrictions on the length of identifiers. +*Gazprea* identifiers are **case-sensitive**: two identifiers that differ +only in the case of one or more letters are distinct. For example, ``a`` and +``A`` name two different variables, as do ``value`` and ``Value``. + diff --git a/gazprea/spec/implicit_casts.rst b/gazprea/spec/implicit_casts.rst new file mode 100644 index 0000000..0e90e58 --- /dev/null +++ b/gazprea/spec/implicit_casts.rst @@ -0,0 +1,238 @@ +.. _sec:implicitCasts: + +Implicit Casts +============== + +An *implicit cast* is a conversion the compiler performs automatically, +with no syntax in the program text. Implicit casts are the counterpart of +the *explicit casts* written ``as(value)`` in +:ref:`sec:typeCasting`; "cast" is the umbrella term for both. + +Most conversions that can be performed implicitly can also be written +explicitly as an ``as<>`` cast. The one caveat is that a scalar-to-array +*explicit cast* must state the destination size explicitly +(:ref:`ssec:typeCasting_stovm`), whereas the corresponding *implicit cast* +takes its size from the array operand. (The ``string`` / ``character[*]`` +conversion, being the array/vector cast specialized to ``character``, has +both an implicit and an explicit ``as<>`` form like any other array/vector +cast; see :ref:`ssec:implicitCasts_string`.) + +A :term:`scalar ` may be implicitly cast to an array of any +rank, including the rank-2 matrix case (see :ref:`ssec:implicitCasts_stoa`). +An array is never implicitly cast to a different rank; only a scalar expands +to fill an array or matrix. + +Attempting any conversion this chapter does not describe as a valid implicit +cast -- in a declaration, an assignment, or between corresponding tuple +members -- is a compile-time error; the compiler must emit a ``TypeError`` +(see :ref:`sec:errors`). + +.. _ssec:implicitCasts_scalar: + +Scalars +------- + +The only automatic implicit cast between scalars is ``integer`` to +``real``. This cast is one way -- a ``real`` is never implicitly cast to +``integer``. + +Automatic conversion follows this table where N/A means no implicit cast is +possible, id means no conversion necessary, and ``as(value)`` means the +value of type "From type" is converted to type "toType" using semantics from +:ref:`sec:typeCasting`. + ++----------+-----------+---------+-----------+---------+---------------+ +| | **To type** | ++----------+-----------+---------+-----------+---------+---------------+ +| | | boolean | character | integer | real | ++ +-----------+---------+-----------+---------+---------------+ +| **From** | boolean | id | N/A | N/A | N/A | ++ +-----------+---------+-----------+---------+---------------+ +| **type** | character | N/A | id | N/A | N/A | ++ +-----------+---------+-----------+---------+---------------+ +| | integer | N/A | N/A | id |as(value)| ++ +-----------+---------+-----------+---------+---------------+ +| | real | N/A | N/A | N/A | id | ++----------+-----------+---------+-----------+---------+---------------+ + +Because ``character`` and ``integer`` are N/A in both directions, there is no +implicit cast between them. A direct consequence is that ``character`` values +are **not orderable**: the relational operators ``<``, ``>``, ``<=``, ``>=`` +are undefined on characters, and ordering them requires an explicit +``as(...)`` cast (see :ref:`ssec:character` and :ref:`sec:typeCasting`). + +.. _ssec:implicitCasts_stoa: + +Scalar to Array +-------------------------- + +All scalar types can be implicitly cast to arrays whose element type the +scalar can be :ref:`implicitly cast to `. +This can occur when an array is used in an operation with a scalar value. + +The scalar is implicitly cast to an array matching the array operand's size +(the operand-size rule of :ref:`sssec:array_ops`); the result's element +type is whichever type the operation requires, and the scalar is first +implicitly cast to that element type. For example: + +:: + + integer i = 1; + integer[*] v = [1, 2, 3, 4, 5]; + integer[*] res = v + i; + + res -> std_output; + +would print the following: + +:: + + [2 3 4 5 6] + +Other examples: + +:: + + 1 == [1, 1] // true + 1..3 || 3 // [1, 2, 3] + +Concatenation (``||``) is an exception to the size-matching rule above: a +scalar operand becomes a single new element regardless of the other +operand's length, rather than being expanded to match it (see +:ref:`sssec:string_ops`). + +Note that an array can never be cast down to a scalar, even explicitly. +Also note that matrix multiply imposes strict requirements on the +dimensionality of the operands. The consequence is that, *as an operand of +matrix multiplication* (``**``), a scalar can only be implicitly cast to a +matrix when the other operand is a square matrix (:math:`m \times m`): the +scalar is then broadcast (filled) into an :math:`m \times m` matrix whose every +element equals the scalar. For higher-rank arrays this generalizes only to +hypercubes with all extents equal; *Gazprea* provides no comprehensive +broadcasting, so a scalar cannot be broadcast to a non-square matrix operand of +``**`` at all. In element-wise operations and initializations a scalar is +implicitly cast to an array (or matrix) of any dimensions. + +.. _ssec:implicitCasts_ttot: + +Tuple to Tuple +-------------- + +A tuple may be implicitly cast to another tuple type when the two have an equal +number of members and each member of the source can be implicitly cast to the +corresponding member of the destination. Each member is cast by the rule for +its own kind: scalar members follow the scalar table above, array members +follow the :ref:`array sizing rules ` -- a shorter value is +padded with the element type's :term:`zero value` and a longer value raises a +``SizeError`` (see :ref:`sec:errors`) -- and a nested ``tuple``, ``vector``, or +array member follows the same implicit-cast rules as a standalone value of that +type. A ``struct`` member is the exception: a ``struct`` is never implicitly +cast (see :ref:`ssec:struct`), so the two struct types must be identical and +the member is copied unchanged. For example: + +:: + + tuple(integer, integer) int_tup = (1, 2); + tuple(real, real) real_tup = int_tup; + + tuple(character, integer, boolean[2]) many_tup = ('a', 1, [true, false]); + tuple(character, real, boolean[2]) other_tup = many_tup; + +If initializing a variable with a tuple via :ref:`sec:typeInference`, the +variable is assumed to be the same type. +Therefore, tuple elements are also copied accordingly. For example: + +:: + + tuple(real, real) foo = (1, 2); + tuple(real, real) bar = (3, 4); + + var baz = foo; + baz.1 -> std_output; // 1.0 + baz.2 -> std_output; // 2.0 + + baz = bar; + baz.1 -> std_output; // 3.0 + baz.2 -> std_output; // 4.0 + + +It is possible for a two-sided implicit cast to occur with tuples. For +example: + +:: + + boolean b = (1.0, 2) == (2, 3.0); + +.. _ssec:implicitCasts_avv: + +Array to/from Vector +-------------------- + +An array value and a :ref:`vector ` are implicitly cast to one +another in both directions. Like every implicit cast this converts a +*value*; it never changes how either side is sized. Each element converts by +the implicit-cast rule for its own type: the scalar table of +:ref:`ssec:implicitCasts_scalar` for a scalar element, or the corresponding +rule elsewhere in this chapter, applied recursively, for a composite element +type. + +- **Vector to array.** The vector's *current* length produces the array + value. Storing that value into an array obeys the array's own + :ref:`fixed length `: a shorter value is padded with + the element type's :term:`zero value` and a longer value raises a + ``SizeError`` (see :ref:`sec:errors`). If the destination is an inferred + ``[*]`` array and this is its :term:`initialization`, the vector's current + length becomes that array's fixed length. + +- **Array to vector.** The array's fixed length produces the vector value. + The receiving vector takes that length and may still grow afterwards via + ``push``/``append``. + +:: + + vector vec = [1, 2, 3]; // current length 3 + integer[3] a = vec; // [1, 2, 3] + integer[5] b = vec; // [1, 2, 3, 0, 0] (padded) + integer[*] c = vec; // length inferred as 3, then fixed + + integer[2] d = [7, 8]; + var vector w = d; // [7, 8]; w may still grow + call w.push(9); // [7, 8, 9] + +.. _ssec:implicitCasts_atoa: + +Array to Array +-------------- + +An array value may be implicitly cast to another array type of the **same +rank** when every element can be implicitly cast to the destination's element +type. Each element converts by the implicit-cast rule for its own type (the +scalar table of :ref:`ssec:implicitCasts_scalar` for scalar elements, applied +recursively for composite elements). The result obeys the destination array's +:ref:`fixed length `: a shorter value is padded with the +element type's :term:`zero value` and a longer value raises a ``SizeError`` +(see :ref:`sec:errors`). An array is never implicitly cast to a different +rank. + +:: + + integer[3] v = [1, 2, 3]; + real[3] u = v; // [1.0, 2.0, 3.0] + +.. _ssec:implicitCasts_string: + +Character Array to/from String +------------------------------- + +A ``string`` value can be implicitly cast to a ``character`` array +(``character[*]``) and vice versa (a two-way implicit cast). Because a +``string`` is a language-supplied typealias for ``vector`` (see +:ref:`ssec:string`), this is simply the array/vector implicit cast of +:ref:`ssec:implicitCasts_avv` specialized to the ``character`` element type; +the conversion of note is between ``string`` and character *arrays*. + +:: + + string str1 = "Hello"; /* str1 == "Hello" */ + character[*] chars = str1; /* chars == ['H', 'e', 'l', 'l', 'o'] */ + string str2 = chars || [' ', 'W', 'o', 'r', 'l', 'd']; /* str2 == "Hello World" */ diff --git a/gazprea/spec/keywords.rst b/gazprea/spec/keywords.rst index 4c83471..ef8c0af 100644 --- a/gazprea/spec/keywords.rst +++ b/gazprea/spec/keywords.rst @@ -3,9 +3,13 @@ Keywords ======== -*Gazprea* has a number of built in keywords that are reserved and should +*Gazprea* has a number of built-in keywords that are reserved and must not be used by a programmer. +The names of the built-in functions are *not* keywords; they are +reserved semantically rather than syntactically -- see :ref:`sec:builtIn` +for the full rule. + - and - as @@ -14,14 +18,10 @@ not be used by a programmer. - break -- by - - call - character -- columns - - const - continue @@ -30,8 +30,6 @@ not be used by a programmer. - false -- format - - function - if @@ -40,8 +38,6 @@ not be used by a programmer. - integer -- length - - loop - not @@ -56,16 +52,10 @@ not be used by a programmer. - returns -- reverse - -- rows - - std_input - std_output -- stream_state - - string - struct diff --git a/gazprea/spec/namespaces.rst b/gazprea/spec/namespaces.rst index c8ed53d..16ace7f 100644 --- a/gazprea/spec/namespaces.rst +++ b/gazprea/spec/namespaces.rst @@ -6,23 +6,43 @@ Namespaces There are two namespaces in *Gazprea*: - Type namespace: user-defined types (structs and typealiases). -- Variable/Function/procedure namespace: functions and procedures. - -Items in separate namespaces may share an :term:`identifier`. Items within the same namespace cannot share an identifier, this is a ``SymbolError``. +- Variable/Function/procedure namespace: variables, functions, and + procedures. + +Items in separate namespaces may share an :term:`identifier`. Items within the +same namespace cannot share an identifier; the compiler must emit a +``SymbolError`` (see :ref:`sec:errors`). + +Both namespaces are :term:`lexically scoped `. The no-sharing rule +applies within a single scope; a name introduced in an inner scope -- a local +variable, or a type defined by a local ``struct`` or ``typealias`` -- **shadows** +any outer name of the same namespace for the extent of that scope and does not +leak back out. In particular, a type defined inside a function or procedure is +not added to the global type namespace. + +A ``struct``'s field names are **not** a third namespace. Each ``struct`` +introduces its own :term:`declaration scope ` for its fields -- the same +mechanism by which a block or a function body scopes its local names -- so a +field name lives in that struct's scope, not in either global namespace, and may +freely coincide with a type name, a variable/function/procedure name, or a field +name of another struct. The only constraint applies *within* a single struct: +its fields must have distinct names. A ``struct`` that declares two fields with +the same name is :term:`ill-formed`, and the compiler must emit a +``SymbolError`` (see :ref:`sec:errors`). :: // Does not conflict with the other statements struct x (integer a, integer b); - // These three statements all confict with each other + // These three statements all conflict with each other // Any two of them in the same program produces a SymbolError - integer x = 3; - function x() returns integer; - procedure x() returns integer; + integer x = 3; + function x() returns integer; + procedure x() returns integer; :: - + // Pro tip: write code that looks like this, employers love it typealias integer a; @@ -30,13 +50,13 @@ Items in separate namespaces may share an :term:`identifier`. Items within the s struct b (a b, a a, main main); // Struct field identifiers do not conflict with anything procedure main() returns integer { - + a a = 1; // type and variable do not conflict b b = b(b: a, a: 2, main: 3); if (true) { // New scope - a a = b.b // New `a` shadows the old `a` + a a = b.b; // New `a` shadows the old `a` } return 0; } diff --git a/gazprea/spec/procedures.rst b/gazprea/spec/procedures.rst index 6db6fe6..0b5c1e9 100644 --- a/gazprea/spec/procedures.rst +++ b/gazprea/spec/procedures.rst @@ -7,26 +7,58 @@ A procedure in *Gazprea* is like a function, except that it does not have to be :term:`pure ` and as a result it may: - Have arguments marked with ``var`` that can be mutated. By default - arguments are ``const`` just like functions. + arguments are ``const`` just like functions (see :ref:`sec:typeQualifiers`). -- A procedure may only accept a literal or expression as an argument if - and only if the procedure declares that argument as ``const``. +- Accept a literal or expression as an argument if and only if the + corresponding parameter is declared ``const``. -- Procedures may perform I/O. +- Perform I/O. -- A procedure can call other procedures. +- Call other procedures. -- Procedures can only be called in the RHS of declaration statements, RHS - of assignment statements or as the procedure being called in a call statement. +In exchange for these capabilities, the ways in which a procedure *call* may be +used are restricted. -- When used within a valid statement, the only legal operators which can - be applied to a procedure call are unary operators and casts. - Additionally, the result of the call may not be used in the direct construction - of a type that does not match the return type of the procedure. +.. _ssec:procedure_call_positions: + +A procedure call may appear only in one of three positions: + +- on the right-hand side of a declaration statement, + +- on the right-hand side of an assignment statement, or + +- as the procedure being called in a ``call`` statement. + +This is the single authoritative list of those positions. A procedure call +may not be used as the control expression of a control-flow statement. The +"right-hand side of an assignment" is a *single*-target assignment or +declaration: a procedure that returns a ``tuple`` is bound to one variable first +(``var t = p();``), and it may **not** appear directly as the source of a +:ref:`tuple-unpacking assignment ` such as ``a, b = p();``. To +destructure the result, unpack the bound variable instead (``a, b = t;``). + +Argument position is deliberately **not** on this list: a procedure call may +not appear as an argument to another call -- neither to a procedure call nor to +a function call. Nesting a procedure call as an argument, as in ``call +foo(p())`` or ``f(p())`` where ``p()`` is a procedure call, is +:term:`ill-formed`, and the compiler must emit a ``CallError`` (see +:ref:`sec:errors`); assign the inner call's result to a temporary and pass that +instead. Only procedure calls are restricted this way -- a *function* call +carries no such restriction and may be nested freely as an argument (subject to +the usual rule that a procedure argument may itself be a function call, but not +a procedure call). + +When a procedure call appears in one of these positions, the only operations +that may be applied to its result are unary operators and +:ref:`casts `. The result may additionally not be used in the +direct construction of a type that does not match the return type of the +procedure. A procedure call used outside these positions, or with any other +operation applied to its result, is :term:`ill-formed`; the compiler must emit +a ``CallError`` (see :ref:`sec:errors`). Aside from this (and the different syntax necessary to declare/define them), procedures are very similar to functions. The extra capabilities -that procedures have makes them harder to reason about, test, and +that procedures have make them harder to reason about, test, and optimize. .. _ssec:procedure_syntax: @@ -37,7 +69,8 @@ Syntax Procedures are almost exactly the same as functions. However, because procedures can cause side effects, the returns clause is optional. Due to this, the ``= ;`` declaration format is not available for -procedures. For example, the following code is :term:`ill-formed`: +procedures. For example, the following code is :term:`ill-formed`, and the +compiler must emit a ``SyntaxError`` (see :ref:`sec:errors`): :: @@ -46,7 +79,10 @@ procedures. For example, the following code is :term:`ill-formed`: If a returns clause is present, then a return statement must be reached by all possible control flows in the procedure before the end of the -procedure is encountered. For instance: +procedure is encountered; if control can reach the end of the body without +executing a ``return``, the compiler must emit a ``ReturnError`` (see +:ref:`sec:errors`), exactly as for :ref:`functions `. For +instance: :: @@ -78,17 +114,22 @@ These procedures can be called as follows: call fibonacci(x,y); /* x == 21 and y == 34 */ Only procedures may be called with ``call``. Functions must -appear in expressions because they can not cause side effects, so using -a function in a ``call`` statement would not do anything. *Gazprea* -should raise an error if a function is used in a ``call`` statement. - -A procedure may never be called within a function, doing so would allow for -impure functions. Procedures may only be called within assignment statements -(procedures may not be used as the control expression in control flow expressions, for instance). -The return value from a procedure call can only be manipulated with -unary operators. A program that uses the results from a procedure call -with binary expressions is :term:`ill-formed`. -For example: +appear in expressions because they cannot cause side effects, so using +a function in a ``call`` statement would not do anything. *Gazprea*'s +compiler must emit a ``CallError`` (see :ref:`sec:errors`) if a +function is used in a ``call`` statement. + +A procedure may never be called within a function, with one exception: a +mutating :ref:`vector/string method ` (``push``, ``append``) +may be called on a variable local to the function. Any other procedure call +within a function would allow for impure functions, and the compiler must emit +a ``CallError`` (see :ref:`sec:errors`). The positions in which a procedure +call may appear are exactly :ref:`those listed at the start of this chapter +`; in particular, a procedure call may not be +used as the control expression of a control-flow statement. As noted there, the +only operations permitted on the result of a procedure call are unary operators +and :ref:`casts `; using the result of a procedure call in a +binary expression is :term:`ill-formed`. For example: :: @@ -102,7 +143,7 @@ These restrictions are made by *Gazprea* in order to allow for more optimizations. Procedures without a return clause may not be used in an expression. -*Gazprea* should raise an error in such a case. +The compiler must emit a ``CallError`` in such a case. :: /* p is some procedure with no return clause */ @@ -114,22 +155,27 @@ Procedure Declarations ---------------------- Procedures can use :ref:`forward declaration ` -just like functions. +just like functions. As with a function, a procedure prototype is only a +forward *declaration* and must be matched by a definition elsewhere in the +program; a procedure that is prototyped but never defined is :term:`ill-formed`, +and the compiler must emit a ``DefinitionError`` (see :ref:`sec:errors`). .. _ssec:procedure_main: Main ---- -Execution of a *Gazprea* program starts with a procedure called -``main``. This procedure takes no arguments, and has an integer return -type. ``main`` is called exclusively by the operating system, and the return value is -used by the operating system, so if you are using multiple compilation units -one and only one compilation unit must define ``main``. +Execution of a *Gazprea* program starts with a procedure called ``main``. This +procedure takes no arguments, and has an integer return type. ``main`` is +called exclusively by the operating system, and the return value is used by the +operating system, so if you are using multiple compilation units one and only +one compilation unit must define ``main``. A program with no ``main``, or whose +``main`` does not match this signature, is :term:`ill-formed`; the compiler +must emit a ``MainError`` (see :ref:`sec:errors`). :: - /* must be writen like this */ + /* must be written like this */ procedure main() returns integer { var integer x = 1; x = x + x; @@ -139,23 +185,26 @@ one and only one compilation unit must define ``main``. return 0; } -.. _ssec:procedure_alias: +.. _ssec:procedure_implicit_casts: -Type Promotion of Arguments +Implicit Casts of Arguments --------------------------- -Argument types can be promoted at call time, but only if the argument is -call by value (``const``). The reason is that mutable arguments are effectively -call by reference, and are therefore *l-values* (pointers). +An argument may be :ref:`implicitly cast ` to the parameter +type at call time, but only if the argument is passed by value (that is, the +parameter is ``const``). A mutable (``var``) parameter is effectively call by +reference, so the parameter and the argument denote the same :term:`l-value +` (a pointer); there is no separate value to convert, and so no +implicit cast can be inserted. :: - 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,18 +215,37 @@ call by reference, and are therefore *l-values* (pointers). return 0; } +In ``byvalue(y)`` the argument ``y`` is a ``character[3]`` and the parameter is +a :ref:`string ` -- a runtime-sized :ref:`vector ` of +``character``. Because the parameter is passed by value, the *value* of ``y`` is +implicitly cast to a ``string``, and that ``string`` is what ``byvalue`` +receives; the caller's array ``y`` is left unchanged. + +The call ``byreference(y)`` is illegal for two independent reasons. First, the +parameter ``var string x`` is call by reference, which admits no implicit cast: +there is no distinct value to convert, only the caller's storage. Second, even +setting that aside, the argument ``y`` is ``const``, and a ``var`` parameter +cannot bind a ``const`` argument; the compiler must emit a ``TypeError`` (see +:ref:`sec:errors`). + Aliasing -------- -Since procedures can have mutable arguments, it would be possible to -cause `aliasing `__. -In *Gazprea* a program that aliases mutable variables is -:term:`ill-formed`. The only case -where aliasing of arguments is allowed is through disjoint tuple or struct field access. This -helps *Gazprea* compilers perform more optimizations. However, the compiler must be able -to catch cases where mutable memory locations are aliased, and an error -should be raised when this is detected. For instance: +Since procedures can have mutable arguments, it would be possible to cause +`aliasing `__. Aliasing is +restricted only when at least one of the aliased arguments is bound to a +``var`` parameter; two arguments bound to ``const`` parameters may always +alias, since neither grants the ability to mutate. A program that aliases two +such arguments, where at least one is bound to a ``var`` parameter, is +:term:`ill-formed`. This helps *Gazprea* compilers perform more optimizations. +However, the compiler must be able to catch cases where mutable memory +locations are aliased, and must emit an ``AliasingError`` (see +:ref:`sec:errors`) when this is detected. ``AliasingError`` is always a +:term:`compile-time ` diagnosis: since exact overlap is +undecidable, the check uses the conservative *same-backing-array* rule -- two +arguments that name the same array, or slices of it, are treated as aliasing +even when their accessed ranges are disjoint. For instance: :: @@ -196,8 +264,8 @@ should be raised when this is detected. For instance: call p(x, y, x, x); /* Argument a is mutable and aliased with c and d. */ /* Legal */ - call p(x, y, z, z); - /* Even though 'z' is aliased with 'c' and 'd' they are both const. */ + call p(x, y, z, z); + /* Even though 'z' is aliased with 'c' and 'd' they are both const. */ return 0; } @@ -215,38 +283,120 @@ passed to procedures. For instance: It is impossible to tell whether or not these overlap at :term:`compile time` due to the halting problem. Thus for simplicity, whenever an array is passed to a procedure *Gazprea* detects aliasing whenever the same array is used, -regardless of whether or not the access would overlap. +regardless of whether or not the access would overlap. A +:ref:`slice ` bound to a ``var`` parameter is a reference +into its backing array, so two ``var`` arguments that slice the same backing +array always alias -- the backing array is the unit of aliasing -- even when +their ranges are disjoint. -Another instance of aliasing relates to tuples, such as passing the -same tuple twice in one procedure, or passing the entire tuple and -separately passing a single tuple field. In both cases this can cause -aliasing. +Another instance of aliasing relates to tuple and struct fields. Passing the +same field to two ``var`` parameters is aliasing, but passing two *disjoint* +fields of the same tuple or struct to two ``var`` parameters is legal, since +disjoint fields occupy non-overlapping storage: :: - call p(t1, t1.1); - /* p is some procedure with a tuple argument and a real argument */ + procedure p(var integer x, var integer y) { + /* Some code here */ + } + + var tuple(integer, integer) t = (1, 2); + + call p(t.1, t.2); /* Legal: disjoint fields, no aliasing. */ + call p(t.1, t.1); /* AliasingError: the two var arguments alias. */ .. _ssec:procedure_vec_mat: Array Parameters and Returns ---------------------------------------- -:ref:`As with functions `, the arguments and return -value of procedures can have both explicit and inferred sizes. +:ref:`As with functions `, the parameters and return +value of a procedure can have both explicit and inferred sizes, and the same +checking rules apply: + +- An explicitly sized array parameter such as ``real[3][3]`` makes that size + part of the procedure's signature; the corresponding argument must match it + in every dimension, or the compiler must emit a ``SizeError`` (see + :ref:`sec:errors`). + +- An inferred-size array parameter such as ``integer[*]`` is + :term:`initialized ` at the call from the argument that is + passed, taking on that argument's length for the duration of the call. An + inferred-size return type is likewise initialized at the ``return``, from the + value being returned. + +- A :ref:`vector ` parameter or return type carries no length in + its type, so no length check applies in either direction. + +Slices can be used wherever arrays are declared as parameters (see +:ref:`sssec:array_slices`). Unlike functions, an array parameter of a procedure +may be ``var``, allowing the array or slice passed to it to be modified. + +Procedures follow the usual ``var``-is-reference, ``const``-is-value +convention, and array slices follow it too. A slice bound to a ``const`` +parameter is passed **by value**: the callee receives a copy of the selected +elements and cannot reach the caller's array through it, so no aliasing arises. +A slice bound to a ``var`` parameter is passed **by reference**: it is a view +that writes *through* to the backing array, exactly as a slice on the left of an +assignment does, so the callee's writes are visible to the caller once the call +returns (and the backing array must therefore be ``var``). An implementation may +pass a ``const`` slice by reference for efficiency -- because the callee only +reads it, the choice is unobservable, and *Gazprea*'s value semantics (realized +directly by *MLIR*) make the copy and the shared reference indistinguishable. + +.. _ssec:procedure_mutation: + +Mutating Array and Vector Parameters +---------------------------------------- + +A ``var`` parameter is call by reference, so a procedure may change what the +caller sees through it. What may change depends on whether the parameter is an +array or a vector: + +- A ``var`` array parameter is mutable in its **contents only**. Because an + array is :ref:`initialization-time sized `, a procedure + cannot change the length of an array it was passed. Assigning a value of the + same length replaces the contents; a shorter value is padded with the + element type's :term:`zero value`; a longer value raises a ``SizeError`` + (see :ref:`sec:errors`). -Similarly, slices can be used whereever arrays are declared as parameters, and -unlike functions, array parameters in procedures can be ``var``, allowing arrays -and slices passed to a procedure to be modified (see :ref:`sssec:array_slices`). +- A ``var`` :ref:`vector ` parameter is runtime sized and so + **may be grown**: ``push`` and ``append`` (see :ref:`sssec:vec_methods`) + add elements, and because the parameter is call by reference the caller + observes the new length once the call returns. + +For instance, ``fill`` overwrites the contents of an array without changing its +length, while ``extend`` lengthens a vector that its caller then observes: + +:: + + procedure fill(var integer[*] a, integer x) { + a = x; /* every element of a becomes x; a's length is unchanged */ + } + + procedure extend(var vector v, integer x) { + call v.push(x); /* v grows by one element */ + } + + procedure main() returns integer { + var integer[3] a = 0; + var vector v = [1, 2]; + + call fill(a, 7); /* a == [7, 7, 7]; still length 3 */ + call extend(v, 3); /* v == [1, 2, 3]; caller now sees length 3 */ + + return 0; + } + +Functions, by contrast, cannot mutate their parameters at all: every function +parameter is ``const``, so a function can change neither the contents nor the +length of an array, vector, or string it receives. .. _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 -gazprea program, nor can you forward declare the same procedure twice. - -Additionally, functions and procedures share the same namespace; you cannot -declare a function and procedure with the same name +Procedure identifiers share the global variable/function/procedure namespace +with every other global identifier; see :ref:`sec:namespaces` for the full +namespacing rules, including the ``SymbolError`` raised on a collision. diff --git a/gazprea/spec/statements.rst b/gazprea/spec/statements.rst index b54c11b..32df2d0 100644 --- a/gazprea/spec/statements.rst +++ b/gazprea/spec/statements.rst @@ -27,8 +27,9 @@ side. x -> std_output; /* Prints 6 */ Type checking must be performed on assignment statements. The expression -on the right hand side must have a type that can be automatically -promoted to the type of the variable. For instance: +on the right hand side must have a type that can be implicitly cast +to the type of the variable. If it does not, the compiler must emit a +``TypeError`` (see :ref:`sec:errors`). For instance: :: @@ -36,15 +37,19 @@ promoted to the type of the variable. For instance: var real real_var = 0.0; var boolean bool_var = true; - /* Since 'x' is an integer it can be promoted to a real number \*/ + /* Since 'int_var' is an integer it can be implicitly cast to a real number */ real_var = int_var; /* Legal */ - /* Real numbers can not be turned into boolean values automatically. \*/ + /* Real numbers cannot be turned into boolean values automatically. */ bool_var = real_var; /* Illegal */ Assignments can also be more complicated than this with arrays and tuples. With arrays indices may be provided in order to change the value of an array -element. In Gazprea, arrays cannot be indexed with array expressions. +element. As in any indexing context, an array cannot be indexed with an array +*value* -- see the :ref:`indexing rules ` for the normative +statement and its ``TypeError`` -- and a range written directly inside an index +position is not an array-valued index but forms a +:ref:`slice `. For instance, with single dimensional arrays: :: @@ -66,10 +71,40 @@ This applies to arrays of any dimension. /* Change the entire matrix M to [[1, 2], [3, 4]] */ M = [[1, 2], [3, 4]]; - /* Change a single position of M \*/ + /* Change a single position of M */ M[1][2] = 7; /* M is now [[1, 7], [3, 4]] */ -Tuples also have a special unpacking syntax in *Gazprea*. A tuple’s +Assigning a whole array value changes an array's *contents*, never its +*length*. Because an array is :term:`initialization`-time sized, its +length is fixed once at :term:`initialization`; the right hand side is +fitted to that fixed length, with a shorter value padded using the +element type's :term:`zero value` and a longer value causing the +compiler to emit a ``SizeError`` (see :ref:`sec:errors` and +:ref:`sssec:array_sizing`) at :term:`compile time` or :term:`run time`. +Assigning to a :ref:`vector ` behaves differently: it +replaces the contents *and* the length together, so there is no padding +and no ``SizeError``. + +:: + + var integer[*] a = [1, 2, 3]; + + /* 'a' keeps its fixed length 3; the shorter value is padded with + the integer zero value, so 'a' becomes [4, 5, 0]. */ + a = [4, 5]; + + /* A longer value cannot fit the fixed length -- SizeError. */ + a = [4, 5, 6, 7]; /* SizeError */ + +:: + + var vector vec = [1, 2, 3]; + + /* A vector replaces contents and length together, so 'vec' + becomes [4, 5] with length 2 -- no padding, no SizeError. */ + vec = [4, 5]; + +Tuples also have a special unpacking syntax in *Gazprea*. A tuple's field may be assigned to comma separated variables instead of a tuple variable. For instance: @@ -84,7 +119,7 @@ variable. For instance: /* x == 1, and y == 2.0 now */ x, y = tup; - /* Types can be promoted */ + /* Types can be implicitly cast */ /* z == 1.0, y == 2.0 */ z, y = tup; @@ -92,11 +127,15 @@ variable. For instance: /* Can swap: z == 2.0, y == 1.0 */ z, y = (y, z); -The types of the variables must match the types of the tuple’s fields, -or the tuple’s fields must be able to be automatically promoted to the -variable’s type. The number of variables in the comma separated list -must match the number of fields in the tuple, if this is not the case an -error should be raised. This assignment is performed left-to-right. +The types of the variables must match the types of the tuple's fields, +or the tuple's fields must be able to be implicitly cast to the +variable's type. The number of variables in the comma separated list +must match the number of fields in the tuple, if this is not the case the +compiler must emit an ``AssignError`` (see :ref:`sec:errors`). This +assignment is performed left-to-right. The entire right-hand side is, however, +fully evaluated into a temporary *before* any left-hand-side variable is +written; this is what lets a swap such as ``z, y = (y, z);`` behave as expected +even though the individual writes then happen left-to-right. Assignments and initializations must perform a deep copy. It should not be possible to cause the aliasing of memory locations with an @@ -118,12 +157,23 @@ assignment. For instance: */ The above is a simple example using arrays. You must ensure that values -can not be aliased with an assignment between any types, including +cannot be aliased with an assignment between any types, including arrays and tuples. -Variables may be declared as const, and in this case a program that -places them on the left hand side of an assignment expression is -:term:`ill-formed`. The compiler should raise an error when this is +This deep-copy rule has no exceptions; :ref:`array slices ` +obey it too. Binding a slice to a variable, as in ``const b = a[1..3];``, +*copies* the selected elements into a fresh, independent array, so ``b`` does +not alias ``a`` and neither one sees the other's later writes. A slice writes +*through* to its backing array only when it is the target on the *left* of an +assignment (``a[1..3] = [4, 5];``) or is bound to a ``var`` reference parameter +-- that is the :term:`lvalue` meaning of a slice, not an aliasing of two +variables. Every assignment and initialization -- whole arrays, slices, tuples, +and structs alike -- deep-copies, so, for example, creating a new struct copies +the right-hand side and never aliases it through indexing. + +Variables may be declared as const, and in this case a program that places them +on the left hand side of an assignment expression is :term:`ill-formed`. The +compiler must emit an ``AssignError`` (see :ref:`sec:errors`) when this is detected, since it does not make sense to change a constant value. The right hand side of an assignment statement is always evaluated @@ -152,9 +202,9 @@ statements in other languages such as *C/C++*. As an example: x -> std_output; "\n" -> std_output; z -> std_output; "\n" -> std_output; } -Is a block statement. Declarations can only appear at the start of a -block. Each block statement introduces a new scope that new variables -may be declared in. For instance this is perfectly valid: +Is a block statement. Declarations may appear anywhere within a block, +interleaved with the other statements (see :ref:`sec:declaration`). Each block +statement introduces a new scope that new variables may be declared in. For instance this is perfectly valid: :: @@ -182,6 +232,11 @@ then the body is executed. If the conditional expression evaluates to false then the body of the if statement is not executed. If statements in *Gazprea* require the conditional expression to be enclosed in parentheses. +The conditional expression must be a **scalar** ``boolean``. Supplying a +non-boolean value, or a boolean *array* such as ``if ([true, false])``, is a +``TypeError`` (see :ref:`sec:errors`). The same requirement applies to the +control expression of a :ref:`predicated loop `. + :: integer x = 0; @@ -219,7 +274,7 @@ is actually equivalent to the following: :: - if (x == 4) { + if (x == 3) { y = 7; } @@ -265,6 +320,18 @@ Now if ``x`` does not have a value of 3, ``y`` is assigned a value of Loop ---- +*Gazprea* has a single ``loop`` keyword that forms four loop variants, all +valid: + +- an **infinite loop** -- ``loop `` with no control expression; +- a **pre-predicated** (``while``-style) loop -- + ``loop while () ``; +- a **post-predicated** (``do``-``while``-style) loop -- + ``loop while ();``; +- an **iterator** (``for``-style) loop -- ``loop in ``. + +Each variant is described below. + .. _sssec:statements_inf_Loop: Infinite Loop @@ -301,7 +368,7 @@ when it is checked. The loop can be pre-predicated, which means that the control expression is tested before the body statement is executed. This is the same -behaviour as while loops in most languages, and is written using the +behavior as while loops in most languages, and is written using the ``while`` token after the ``loop``, followed by a boolean expression for the predicate. For example: @@ -318,7 +385,7 @@ predicate. For example: A post-predicated loop is also available. In this case the control expression is tested after the body statement is executed. This also uses the ``while`` token followed by the control expression, but it appears -at the end of the loop. Post Predicated loop statements must end in a +at the end of the loop. Post-predicated loop statements must end in a semicolon. :: @@ -328,12 +395,34 @@ semicolon. /* Since the conditional is tested after the execution '10' is printed */ loop x -> std_output; while (x == 0); +The body may equally be a block statement; the trailing ``while`` and its +required semicolon are what distinguish a post-predicated loop from a plain +:ref:`infinite loop ` over a block: + +:: + + var integer x = 0; + + /* Prints 1 to 10; the condition is tested after each pass */ + loop { + x = x + 1; + x -> std_output; "\n" -> std_output; + } while (x < 10); + +The single-statement post-predicated form (``loop ; while (cond);``) is +distinguished from a plain :ref:`infinite loop ` whose +body is that same statement only by the trailing ``while``, so the two +productions can diverge arbitrarily far into the input. *ANTLR*'s adaptive +``LL(*)`` prediction resolves this without special effort, but a hand-written or +fixed-lookahead ``LL(k)`` grammar will need care around this production. + .. _sssec:statements_iter_loop: 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, or +over a :ref:`vector ` or :ref:`string `. This is done by using :term:`domain expressions ` (for instance ``i in v``) in conjunction with a loop statement. In a domain expression ``x in E``, ``x`` is the :term:`iterator variable` @@ -358,30 +447,18 @@ Array ranges can also be used instead: :: // This will print 123 - loop i in 1..3 { + loop i in 1..4 { i -> std_output; } -The domain is evaluated once, when control first reaches the loop, and -the resulting value is captured for the lifetime of the loop. Each -iteration then performs :term:`re-initialization`: a fresh binding of -the iterator variable to the next element of the captured domain. -Subsequent modifications to any variable that appeared in the domain -expression do not affect the captured domain. For instance: +The domain is evaluated once, when control first reaches the loop; see +:ref:`ssec:expressions_dom_expr` for the full evaluate-once and +:term:`re-initialization` semantics of the iterator variable on each +pass. -:: - - var integer[*] v = [i in 1..3 | i]; - - /* Since 'v' is captured on loop entry this loop prints 1, - 2, and then 3 even though after the first iteration 'v' - is the zero array. */ - loop i in v { - v = 0; - i -> std_output; "\n" -> std_output; - } - -Note that multiple domain expressions are *not* allowed: +Note that multiple domain expressions are *not* allowed; the compiler +must emit a ``SyntaxError`` (see :ref:`sec:errors`) for an iterator loop +with more than one domain expression. :: @@ -404,17 +481,18 @@ Break A ``break`` statement may only appear within the body of a loop. When a ``break`` statement is executed the loop is exited, and *Gazprea* continues -to execute after the loop. This only exits the innermost loop, which +to execute after the loop. This only exits the innermost loop that actually contains the ``break``. :: /* Prints a 3x3 square of *'s */ - integer x = 0; + var integer x = 0; var integer y = 0; loop while (y < 3) { y = y + 1; + x = 0; /* reset the column counter at the start of each row */ /* Normally this would loop forever, but the break exits this inner loop */ loop { @@ -427,8 +505,8 @@ actually contains the ``break``. "\n" -> std_output; } -If a ``break`` statement is not contained within a loop an error must be -raised. +If a ``break`` statement is not contained within a loop the compiler must +emit a ``StatementError`` (see :ref:`sec:errors`). .. _ssec:statements_continue: @@ -437,10 +515,11 @@ Continue Similarly to ``break``, ``continue`` may only appear within the body of a loop. When a ``continue`` statement is executed the innermost loop -that contains the ``continue`` statements starts its next iteration. -``continue`` stops the execution of the loop’s body statement, the loop +that contains the ``continue`` statement starts its next iteration. +``continue`` stops the execution of the loop's body statement, the loop then continues as though the body statement finished its execution -normally. +normally. If a ``continue`` statement is not contained within a loop the +compiler must emit a ``StatementError`` (see :ref:`sec:errors`). :: @@ -465,9 +544,10 @@ procedure. When a function/procedure returns then execution continues where the function/procedure was called. If the function/procedure has a return type then the ``return`` statement must -be given a value that is the same as or able to be promoted to (see -:ref:`sec:typePromotion`) the return type; this will be the result of the -function/procedure call. Here is an example: +be given a value that is the same as or able to be implicitly cast to (see +:ref:`sec:implicitCasts`) the return type; this will be the result of the +function/procedure call. If the value is neither, the compiler must emit a +``TypeError`` (see :ref:`sec:errors`). Here is an example: :: @@ -475,6 +555,13 @@ function/procedure call. Here is an example: return x * x; } +A :ref:`function `, and a :ref:`procedure ` that +has a ``returns`` clause, must return a value on **every** control-flow path. +If control can reach the end of the body without executing a ``return``, the +program is :term:`ill-formed` and the compiler must emit a ``ReturnError`` +(see :ref:`sec:errors`); see :ref:`sec:function` and :ref:`sec:procedure` for +the full rule and examples. + If a procedure has no ``returns`` clause, then it has no return type and a ``return`` statement is not required but may still be present in order to return early. In this case return is used as follows: @@ -490,8 +577,9 @@ return early. In this case return is used as follows: Stream Statements ----------------- -Stream statements are the statements used to read and write values in -*Gazprea*. +See :ref:`sec:streams` for the streams *Gazprea* provides and their +output/input formatting rules. Stream statements are the statements +used to read and write values in *Gazprea*. Output example: diff --git a/gazprea/spec/streams.rst b/gazprea/spec/streams.rst index 465dbed..1ab0f29 100644 --- a/gazprea/spec/streams.rst +++ b/gazprea/spec/streams.rst @@ -26,23 +26,24 @@ Output Format Values of the following :term:`primitive types ` are treated as follows when sent to an output stream: -- :ref:`ssec:character`: The character is printed. +- :ref:`ssec:character`: Prints the character. -- :ref:`ssec:integer`: Converted to a string representation, and then printed. +- :ref:`ssec:integer`: Converts it to a string representation, and then prints + it. -- :ref:`ssec:real`: Converted to a string representation, and then printed. - This is the same behaviour as the `%g specifier in +- :ref:`ssec:real`: Converts it to a string representation, and then prints it. + This is the same behavior as the `%g specifier in printf `__. - :ref:`ssec:boolean`: Prints T for true, and F for false. -:ref:`Arrays ` print their contents according to the rules above, with square -braces surrounding its elements and with spaces only *between* values. -For example: +:ref:`Arrays ` print their contents according to the rules above, +with square braces surrounding their elements and with spaces only *between* +values. For example: :: - integer[*] v = 1..3; + integer[*] v = 1..4; v -> std_output; prints the following: @@ -51,8 +52,14 @@ prints the following: [1 2 3] -:ref:`strings ` print their contents as a contiguous sequence of characters. -For example: +:ref:`Vectors ` print exactly as :ref:`arrays ` +do, using whatever length the vector holds at the time of the output +statement. A :ref:`string ` is the sole exception: although +a string is a vector of characters, it prints its characters contiguously +rather than in bracketed array form, as shown next. + +:ref:`Strings ` print their contents as a contiguous sequence of +characters. For example: :: @@ -77,10 +84,26 @@ prints the following: [[1 2 3] [4 5 6] [7 8 9]] -No other type may be sent to a stream. For instance, -procedures with no return type and tuples cannot be sent to streams. -Also, empty arrays and matrices can be send to streams, but not empty -literals (e.g. ``[]``), because they have no type. +No other type may be sent to a stream; the compiler must emit a ``TypeError`` +(see :ref:`sec:errors`). For instance, a tuple or a struct cannot be sent to a +stream. A procedure call may not appear as a stream operand at all, since that +is not one of the :ref:`positions in which a procedure call may appear +`; the compiler must emit a ``CallError`` (see +:ref:`sec:errors`). Also, empty arrays and matrices can be sent to streams, but +not empty literals (e.g. ``[]``), because they have no type; sending one must +emit a ``TypeError`` (see :ref:`sec:errors`). A *typed* empty array prints as an +empty pair of brackets: + +:: + + integer[*] empty = []; + empty -> std_output; + +prints the following: + +:: + + [] Note that there is **no automatic new line or spaces printed.** To print a new line, a user must explicitly print the new line or space @@ -100,60 +123,78 @@ Input streams use the following syntax: :: - <- std_input; + <- std_input; -An :term:`lvalue` may be anything that can appear on the left hand side -of an assignment statement. +An :term:`lvalue` may be anything that can appear on the left hand side of an +assignment statement (see :ref:`sec:expressions`) -- not only a plain variable +but also, for example, an array element: + +:: + + var integer[3] v = [0, 0, 0]; + v[2] <- std_input; // reads a single integer into element 2 of v + +The primitive-only restriction below still applies: the target must designate a +single primitive location. Input streams may only work on the following primitive types: -- ``character``: Reads a single character from stdin. Note that there - can be no :ref:`error state ` for reading characters. +- ``character``: Reads a single character from stdin. Note that a + character read never sets :ref:`error state ` 1; + reaching the end of the stream still sets state 2. - ``integer``: Reads an integer from stdin. If an integer could not be read, an :ref:`error state ` is set on this stream. -- ``real``: Reads a real from stdin. If a real could not be read, an :ref:`error state ` is - set on this stream. +- ``real``: Reads a real from stdin. If a real could not be read, an + :ref:`error state ` is set on this stream. - ``boolean``: Reads a boolean from stdin. If a boolean value could not be read, an :ref:`error state ` is set on this stream. -Type promotion is not performed for stream input over any type. +Implicit casting is not performed for stream input over any type. - .. _sssec:input_format: +.. _sssec:input_format: Input Semantics ~~~~~~~~~~~~~~~ ``std_input`` expects an input stream of values which do not need to be -whitespace separated. A read will consume the stream until a character or -EOF occurs that breaks the pattern match for the given types specifier. The longest -successful match is returned. - -In general input stream semantics are designed for parity with ``scanf``. -The only differences are the :ref:`ssec:builtIn_stream_state`, a boolean specifier -and a restriction on the maximum number of bytes that can be consumed in a single read to 512. - -For each of the allowed types the semantics are given below. - -Reading a ``character`` from stdin consumes the first byte that can be read from the -stream. If the end of the stream is encountered, then a value of ``-1`` is set. There -is no concept of skipping whitespace for characters, since space and escaped characters -must be readable. +whitespace separated. A read will consume the stream until a character or EOF +occurs that breaks the pattern match for the given type's specifier. The +longest successful match is returned. + +In general input stream semantics are designed for parity with ``scanf``. The +only differences are the :ref:`ssec:builtIn_stream_state`, a boolean specifier +and a restriction on the maximum number of bytes that can be consumed in a +single read to 512. + +For each of the allowed types the semantics are given below. + +Reading a ``character`` from stdin consumes the first byte that can be read +from the stream. If the end of the stream is encountered, the character read is +``0xFF`` (``255``) -- ``character`` values are :ref:`unsigned bytes ` +in ``0`` to ``255``, so there is no ``-1`` -- and the end-of-stream +:ref:`error state ` is set. Because a legitimate ``0xFF`` +byte is indistinguishable from end-of-stream by its value alone, a program must +consult :ref:`stream_state ` to tell the two apart; +this is the reason ``stream_state`` exists. There is no concept of skipping +whitespace for characters, since space and escaped characters must be readable. An ``integer`` from stdin can take any legal format described in the :ref:`integer literal ` section. It may also be preceded by -a single negative or positive sign. All preceding whitespace before the number or -sign character may be skipped up to the limit imposed by the 512 byte read restriction. +a single negative or positive sign. All preceding whitespace before the number +or sign character may be skipped up to the limit imposed by the 512 byte read +restriction. A ``real`` input from stdin can take any legal format described in the -:ref:`real literal ` section with the exception that no -whitespace may be present. It may also be preceded by a single negative or -positive sign. Preceding whitespace may be skipped in the same way as integers. +:ref:`real literal ` section. It may be preceded by a single +negative or positive sign, and preceding whitespace may be skipped in the same +way as integers; the sign and the digits of the number itself, however, must be +contiguous -- no whitespace may appear *within* the value. -A ``boolean`` input from stdin is either ``T`` or ``F``. Preceding whitespace may be -skipped in the same way as integers and reals. +A ``boolean`` input from stdin is either ``T`` or ``F``. Preceding whitespace +may be skipped in the same way as integers and reals. For the following program: @@ -182,9 +223,11 @@ The output would be: :: - F 1.0 + F 1 -because the white space is consumed for characters and skipped for other types. +(``1.`` reads as the real 1.0, which prints as ``1`` under the ``%g`` +format rule above) because the white space is consumed for characters and +skipped for other types. .. _sssec:stream_error: @@ -194,16 +237,27 @@ Error Handling When reading ``boolean``, ``integer``, and ``real`` from stdin, it is possible that the end of the stream or an error is encountered. In order to -handle these situations *Gazprea* provides a built in procedure that is +handle these situations *Gazprea* provides a built-in procedure that is implicitly defined in every file: ``stream_state`` (see -:ref:`ssec:builtIn_stream_state`). - -Reading a ``character`` can never cause an error. The character will either be -successfully read or the end of the stream will be reached and ``-1`` will be -returned on this read. - -When an error occurs the null value is assigned and the input stream -remains pointing to the same position as before the read occurred. +:ref:`ssec:builtIn_stream_state` for its signature). ``stream_state`` +returns ``0`` if the last read succeeded, ``1`` if it encountered an +error, and ``2`` if it encountered the end of the stream. Before any read +has been issued it returns ``0``. + +Reading a ``character`` can never set error state 1. The character will +either be successfully read, or the end of the stream will be reached: the +read then yields the ``character`` byte ``0xFF`` (``255``, i.e. +``as(-1)``) and sets state 2. + +When a read sets error state ``1`` -- which is possible only for ``boolean``, +``integer``, and ``real`` (a ``character`` read never sets state ``1``) -- the +:term:`zero value` for the type being read is assigned to the target, the +implicit ``stream_state`` is set to ``1``, and the input stream remains +pointing to the same position as before the read occurred. Reaching the end of +the stream (state ``2``) instead assigns the value from the Return column of the +table below -- the type's zero value for ``boolean``/``integer``/``real``, and +``0xFF`` (``255``, i.e. ``as(-1)``) for a ``character`` -- and sets +``stream_state`` to ``2``. The program below demonstrates 4 reads which set the error states 1,0,0,2 respectively. @@ -221,15 +275,15 @@ states 1,0,0,2 respectively. ss -> std_output; c <- std_input; //eat the . - + i <- std_input; i -> std_output; - + c <- std_input; ss = stream_state(std_input); ss -> std_output; - -With the input stream: + +With the input stream: :: @@ -241,7 +295,7 @@ And the expected output: 0172 -This table summarizes an input stream’s possible error states after a read of a +This table summarizes an input stream's possible error states after a read of a particular data type. ========= ============= ========= ================= @@ -250,7 +304,7 @@ Type Situation Return ``stream_state`` Boolean error ``false`` 1 \ end of stream ``false`` 2 Character error N/A N/A -\ end of stream ``-1`` 2 +\ end of stream ``0xFF`` 2 Integer error ``0`` 1 \ end of stream ``0`` 2 Real error ``0.0`` 1 diff --git a/gazprea/spec/type_casting.rst b/gazprea/spec/type_casting.rst index 254c74f..46d8749 100644 --- a/gazprea/spec/type_casting.rst +++ b/gazprea/spec/type_casting.rst @@ -5,7 +5,7 @@ Type Casting *Gazprea* provides explicit :term:`type casting`. Type casting is an :term:`expression`. A value may be converted to a different type using the -following syntax where ``value`` is an expression and ``toType`` is our +following syntax where ``value`` is an expression and ``toType`` is the destination type: :: @@ -14,7 +14,11 @@ destination type: Conversion from one type to another is not always legal. For instance converting from an ``integer`` array to an ``integer`` has no -reasonable conversion. +reasonable conversion. Attempting such a conversion is a compile-time +error; the compiler must emit a ``TypeError`` (see :ref:`sec:errors`). More +generally, any ``as<>`` conversion this chapter does not describe as legal is +a compile-time error, and the compiler must emit a ``TypeError`` (see +:ref:`sec:errors`). .. _ssec:typeCasting_stos: @@ -24,31 +28,43 @@ Scalar to Scalar This table summarizes all of the conversion rules between scalar types where N/A means no conversion is possible, id means no change is necessary, and anything else describes how to convert the value to the -new type: +new type. Attempting a conversion marked N/A is a compile-time error; +the compiler must emit a ``TypeError`` (see :ref:`sec:errors`): +----------+-------------------------------------------------------------------------------------------------------------------------------------+ | | **To type** | +----------+-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ | | | boolean | character | integer | real | | +-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ -| | boolean | id | ‘\\0’ if false, 0x01 otherwise | 1 if true, 0 otherwise | 1.0 if true, 0.0 otherwise | +| | boolean | id | '\\0' if false, 0x01 otherwise | 1 if true, 0 otherwise | 1.0 if true, 0.0 otherwise | | +-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ -| **From** | character | false if ‘\\0’, true otherwise | id | *ASCII* value as integer | *ASCII* value as real | +| **From** | character | false if '\\0', true otherwise | id | unsigned byte (0-255) | unsigned byte (0-255) | | +-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ | **type** | integer | false if 0, true otherwise | unsigned integer value mod 256 | id | real version of integer | | +-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ | | real | N/A | N/A | truncate | id | +----------+-----------+--------------------------------+--------------------------------+--------------------------+----------------------------+ +A ``character`` is interpreted as an **unsigned** byte when cast to a numeric +type, so ``character`` to ``integer`` (or ``real``) yields a value in ``0`` to +``255`` -- for example ``as('\xFF')`` is ``255``, not ``-1``. This is +the inverse of the ``integer`` to ``character`` rule, under which an ``integer`` +``n`` becomes the byte ``n`` reduced modulo 256 into the range ``0`` to ``255`` +-- the mathematical, non-negative remainder, so ``as(-1)`` is +``0xFF`` (= ``255``) and ``as(256)`` is ``0x00`` (the null character). +For printable *ASCII* characters (``0`` to ``127``) this is exactly the *ASCII* +code. + .. _ssec:typeCasting_stovm: Scalar to Array ----------------------- -A scalar may be promoted to an array of any dimension with an element type that -the original scalar can be cast to according to the rules in :ref:`ssec:typeCasting_stos`. -A scalar to array cast *must* include a size with the type to cast to as this -cannot be inferred from the scalar value. For example: +A scalar may be explicitly cast to an array of any dimension with an element +type that the original scalar can be explicitly cast to according to the rules +in :ref:`ssec:typeCasting_stos`. A scalar to array cast *must* include a size +with the type to cast to as this cannot be inferred from the scalar value. For +example: :: @@ -63,17 +79,18 @@ cannot be inferred from the scalar value. For example: Array to Array ---------------- -Conversions between array types are also possible. First, the -values of the original are cast to the destination type’s element type -according to the rules in :ref:`ssec:typeCasting_stos` and then the destination -is padded with destination element type’s zero or truncated to match the -destination type size. Note that the size is not required for array to -array casting; if the size is not included in the cast type, the new -size is assumed to be the old size. For example: +Conversions between array types are also possible. First, the values of the +original are cast to the destination type's element type according to the rules +in :ref:`ssec:typeCasting_stos` and then the destination is padded with +destination element type's :term:`zero value` or truncated to match the +destination type size. Note that a concrete size is not required for array to +array casting: writing the destination element type with an unspecified length +(``[*]``) keeps the old size, so no padding or truncation occurs. Padding or +truncation happens only when a concrete size is given. For example: :: - real[3] v = [i in 1..3 | i + 0.3 * i]; + real[3] v = [i in 1..4 | i + 0.3 * i]; // Convert the real array to an integer array. integer[3] u = as(v); @@ -85,7 +102,8 @@ size is assumed to be the old size. For example: real[2] y = as(v); A cast of a non-variable empty array literal ``[]`` is :term:`ill-formed`, -because a literal empty array does not have a type. +because a literal empty array does not have a type; the compiler must emit a +``TypeError`` (see :ref:`sec:errors`). .. _ssec:typeCasting_mtom: @@ -110,15 +128,60 @@ truncation can occur in all dimensions. For example: real[1][3] d = as(a); real[3][1] e = as(a); +.. _ssec:typeCasting_vec: + +Array and Vector +---------------- + +A :ref:`vector ` participates in ``as<>`` casts on both sides. + +- As the **operand** of an array cast, a vector supplies its *current* length + as the source size; the cast then pads with the element type's :term:`zero + value` or truncates to the destination array's stated size, exactly as in + :ref:`ssec:typeCasting_vtov`. + +- As the **destination** type, a ``vector`` takes no size specifier: the + result simply has the length of the value being cast, so there is nothing + to pad or truncate. Only the element type is converted, per + :ref:`ssec:typeCasting_stos`. + +- A :term:`scalar ` may be cast directly to a ``vector`` + destination, producing a single-element vector. Because a vector carries no + size specifier, the element type ``T`` must be written explicitly -- there + is no size or element-type inference for this cast. + +:: + + vector v = [1.5, 2.5, 3.5]; + + // Vector as operand: its current length (3) is the source size. + integer[2] a = as(v); // [1, 2] (truncated) + integer[5] b = as(v); // [1, 2, 3, 0, 0] (padded) + + // Vector as destination: no size; takes the value's length. + integer[3] w = [4, 5, 6]; + vector u = as >(w); // [4, 5, 6] + + // Scalar to vector: single-element vector; T must be explicit. + vector s = as >(5); // [5] + .. _ssec:typeCasting_ttot: Tuple to Tuple -------------- -Conversions between ``tuple`` types are also possible. The original type -and the destination type must have an equal number of internal types and -each element must be pairwise castable according to the rules -in :ref:`ssec:typeCasting_stos`. For example: +Conversions between ``tuple`` types are also possible. The source type and +the destination type must have an equal number of members, and each member +must be pairwise castable; a mismatch in the number of members, or a member +that cannot be cast under its own kind's rule, is a compile-time error and +the compiler must emit a ``TypeError`` (see :ref:`sec:errors`). Every +member is cast by the rule for its own kind: scalar members follow +:ref:`ssec:typeCasting_stos`, array members +follow :ref:`ssec:typeCasting_vtov` (including padding and truncation), and +a nested ``tuple``, ``vector``, or array member follows the same +cast rules as a standalone value of that type. A ``struct`` member is the +exception: a ``struct`` cannot be cast (see :ref:`ssec:struct`), so the two +struct types must be identical and the member is copied unchanged. For example: :: diff --git a/gazprea/spec/type_inference.rst b/gazprea/spec/type_inference.rst index c1dd93d..ced7e89 100644 --- a/gazprea/spec/type_inference.rst +++ b/gazprea/spec/type_inference.rst @@ -19,31 +19,42 @@ provided. For instance, instead of writing: var x = 2; const y = x * 2; -This is allowed because the compiler knows that the initialization -expression, 2, has the type integer. Because of this the compiler can -automatically give x an integer type. A *Gazprea* programmer can use -``var`` or ``const`` for any declaration with an initial value -expression, as long as the compiler can guess the type for the -expression. +This is allowed because the compiler knows that the :term:`initializer`, +2, has the type integer. Because of this the compiler can automatically +give x an integer type. A *Gazprea* programmer can use ``var`` or +``const`` for any declaration with an initial value expression, as long +as the compiler can infer the type for the expression. -Note that although the qualifier may be elided (default is ``const``) and -the type may be elided (inferred from the RHS), a declaration that -elides both is :term:`ill-formed`: +Note that although the qualifier may be elided (default is ``const``; see +:ref:`sec:typeQualifiers`) and the type may be elided (inferred from the +RHS), a declaration that elides both is :term:`ill-formed`: :: x = 2; // assignment or declaration? Interpreted as a declaration, the full form would be ``const integer x = 2;``. -However, with both the modifier and type assumed we can't differentiate this -declaration from an assignment statement. To prevent this ambiguity, we require -at least one of the qualifier or the type to be present: +However, with both the modifier and type assumed, the compiler cannot +differentiate this declaration from an assignment statement. To prevent this +ambiguity, *Gazprea* requires at least one of the qualifier or the type to be +present: :: const integer x = 2; // full form - legal integer x = 2; // defaults to const - legal var x = 2; // infers integer - legal - x = 2; // assignment to undeclared variable? - illegal - var x; // can't infer type - illegal + x = 2; // assignment to undeclared x - illegal + var x; // can't infer type - illegal (TypeError) integer x; // const integer initialized to 0 - legal + +Since neither the qualifier nor the type is present, ``x = 2;`` cannot be +parsed as a declaration and is instead an assignment; because ``x`` has not +been previously declared, the compiler must emit a ``SymbolError`` (see +:ref:`sec:errors`). + +The declaration ``var x;`` is :term:`ill-formed` for a different reason: the +qualifier is present, so it *is* parsed as a declaration, but with the type +elided the compiler must infer it from an initializer -- and none is given. +Because the type cannot be resolved, the compiler must emit a ``TypeError`` +(see :ref:`sec:errors`). diff --git a/gazprea/spec/type_promotion.rst b/gazprea/spec/type_promotion.rst deleted file mode 100644 index e78fcb5..0000000 --- a/gazprea/spec/type_promotion.rst +++ /dev/null @@ -1,130 +0,0 @@ -.. _sec:typePromotion: - -Type Promotion -============== - -:term:`Type promotion` is *Gazprea*'s implicit type-conversion mechanism. -It is deliberately distinct from :ref:`type casting `, -which is the explicit mechanism invoked via ``as(value)``. - -Any conversion that can be done implicitly via promotion can also be -done explicitly via a typecast expression. The notable exception is -array promotion to a higher dimension, which occurs as a consequence of -scalar to array promotion. - -.. _ssec:typePromotion_scalar: - -Scalars -------- - -The only automatic type promotion for scalars is ``integer`` to -``real``. This promotion is one way - a ``real`` cannot be automatically -converted to ``integer``. - -Automatic type conversion follows this table where N/A means no implicit -conversion possible, id means no conversion necessary, -``as(var)`` means var of type "From type" is converted to type -"toType" using semantics from :ref:`sec:typeCasting`. - -+----------+-----------+---------+-----------+---------+---------------+ -| | **To type** | -+----------+-----------+---------+-----------+---------+---------------+ -| | | boolean | character | integer | real | -+ +-----------+---------+-----------+---------+---------------+ -| **From** | boolean | id | N/A | N/A | N/A | -+ +-----------+---------+-----------+---------+---------------+ -| **type** | character | N/A | id | N/A | N/A | -+ +-----------+---------+-----------+---------+---------------+ -| | integer | N/A | N/A | id | as(var) | -+ +-----------+---------+-----------+---------+---------------+ -| | real | N/A | N/A | N/A | id | -+----------+-----------+---------+-----------+---------+---------------+ - -.. _ssec:typePromotion_stoa: - -Scalar to Array --------------------------- - -All scalar types can be promoted to arrays that have an internal type that the -scalar can be :ref:`converted to implicity `. -This can occur when an array is used in an operation with a scalar value. - -The scalar will be implicitly converted to an array of -equivalent dimensions and equivalent internal type. For example: - -:: - - integer i = 1; - integer[*] v = [1, 2, 3, 4, 5]; - integer[*] res = v + i; - - res -> std_output; - -would print the following: - -:: - - [2 3 4 5 6] - -Other examples: - -:: - - 1 == [1, 1] // True - 1..2 || 3 // [1, 2, 3] - -Note that an array can never be downcast to a scalar, -even if type casting is used. Also note that matrix multiply imposes strict -requirements on the dimensionality of the the operands. The consequence is -that scalars can only be promoted to a matrix if the matrix multiply -operand is a square matrix (:math:`m \times m`). - -Tuple to Tuple --------------- - -Tuples may be promoted to another tuple type if it has an equal number of -internal types and the original internal types can be implicitly -converted to the new internal types. For example: - -:: - - tuple(integer, integer) int_tup = (1, 2); - tuple(real, real) real_tup = int_tup; - - tuple(char, integer, boolean[2]) many_tup = ('a', 1, [true, false]); - tuple(char, real, boolean[2]) other_tup = many_tup; - -If initializing a variable with a tuple via :ref:`sec:typeInference`, the -variable is assumed to be the same type. -Therefore, tuple elements also copied accordingly. For example: - -:: - - tuple(real, real) foo = (1, 2); - tuple(real, real) bar = (3, 4); - - var baz = foo; - baz.1 -> std_output; // 1 - baz.2 -> std_output; // 2 - - baz = bar; - baz.1 -> std_output; // 3 - baz.2 -> std_output; // 4 - - -It is possible for a two sided promotion to occur with tuples. For example: - -:: - - boolean b = (1.0, 2) == (2, 3.0); - -Character Array to/from String -------------------------------- - -A ``string`` can be implicitly converted to a vector of ``character``\ s and vice-versa (two-way type promotion). - -:: - - string str1 = "Hello"; /* str1 == "Hello" */ - character[*] chars = str1; /* chars == ['H', 'e', 'l', 'l', 'o'] */ - string str2 = chars || [' ', 'W', 'o', 'r', 'l', 'd']; /* str2 == "Hello World" */ diff --git a/gazprea/spec/type_qualifiers.rst b/gazprea/spec/type_qualifiers.rst index 613ffc4..a4ee574 100644 --- a/gazprea/spec/type_qualifiers.rst +++ b/gazprea/spec/type_qualifiers.rst @@ -4,9 +4,12 @@ Type Qualifiers =============== *Gazprea* has two :term:`type qualifiers `: ``const`` and -``var``. These qualifers can prefix a type to specify its mutability or +``var``. These qualifiers can prefix a type to specify its mutability or entirely replace the type to request that it be inferred. Mutability -refers to a value's ability to be an :term:`rvalue` or :term:`lvalue`. +refers to a value's ability to be an :term:`lvalue`: every value can be an +:term:`rvalue`, but only a mutable one can also be an lvalue (an array +slice's lvalue-ness follows the mutability of its backing array; see +:ref:`sssec:array_slices`). The two qualifiers cannot be combined as they are mutually exclusive. .. _ssec:typeQualifiers_const: @@ -22,10 +25,18 @@ can be an rvalue. For example: const integer i; Because a ``const`` value is not an lvalue, it cannot be passed to a -``var`` argument in a ``procedure``. +``var`` parameter in a ``procedure``; the compiler must emit a +``TypeError`` (see :ref:`sec:errors`). -Note that ``const`` is the default *Gazprea* behaviour and is essentially a -no-op unless it is entirely replacing the type. +``const`` is the default in *Gazprea*: a declaration with no qualifier +declares a ``const`` variable. Both ``T x`` (qualifier elided) and +``const T x`` (qualifier written explicitly) are legal spellings of the +same declaration. Writing ``const`` is therefore redundant, except where +the qualifier entirely replaces the type (see +:ref:`ssec:typeQualifiers_infer`). + +.. This section is the normative home of the const-by-default rule; other + chapters reference it. .. _ssec:typeQualifiers_var: @@ -40,26 +51,27 @@ For example: var integer i; -The compiler should raise an error if an attempt is made to modify a variable -that is not explicitly declared ``var``. +The compiler must emit an ``AssignError`` (see :ref:`sec:errors`) if an +attempt is made to modify a variable that is not explicitly declared +``var``. .. _ssec:typeQualifiers_infer: Type Inference Using Qualifiers ------------------------------- -Type qualifiers may be used in place of a type, in which case the real -type must be inferred. A variable declared in this manner must be -**immediately initialised** to enable inference. For example: +Type qualifiers may be used in place of a type, in which case the +compiler must infer the real type. A variable declared in this manner must be +**immediately initialized** to enable inference. For example: :: var i = 1; // integer - const i = 1; // integer + const j = 1; // integer var r = 1.0; // real const c = 'a'; // character var t = (1, 2, 'a', [1, 2, 3]); // tuple(integer, integer, character, integer[3]) const v = ['a', 'b', 'c', 'd']; // character[4] -See :ref:`sec:typeInference` for a larger description of type inference, this section only -provides the syntax for inference using ``const`` and ``var``. +See :ref:`sec:typeInference` for a larger description of type inference; this +section only provides the syntax for inference using ``const`` and ``var``. diff --git a/gazprea/spec/typealias.rst b/gazprea/spec/typealias.rst new file mode 100644 index 0000000..0a85b99 --- /dev/null +++ b/gazprea/spec/typealias.rst @@ -0,0 +1,101 @@ +.. _sec:typealias: + +Typealias +========= + +Custom names for types can be defined using ``typealias``. A type alias does +not introduce a new type: the alias name and the original type are the same +type by strong equivalence, and the two names may be used interchangeably +anywhere a type is expected. A ``typealias`` may be declared at global scope or inside a function or +procedure body. A local alias is :term:`scoped ` to the block that +contains it and **shadows** any outer alias or type of the same name for the +rest of that block, without affecting the outer name outside it (two aliases +sharing a name in the *same* scope remain a conflict; see below). A type alias +may use any valid identifier for the name of the type. After the type alias has +been defined, the new name may be used anywhere the original type could be used +-- in global or local declarations, and in function or procedure signatures and +bodies. For instance: + +:: + + typealias integer int; + const int a = 0; + +Note that these new type names can *appear* to conflict with symbol names. +However, the compiler can use context to differentiate a type alias from a +symbol. The following is therefore legal: + +:: + + typealias character main; + typealias integer i; + + const main A = 'A'; + + procedure main() returns i { + i i = 0; // = ; + return i; + } + +In addition to :term:`primitive types `, ``typealias`` can be +used with any :term:`aggregate type ` (arrays, matrices, +vectors, tuples, and structs), as well as with ``string``, itself a typealias +for ``vector``. +Using ``typealias`` on tuples, or on arrays with sizes helps reusability and +consistency: + +:: + + typealias tuple(character[64], integer, real) student_id_grade; + student_id_grade chucky_cheese = ("C. Cheese", 123456, 77.0); + + typealias integer[2][3] two_by_three_matrix; + two_by_three_matrix m = [i in 1..3, j in 1..4 | i + j]; + +Type aliases of arrays with inferred sizes (``[*]``) are allowed, but +declarations of variables using the type alias must be initialized +appropriately (see :ref:`sssec:array_sizing`). + +Because a ``typealias`` is an aliased name for a type, a ``typealias`` may +also be defined in terms of another ``typealias``: + +:: + + typealias integer int; + typealias int also_int; + +The compiler must emit a ``SymbolError`` (see :ref:`sec:errors`) for two aliases +that share a name *in the same scope*. (A local alias that shares its name with +one in an enclosing scope is not a conflict -- it shadows it, as above.) + +:: + + typealias integer ty; + typealias character ty; + +Some type aliases may be parameterized with an expression, such as the size of +an array. Such size expressions must be valid +:ref:`constant expressions `. This permits not only constant +folding of scalar literals but also constant propagation through other +``constexpr`` values, such as global constants. + +:: + + typealias integer[1 + 3 - 2] vec_of_two; + procedure main() returns integer { + vec_of_two v = 1..4; + return 0; + } + +The compiler must emit a ``SizeError`` (see :ref:`sec:errors`) on line 3 +since the ``vec_of_two`` type has a size of 2 and an array of size 3 is +being assigned. + +Because the size may be any ``constexpr``, it can reference other constant +expressions rather than being limited to literals: + +:: + + const WIDTH = 4; + typealias integer[WIDTH] row; // legal: WIDTH is a constexpr + diff --git a/gazprea/spec/typedef.rst b/gazprea/spec/typedef.rst deleted file mode 100644 index fd0848a..0000000 --- a/gazprea/spec/typedef.rst +++ /dev/null @@ -1,88 +0,0 @@ -.. _sec:typealias: - -Typealias -========= - -Custom names for types can be defined using ``typealias``. Type aliases may only -appear at global scope, they may not appear within functions or procedures. A -type alias may use any valid identifier for the name of the type. After the type -alias has been defined any global declaration or function defined may use the -new name to refer to the old type. For instance: - -:: - - typealias integer int; - const int a = 0; - -Note that these new type names can *appear* to conflict with symbol names. -However, the compiler can use context to differentiate a type alias from a -symbol. The following is therefore legal: - -:: - - typealias character main; - typealias integer i; - - const main A = 'A'; - - procedure main() returns i { - i i = 0; // = ; - return i; - } - -In addition to :term:`primitive types `, ``typealias`` can be used -with compound types (arrays, vectors, and strings) and -:term:`aggregate types ` (structs and tuples). -Using ``typealias`` on tuples, or on arrays with sizes helps reusability and -consistency: - -:: - - typealias tuple(character[64], integer, real) student_id_grade; - student_id_grade chucky_cheese = ("C. Cheese", 123456, 77.0); - - typealias integer[2][3] two_by_three_matrix; - two_by_three_matrix m = [i in 1..2, j in 1..3 | i + j]; - -Type aliases of arrays with inferred sizes are allowed, but declarations -of variables using the type alias must be initialized appropriately. - -Because a ``typealias`` is an aliased name for a type, you can use -``typealias`` on type alias'ed types: - -:: - - typealias integer int; - typealias int also_int; - -Duplicate alias names should raise a `SymbolError` - -:: - - typealias integer ty; - typealias character ty; - -Some type aliases may be parameterized with an expression, such as the size of -an array. Such size expressions must be valid -:ref:`constant expressions `. This permits not only constant -folding of scalar literals but also constant propagation through other -``constexpr`` values, such as global constants. - -:: - - typealias integer[1 + 3 - 2] vec_of_two; - procedure main() returns integer { - vec_of_two v = 1..3; - } - -Should raise a ``SizeError`` on line 3 since the ``vec_of_two`` type has a size -of 2 and an array of size 3 is being assigned. - -Because the size may be any ``constexpr``, it can reference other constant -expressions rather than being limited to literals: - -:: - - const WIDTH = 4; - typealias integer[WIDTH] row; // legal: WIDTH is a constexpr - diff --git a/gazprea/spec/types.rst b/gazprea/spec/types.rst index 80bd4a8..6bdddcc 100644 --- a/gazprea/spec/types.rst +++ b/gazprea/spec/types.rst @@ -3,6 +3,9 @@ Types ===== +Type names appear in lower case as code (``string``, ``vector``, +``integer``); section titles use ordinary title capitalization. + .. toctree:: :maxdepth: 2 @@ -16,3 +19,43 @@ Types types/vector types/string types/matrix + +.. _ssec:storable_types: + +Storable Types, Nesting, and Recursion +-------------------------------------- + +A *storable type* is any type whose values may be stored in a variable, +passed as an argument, returned, or held as a member of an +:term:`aggregate `. Every type in *Gazprea* is storable +except :ref:`streams `, which name I/O endpoints rather than +values. + +Aggregates may be nested to any depth. An :ref:`array ` of any +rank (a :ref:`matrix ` is the rank-2 case), a +:ref:`vector `, a +:ref:`tuple `, and a :ref:`struct ` may each hold +any storable element or field type, including one another. For example a +``vector`` (for a struct type ``S``), a struct with a ``tuple`` field, +and a ``tuple(S, integer[3][3])`` are all :term:`well-formed`. + +Nesting must be **acyclic** through :term:`value types `. A +``struct`` or ``tuple`` whose fields, directly or transitively, contain a value +of its own type has no finite size and is :term:`ill-formed`; the compiler must +emit a ``TypeError`` (see :ref:`sec:errors`). A type may, however, refer to +itself *through a* :ref:`vector `, because a vector is dynamically +sized and stored by indirection: + +:: + + struct Tree (integer value, vector children); // well-formed + struct Bad (integer value, Bad next); // TypeError: infinite size + +.. note:: + + *Implementation.* Nested aggregates are laid out and accessed + structurally (a chain of ``getelementptr`` in the LLVM dialect); the + vector at a recursion boundary is the sole point of indirection. Flat, + non-nested sequences continue to lower through the tensor/linalg path, + and should be implemented as contiguous blobs of memory, not lists of + lists or structs of arrays pointing to arrays. diff --git a/gazprea/spec/types/array.rst b/gazprea/spec/types/array.rst index 7b11806..8f9253f 100644 --- a/gazprea/spec/types/array.rst +++ b/gazprea/spec/types/array.rst @@ -3,10 +3,47 @@ Arrays ------ -Arrays are fixed size collections, where each element of the array has -the same type. Arrays can contain any of *Gazprea*'s -:term:`primitive types ` (``boolean``, ``integer``, -``real``, and ``character``) or compound types (structs and tuples). +Arrays are fixed size collections, where each element of the array has the same +type. An array element may be of any :ref:`storable type +`: a :term:`primitive type ` (``boolean``, +``integer``, ``real``, ``character``), or an +:term:`aggregate type ` +such as a ``struct``, ``tuple``, ``vector``, ``string``, or another +array (which yields a higher-rank array; see :ref:`ssec:matrix`). + +.. _sssec:array_sizing: + +Sizing +~~~~~~ + +Arrays are **initialization-time sized**: the length of an array variable -- +and, for a :ref:`matrix or higher-rank array `, each of its +dimensions -- is settled exactly *once*, at the variable's +:term:`initialization`, and from that point on is fixed for the entire +:term:`lifetime` of the variable. The glossary entry for +:term:`initialization` gives the general rule -- a size is any integer +expression, evaluated a single time at the declaration's program point, and +need not be a :term:`compile time` constant. Concretely: + +- A declaration such as ``integer[n] v;`` evaluates ``n`` once, at + initialization. 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 initialization. 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 padded with the element type's + :term:`zero value`, or the compiler must emit a ``SizeError`` (see + :ref:`sec:errors`) at :term:`compile time` or :term:`run time`, as + described 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: @@ -17,13 +54,12 @@ Aside from any type specifiers, the element type of the array is the first portion of the declaration. An array is then declared using square brackets immediately after the element type. -If possible, initialization expressions may go through an implicit type -conversion. For instance, when declaring a real array that is -initialized with an integer value the integer will be promoted to a real -value, and then used as a scalar initialization of the array. -Be careful about type inference! If the type of the array is being inferred -from the right had side, the previous example would create an ``integer`` -array instead of a ``real`` array. +If possible, initialization expressions may go through an implicit cast. For +instance, when declaring a real array that is initialized with an integer value +the integer will be implicitly cast to a real value, and then used as a scalar +initialization of the array. Be careful about type inference! If the type of +the array is being inferred from the right hand side, the previous example +would create an ``integer`` array instead of a ``real`` array. #. Explicit Size Declarations @@ -43,15 +79,17 @@ array instead of a ``real`` array. The size of the array is given by the integer expression between the square brackets. - 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. + 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. - An array may also be initialized with another array. Initialization occurs element-wise, - with the RHS element type's initialization semantics applying from left to right. - If the LHS array is initialized using a RHS array that is too small then the LHS array will - be padded with zeros. However, if the LHS array is initialized with a RHS - array that is too large then a ``SizeError`` should be thrown at - :term:`compile time` or :term:`run time`. + An array may also be initialized with another array. Initialization occurs + element-wise, with the RHS element type's initialization semantics applying + from left to right. If the LHS array is initialized using a RHS array that + is too small then the LHS array will be padded with the element type's + :term:`zero value`. However, if the LHS array is initialized with a RHS + array that is too large then the compiler must emit a ``SizeError`` (see + :ref:`sec:errors`) at :term:`compile time` or :term:`run time`. #. Inferred Size Declarations @@ -80,9 +118,10 @@ array instead of a ``real`` array. In this example the compiler can infer both the size and the type of - ``w`` from ``v``. As with any array, this inferred size is known at - :term:`compile time`; a collection whose size is only known at - :term:`run time` must be a :ref:`vector `. + ``w`` from ``v``. As with any array, this inferred size is fixed once, + at :term:`initialization`, and never changes afterwards; a collection + whose length must *change* after it is created requires a + :ref:`vector `. .. _sssec:array_constr: @@ -99,7 +138,7 @@ notation: Each ``expK`` is an expression with a compatible type. In the simplest cases each expression is of the same type, but it is possible to mix the -types as long as all of the types can be promoted to a common type. For +types as long as all of the types can be implicitly cast to a common type. For instance it is possible to mix integers and real numbers. :: @@ -121,6 +160,60 @@ method of construction. real[*] v = []; /* Should create an empty array */ +Because the length of an array is fixed at :term:`initialization`, 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. + +Note that the empty array literal ``[]`` carries no element type of its own, so +the element type must come from context (the declared type, as in +``real[*] v = []`` above). A declaration that elides the type and asks the +compiler to infer it from an empty literal -- such as ``var v = [];`` -- is +:term:`ill-formed`, because the element type cannot be deduced; the compiler +must emit a ``TypeError`` (see :ref:`sec:errors`). The same holds anywhere a +bare ``[]`` appears without a type to fix its element type (see also +:ref:`ssec:typeCasting_vtov` and :ref:`ssec:expressions_dom_expr`). + +.. _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: + +.. list-table:: + :header-rows: 1 + :widths: 34 33 33 + + * - + - **Array** (``T[N]``, ``T[*]``, and higher-rank arrays / matrices) + - **Vector** (``vector``, ``string``) + * - When is the length set? + - Once, at initialization + - Continuously, at run time + * - Can it change afterwards? + - No + - Yes + * - Written in the type? + - Yes (``[N]``), or inferred once (``[*]``) + - No + * - Grows via ``push`` / ``append``? + - No -- ``TypeError`` (arrays have no methods) + - Yes + * - Too-short value stored into it + - Padded with the element type's :term:`zero value` + - The vector takes the value's length + * - Too-long value stored into it + - ``SizeError`` + - The vector takes the value's 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 stored 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 @@ -131,16 +224,8 @@ Operations a. length The number of elements in an array is given by the built-in - functions ``length``. For instance: - - :: - - integer[*] v = [8, 9, 6]; - integer numElements = length(v); - - - In this case ``numElements`` would be 3, since the array ``v`` - contains 3 elements. + function ``length``; see :ref:`ssec:builtIn_length` for its full + definition. b. Concatenation @@ -155,7 +240,7 @@ Operations Concatenation is also allowed between arrays of different element - types, as long as one element type is coerced automatically to the + types, as long as one element type can be implicitly cast to the other. For instance: :: @@ -165,8 +250,8 @@ Operations real[6] j = v || u; - would be permitted, and the integer array ``v`` would be promoted to - a real array before the concatenation. + would be permitted, and the integer array ``v`` would be implicitly + cast to a real array before the concatenation. Concatenation may also be used with scalar values. In this case the scalar values are treated as though they were single element @@ -178,12 +263,28 @@ Operations 1 || [2, 3, 4] // produces [1, 2, 3, 4] - An interesting corollary to array-scalar concatenation is that - two scalars can be concatenated to produce an array: + At least one operand of ``||`` must be a composite value (an array, + :ref:`vector `, or ``string``). Concatenating two scalars + is a ``TypeError``; wrap one operand in a one-element array first: :: - integer[3] v = 1 || 2 || 3; // produces [1, 2, 3] + integer[3] v = 1 || 2 || 3; // TypeError: all operands are scalars + integer[3] w = [1] || 2 || 3; // [1, 2, 3]: left operand is an array + + + Concatenation is right-associative, and its *receiver* -- the rightmost + operand -- fixes the **kind** of the result. When the receiver is a + :ref:`vector ` (a ``vector`` or a ``string``), the whole + concatenation is a vector of that element type; otherwise -- when the + 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 + concatenation such as ``"x = " || format(x)`` a ``string`` (its receiver + ``format(x)`` is a string), so it renders as text when sent to a stream, + while a vector result can still be stored into an array through the usual + :ref:`vector/array interoperability `. Remember that arrays have a fixed length, which means you cannot grow an @@ -201,9 +302,17 @@ Operations c. Dot Product - Two arrays with the same size and a numeric element type(types with - the ``+``, and ``\*`` operator) may be used in a dot product operation. - For instance: + Two rank-1 arrays with the same size and a numeric element type + (types with the ``+`` and ``*`` operators) may be used in a dot + product operation using the ``**`` operator. The two operands must + have the same size; if they do not, the compiler must emit a + ``SizeError`` (see :ref:`sec:errors`) at :term:`compile time` or + :term:`run time`. The dot product is the rank-1 case of a single rule: + ``**`` is defined for numeric arrays of any rank as the linear-algebra + contraction of the last dimension of the left operand with the first + dimension of the right operand, so the rank-2 case is matrix + multiplication. See :ref:`ssec:matrix` for the general definition and its + ``SizeError``. For instance: :: @@ -211,17 +320,26 @@ Operations integer[3] u = [4, 5, 6]; /* v[1] * u[1] + v[2] * u[2] + v[3] * u[3] */ - /* 1 * 4 + 2 * 5 + 3 * 6 &=& 32 */ + /* 1 * 4 + 2 * 5 + 3 * 6 = 32 */ integer dot = v ** u; /* Perform a dot product */ + A :term:`scalar ` operand broadcasts to the other operand's + length here, just as it does for element-wise array operations: because a + rank-1 array has a single dimension, the broadcast shape is unambiguous. + Thus a scalar may be dotted with a rank-1 array -- ``[1, 2, 3] ** 4`` is + the dot product ``[1, 2, 3] ** [4, 4, 4]``, i.e. ``1*4 + 2*4 + 3*4 == 24``. + d. Range The ``..`` operator creates an integer array holding the specified range of integer values. This operator must have an expression resulting in an integer on both - sides of it. These integers mark the *inclusive* upper and lower bounds - of the range. + sides of it. The range is **half-open**: the left bound is *inclusive* + and the right bound is *exclusive*, so ``i..j`` holds the integers ``i, + i+1, ..., j-1``. This is the same convention used when a range is written + inside an index position to form a :ref:`slice `, so a + range value and a slice agree on exactly which endpoints they include. For example: @@ -234,8 +352,8 @@ Operations :: - [1 2 3 4 5 6 7 8 9 10] - [2 3 4 5 6 7 8 9 10 11] + [1 2 3 4 5 6 7 8 9] + [2 3 4 5 6 7 8 9 10] The number of integers in a range may not be known at :term:`compile time` when the integer expressions use variables. In another example, assuming @@ -249,15 +367,24 @@ Operations :: - [-4 -3 -2 -1 0 1 2 3 4 5] + [-4 -3 -2 -1 0 1 2 3 4] Therefore, it is *valid* to have bounds that will produce an empty - array because the difference between them is negative. + array: because the right bound is *exclusive*, ``i..j`` is empty whenever + ``i >= j`` (for example ``5..5`` or ``5..2``). - d. Indexing + e. Indexing An array may be indexed in order to retrieve the values stored in - the array. An array may be indexed using integers. + the array. An array may be indexed using an integer, in which case + the index yields a single element, or using range syntax written + directly at the index position, in which case the index yields a + slice (see :ref:`sssec:array_slices`). An array *value* is **not** a legal + index: ``v[w]`` is illegal whenever ``w`` evaluates to an array value -- + even one holding a range, and whether it comes from an array variable, an + expression, or a function call -- and the compiler must emit a + ``TypeError`` (see :ref:`sec:errors`). A range written *directly* inside an + index position is not an array-valued index; it forms a slice. *Gazprea* is 1-indexed, so the first element of an array is at index 1 (as opposed to index 0 in languages like *C*). For instance: @@ -265,74 +392,31 @@ Operations integer[3] v = [4, 5, 6]; integer x = v[2]; /* x == 5 */ - integer y = [4,5,6][3] /* y == 6 */ + integer y = [4,5,6][3]; /* y == 6 */ Like Python, *Gazprea* allows negative indices, which are interpreted as - starting from the _back_ of the array instead of the front: + starting from the *back* of the array instead of the front: :: integer[3] v = [4, 5, 6]; integer x = v[-2]; /* x == 5 */ - integer y = [4,5,6][-1] /* y == 6 */ - - Out of bounds indexing should cause an error. - - e. Stride + integer y = [4,5,6][-1]; /* y == 6 */ - The ``by`` operator is used to specify a step-size greater than 1 when - indexing across an array. It produces an array with the values - indexed by the given stride. For instance: + A negative index ``-k`` refers to element ``n + 1 - k``, so ``-1`` is the + last element and ``-n`` the first. An index is in bounds when it lies in + ``1..n`` or in ``-n..-1``; ``0``, or any magnitude past ``n`` in either + direction, is out of bounds, and the compiler must emit an ``IndexError`` + (see :ref:`sec:errors`) at :term:`compile time` or :term:`run time`. - :: - - integer[*] v = 1..5 by 1; /* [1, 2, 3, 4, 5] */ - integer[*] u = v by 1; /* [1, 2, 3, 4, 5] */ - integer[*] w = v by 2; /* [1, 3, 5] */ - integer[*] l = v by 3; /* [1, 4] */ - integer[*] s = v by 4; /* [1, 5] */ - - d. Slices - - A slice is a contiguous subset of array elements. The subset is described - by a range - The left hand index is inclusive, while the right is exclusive. - - :: - - integer[*] a = 0..10 by 2; /* a = [0, 2, 4, 6, 8, 10] */ - integer[2] x = a[2..4]; /* subset is a[2] and a[3], x == [2, 4] */ - integer[*] y = a[..4]; /* slice used as an r-value */ - a[4..] = 0; /* slice being used as an l-value */ - - Note that for slicing the range always has a stride of 1. - For indexing purposes three additions are made to range syntax: - - +---------+---------------------------------+ - | | Interpretation | - +---------+---------------------------------+ - + `..` | all elements | - +---------+---------------------------------+ - + `i..` | ith to nth elements | - +---------+---------------------------------+ - + `..-i` | first to n-i-1th 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..]; /* y == [6, 8, 10] */ - integer z = a[..-1]; /* z == [0, 2, 4, 6, 8] */ + f. Slices + A slice is a contiguous subset of array elements. Slice bounds + and shorthand forms are specified in :ref:`sssec:array_slices`. #. Operations of the Element Type - Unary operations that are valid for the Element type of an array may be + Unary operations that are valid for the element type of an array may be applied to the array in order to produce an array whose result is the equivalent to applying that unary operation to each element of the array. For instance: @@ -346,23 +430,28 @@ Operations ``nv`` would have a value of ``[not true, not false, not true, not true] = [false, true, false, false]``. - Similarly most binary operations that are valid to the element type of a - array may be also applied to two arrays. When applied to two - arrays of the same size, the result of the binary operation is a + Similarly, every binary operation that is valid for the element type of an + array may also be applied to two arrays. When applied to two + arrays of the same size, the result of the binary operation is an array formed by the element-wise application of the binary operation - to the array operands. + to the array operands. The sole exceptions are the equality operators + ``==`` and ``!=``, which collapse to a single ``boolean`` rather than a + ``boolean`` array (see below); the ordering comparisons ``<``, ``>``, ``<=``, + ``>=`` are *not* exceptions -- they apply element-wise and yield a ``boolean`` + array. :: [1, 2, 3, 4] + [2, 2, 2, 2] // results in [3, 4, 5, 6] - Attempting to perform a binary operation between two arrays of - different sizes should result in a ``SizeError``. + The compiler must emit a ``SizeError`` (see :ref:`sec:errors`) when a + binary operation is performed between two arrays of different sizes, at + :term:`compile time` or :term:`run time`. When one of the operands of a binary operation is an array and the other operand is a scalar, the scalar value must first - be promoted to an array of the same size as the array operand and + be implicitly cast to an array of the same size as the array operand and with the value of each element equal to the scalar value. For example: :: @@ -370,9 +459,9 @@ Operations [1, 2, 3, 4] + 2 // results in [3, 4, 5, 6] - Additionally the element types of arrays may be promoted, for instance - in this case the integer array must be promoted to a real array in - order to perform the operation: + Additionally the element types of arrays may be implicitly cast, for + instance in this case the integer array must be implicitly cast to a real + array in order to perform the operation: :: @@ -402,31 +491,179 @@ Operations The ``!=`` operation also produces a boolean instead of a boolean array. The result is the logical negation of the result of the ``==`` operator. + Only ``==`` and ``!=`` collapse to a single boolean in this way. The + *ordering* comparisons ``<``, ``>``, ``<=``, and ``>=`` follow the ordinary + element-wise rule: applied between two arrays of the same size they produce + a ``boolean`` array (a bitmask) of that size, whose element ``k`` is the + comparison of the two operands' element ``k``. As with any element-wise + binary operation, a size mismatch is a ``SizeError`` (see :ref:`sec:errors`) + and a scalar operand is first broadcast to the array's size. For example: + + :: + + [1, 5, 3] < [2, 2, 2] // results in [true, false, false] + [1, 2, 3] <= 2 // results in [true, true, false] + + Operator precedence and associativity are specified once, for all types, + in the :ref:`table of operator precedence `. + .. _sssec:array_slices: Array Slices ~~~~~~~~~~~~ An array slice is a contiguous subset of elements, described by a range. -An array slice behaves semantically as a new array containing -the array elements captured by the slice, as shown below. +The left hand bound is *inclusive* and the right hand bound is *exclusive*: +``a[i..j]`` selects the elements from ``i`` up to but not including ``j``. This +is the identical half-open convention used by a range *value* (see the +:ref:`range operator `), so ``i..j`` picks out the same +endpoints whether it is written as a value or inside an index position. A slice +always selects a contiguous run of elements. + +The following forms are accepted inside an index position, where ``n`` is +the length of the array being sliced and elements are 1-indexed. A negative +right bound ``-i`` counts ``i`` positions back from the end and is likewise +exclusive, so ``..-1`` selects everything up to but not including the final +element: + ++-----------+-----------------------------------------+ +| Form | Elements selected | ++===========+=========================================+ +| ``..`` | all elements, ``1`` through ``n`` | ++-----------+-----------------------------------------+ +| ``i..`` | ``i`` through ``n`` | ++-----------+-----------------------------------------+ +| ``..j`` | ``1`` through ``j-1`` | ++-----------+-----------------------------------------+ +| ``..-i`` | ``1`` through ``n-i`` | ++-----------+-----------------------------------------+ +| ``i..j`` | ``i`` through ``j-1`` | ++-----------+-----------------------------------------+ + +Whether a slice copies or writes through depends on where it appears: a slice is +a **copy when read** and a **view when assigned to**. + +- **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** + holding a *copy* of the selected elements. Binding it to a variable creates a + new array; the copy and the original never observe each other's later writes. + Because the elements are copied, the source array need not be mutable -- a + slice of a ``const`` array is perfectly legal here -- and the copy's own + mutability is decided by the declaration that receives it: + + :: + + integer[3] a = [1, 2, 3]; // a is const (the default) + var b = a[1..3]; // b is a fresh var integer[2] == [1, 2] (a copy) + b[1] = 9; // b == [9, 2]; a is unchanged, still [1, 2, 3] + + (``a[1..3]`` selects indices 1 and 2.) + +- **In assignment-target position** (an :term:`lvalue`) -- on the *left* of an + 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 + assigned value is fitted to the slice's length exactly as for a whole-array + assignment -- a shorter value is padded with the element type's + :term:`zero value` and a longer value is a ``SizeError`` (see + :ref:`sssec:array_sizing`): + + :: + + var integer[3] a = [1, 2, 3]; + a[1..3] = [4, 5]; // writes through: a == [4, 5, 3] + a -> std_output; // [4, 5, 3] + +This copy-on-read, view-on-assignment split applies unchanged to arrays of any +rank; the higher-rank case is described below and in :ref:`ssec:matrix`. + +Slicing shorthand forms are shown below. Each names a slice in value position, +so each is an ordinary array value (a copy): + +:: + + // 0..10 is a range value, not a slice + integer[*] a = [0, 2, 4, 6, 8, 10]; + integer[2] x = a[2..4]; /* x == [2, 4] (a fresh copy) */ + + integer[*] u = a[..4]; /* u == [0, 2, 4] */ + integer[*] v = a[4..]; /* v == [6, 8, 10] */ + integer[*] w = a[..-1]; /* w == [0, 2, 4, 6, 8] */ + +To index *into* the array that a slice produces, bind it to a variable (or +parenthesize the slice) and index that value; being a copy, it behaves as any +other array value: :: - // 0..10 is a range, not a slice - integer[*] a = 0..10 by 2; /* a = [0, 2, 4, 6, 8, 10] */ - integer[2] x = a[2..4]; /* x == [2, 4] */ - integer y = a[2..4][1]; /* y == 2 */ + integer[*] a = [0, 2, 4, 6, 8, 10]; + var s = a[2..4]; /* s is a fresh integer[2] copy == [2, 4] */ + integer y = s[1]; /* y == 2 */ + + +The **right** bound of a slice may be negative: ``-j`` counts from the end, +resolving to ``n + 1 - j`` (so ``..-1`` stops just before the last element), and +this applies equally in the two-sided form ``a[i..-j]``. The **left** bound may +**not** be negative -- a negative left bound is an ``IndexError`` (see +:ref:`sec:errors`). After resolving any negative right bound, both ``i`` and +``j`` in ``a[i..j]`` must lie between ``1`` and ``n + 1`` inclusive (a slice may +stop just past the last element); a bound outside that range is an +``IndexError``, at :term:`compile time` or :term:`run time`, exactly as for a +single-element index. + +A slice whose (in-bounds) left bound is greater than its right bound, such as +``a[4..2]``, is **not** an error: like a range value with a negative difference +(see :ref:`sssec:array_ops`), it simply selects no elements and yields an empty +array of ``a``'s element type. + +Slicing generalizes to arrays of any rank. Indexing is *positional*: in a +subscript chain ``a[s1][s2]...[sk]`` the subscript ``sm`` applies to axis ``m`` +of ``a``, and a rank-``k`` array accepts up to ``k`` index positions (see +:ref:`ssec:matrix`). An axis indexed by a single integer is dropped from the +result; an axis indexed by a range is kept, holding the selected run -- so the +rank of the result is the number of index positions that are ranges. The +copy-on-read, view-on-assignment rule carries over per selection: the result is +a copy when read and a write-through view when it is the target of an +assignment. + +:: + + // a rank-3 array whose 27 elements are 1, 2, 3, ..., 27 in order + var integer[3][3][3] a = ...; + + // axis 1 by an integer (dropped); axes 2 and 3 by ranges (kept): + var b = a[1][1..3][1..3]; // a fresh integer[2][2] copy == [[1, 2], [4, 5]] + + // the same positional selection as an lvalue writes through to a: + a[2][1..3][1..3] = [[28, 29], [30, 31]]; // updates those four elements of a + +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 +variable (or parenthesize it) and index the resulting value, as shown above. - // A slice of the entire array behaves as the array itself, this can be repeated - integer z1 = a[4]; /* z1 == 6 */ - integer z2 = a[1..7][1..7][1..7][4]; /* z2 == 6 */ +A slice may also be handed to a :ref:`function ` or +:ref:`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 + receives a copy of the selected elements and cannot reach the caller's array + through it. Every :ref:`function ` parameter is ``const`` + (functions are pure), so a slice passed to a function is always such a copy. -Array slices are always l-values, although they can be used as r-values. -When they are used in a parameter call or on the left side of an assignment, -i.e. as an l-value they allow modification of the source array: +- A slice bound to a ``var`` parameter -- available only for + :ref:`procedures `, whose parameters may be ``var`` -- + is passed **by reference**: it is a view that writes *through* to the backing + array, exactly as an lvalue slice does, so the callee's writes are visible to + the caller once the call returns. This requires the backing array to be + ``var``. +An implementation may still pass a ``const`` slice by reference for efficiency: +because the callee only reads it, the choice is unobservable, and *Gazprea*'s +value semantics -- realized directly by *MLIR* -- make the copy and the shared +reference indistinguishable in that case. :: @@ -436,34 +673,34 @@ i.e. as an l-value they allow modification of the source array: procedure main() returns integer { - integer[6] a = 0..10 by 2; /* a = [0, 2, 4, 6, 8, 10] */ - integer[6] b = 0..15 by 3; /* b = [0, 3, 6, 9, 12, 15] */ - var integer[6] c; /* c must be var */ + integer[6] a = [0, 2, 4, 6, 8, 10]; /* a and b are const */ + integer[6] b = [0, 3, 6, 9, 12, 15]; + var integer[6] c; /* c must be var */ - /* procedure works normally with an array */ + /* procedure works normally with whole arrays */ call sum_arrays(a, b, c); c -> std_output; /* [0, 5, 10, 15, 20, 25] */ - /* procedure can also modify a slice */ + /* a[1..4] and b[1..4] are copied into the const parameters; + c[4..7] is a var slice, so writes pass through to c */ call sum_arrays(a[1..4], b[1..4], c[4..7]); c -> std_output; /* [0, 5, 10, 0, 5, 10] */ - /* slice can be assigned to, modifying c */ + /* a slice on the left of an assignment writes through to c */ c[3..5] = [415, 429]; c -> std_output; /* [0, 5, 415, 429, 5, 10] */ return 0; } -This behaviour is consistent with the slice being thought of as a -reference to the original array's elements, where in the first -examples, the assignments perform a deep copy as usual and in the -procedure example, the parameters are passed by reference as usual. +Here ``c[4..7]`` and ``c[3..5]`` are lvalue slices of the mutable array ``c``, +so each write passes through to ``c`` itself; ``a[1..4]`` and ``b[1..4]``, bound +to ``const`` parameters, are copied and leave ``a`` and ``b`` untouched. -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To see the types that an array may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +To see the types that an array may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/boolean.rst b/gazprea/spec/types/boolean.rst index aa3a00d..b8151ff 100644 --- a/gazprea/spec/types/boolean.rst +++ b/gazprea/spec/types/boolean.rst @@ -11,8 +11,8 @@ represented by an ``i1`` in *MLIR*. Declaration ~~~~~~~~~~~ -A ``boolean`` value is declared with the keyword ``boolean``. -If the variable is not initialized explicitly, it is set to ``false`` (zero). +A ``boolean`` value is declared with the keyword ``boolean``. If the variable +is not initialized explicitly, it is set to ``false`` (its :term:`zero value`). .. _sssec:boolean_lit: @@ -34,47 +34,33 @@ The following operations are defined on ``boolean`` values. In all of the usage examples ``bool-expr`` means some ``boolean`` yielding expression. -============= ========== =========================== ================= -**Operation** **Symbol** **Usage** **Associativity** -============= ========== =========================== ================= -parenthesis ``()`` ``(bool-expr)`` N/A -negation ``not`` ``not bool-expr`` right -logical or ``or`` ``bool-expr or bool-expr`` left -logical xor ``xor`` ``bool-expr xor bool-expr`` left -logical and ``and`` ``bool-expr and bool-expr`` left -equals ``==`` ``bool-expr == bool-expr`` left -not equals ``!=`` ``bool-expr != bool-expr`` left -============= ========== =========================== ================= - -Unlike many languages the ``and`` and ``or`` operators do not `short -circuit +============= ========== =========================== +**Operation** **Symbol** **Usage** +============= ========== =========================== +parenthesis ``()`` ``(bool-expr)`` +negation ``not`` ``not bool-expr`` +logical or ``or`` ``bool-expr or bool-expr`` +logical xor ``xor`` ``bool-expr xor bool-expr`` +logical and ``and`` ``bool-expr and bool-expr`` +equals ``==`` ``bool-expr == bool-expr`` +not equals ``!=`` ``bool-expr != bool-expr`` +============= ========== =========================== + +Unlike many languages, the ``and`` and ``or`` operators do not `short-circuit evaluation `__. Therefore, both the left hand side and right hand side of an expression -must always be evaluated. - -This table specifies ``boolean`` operator precedence. Operators without -lines between them have the same level of precedence. - -+----------------+---------------+ -| **Precedence** | **Operation** | -+================+===============+ -| HIGHER | ``not`` | -+----------------+---------------+ -| | ``==`` | -| | | -| | ``!=`` | -+----------------+---------------+ -| | ``and`` | -+----------------+---------------+ -| | ``or`` | -| | | -| LOWER | ``xor`` | -+----------------+---------------+ - - -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To see the types that ``boolean`` may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +must always be evaluated. This has a practical consequence: a guard like +``x != 0 and 1/x > 0`` does **not** protect the division -- ``1/x`` is evaluated +even when ``x`` is ``0``, raising a ``MathError`` (see :ref:`ssec:integer` and +:ref:`sec:errors`). To guard a fallible expression, nest an ``if`` instead. + +Operator precedence and associativity are specified once, for all +types, in the +:ref:`table of operator precedence `. + +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To see the types that ``boolean`` may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/character.rst b/gazprea/spec/types/character.rst index 98a6ec3..80d3c74 100644 --- a/gazprea/spec/types/character.rst +++ b/gazprea/spec/types/character.rst @@ -3,8 +3,11 @@ Character --------- -A ``character`` is a signed 8-bit value. A ``character`` can be -represented by an ``i8`` in *MLIR*. +A ``character`` is an 8-bit value. A ``character`` can be +represented by an ``i8`` in *MLIR*. When a ``character`` is cast to +``integer`` or ``real`` its bit pattern is interpreted as an *unsigned* byte, +so its numeric value ranges from ``0`` to ``255`` (for example ``'\xFF'`` casts +to ``255``, not ``-1``); see :ref:`sec:typeCasting`. .. _sssec:character_decl: @@ -52,33 +55,51 @@ Carriage Return ``\r`` ``0x0D`` Quotation Mark ``\"`` ``0x22`` Apostrophe ``\'`` ``0x27`` Backslash ``\\`` ``0x5C`` -UTF-8 ``\xH[H]`` e.g. ``x61 ('a')`` +Hex escape ``\xH[H]`` ``0x00`` to ``0xFF`` =============== =================== =============== +A hex escape must be followed by at least one hexadecimal digit (``\xH`` or +``\xHH``); a ``\x`` with no following hex digit is :term:`ill-formed`, and the +compiler must emit a ``LiteralError`` (see :ref:`sec:errors`). + .. _sssec:character_ops: Operations ~~~~~~~~~~ -The following operations are defined between ``character`` values. - -+------------+--------------------------+------------+---------------------------+-------------------+ -| **Class** | **Operation** | **Symbol** | **Usage** | **Associativity** | -+============+==========================+============+===========================+===================+ -| Grouping | parentheses | ``()`` | ``(character)`` | N/A | -+------------+--------------------------+------------+---------------------------+-------------------+ -| Comparison | equals | ``==`` | ``character == character``| left | -| +--------------------------+------------+---------------------------+-------------------+ -| | not equals | ``!=`` | ``character != character``| left | -+------------+--------------------------+------------+---------------------------+-------------------+ +The following operations are defined between ``character`` values. + ++------------+---------------+------------+----------------------------+ +| **Class** | **Operation** | **Symbol** | **Usage** | ++============+===============+============+============================+ +| Grouping | parentheses | ``()`` | ``(character)`` | ++------------+---------------+------------+----------------------------+ +| Comparison | equals | ``==`` | ``character == character`` | +| +---------------+------------+----------------------------+ +| | not equals | ``!=`` | ``character != character`` | ++------------+---------------+------------+----------------------------+ + +``character`` values are **not orderable**: the relational operators ``<``, +``>``, ``<=``, and ``>=`` are not defined on characters (only ``==`` and +``!=`` are), and there is no implicit cast between ``character`` and +``integer`` (see :ref:`sec:implicitCasts`). Applying a relational operator to +characters is therefore a ``TypeError`` (see :ref:`sec:errors`). To order +characters -- for example to test ``'a' <= c and c <= 'z'`` -- explicitly cast +each operand to ``integer`` with ``as(...)`` (see +:ref:`sec:typeCasting`), which yields the character's unsigned byte value. :term:`Scalar ` values with type ``character`` may be -concatenated onto variables with type ``string`` or arrays with type -``character``. +concatenated onto values of type ``string`` or arrays with type +``character``. See :ref:`sssec:string_ops` for the full concatenation +rules, including the ``TypeError`` (see :ref:`sec:errors`) raised when +both operands of ``||`` are scalar, e.g. ``character || character``. + +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To see the types that ``character`` may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +To see the types that ``character`` may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/integer.rst b/gazprea/spec/types/integer.rst index 1972cf0..8f7d14d 100644 --- a/gazprea/spec/types/integer.rst +++ b/gazprea/spec/types/integer.rst @@ -11,7 +11,7 @@ represented by an ``i32`` in *MLIR*. Declaration ~~~~~~~~~~~ -A ``integer`` value is declared with the keyword ``integer``. +An ``integer`` value is declared with the keyword ``integer``. .. _sssec:integer_lit: @@ -26,7 +26,8 @@ An ``integer`` literal is specified in base 10. For example: 2 0 -An ``integer`` literal must be a representable ``i32`` value. +An ``integer`` literal must be a representable ``i32`` value; the compiler +must emit a ``LiteralError`` (see :ref:`sec:errors`) otherwise. .. _sssec:integer_ops: @@ -37,88 +38,77 @@ The following operations are defined between ``integer`` values. In all of the usage examples ``int-expr`` means some ``integer`` yielding expression. -+------------+--------------------------+------------+--------------------------+-------------------+ -| **Class** | **Operation** | **Symbol** | **Usage** | **Associativity** | -+============+==========================+============+==========================+===================+ -| Grouping | parentheses | ``()`` | ``(int-expr)`` | N/A | -+------------+--------------------------+------------+--------------------------+-------------------+ -| Arithmetic | addition | ``+`` | ``int-expr + int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | subtraction | ``-`` | ``int-expr - int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | multiplication | ``*`` | ``int-expr * int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | division | ``/`` | ``int-expr / int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | remainder | ``%`` | ``int-expr % int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | exponentiation | ``^`` | ``int-expr ^ int-expr`` | right | -| +--------------------------+------------+--------------------------+-------------------+ -| | unary negation | ``-`` | ``- int-expr`` | right | -| +--------------------------+------------+--------------------------+-------------------+ -| | unary plus (no-op) | ``+`` | ``+ int-expr`` | right | -+------------+--------------------------+------------+--------------------------+-------------------+ -| Comparison | less than | ``<`` | ``int-expr < int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | greater than | ``>`` | ``int-expr > int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | less than or equal to | ``<=`` | ``int-expr <= int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | greater than or equal to | ``>=`` | ``int-expr >= int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | equals | ``==`` | ``int-expr == int-expr`` | left | -| +--------------------------+------------+--------------------------+-------------------+ -| | not equals | ``!=`` | ``int-expr != int-expr`` | left | -+------------+--------------------------+------------+--------------------------+-------------------+ ++------------+--------------------------+------------+--------------------------+ +| **Class** | **Operation** | **Symbol** | **Usage** | ++============+==========================+============+==========================+ +| Grouping | parentheses | ``()`` | ``(int-expr)`` | ++------------+--------------------------+------------+--------------------------+ +| Arithmetic | addition | ``+`` | ``int-expr + int-expr`` | +| +--------------------------+------------+--------------------------+ +| | subtraction | ``-`` | ``int-expr - int-expr`` | +| +--------------------------+------------+--------------------------+ +| | multiplication | ``*`` | ``int-expr * int-expr`` | +| +--------------------------+------------+--------------------------+ +| | division | ``/`` | ``int-expr / int-expr`` | +| +--------------------------+------------+--------------------------+ +| | remainder | ``%`` | ``int-expr % int-expr`` | +| +--------------------------+------------+--------------------------+ +| | exponentiation | ``^`` | ``int-expr ^ int-expr`` | +| +--------------------------+------------+--------------------------+ +| | unary negation | ``-`` | ``- int-expr`` | +| +--------------------------+------------+--------------------------+ +| | unary plus (no-op) | ``+`` | ``+ int-expr`` | ++------------+--------------------------+------------+--------------------------+ +| Comparison | less than | ``<`` | ``int-expr < int-expr`` | +| +--------------------------+------------+--------------------------+ +| | greater than | ``>`` | ``int-expr > int-expr`` | +| +--------------------------+------------+--------------------------+ +| | less than or equal to | ``<=`` | ``int-expr <= int-expr`` | +| +--------------------------+------------+--------------------------+ +| | greater than or equal to | ``>=`` | ``int-expr >= int-expr`` | +| +--------------------------+------------+--------------------------+ +| | equals | ``==`` | ``int-expr == int-expr`` | +| +--------------------------+------------+--------------------------+ +| | not equals | ``!=`` | ``int-expr != int-expr`` | ++------------+--------------------------+------------+--------------------------+ Unary negation produces the additive inverse of the ``integer`` expression. Unary plus always produces the same result as the -``integer`` expression it is applied to. Remainder mirrors the behaviour +``integer`` expression it is applied to. Remainder mirrors the behavior of remainder in *C99*. -Exponentiation between integers gives an ``integer`` result. This is the same behavior as performing exponentiation on reals then truncating to an ``integer``. - -This table specifies ``integer`` operator precedence. Operators without -lines between them have the same level of precedence. Note that -parentheses are not included in this list because they are used to -override precedence and create new atoms in an expression. - -+----------------+----------------+ -| **Precedence** | **Operations** | -+================+================+ -| HIGHER | ``unary +`` | -| | | -| | ``unary -`` | -+----------------+----------------+ -| | ``^`` | -+----------------+----------------+ -| | ``*`` | -| | | -| | ``/`` | -| | | -| | ``%`` | -+----------------+----------------+ -| | ``+`` | -| | | -| | ``-`` | -+----------------+----------------+ -| | ``<`` | -| | | -| | ``>`` | -| | | -| | ``<=`` | -| | | -| | ``>=`` | -+----------------+----------------+ -| | ``==`` | -| | | -| LOWER | ``!=`` | -+----------------+----------------+ - - -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To see the types that ``integer`` may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +Exponentiation between integers gives an ``integer`` result. This is the same +behavior as performing exponentiation on reals then truncating to an +``integer``. + +Signed 32-bit arithmetic that overflows the ``i32`` range (``+``, +``-``, ``*``, ``/``, ``^``, and unary ``-``) causes the implementation to +raise a ``MathError`` (see :ref:`sec:errors`). This includes the two overflow +cases that arise from ``/`` and unary negation specifically: ``INT_MIN / -1`` +and ``-INT_MIN``, whose mathematical results are not representable as an +``i32``. +Division and remainder (``%``) where the right operand is ``0``, and +exponentiation where the base is ``0`` and the exponent is ``<= 0``, +cause the implementation to raise a ``MathError`` (see +:ref:`sec:errors`) at :term:`compile time` or :term:`run time`. + +The sole exception is under the ``-ffast-math`` compiler flag -- which every +conforming implementation must support, but which is off unless explicitly +enabled -- under which every one of these integer faults -- overflow, divide by ``0``, ``%`` by +``0``, and exponentiation of base ``0`` with a non-positive exponent -- becomes +:term:`undefined behavior` instead of raising a ``MathError``. This is the only +construct in which *Gazprea* leaves behavior undefined, and it is provided +solely for performance testing; see :ref:`sec:flags` for its precise semantics +and the rules governing its use. + + +Operator precedence and associativity are specified once, for all +types, in the :ref:`table of operator precedence +`. + +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To see the types that ``integer`` may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/matrix.rst b/gazprea/spec/types/matrix.rst index 5ed0d3e..4b8cc89 100644 --- a/gazprea/spec/types/matrix.rst +++ b/gazprea/spec/types/matrix.rst @@ -3,9 +3,19 @@ Matrices -------- -*Gazprea* supports two dimensional matrices as arrays of arrays. -Although the syntax and concepts are easily generalizable to many dimensions, -we are restricting the language to two dimensions for now. +*Gazprea* arrays generalize to arbitrary rank: a type of the form +``T[n1][n2]...[nk]`` is a rank-``k`` array whose element type ``T`` may be +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 +arrays of **any** rank, as described in :ref:`sssec:matrix_ops`. .. _sssec:matrix_decl: @@ -24,6 +34,12 @@ valid matrix declarations: integer[*][2] D = [[1, 2], [4, 5], [7, 8]]; integer[*][*] E = [[1, 2], [4, 5], [7, 8]]; +Both matrix dimensions are :term:`initialization`-time sized: each length is +fixed once when the matrix is :term:`initialized ` and never +changes thereafter. A ``[*]`` in either position infers that dimension once +from the initializer — exactly as ``[*]`` infers the length of a 1-D array — +after which it too is fixed. + .. _sssec:matrix_constr: Construction @@ -32,10 +48,19 @@ Construction A 2D matrix can be viewed as an array of arrays. The elements in each array form a single row of the matrix. All rows with fewer elements than the row of maximum row length are padded with -zeros on the right. Similarly, if the matrix is declared with a row -length larger than the number of rows provided, the bottom rows of the -matrix are zero. If the number of rows or columns exceeds the -amounts given in a declaration an error is to be produced. +the element type's :term:`zero value` on the right. Similarly, if the matrix is +declared with more rows than are provided, the bottom rows hold the element +type's zero value. If the number +of rows or columns exceeds the +amounts given in a declaration the compiler must emit a ``SizeError`` +(see :ref:`sec:errors`) at :term:`compile time` or :term:`run time`. + +This pad-to-longest-row rule is a property of the nested array literal itself, so +it applies identically whether the literal initializes a matrix, an array +variable, or a :ref:`vector of arrays `. Only *incrementally* +growing a vector with ``push``/``append`` behaves differently, fitting each new +element to the size fixed by the first element; :ref:`ssec:vector` walks through +the contrast with worked examples. :: @@ -57,11 +82,17 @@ Also matrices can be initialized with a :term:`scalar ` value. Initializing with a scalar value makes every element of the matrix equal to the scalar. -Gazprea supports empty matrices. +Gazprea supports empty matrices. A rank-2 array initialized from the empty +literal ``[]`` is the empty rank-2 array, written ``[[]]``: :: - integer[*][*] m = []; /* Should create an empty matrix */ + integer[*][*] m = []; /* m == [[]], an empty rank-2 array */ + +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 +``[[]]`` notation). .. _sssec:matrix_ops: @@ -75,56 +106,109 @@ operations are applied between elements with the same position in the arrays. The operators ==, and != also have the same behavior independent of the dimensionality of the array. -These operations compare whether or not **all** elements of are equal. +These operations compare whether or not **all** elements of the two matrices +are equal. Two dimensional arrays have several special operations defined on them. If the element type is numeric (supports addition and multiplication), then matrix multiplication is supported using the operator \**. Matrix multiplication is only defined between matrices with compatible element types, and the dimensions of the matrices must be valid for performing matrix -multiplication. -Specifically, the number of columns of the first operand must equal the number +multiplication. When the two operands have differing element types (e.g. +``integer ** real``), each element is implicitly cast to a common type (see +:ref:`sec:implicitCasts`) before multiplication, just as for element-wise +binary operations. Specifically, the number of columns of the first operand must equal the number of rows of the second operand, e.g. an :math:`m \times n` matrix multiplied by an :math:`n \times p` matrix will produce an :math:`m \times p` matrix. -If the dimensions are not correct a ``SizeError`` should be raised. - -Arrays of any dimension support the built in functions ``rows`` and ``columns``, -which when passed a 2D array yields the number of rows and columns in the -matrix respectively. For instance: +If the dimensions are not correct the compiler must emit a ``SizeError`` +(see :ref:`sec:errors`). + +When one operand of ``**`` is a scalar it can be broadcast to a matrix operand +of matrix multiplication **only if the other operand is a square matrix**: a +scalar ``s`` paired with an :math:`n \times n` matrix is filled into an +:math:`n \times n` matrix whose every element is ``s`` before the +multiplication. If the other operand is not square the scalar cannot be +broadcast and the compiler must emit a ``TypeError`` (see :ref:`sec:errors`). +Scalars broadcast this way only to ``**`` operands whose extents are all equal -- +a rank-1 array (trivially, since it has a single extent, so a scalar may be +dotted with a vector; see the :ref:`dot product `), a square +matrix, or a higher-rank hypercube with equal extents; *Gazprea* does **not** +provide comprehensive broadcasting. See :ref:`sec:implicitCasts`. + +More generally, ``**`` is defined for numeric arrays of **any** rank as the +single-axis contraction familiar from linear algebra: the **last** dimension of +the left operand is contracted with the **first** dimension of the right +operand. Writing the left operand as a rank-:math:`a` array ``A`` and the right +operand as a rank-:math:`b` array ``B``, the last extent of ``A`` must equal +the first extent of ``B`` -- otherwise the compiler must emit a ``SizeError`` +(see :ref:`sec:errors`) -- and the result ``C`` has rank :math:`a + b - 2`, +given by +:math:`C[i_1 \ldots i_{a-1},\, k_2 \ldots k_b] = \sum_j A[i_1 \ldots i_{a-1},\, j] \cdot B[j,\, k_2 \ldots k_b]`. +The rank-1-with-rank-1 case is therefore the :ref:`dot product +` (a scalar) and the rank-2-with-rank-2 case is the matrix +multiplication described above; both are instances of the one contraction rule, +which corresponds directly to the contraction operations already available in +*MLIR*. + +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. + + +Matrix indexing is done similarly to array indexing, except that one subscript +is written per axis: :: - integer[*][*] M = [[1, 1, 1], [1, 1, 1]]; - - integer r = rows(M); /* This has a value of 2 */ - integer c = columns(M); /* This has a value of 3 */ + M[i][j] -> std_output; -Matrix indexing is done similarly to array indexing, however, two -indices must be used. Because matrices are arrays of arrays the indexing is -composite: +The first index selects along the first axis (the row) and the second along the +second axis (the column). When both indices are single integers, as here, the +result is the one element at that row and column: :: - M[i][j] -> std_output; - + integer[*][*] M = [[11, 12, 13], [21, 22, 23]]; -The first index specifies the row of the matrix, and the second index -specifies the column of the matrix. The result is retrieved from the row -and column. Both the row and column indices must be integers. + /* M[1][2] == 12 */ + +As with arrays, out of bounds indexing on matrices must emit an +``IndexError`` (see :ref:`sec:errors`) at :term:`compile time` or +:term:`run time`. + +Every index position accepts the same forms as a 1-D array index (see +:ref:`sssec:array_slices`): a single integer selects one element along that +axis, and a range written directly in an index position selects a contiguous +run along that axis (a slice, with the same inclusive-left, exclusive-right +bounds as for 1-D arrays). Indexing is *positional*: in a subscript chain +``M[s1][s2]``, ``s1`` applies to the first axis (rows) and ``s2`` to the second +(columns), and a rank-``k`` array accepts up to ``k`` such positions. An axis +indexed by a single integer is dropped from the result, while an axis indexed by +a range is kept, so the rank of the result is the number of index positions that +are ranges: ``M[i]`` selects a whole row (a rank-1 array), ``M[i][j]`` selects +one element, ``M[1..3]`` selects a contiguous band of rows (a rank-2 +sub-matrix), and ``M[1..3][2]`` selects column 2 of that band (a rank-1 array). +Higher-rank arrays generalize this to ``k`` index positions. Slices of a matrix +carry the same copy-on-read, write-through-on-assignment semantics as for 1-D +arrays: a slice read as a value copies the selected elements, while a slice on +the left of an assignment writes through to the matrix (which must be ``var``). :: - integer[*][*] M = [[11, 12, 13], [21, 22, 23]]; + integer[*][*] M = [[11, 12, 13], [21, 22, 23], [31, 32, 33]]; - /* M[1, 2] == 12 */ + /* M[2] == [21, 22, 23] (a whole row) */ + /* M[1..3] == [[11,12,13],[21,22,23]] (rows 1 and 2) */ + /* M[1..3][2] == [12, 22] (column 2 of those rows) */ -As with arrays, out of bounds indexing is an error on Matrices. +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To see the types that matrix may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +To see the types that a matrix may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/real.rst b/gazprea/spec/types/real.rst index 45d0b78..6752cb6 100644 --- a/gazprea/spec/types/real.rst +++ b/gazprea/spec/types/real.rst @@ -3,8 +3,8 @@ Real ---- -A ``real`` is an IEEE 754 32-bit floating point value. A ``real`` can be -represented by a ``f32`` in *MLIR*. +A ``real`` is an IEEE 754 32-bit floating-point value. A ``real`` can be +represented by an ``f32`` in *MLIR*. .. _sssec:real_decl: @@ -33,18 +33,18 @@ parsed. For example: .42 . // Illegal. -A ``real`` literal can also be created by any valid ``real`` or -``integer`` literal followed by scientific notation indicated by the -character ``e`` or ``E`` and another valid ``integer`` literal. Scientific notation -multiplies the first literal by :math:`{10}^{x}`. For example, -:math:`4.2\mathrm{e}{-3}=4.2 \times10^{-3}`. For example: +A ``real`` literal can also be created by any valid ``real`` or ``integer`` +literal followed by scientific notation indicated by the character ``e`` or +``E`` and another valid ``integer`` literal. Scientific notation multiplies the +first literal by :math:`{10}^{x}`, e.g. :math:`4.2\mathrm{e}{-3}=4.2 +\times10^{-3}`. For example: :: 4.2e-1 4.2e+9 4.2E5 - 42.e+37 + 42.e+7 .42e-7 42E6 @@ -53,15 +53,46 @@ multiplies the first literal by :math:`{10}^{x}`. For example, Operations ~~~~~~~~~~ -Floating point operations and precedence are equivalent to :ref:`integer operation and precedence `. - -Operations on real numbers should adhere to the IEEE 754 spec with -regards to the representation of not-a-number(NaNs), infinity(infs), and -zeros. - -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To see the types that ``real`` may be cast and/or promoted to, see -the sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` +Floating-point operations are equivalent to :ref:`integer operations +`. + +The ``%`` operator is defined on ``real`` operands as the decimal remainder; for +example ``5.5 % 2.0 == 1.5``. Because ``==`` on reals is exact IEEE 754 equality +(bit-for-bit), such an equation holds only when the operands and the result are +all exactly representable; approximate numeric equality is not provided and would +require a user-defined tolerance comparison. + +Real values always use the IEEE 754 representation and semantics for +not-a-number (``NaN``), the signed infinities (``Infinity``), and signed +zeros. Real arithmetic therefore **never** raises a ``MathError``: overflowing +the finite ``real`` range yields a signed ``Infinity``, division or ``%`` by +``0.0`` yields a signed ``Infinity`` (or ``NaN`` for ``0.0 / 0.0``), and every +subsequent operation on ``Infinity`` and ``NaN`` operands follows IEEE 754. The +``-ffast-math`` flag has **no effect** on how ``real`` values are produced or +handled -- in particular it does not change the generation of ``Infinity`` or +``NaN``. (``-ffast-math`` affects only integer arithmetic; see :ref:`sec:flags` +and :ref:`ssec:integer`.) + +Comparisons follow from this rule, exactly as in IEEE 754. With at least one +``NaN`` operand, every *affirmative* comparison -- ``==``, ``<``, ``>``, +``<=``, ``>=`` -- evaluates to ``false``, while the *negative* comparison +``!=`` evaluates to ``true``. A ``NaN`` is unordered with respect to every +value, including an ``Infinity`` and including another ``NaN``; so when ``x`` +is ``NaN``, ``x == x`` is ``false`` and ``x != x`` is ``true``. Comparisons +that involve only finite values and the infinities behave as ordinary IEEE 754 +comparisons; for example ``1.0 / 0.0`` compares greater than every finite +``real``, and ``+Infinity`` compares equal to ``+Infinity``. + +Exponentiation (``^``) likewise follows IEEE 754: a negative base raised to a +fractional exponent -- for example ``(-2.0)^0.5`` -- is not a ``MathError`` but +yields ``NaN``. + +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. + +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To see the types that ``real`` may be cast and/or implicitly cast to, see +the sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/string.rst b/gazprea/spec/types/string.rst index 716e461..282386e 100644 --- a/gazprea/spec/types/string.rst +++ b/gazprea/spec/types/string.rst @@ -3,16 +3,34 @@ 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. +A ``string`` is a language-supplied *typealias* for ``vector``: +the two are the same type by strong equivalence, not a distinct sub-type. +Anything true of a ``vector`` is therefore true of a ``string``, +and the two may be used interchangeably. + +Because a ``string`` *is* a ``vector``, it is runtime-sized and unbounded +like any other vector: its length is simply the length of its underlying +character sequence, which may grow (for example through the ``push`` and +``append`` methods). There is no separate sized or bounded string type. +Growth needs a mutable receiver, though: ``push`` and ``append`` require a +``var`` string, and -- as with every declaration -- a ``string`` is ``const`` by +default, so a ``const string`` (or one whose qualifier is elided) is effectively +fixed for its lifetime: -String vectors behave a lot like character arrays, but there are several -differences between the two types: -an :ref:`extra literal style `, -the :ref:`result of a concatenation ` -and :ref:`behaviour when sent to an output stream `. +:: + + var string greeting = "hi"; + call greeting.append(" there"); // greeting == "hi there" + call greeting.push('!'); // greeting == "hi there!" + + const string fixed = "constant"; // const by default; it cannot grow + +Although a ``string`` and a plain ``character`` array behave alike in most +respects, *Gazprea* still treats the two differently in a couple of places: +strings have an :ref:`extra literal style ` and special +:ref:`behavior when sent to an output stream `. +(Concatenation is *not* one of these differences -- see +:ref:`sssec:string_ops`.) .. _sssec:string_decl: @@ -25,7 +43,9 @@ that all lengths are inferred: :: - [] string = ; + [] string ; + [] string = ; + [] string = ; .. _sssec:string_lit: @@ -39,7 +59,7 @@ double quotes. For instance: :: - string cats_meow = "The cat said \"Meow!\"\nThat was a good day.\n" + string cats_meow = "The cat said \"Meow!\"\nThat was a good day.\n"; Although strings and character arrays look similar, they are still treated differently by the compiler: @@ -65,23 +85,34 @@ prints: Operations ~~~~~~~~~~ -As character vectors, strings have all of the same operations defined on them as -the other array data types. -Remember that because a ``string`` and vector of ``character`` are fundamentally -the same, the concatenation operation may be used to concatenate values of the -two types. You may also append a slice of characters to a string using the -append method. -As well, a :term:`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: +As character vectors, strings have all of the same operations defined on them +as the other array data types. Remember that because a ``string`` *is* a +``vector``, the concatenation operator ``||`` may be used to combine +``string`` values with ``character`` arrays (which are a distinct array type). +Concatenation takes the kind of its :ref:`receiver `, the +rightmost operand: when that receiver is a ``string`` (or any vector), the whole +concatenation is a ``string``, so ``"x = " || format(x)`` is a ``string`` and +prints as text when sent to a stream. When instead the receiver is a +``character`` array, the result is a ``character`` array, which is implicitly +cast back to a ``string`` whenever it is stored into one (see +:ref:`ssec:implicitCasts_string`). Either way +``var string letters = ['a', 'b'] || "cd";`` below is legal -- here its receiver +``"cd"`` is a ``string``, so the concatenation is itself a ``string``. At least one operand of ``||`` must be a +composite type (a ``string`` or an array); concatenating two :term:`scalar +` values -- for example ``character || character`` -- must emit a +``TypeError`` (see :ref:`sec:errors`). 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 ``string`` is a typealias for +``vector``, concatenation may also be accomplished with the +``append`` and ``push`` methods (see :ref:`sssec:vec_methods`; strings have +exactly the vector method set): :: var string letters = ['a', 'b'] || "cd"; - letters.concat("ef"); - letters.push('g'); + call letters.append("ef"); + call letters.push('g'); letters -> std_output; prints the following: @@ -90,9 +121,13 @@ prints the following: abcdefg +Operator precedence and associativity are specified once, for all types, +in the :ref:`table of operator precedence `. + -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To see the types that ``string`` may be cast and/or promoted to, see the -sections on :ref:`sec:typeCasting` and :ref:`sec:typePromotion` respectively. +To see the types that a ``string`` may be cast to -- explicitly with +``as<>()`` or through an implicit cast -- see the sections on +:ref:`sec:typeCasting` and :ref:`sec:implicitCasts` respectively. diff --git a/gazprea/spec/types/struct.rst b/gazprea/spec/types/struct.rst index 2c56d61..f9c88eb 100644 --- a/gazprea/spec/types/struct.rst +++ b/gazprea/spec/types/struct.rst @@ -6,9 +6,16 @@ Structs Like ``tuples``, a ``struct`` is a way of grouping multiple values with different types into an :term:`aggregate ` data structure. The main differences between tuples and structs are that the fields of a struct -are named, and the type signature of a struct is named as a user defined type. -Any type except ``tuple``, another ``struct`` and :ref:`streams` -may be stored within a struct. Also like tuples, structs must contain *at least two fields*. +are named, and the type signature of a struct is named as a user-defined type. +Any :ref:`storable type ` may be stored within a +struct, including arrays of any rank (a matrix is the rank-2 case), +``vector``, ``string``, ``tuple``, and other ``struct`` types, nested to any +depth (subject to the +:ref:`acyclicity rule `). Only +:ref:`streams` may not be stored within a struct. Also like +tuples, structs must contain *at least two fields*; a ``struct`` type with fewer +than two fields is :term:`ill-formed`, and the compiler must emit a ``TypeError`` +(see :ref:`sec:errors`). .. _sssec:struct_decl: @@ -24,17 +31,30 @@ and consist of a ```` pair: :: struct s1 (integer i, real r, integer[10] iv) t1; - struct Another (character char, real float, string[256] str, s1 struct_field); + struct Another (character ch, real f, string str, s1 struct_field); var Another t2; The examples show two structs declared with types ``s1`` and ``Another``. -Struct type ``s`` has three fields: ``i`` of type ``integer``, ``r`` of type +Struct type ``s1`` has three fields: ``i`` of type ``integer``, ``r`` of type ``real``, and ``iv`` of type ``integer[10]``. -Struct type ``Another`` has four fields named ``char``, ``float``, ``str``, +Struct type ``Another`` has four fields named ``ch``, ``f``, ``str``, and ``struct_field``. The instance variables ``t1`` and ``t2`` have types ``s1`` and ``Another``, respectively. +A struct declaration may optionally be followed by an identifier, as in +the first example: ``struct s1 (...) t1;`` declares the type ``s1`` *and* +a variable ``t1`` of that type in one statement, exactly equivalent to +``struct s1 (...); s1 t1;``. The combined form takes an optional qualifier +(``var`` or ``const``), exactly like any other +:ref:`declaration `: the bare ``struct s1 (...) t1;`` and +the explicit ``const struct s1 (...) t1;`` both declare an immutable ``t1`` +(``const`` is the default), while ``var struct s1 (...) t1;`` declares a +mutable one. The split form, as the ``t2`` example shows, is equivalent. +A mutable struct instance such as ``var struct s1 (...) t1;`` (or the split +``var s1 t1;``) is legal in exactly the same positions as a mutable ``var`` +:ref:`tuple `. + .. _sssec:struct_typealias: @@ -42,26 +62,34 @@ respectively. Type Aliasing ~~~~~~~~~~~~~ -A struct can be typealiased and used in any context a regular struct declaration may occur. Notably, the alias can only be used in a type positions, not literal constructors. +A struct type can be given a :ref:`type alias `. Like any type +alias (see :ref:`sec:typealias`), and like a plain struct *definition*, the +``typealias struct`` form may appear at global scope or inside a function or +procedure body; a local one is :term:`scoped ` to its block and shadows +any outer type or alias of the same name. The combined form below both defines +the struct type ``S`` and introduces ``Pair`` as an alias for it: the struct's +own name ``S`` remains usable, for example as a literal constructor. Once +declared, the alias may be used in place of the struct's type name in type +positions. It may not, however, be used as a literal constructor. :: - + typealias struct S(integer x, integer y) Pair; - + function add(Pair p1, Pair p2) returns Pair { - Pair p3 = S(x: p1.x + p2.x, y: p1.y + p2.y); // Pair can not be used in place of S + Pair p3 = S(x: p1.x + p2.x, y: p1.y + p2.y); // Pair cannot be used in place of S return p3; } - + .. _sssec:struct_acc: Access ~~~~~~ - * ``field`` is a field within struct ``T`` +Struct fields are accessed with dot notation, ``instance.field``, where +``field`` is a field of the instance's struct type. For example: -For example: :: struct s1 (integer i, real r, integer[10] iv); @@ -70,8 +98,9 @@ For example: t1.iv[2] t1.r -Struct fields can be used as both LVALs and RVALs, i.e. on either the left -or right hand side of an expression: +Struct fields can be used as both :term:`lvalues ` and +:term:`rvalues `, i.e. on either the left or right hand side of an +expression: :: @@ -93,13 +122,15 @@ the struct type name: struct S (integer i, character[5] c, integer[3] a3); const S cs = S(i: x, c: "hello", a3: [1, 2, 3]); var S vs = S(c: ' ', i: 0, a3: 0); - struct V (integer i, real r, integer[10] arr) v = V(i: 1, r: 2.1, arr: [i in 1..10 | i]); + struct V (integer i, real r, integer[10] arr) v = V(i: 1, r: 2.1, arr: [i in 1..11 | i]); The fields may be listed in any order, but all fields must be present. The type -of each value must match the type of the corresponding field definition in the -struct. To save having to explicitly specify a value for each index in an array, -*Gazprea* allows a single scalar to be propagated across all elements in the -array. Finally, note that the field values may need to be evaluated at run-time. +of each value must match, or be implicitly castable to (see +:ref:`sec:implicitCasts`), the type of the corresponding field definition in +the struct. A scalar value given for an array-typed field is implicitly cast to +fill the array, following the same scalar-to-array broadcast rule used for +array operations (see :ref:`sssec:array_ops`). Finally, note that the field +values may need to be evaluated at :term:`run time`. .. _sssec:struct_ops: @@ -110,20 +141,28 @@ The following operations are defined on ``struct`` instances. In all of the usage examples, ``struct-type`` means some struct yielding expression of a particular type, while ``id`` is a field within the struct. -+------------+---------------+------------+--------------------------------+-------------------+ -| **Class** | **Operation** | **Symbol** | **Usage** | **Associativity** | -+------------+---------------+------------+--------------------------------+-------------------+ -| Access | dot | ``.`` | ``struct-type.id`` | left | -+------------+---------------+------------+--------------------------------+-------------------+ -| Comparison | equals | ``==`` | ``struct-type == struct-type`` | left | -+ +---------------+------------+--------------------------------+-------------------+ -| | not equals | ``!=`` | ``struct-type != struct-type`` | left | -+------------+---------------+------------+--------------------------------+-------------------+ ++------------+---------------+------------+--------------------------------+ +| **Class** | **Operation** | **Symbol** | **Usage** | ++------------+---------------+------------+--------------------------------+ +| Access | dot | ``.`` | ``struct-type.id`` | ++------------+---------------+------------+--------------------------------+ +| Comparison | equals | ``==`` | ``struct-type == struct-type`` | ++ +---------------+------------+--------------------------------+ +| | not equals | ``!=`` | ``struct-type != struct-type`` | ++------------+---------------+------------+--------------------------------+ Note that in the above table ``struct-type`` may only refer to a variable -instance for *Access*, while for *Comparison* at least one of the operands must -resolve to a struct type ``T``. -This allows struct instances to be compared to struct literals: +instance for *Access*; accessing a field via dot notation on a non-variable +(for example, the result of an expression or a struct literal) must emit a +``TypeError`` (see :ref:`sec:errors`). For *Comparison*, **both** operands must +be structs of the same struct type ``T`` (one of them may be a struct literal, +since a struct literal already carries a struct type). A ``struct`` can only be +compared against another ``struct`` of the same type; there is no implicit cast +from a ``tuple`` to a ``struct``, so to compare a ``struct`` against a +``tuple`` the tuple's value must first be used to construct a ``struct`` of +type ``T``. Comparing two structs of different types is a ``TypeError`` (see +:ref:`sec:errors`). This rule still allows a struct instance to be compared to +a struct literal of the same type: :: @@ -131,20 +170,71 @@ This allows struct instances to be compared to struct literals: if (c == Complex(r: 0.0, i: i)) { } Two structs are equal when all fields within each struct have the same value. -It is an error to compare two structs of different types. -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. + +.. _sssec:struct_casting: + +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A struct itself cannot be cast or implicitly cast. However, the fields within a +struct can be individually cast or implicitly cast, as described in +sections :ref:`sec:typeCasting` and :ref:`sec:implicitCasts`. + +.. _sssec:struct_namespacing: + +Struct Namespacing and Type Identity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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`. +Struct type identifiers live in the :ref:`type namespace `, +which is :term:`lexically scoped `: a struct defined at global scope is +visible program-wide, while a struct defined inside a function or procedure +belongs only to that block and is **not** propagated outward. A struct +definition whose name matches one in an enclosing scope *shadows* it for the +rest of the block, just as a local variable shadows an outer one. -.. _ssec:struct_namespacing: +Structs are **nominal**: a struct's type identity is the *declaration* that +introduced it, not its field layout. Each ``struct`` (or ``typealias struct``) +declaration mints a fresh, distinct type, so two struct definitions have +different types even when their fields are identical -- including a local +definition that shadows a global one under the same name. A ``typealias``, by +contrast, introduces **no** new type: an alias is a transparent synonym that +carries the identity of whatever type it names (see :ref:`sec:typealias`), so an +alias of a struct *is* that struct's nominal type. -Struct Namespacing -~~~~~~~~~~~~~~~~~~ +A variable's struct type is fixed at its declaration to whichever definition is +then in scope; a later redefinition of the name does not change it. Since +comparing two different struct types is a ``TypeError`` (see +:ref:`sssec:struct_ops`), this fixes exactly which comparisons are legal: + +:: + + typealias struct S(integer a, integer b) Pair; // global S; Pair == global S + + function f() returns integer { + S s1 = S(a: 2, b: 3); // s1 : global S + Pair p1 = S(a: 2, b: 3); // p1 : Pair, i.e. global S + + // A new, distinct type despite identical fields; Pair now aliases it: + typealias struct S(integer a, integer b) Pair; // local S + + S s2 = S(a: 2, b: 3); // s2 : local S + Pair p2 = S(a: 2, b: 3); // p2 : local S + + s1 == p1 -> std_output; // T: both global S, equal field values + s2 == p2 -> std_output; // T: both local S + s1 == s2 -> std_output; // TypeError: global S vs local S + p1 == p2 -> std_output; // TypeError: global S vs local S + s1 == p2 -> std_output; // TypeError: global S vs local S + s2 == p1 -> std_output; // TypeError: local S vs global S + return 1; + } -In *Gazprea*, struct declarations can occur in *any* scope. -This means that two struct types with the same name *can* coexist in the same -gazprea program so long as they are not in the same scope +A struct's field identifiers are not a namespace of their own: each struct +introduces its own :term:`declaration scope ` for its fields, so a field +name may coincide with a type, a variable/function/procedure, or a field of +another struct, while the fields *within* one struct must be distinct. See +:ref:`sec:namespaces` for the full rules, including the ``SymbolError`` raised +when a struct declares two fields with the same name. diff --git a/gazprea/spec/types/tuple.rst b/gazprea/spec/types/tuple.rst index cb6c8ab..eb3e284 100644 --- a/gazprea/spec/types/tuple.rst +++ b/gazprea/spec/types/tuple.rst @@ -3,7 +3,15 @@ Tuples ------ -A ``tuple`` is a way of grouping multiple values with potentially different types into an aggregate data structure. Tuples are similar to :ref:`structs`, except that a tuple's fields are indexed instead of named. Tuples are often used to return multiple values from a function or procedure. Any type may be stored within tuples except structs and tuples. Additionally streams can not be stored in tuples. +A ``tuple`` is a way of grouping multiple values with potentially different +types into an aggregate data structure. Tuples are similar to +:ref:`structs`, except that a tuple's fields are indexed instead +of named. Tuples are often used to return multiple values from a function or +procedure. Any :ref:`storable type ` may be stored within +a tuple, including arrays of any rank (a matrix is the rank-2 case), +``vector``, ``string``, ``struct``, and other ``tuple`` types, nested to any +depth (subject to the :ref:`acyclicity rule `). Only +:ref:`streams ` may not be stored in a tuple. .. _sssec:tuple_decl: @@ -12,7 +20,9 @@ Declaration A tuple value is declared with the keyword ``tuple`` followed by a parentheses-surrounded, comma-separated list of types. The list must -contain *at least two elements*. As with any other type, a tuple variable +contain *at least two elements*; a ``tuple`` type with fewer than two members is +:term:`ill-formed`, and the compiler must emit a ``TypeError`` (see +:ref:`sec:errors`). As with any other type, a tuple variable is mutable only when declared ``var`` (see :ref:`sec:typeQualifiers`). For example: @@ -28,19 +38,28 @@ The number of fields in a ``tuple`` must be known at :term:`compile time`. This includes instances of :ref:`type inference`, where a variable is declared without an explicit type signature using ``var`` or ``const``. -In this case, the variable must be initialised immediately with a -:term:`literal` whose type is known at compile time. +In this case, the variable must be initialized immediately with an +expression whose type is known at compile time. .. _sssec:tuple_acc: Access ~~~~~~ -The elements in a tuple are accessed using dot notation. Dot -notation can only be applied to tuple variables and *not* tuple literals. +The elements in a tuple are accessed using dot notation. Dot notation can only +be applied to tuple *variables*: applying it to a non-variable -- the result of +an expression, or a tuple literal -- must emit a ``TypeError`` (see +:ref:`sec:errors`), exactly as for :ref:`struct field access `. Dot notation means an identifier followed by a period and then a literal -integer. Spaces are not allowed between elements in dot notation. -Field indices *start at one*, not zero. For example: +integer. Spaces are not allowed between elements in dot notation. Because a real +literal may begin with a period (``.1`` denotes ``0.1``; see :ref:`ssec:real`), +a naive lexer can mis-tokenize ``t1.1`` as ``t1`` followed by the real ``.1``; +an implementation must lex the ``.`` after a tuple variable as the field-access +operator, not absorb it into a real literal. +Field indices *start at one*, not zero. Because a tuple index is a literal, an +index less than one or greater than the tuple's number of fields is caught at +:term:`compile time`, and the compiler must emit an ``IndexError`` (see +:ref:`sec:errors`). For example: :: @@ -66,42 +85,50 @@ parentheses in a comma separated list. For example: :: - tuple(integer, character[5], integer[3]) my_tuple = (x, "hello", [1, 2, 3]); - var my_tuple = (x, "hello", [1, 2, 3]); + tuple(integer, character[5], integer[3]) my_tuple = (x, "hello", [1, 2, 3]); + var our_tuple = (x, "hello", [1, 2, 3]); const your_tuple = (x, "hello", [1, 2, 3]); - tuple(integer, real, integer[10]) tuple_var = (1, 2.1, [i in 1..10 | i]); + tuple(integer, real, integer[10]) tuple_var = (1, 2.1, [i in 1..11 | i]); .. _sssec:tuple_ops: Operations ~~~~~~~~~~ -The following operations are defined on tuple values. In all of the -usage examples ``tuple-expr`` means some expression yielding tuples with the same type signature, -while ``int_lit`` is an integer literal as defined in :ref:`Integer Literals ` and ``tuple-inst`` is the -name of tuple instance as defined in :ref:`sec:identifiers`. - -+------------+---------------+------------+------------------------------+-------------------+ -| **Class** | **Operation** | **Symbol** | **Usage** | **Associativity** | -+------------+---------------+------------+------------------------------+-------------------+ -| Access | dot | ``.`` | ``tuple-inst.int_lit`` | left | -+------------+---------------+------------+------------------------------+-------------------+ -| Comparison | equals | ``==`` | ``tuple-expr == tuple-expr`` | left | -+ +---------------+------------+------------------------------+-------------------+ -| | not equals | ``!=`` | ``tuple-expr != tuple-expr`` | left | -+------------+---------------+------------+------------------------------+-------------------+ - -Note that in the above table ``tuple-expr`` may refer to a variable for access. -Accessing a literal could be replaced immediately with the scalar inside the tuple literal, however, ``tuple-expr`` may -refer to a literal in comparison operations to enable shorthand like this: +The following operations are defined on tuple values. In all of the usage +examples ``tuple-expr`` means some expression yielding tuples with the same +type signature, while ``int_lit`` is an integer literal as defined in +:ref:`Integer Literals ` and ``tuple-inst`` is the name of +tuple instance as defined in :ref:`sec:identifiers`. + ++------------+---------------+------------+------------------------------+ +| **Class** | **Operation** | **Symbol** | **Usage** | ++------------+---------------+------------+------------------------------+ +| Access | dot | ``.`` | ``tuple-inst.int_lit`` | ++------------+---------------+------------+------------------------------+ +| Comparison | equals | ``==`` | ``tuple-expr == tuple-expr`` | ++ +---------------+------------+------------------------------+ +| | not equals | ``!=`` | ``tuple-expr != tuple-expr`` | ++------------+---------------+------------+------------------------------+ + +Note that in the above table ``tuple-inst`` always refers to a variable for +*Access*. Accessing a literal could be replaced immediately with the scalar +inside the tuple literal, however, ``tuple-expr`` may refer to a literal in +comparison operations to enable shorthand like this: :: if ((a, b) == (c, d)) { } -Comparisons are performed pairwise. Two tuples are equal when for every expression pair, the equality operator returns true. -Two tuples are unequal when one or more expression pairs are unequal or the types mismatch. This table describes how the -comparisons are completed, where ``t1`` and ``t2`` are tuple yielding expressions including literals: +Comparisons are performed pairwise. Two tuples are equal when for every +expression pair, the equality operator returns true. Two tuples are unequal +when one or more expression pairs are unequal. Comparing two tuples of +different type signatures **with no common implicit-cast target** must emit a +``TypeError`` (see :ref:`sec:errors`); two signatures that differ but share a +common implicit-cast target compare legally after a two-sided implicit cast +(for example ``(1.0, 2) == (2, 3.0)`` -- see :ref:`ssec:implicitCasts_ttot`). +This table describes how the comparisons are completed, where ``t1`` and ``t2`` +are tuple yielding expressions including literals: ============= ========================================= **Operation** **Meaning** @@ -110,15 +137,18 @@ comparisons are completed, where ``t1`` and ``t2`` are tuple yielding expression ``t1 != t2`` ``t1.1 != t2.1 or ... or t1.n != t2.n`` ============= ========================================= +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. .. _sssec:tuple_unpack: Unpacking ~~~~~~~~~ -Any tuple expression may be assigned (unpacked) into multiple lvalues. If the size of -the tuple being unpacked does not match the number of lvalues being asigned, an ``AssignError`` -is raised. There is no partial unpacking of tuples. +Any tuple expression may be assigned (unpacked) into multiple lvalues. If the +size of the tuple being unpacked does not match the number of lvalues being +assigned, the compiler must emit an ``AssignError`` (see :ref:`sec:errors`). +There is no partial unpacking of tuples. :: @@ -127,8 +157,8 @@ is raised. There is no partial unpacking of tuples. a, b = (3.14, 1.5); -Type Casting and Type Promotion -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Type Casting and Implicit Casts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To see the types that tuple may be cast and/or promoted to, see the sections on :ref:`sec:typeCasting` -and :ref:`sec:typePromotion`, respectively. +To see the types that tuple may be cast and/or implicitly cast to, see the +sections on :ref:`sec:typeCasting` and :ref:`sec:implicitCasts`, respectively. diff --git a/gazprea/spec/types/vector.rst b/gazprea/spec/types/vector.rst index b3de2c5..bafa5fe 100644 --- a/gazprea/spec/types/vector.rst +++ b/gazprea/spec/types/vector.rst @@ -3,104 +3,280 @@ 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 runtime-sized arrays. +Unlike an array, whose length is fixed once at its :term:`initialization` +(see :ref:`sssec:array_vs_vector`), a vector is *runtime sized*: it begins +at some length and may grow over its lifetime through its mutating methods +(``push`` and ``append``). + +Once created, ``vectors`` in *Gazprea* interoperate with arrays for the +element types they both support: 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 functions +and procedures. When a vector appears in an expression it is used as an +array value of its *current* length. Vectors are nevertheless a distinct +type, and the differences include (non-exhaustively): vectors have methods +where arrays have none; a mixed *element-wise* binary operation between a vector and +an array produces an *array* result (element-wise operators do not propagate +vector-ness), though :ref:`concatenation ` with ``||`` yields a +vector when its rightmost operand is a vector; and a ``vector`` (a vector of +inferred-size arrays) fixes its element size once, from the first array value +stored into it, and fits every later element to that size (see below). .. _sssec:vec_decl: Declaration ~~~~~~~~~~~ -Vectors are declared and (optionally) initialized as follows. -(Note that we have replaced ``<>`` with ``|`` in the notation below since -the literals ``<`` and ``>`` are used in the declaration) +Vectors are declared and (optionally) initialized as follows, where +``qualifier``, ``elem-type``, ``id``, and ``value`` are placeholders (the angle +brackets of ``vector<...>`` are literal): :: - [] vector<|type|> |identifier|; - [] vector<|type|> |identifier| = |type-expr|; - [] vector<|type|> |identifier| = |type-array|; + [qualifier] vector id; + [qualifier] vector id = value; + [qualifier] vector id = array-value; 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. - +specifier, often called *capacity* in other languages. + +The element type ``T`` of a ``vector`` may be any :ref:`storable type +`: a :term:`primitive type ` (``boolean``, +``integer``, ``real``, ``character``), an array of any rank (a matrix is the +rank-2 case), a ``string``, a ``tuple``, a ``struct``, or another ``vector`` — +nested to any depth. Only a :ref:`stream ` may not be stored. +Below are some examples of ``vector`` declarations. + :: const vector v1 = 3; // [[3, 3]] const vector v2 = [4, 5]; // [[4, 5]] const vector v3 = 42; // [42] var vector v4 = 42; // [42], mutable - vector v4 = 42; // [42], implied const - const vector v5 = 1; // [1.0] + vector v5 = 42; // [42], implied const + const vector v6 = 1; // [1.0] + + +A vector declaration ``vector v = E`` is resolved in exactly one of two ways, +chosen by the rank of the right-hand side ``E`` relative to the element type +``T``. The two cases are mutually exclusive, so there is never any ambiguity +about how many elements the vector has: + +- **Single-element declaration** -- ``E`` is a :term:`scalar `, or + an array of the same rank as ``T``, and is implicitly cast or broadcast to + ``T``. The vector then has exactly **one** element: ``E`` converted to ``T``. A + scalar is broadcast to fill that element; a same-rank array is cast to ``T`` + element-wise and, when ``T`` is a fixed-size array, fitted to ``T``'s size by + the usual :ref:`array-to-array rules ` -- a shorter + value is **padded** with the element type's :term:`zero value`, a longer one is + a ``SizeError``. So ``vector v = [4, 5]`` is the one-element + ``[[4, 5]]``, and ``vector v = [4, 5]`` is the one-element + ``[[4, 5, 0]]`` (the ``integer[2]`` value is padded to ``integer[3]``). + +- **Multi-element declaration** -- ``E`` has the rank of the vector's underlying + array type ``T[]``, one rank higher than ``T``. Each element of ``E`` must be + implicitly castable to ``T`` (see :ref:`sec:implicitCasts`); the vector holds + those elements, in order, each converted to ``T``. + +Any other rank of ``E`` is a ``TypeError`` (see :ref:`sec:errors`). A scalar is +always the single-element case, so ``vector v = 42`` is ``[42]``; to +supply several elements you write the literal one rank deeper. The two spellings +can therefore denote the same value: for ``const vector a = [1, 2]`` +the right-hand side is a single ``integer[*]`` element, so ``a == [[1, 2]]``, +while ``const vector a = [[1, 2]]`` is a multi-element declaration +with one element, so ``a == [[1, 2]]`` as well. When ``T`` is an inferred-size +array (``T[*]``), the element(s) selected by whichever case applies fix that +inferred size once, as described next. + + +A ``vector`` -- a vector whose element is an inferred-size array -- fixes +that element size (the ``*``) exactly once, from the **first array value that +enters the vector**, and fits every later element to it: a shorter array is +padded with the element type's :term:`zero value`, and a longer one raises a +``SizeError`` (see :ref:`sec:errors`). What counts as the "first value" depends +on how the vector is populated, and the two paths must not be conflated: + +- **Initialized from an array value** (including a nested array *literal*): the + right-hand side is evaluated to an array *value* on its own first, and only + then stored. A nested literal such as ``[[1.0], [2.0, 3.0]]`` is an ordinary + array literal, so it is normalized to a rectangle by padding every sub-array + to the **longest** one -- exactly as in :ref:`matrix construction + ` -- *before* the vector ever sees it. This padding is a + property of the literal, so it is identical whether the literal initializes an + array variable or a vector. + +- **Built up incrementally** with ``push`` / ``append`` from a shorter or empty + vector: the elements arrive one at a time, so the **first** element stored + fixes the size and each later element is fitted to it. + +A vector of arrays is therefore never ragged: once the element size is fixed, +every element has that shape. A ``vector>``, by contrast, *may* be +ragged, because each inner vector carries its own runtime length and no element +imposes its shape on the others. This version of the language has no +broadcasting and no ``shape()`` operation. + +Because a nested literal is padded to its longest sub-array before it is stored, +neither initializer below is ragged and neither is an error -- the short +sub-array is simply padded, whichever side it is on: + + :: + const vector vec = ['a', 'b', 'c']; // ['a', 'b', 'c'] -Vectors of inferred sized arrays assume the size of the *first* array in the vector. -Subsequent array elements of less than the inferred size are padded. -Those greater raise a :term:`run time` ``SizeError``. + // Each RHS is normalized to its longest sub-array, then stored: + const vector x = [[1.0], [2.0, 3.0]]; // x == [[1.0, 0.0], [2.0, 3.0]] + const vector w = [[1.0, 2.0], [1.0]]; // w == [[1.0, 2.0], [1.0, 0.0]] + + const vector const_vec = vec; // copy of vec + +Growing a vector one element at a time is different: there is no surrounding +literal to normalize, so the first stored element fixes the size and each later +element is fitted to it. This is why the same value pads differently depending +on the path -- ``x`` above is padded as a whole literal, whereas ``y`` below +pads only its newly pushed element: :: - const vector vec = ['a', 'b', 'c']; - const vector ragged_right = [[1.0], [2.0, 2.0]]; // SizeError - const vector padded_right = [[1.0, 2.0], [1.0]]; // Pads second element - const vector const_vec = vec; + var vector y = [[1.0, 2.0]]; // element size fixed at 2 + call y.push([3.0]); // [3.0] padded to [3.0, 0.0] + // y == [[1.0, 2.0], [3.0, 0.0]] +An initially empty ``vector`` takes its element size from the first array +appended, after which the usual pad / ``SizeError`` rules apply. Each call below +is shown for its own effect on the freshly emptied vector: -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. All binary operations between vector and arrays produce array results. + var vector z; // empty; element size not yet fixed + call z.append([1, 2]); // first element fixes the size at 2: z == [[1, 2]] + call z.append([1]); // shorter: padded to [1, 0] + call z.append([1, 2, 3]); // longer than the fixed size 2: SizeError + call z.append(1); // scalar 1 broadcasts to [1, 1], then appended -As a language supported object, *Gazprea* provides several methods for ``vector``: -- ``push(T)`` - pushes a new element to the back of the vector, where ``T`` is the element type of the vector +.. _sssec:vec_ops: -- ``len()`` - number of elements in the vector +Operations +~~~~~~~~~~~ -- ``append(T[*])`` - append another array to the vector where ``T[*]`` is the type of the original vector or a type that can be implicitly cast to it. +Operations on vectors use the same syntax as operations on arrays and, +except for the differences enumerated above, share their semantics: in an +expression a vector is treated as an array value of its current length. +In particular, operand lengths must match for binary expressions and dot +product. Every *element-wise* binary operation with a vector operand -- whether +the other operand is a vector or an array -- produces an *array* result; +vector-ness is never propagated through those operators, and the resulting array +may of course be implicitly cast back to a vector (or ``string``) when it is +stored into one (see :ref:`ssec:implicitCasts_avv`). **Concatenation** with +``||`` is the exception: it is right-associative and its result takes the kind +of its *receiver*, the rightmost operand, so a concatenation whose receiver is a +vector is itself a vector -- in particular a ``string`` concatenation stays a +``string`` (see :ref:`sssec:array_ops`). + +Operator precedence and associativity are specified once, for all types, in +the :ref:`table of operator precedence `. + +.. _sssec:vec_methods: + +Method Calls +~~~~~~~~~~~~ + +As a language-supported object, *Gazprea* provides methods for ``vector`` +(and therefore for the typealias :ref:`string `, which is just +``vector``). A method call has the form +``receiver.method(arguments)`` and is governed by the following rules: + +- Each method is either a :ref:`function ` or a + :ref:`procedure `, according to whether it observes the + receiver or mutates it. A *stateless* + method such as ``len`` is a **function**: it is pure, returns a value, and + does not change the receiver. A *stateful* method such as ``push`` or + ``append`` is a **procedure**: it mutates the receiver. A method ``m(args)`` + invoked on a ``vector`` receiver behaves exactly as a call to + ``function m(vector self, args...) returns U`` (stateless) or + ``procedure m(var vector self, args...)`` (stateful): the receiver is + bound to ``self`` and the call has ordinary function- or procedure-call + semantics. Only ``vector`` (and thus ``string``, its typealias) has methods + in this version of the language; user-defined methods on ``struct`` types + are a future extension. + +- The receiver must be a variable of a language-supported object type + (``vector`` or ``string``). Arrays, array slices, and the (array-valued) + results of expressions have no methods; calling a method on them is a + :term:`compile time` ``TypeError`` (see :ref:`sec:errors`). + +- A **function** method (such as ``len``) is an expression: its result is a + value, so it may appear in any expression position -- on the right of a + declaration or assignment, as an argument, or in an output-stream + expression such as ``v.len() -> std_output``. Like any function call it may + not stand alone as a statement, and ``call`` does not apply to it. + +- A **procedure** method (such as ``push`` and ``append``) is used as a + statement and, like any other + :ref:`procedure call `, must be written as a + ``call`` statement: ``call v.push(1);``. Written without the ``call`` + keyword -- a bare ``v.push(1);`` -- it is a :ref:`CallError `. + +- Mutating methods (``push``, ``append``) additionally require the + receiver to be declared ``var``. Inside a :ref:`function `, + mutating methods may be applied only to variables local to the function; + this preserves function purity, since no state outside the function can + change. + +The methods are: + +- ``push(x)`` (procedure) - pushes ``x`` onto the back of the vector as a single + new element; ``x`` is cast to the element type ``T`` exactly as in the + single-element case of ``append`` and of a + :ref:`vector declaration ` (a scalar broadcasts, a shorter + array pads) + +- ``len()`` (function) - number of elements in the vector + +- ``append(x)`` (procedure) - append to the vector, where ``T`` is the element + type. ``x`` is split into elements of ``T`` by the same single-versus-multi + test as a vector declaration (see :ref:`sssec:vec_decl`): if ``x`` is a scalar + or an array of the same rank as ``T`` it is cast to ``T`` and appended as a + **single** element; if ``x`` has the rank of ``T[]`` (one higher than ``T``) + each of its elements is cast to ``T`` and they are appended **in order**. The + two cases are mutually exclusive, so no tie-break is needed. :: var vector v1; // v1 == [] v1.len() -> std_output; // 0 - v1.push(1); // v1 == [1] + call v1.push(1); // v1 == [1] v1.len() -> std_output; // 1 - v1.push(2); // v1 == [1, 2] + call v1.push(2); // v1 == [1, 2] v1.len() -> std_output; // 2 - v1.append([3, 4, 5]) // v1 == [1, 2, 3, 4, 5] + call v1.append([3, 4, 5]); // v1 == [1, 2, 3, 4, 5] v1.len() -> std_output; // 5 var vector v2; // v2 == [] - const x = 1..10; - - // `1` is promoted to `[1.0, 1.0]` before appending - v2.append(1); // v2 == [[1.0, 1.0]] + const x = 1..11; + + // `1` is implicitly cast to `[1.0, 1.0]` before appending + call v2.append(1); // v2 == [[1.0, 1.0]] // length 1 array padded to length 2 - v2.append([3.0]); // v2 == [[1.0, 1.0], [3.0, 0.0]] - - // slices - v2.append(x[5..7]); // v2 == [[1.0, 1.0], [3.0, 0.0], [5.0, 6.0]] + call v2.append([3.0]); // v2 == [[1.0, 1.0], [3.0, 0.0]] - v2.len() -> std_output // 3 + // slices + call v2.append(x[5..7]); // v2 == [[1.0, 1.0], [3.0, 0.0], [5.0, 6.0]] - v2.len(); // Does nothing + v2.len() -> std_output; // 3 - (v1 + v2).push(3); // Effectively does nothing, reference to the sum is dropped after the statement + call (v1 + v1).push(3); // TypeError: the sum is an array + // value, and arrays have no methods Slicing a vector produces an array slice (there are no "vector slices"). :: - // Slicing a vector produces an array slice - vec[2..5].append(x[5..7]) // TypeError; cannot do `append` on an array slice + var vector v3 = x; + call v3[2..5].append(x[5..7]); // TypeError; cannot do `append` on an array slice diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dba7229 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "415-docs" +version = "0.0.0" +description = "CMPUT 415 course specification documents (Sphinx sources)." +requires-python = ">=3.10" +dependencies = [ + "sphinx==6.2.1", + "sphinx-rtd-theme==1.2.0", + "jinja2>=3.1", + "pyyaml>=6.0", +] + +[tool.uv] +package = false diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..3717aec --- /dev/null +++ b/uv.lock @@ -0,0 +1,491 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "415-docs" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, +] + +[package.metadata] +requires-dist = [ + { name = "jinja2", specifier = ">=3.1" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "sphinx", specifier = "==6.2.1" }, + { name = "sphinx-rtd-theme", specifier = "==1.2.0" }, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "docutils" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b1/b880503681ea1b64df05106fc7e3c4e3801736cf63deffc6fa7fc5404cf5/docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06", size = 2043249, upload-time = "2021-11-23T17:49:42.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/14/69b4bad34e3f250afe29a854da03acb6747711f3df06c359fa053fae4e76/docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", size = 570050, upload-time = "2021-11-23T17:49:38.556Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "sphinx" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/6d/392defcc95ca48daf62aecb89550143e97a4651275e62a3d7755efe35a3a/Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b", size = 6681092, upload-time = "2023-04-25T11:01:40.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/d8/45ba6097c39ba44d9f0e1462fb232e13ca4ddb5aea93a385dcfa964687da/sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912", size = 3024615, upload-time = "2023-04-25T11:01:08.562Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/b4/40faec6790d4b08a6ef878feddc6ad11c3872b75f52273f1418c39f67cd6/sphinx_rtd_theme-1.2.0.tar.gz", hash = "sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8", size = 2784826, upload-time = "2023-02-07T21:50:03.295Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/46/c167351699e5dc126798385cf37c26ba9df7a26c6f8855661d9f966d6ced/sphinx_rtd_theme-1.2.0-py2.py3-none-any.whl", hash = "sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2", size = 2824718, upload-time = "2023-02-07T21:50:00.495Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +]