Skip to content

Releases: hiroshiyui/afterschool_pascal

v4.0.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 19:19

The library reads as methods of its types, and every program that calls it
must change.
Sixteen modules export 203 names where they exported 354; the
151 that went are gone rather than deprecated, because §6.11.2 puts every
imported name in one scope and keeping both spellings would keep the collision
the change exists to remove. There is no migration period. What a caller does:

was is
JsonIntegerOr(JsonMember(p, 'line'), -1) p.Member('line').IntegerOr(-1)
TomlPath(doc, 'server.port') doc.Path('server.port')
ErrorText(e) e.Text
IVecPush(v, 10) / SVecGet(v, 1) v.Push(10) / v.At(1)
NetWriteLine(s, t) / TlsWriteLine(c, t) s.WriteLine(t) / c.WriteLine(t)
StreamReadLine(f, l) / NextEntry(d, n) f.ReadLine(l) / d.NextEntry(n)

A name that is still exported is one with no receiver to select from: a
type, a bound, a constructor (JsonNewObject, TomlNewTable, IVecNew), or
an entry point that answers a value rather than acting on one (JsonParse,
RegexCompile, NetConnect). pascalc --dump-uses and the language server
answer where a method is defined; grep for a bare Put no longer does, which
is the one cost no gate sees.

This is the major number because of that table, not because of the language:
every construct that compiled before this release still compiles, with two
exceptions listed under Removed.

Added

  • A module's implementations reach the components that import it
    (ADR-0411, AP 6.7.10.5). impl Circle; and impl Renders for Circle;
    written in a module-block are selected in every program-component that can
    name Circle — so a library's types carry their own routines, and
    c.Area, Area(c) and a dyn Renders built by the client all reach them.
    Nothing about the spelling changed; what changed is which translations may
    read it.

    Until now this did not work and was not refused: the call compiled and the
    program failed at the assembler or the linker, naming a symbol no source
    spells. AP 6.7.10.1 NOTE 9 had recorded it as not provided (ADR-0341).

    A method's heading is now part of what a component is translated against,
    so changing one without rebuilding the client is refused at the link, the
    way a changed module-heading already was (AP 6.13.2). Changing a method's
    body still costs no relink.

    A method may be external, which had been admitted since methods landed and
    is now pinned by a case; it is the one method shape whose linkage name is
    C's rather than this compiler's.

  • A type may have routines of its own: impl T; and x.M(a) (ADR-0410,
    AP 6.7.10, AP 6.7.10.4). impl Point; declares routines belonging to
    Point, with the receiver written as an ordinary first parameter, and
    p.Shift(1, 1) calls one. It is not a second mechanism: x.M(a) and
    M(x, a) denote the same call, the routine having been selected from its
    first argument's type since traits landed, so a method may be written either
    way and any variable may be the receiver — p.Len, q^.Len, a[1].Len,
    b.inner.Len.

    What it buys is that a method is not an exported name: a module exports
    the type, and its routines travel with it, so two modules may each have a
    Put where §6.11.2 refuses that to two exported names. 139 of this
    library's 486 exported names repeat their own module's noun to work around
    exactly that.

    A type may not have a field and a routine of one name, and is told so where
    the implementation is written. A method called on what a method returned
    needs that second method to take its receiver by value, §6.6.3.3 wanting a
    variable for a var parameter. impl reserves nothing.

    This completes ADR-0315's three increments, all of them now built.

  • A collection may hold values of different types: dyn T, the trait
    object
    (ADR-0408, ADR-0409, AP 6.7.11). type Shape = dyn Renders; denotes
    something that implements Renders, and owned ^Shape owns one; take
    moves a concrete value in and attaches its implementation, and a call
    through the result selects the implementation where the value is used
    rather than where the call is translated. This is what a bound
    (AP 6.7.3.10.5) cannot do — a bound chooses where the type is written, so
    one collection is one type — and it is the first place this compiler emits a
    dispatch table.

    A trait object stands in two positions: the domain of an owned pointer and
    a var or protected var parameter. Everything else is refused with a
    message saying where one can stand, because every other position would hold
    a value whose lifetime nothing states. The implementation travels with the
    value and so does the release, so disposing an owned ^Shape releases
    the concrete value and whatever that owns.

    Not every trait has a trait object: each routine must take its receiver as a
    var or protected var first parameter and name Self nowhere else, and a
    trait that does not is told so where the dyn type is declared, with the
    heading named. dyn reserves nothing — a program may still declare a type,
    a field and a parameter of that name.

    AP 6.7.11 was published one release early, marked [not yet implemented]
    under AP 5.6 — the first and only clause ever to carry that marker — and the
    marker is now gone. Building it corrected two things the clause had said
    (AP Annex E.13, E.14).

Changed

  • PasJson's routines are methods of its types (ADR-0412, AP 6.7.10).
    The module exports 25 names where it exported 50, and a document is read
    by doc.Member('id').IntegerOr(-1) rather than
    JsonIntegerOr(JsonMember(doc, 'id'), -1). JsonKindOf is Kind,
    JsonCharsAddLine is AddText, JsonTextInto is TextInto, and so on
    for 24 routines; what stays exported is what has no receiver to select
    from — the types, the three bounds, the seven JsonNew* constructors and
    JsonParse/JsonParseChars.

    This renames a library's interface and every caller must change. The
    old spellings are gone rather than deprecated: §6.11.2 puts every
    imported name in one scope, and keeping both would keep the collision the
    change exists to remove. JsonChars and JsonPtr now each have a Free,
    a Len and an At, which two exported names could not have been.

  • PasToml's routines are methods of its types too (AP 6.7.10). The same
    change one format over, and the module it was modelled on: 31 names are
    exported where 59 were, and a configuration is read by
    doc.Path('server.port').IntegerOr(80) rather than
    TomlIntegerOr(TomlPath(doc, 'server.port'), 80). TomlKindOf is Kind,
    TomlCharsAddLine is AddText, TomlPositionOf is TomlChars.PositionOf
    -- a buffer asked about itself -- and TomlChars and TomlPtr now each
    have a Free, a Len and an At. TomlParse, TomlParseChars and the
    eight TomlNew* constructors keep their names, a parse answering a document
    rather than being an operation of a buffer. Every caller must change,
    for the reason above; the three TOML cases answer their goldens unchanged,
    which is what says the rewrite moved no behaviour.

  • Eight more library modules read as methods of their types (AP 6.7.10).
    PasVector exports 4 names where it exported 15, PasMap 6 of 16,
    PasStrVec 6 of 19, PasRegex 20 of 31, PasProcess 12 of 22, PasNet 10
    of 14, PasTls 16 of 20 and PasHttp 27 of 38 — with PasJson and
    PasToml, 157 exported names where there were 284. A vector is
    v.Push(x), v.At(i) and v.Len; a socket and a TLS connection both have
    Close, WriteText, WriteLine and ReadLine; a match is
    m.GroupInto(1, s). Every caller must change, and the old spellings are
    gone rather than deprecated, for the reason given above.

    Five spellings could not simply lose a prefix and the compiler said so:
    IVecSet/SVecSet are Put (§6.1.2 reserves set) and BeginRequest/
    BeginResponse are BeginWrite/BeginRead (it reserves begin);
    RegexFaultOf, RegexGroups and RegexSteps are FaultOf, GroupCount
    and StepCount, each beside a field of that spelling; and RegexFaultText
    is RegexFault's own Text, an enumerated type being able to carry an
    implementation. Nine receivers gained protected var: §6.7.3.1's advice is
    deferred for an exported routine and fires on a method.

    PasFile is deliberately unconverted, and so are PasProcess's Run,
    Capture and CaptureLines and PasNet's NetWait: their first parameter
    is a type produced from a schema, or a schema, and neither may carry an
    inherent implementation.

  • PasError and PasLspDiag too, which finishes the conversion at
    sixteen modules and 203 exported names where there were 354. ErrorText(e)
    is e.Text and Failed(e) is e.Failed — an enumerated receiver, so
    errNone.Text works on a constant — and DiagJson(d, line, enc) is
    d.Json(line, enc). 234 call sites across 48 files. What could not convert in
    either module says what a receiver may be: ValueOr takes a type-parameter
    first, and HoldsNul, DiagParse and Utf16Column take string or a
    production of it, which no module owns.

  • Four more library modules read as methods of their types (AP 6.7.10).
    PasList exports 3 names where it exported 13, PasStream 6 of 11, PasLsp
    7 of 10 and PasDir 5 of 7 — with the ten before them, 177 exported names
    where there were 325
    . A list is l.Push(x), l.Len, l.Reverse; a stream
    and a socket and a TLS connection now share WriteText, WriteLine,
    ReadLine and Close. PasList keeps no constructor, a fresh variable of
    an owned ^ being an empty list already. Every caller must change, for
    the reason given above.

  • PasNet.NetService is Socket.Service (AP 6.7.10), the one judgement
    call the previous batch left open. It takes a socket tha...

Read more

v3.11.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 03:02

The editor replaces, and two things it shipped broken a day ago do not do
that any more.
v3.10.0 put eight documents behind one screen in a day, and
two questions that had been complete became incomplete without changing: the
quit guard asked about the document on screen, so Ctrl-Q over a clean one
discarded another's work in silence; and the menu's key dispatch did not name
the two kinds that feature had added, so F3 or F6 with the menu open stopped
the program. Both are fixed, and the gate that could have caught the second is
now pointed at every program here instead of only at the compiler.

Added

  • The editor replaces. Ctrl-R asks for a pattern and a replacement and
    changes every occurrence in the document, matching case-insensitively as the
    search does, with an empty replacement meaning deletion — and one Ctrl-Z
    takes the whole of it back
    (ADR-0403). Search ▸ Replace is the same thing
    from the menu. That last part is why it is not a small feature: the undo
    journal knew about operations and not about actions, so an entry may
    now say the undo continues through it — a property of an entry, deliberately
    not a fifth operation, so the four routines are still the only code that
    touches the buffer. Confirm-each was rejected rather than deferred: what a
    person wants after a replace that went wrong is to undo it.
  • Every program here is checked for a case that has stopped covering its
    enumeration
    , not only the compiler (ADR-0404). The editor and the language
    server join the sweep — five corpora, and adding one is a line — because
    --dump-dispatch answers that question about any Pascal program and had
    been asked about one. 63 case-statements over 13 enumerations became 79 over
    35. The crash below is what the widening found.
  • The editor's shell is checked. tui-terminal drives the real editor
    under a pseudo-terminal and requires every coloured run it writes to be one
    the model decided, at the same columns, in the colour that role's table
    gives, over both the twenty-four-bit and the eight-colour table (ADR-0402).
    Nothing had ever read a byte the editor wrote to a terminal: a shell
    painting every row in one flat colour passed every oracle here. The
    expectation is derived from the session golden's own run decomposition
    rather than recorded.

Changed

  • The runtime's POSIX half is two translation units. What the operating
    system is asked about — a file's size and kind, a directory's entries,
    whether a descriptor has anything to read — is now runtime/pasrt_file.c,
    and what it is asked to do — a process, a socket, a terminal, a private
    directory — stays in runtime/pasrt_posix.c (ADR-0405). Nothing a program
    can call changed; what changed is that a target with a file system and no
    processes gets the first half, which is what wasm32-wasi is. 560 of the
    598 corpus programs ran there and 563 do.

Fixed

  • The editor no longer discards another document's changes on one Ctrl-Q.
    Since eight documents landed in v3.10.0, the quit guard asked whether the
    document on screen was modified, so typing into one file, opening a clean
    second over it and pressing Ctrl-Q once exited with no prompt and no
    message. It now asks about every open document and the message says how many
    hold changes (ADR-0401). Two presses still discard, as they always have.
  • F3 and F6 no longer stop the editor when the menu is open. MenuKey
    dispatches over the key kinds with a case and no otherwise — the right
    shape, since an unmatched selector is a reported trap rather than a
    swallowed key — and the two kinds the second document added in v3.10.0 were
    not listed, so opening a file or cycling documents from under an open menu
    exited with case: no label matches the selector.
  • A test case is run in a directory of its own, not the one the harness
    was invoked from (ADR-0406). Every case shared the invoker's, so the one
    case that names a file relatively raced its own concurrent copy — eight
    failures in twenty concurrent pairs, and none after. Only the two harnesses
    that start the program under test changed; a toolchain still runs where it
    was invoked.

v3.10.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 23:36

A text value knows how wide it is, and the editor is one you can work in.
AP 6.4.15.13 defines display width and PasUnicode.Columns answers it, so a
column in the editor is a cell rather than a byte; and the editor opens eight
documents, scrolls sideways, lands on a diagnostic in whichever of them it
names, and draws in twenty-four-bit colour where the terminal has it.

Added

  • A question is a box (ADR-0392). Find, Go to line and Save as are
    centred framed dialogs instead of a line at the bottom, so the editor draws
    one kind of thing for one kind of thing. It is the second user of the panel
    primitives, which is what tests whether they are an abstraction.
  • The editor has a menu bar (ADR-0391): File Edit Search Run along
    the top, F10 (or Ctrl-O) to open, arrows to move, Enter to choose, Ctrl-C to
    close — drawn as a framed drop-down in real box characters. A screen
    became one array element per display column
    to make that possible: ┌ is
    three bytes and one column, so a row indexed by byte could not hold a frame
    and keep the colour plane naming what a person sees. A menu item names a
    key, so choosing one is the same path as pressing it — no second dispatch.
  • The editor decodes the function keys and binds two of them in Turbo
    Pascal's positions: F2 is Save and F9 is Build. Both spellings a
    terminal uses are understood — ESC O Q (SS3) and ESC [ 1 2 ~ (CSI) are
    the same key — and the decoder maps a bound one to the key it is bound to,
    so nothing above it learns that Save has two spellings. The other ten are
    decoded and reported by number (F5 is not bound) rather than ignored. The
    hint bar now reads F2 Save F9 Build ….

Added

  • A second reading of the width properties (ADR-0400).
    East_Asian_Width is the one Unicode property here with no conformance
    file, so the new icu-width case compares AP 6.4.15.13's table against
    ICU's reading of the same two files — applying the clause's own rules
    to UCHAR_EAST_ASIAN_WIDTH and u_charType, so what is compared is two
    transcriptions and not two opinions about rendering. 1 112 064 code
    points, exact agreement. It abstains when ICU's Unicode version is not
    the pinned one, which is what makes exactness a claim worth making.
  • The editor scrolls sideways (ADR-0397). A line wider than the window
    was cut; the window follows the cursor now, by column rather than by
    byte, so a line of Japanese scrolls by what a person sees and a wide
    character straddling the edge is dropped rather than half-drawn.
  • The editor opens more than one file at once (ADR-0396). F3 opens, F6
    goes to the next, up to eight — each with its own cursor, scroll, dirty mark
    and undo journal, and the status line says which of how many. This is what
    makes it usable on this compiler: pascalc translates every
    program-component and this compiler is three of them, so a diagnostic
    naming an open document is now landed on
    , switching to that file.
  • PasUnicode.Columns, and AP 6.4.15.13 to define it (ADR-0395). How many
    cells a text value occupies when a fixed-pitch terminal draws it: Wide and
    Fullwidth take two, a mark or a format character takes none, and the unit is
    the element, so e with a combining acute is one cell and a family
    emoji is two rather than six. AP 6.4.15 NOTE 14 had put this outside the
    language and is amended to point at the clause — it was right that no
    property of a character answers for a proportional font, and wrong that
    nothing was left to answer.
  • The editor edits East Asian text correctly (ADR-0395). A column is a
    cell rather than a byte, the arrows and Backspace and Delete move by
    element, and a wide character occupies two cells with the second holding
    nothing. ed.col is still a byte and the status line still shows one,
    because pascalc reports a diagnostic's column in bytes and Ctrl-B has to
    land on it.
  • PasTerm.SetRgb, twenty-four-bit colour (ADR-0394). SGR's direct
    38;2;r;g;b form over an Octet subrange, beside the eight ANSI colours
    the module has had. It has no clDefault and cannot — the terminal's own
    colour has no numeric value to name — so a program drawing a document still
    writes SetColour(clDefault, clDefault) and leaves the person's own colours
    alone. Whether a terminal understands the form is the caller's policy: there
    is no query one lacking it answers safely, and COLORTERM is what emulators
    set.
  • The editor draws a published palette where the terminal can show it
    (ADR-0394). apide reads COLORTERM once and keeps two tables: the eight
    ANSI colours for every terminal, and five colours of the LUXE scheme for
    one that says it understands twenty-four bits. Five of ten is the finding
    rather than a compromise — the scheme's rose, khaki, slate, blue and
    vermilion reach at best 6.7:1 against anything else in it, and every cell of
    a text editor is text. Both tables are held to the same floor.

Fixed

  • Ctrl-B no longer lands on the wrong line of the wrong file. EditFault
    parsed file:line:col: and threw the filename away, so a diagnostic naming
    one of the other program-components jumped to that line number in whatever
    document was open
    and displayed the message as though it had arrived there.
    pascalc translates every component and this compiler is three of them, so
    that was the ordinary case rather than a corner. A diagnostic about another
    file is now reported as the compiler wrote it, position and all, and the
    cursor stays where it was.
  • The editor's colour is legible, and a role that nothing draws now fails a gate (ADR-0393). A drop-down's body was cyan on blue, which is 4.8:1 on xterm's own palette and worse on a muted theme, and the message line was yellow on the terminal's own background, which on a light terminal is yellow on white. Every role but the document's text now pairs black or white with a colour, at 7.5:1 or better. And a dialog's answer is drawn as a field again: crPrompt — the editor is waiting for you — had been declared, coloured and drawn on no screen since the prompts became boxes, with all fifteen session goldens agreeing. The new tui-palette case holds both claims in both directions.
  • A menu that was legible and indistinguishable (ADR-0394). The fix above
    put the selected menu title at black on white against a black-on-cyan bar:
    16.7:1 to read and 1.6:1 away from the four titles beside it, so nothing
    said which menu was open. A contrast floor cannot see that — both pairs pass
    it — so tui-palette now also requires two regions a person sees at once to
    differ in their backgrounds by 3:1, and the three bars along the bottom
    were rechosen together.

Changed

  • The editor is apide, and that is now its only name. v3.9.0 installed
    it as afterschool, on the convention pascalc sets over
    selfhost/compiler.pas — but tui/build.py names its default output after
    the program it was given, so the script wrote build/bin/apide and the
    CMake target wrote build/bin/afterschool, leaving one program under two
    names with nothing to say which one a document meant. The script's default
    is what keeps build.py '' session.pas from overwriting the editor and so
    could not move; the binary did. cmake --build now produces
    build/bin/apide and cmake --install puts it in <prefix>/bin/apide.

v3.9.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 14:53

Two more targets and a program that runs on one of them, and an editor
you can install and use
. The compiler's --target= now admits seven
triples, two of which are not machines: wasm32-wasi, where 560 of the 598
programs in this corpus compile, link and print what they print everywhere
else
under a WASI engine, and wasm64-wasi, admitted on the layout claim
alone because no sysroot for memory64 exists yet. And tui/ — withdrawn by
decision nine days ago and un-withdrawn for reasons that decision could not
weigh — is now afterschool, a CMake target installed beside pascalc, with
undo, search, go-to-line, save-as, colour and a hint bar.

Nothing an already-valid program does has changed. The runtime stopped
calling tmpfile and builds §6.7.5.5's auxiliary file out of two other ISO C
functions instead, which is a portability improvement on every target and no
change to behaviour; the runtime still holds zero preprocessor
conditionals.

Added

  • The editor has colour and a hint bar (ADR-0389). A cell carries a
    role — document text, status, hint, message, prompt — and the shell maps a
    role to a colour, so ApEdit still names no terminal capability and a
    palette change touches no recorded screen. The bottom row now lists the
    bindings, which were discoverable only by reading the README. PasTerm had
    gained colour for the editor with ADR-0381 and the editor had no way to
    express it; this is what connects them.

  • A document with no name asks for one (ADR-0388). Ctrl-S on a new
    document opens a Save as: prompt instead of advising a restart, which is
    what the fileless start needed to be usable: run afterschool, type, save.
    The decision is the model's and the write is the shell's, so a session
    drives the prompt and a golden holds it — ADR-0387's division of labour, and
    the third prompt cost almost nothing because the first was built there. It
    also removed a second copy of the document's name that the shell had been
    keeping since milestone one.

  • The editor is afterschool, and it starts with no argument. It is a
    CMake target and an installed program now — cmake --build produces
    build/bin/afterschool and cmake --install puts it in <prefix>/bin
    beside pascalc — where launching it used to mean tui/build.py with three
    arguments and an output path a person had to invent and then remember. With
    no file it opens a new unnamed document rather than printing what looked
    like a usage line, and a document with no name says so instead of failing at
    the first Ctrl-S. tui/build.py is still what builds it, and every one of
    its arguments now has a default.

  • The editor gains undo, find and go-to-line (ADR-0387), milestone two of
    tui/. Ctrl-Z and Ctrl-Y over a journal in which every edit is one of four
    named operations — insert, remove, split, join — so one entry both reverses
    an edit and performs it again; a typed run is one undo and not one per
    character. Ctrl-F and Ctrl-L search, case-insensitively because this is an
    editor for a language whose identifiers are, wrapping round the end and
    saying so. Ctrl-G goes to a line. A prompt is a mode of the model rather
    than a loop in the shell
    , which is what lets a session drive one and a
    golden hold it; Ctrl-C closes it, because a decoder handed one byte at a
    time cannot tell a bare Escape from the start of an arrow.

  • Programs run as WebAssembly (ADR-0385, ADR-0390). 560 of the 598 programs in this
    repository's corpus compile for wasm32-wasi, link, and print what they
    print everywhere else under a WASI runtime. tools/pascalcc knows what the
    target needs — the SjLj lowering the non-local goto compiles through,
    libsetjmp, no -pthread, and a stack of a megabyte rather than wasm's
    64 KB default. It was 519 until the runtime stopped calling tmpfile, which
    is ISO C and which wasi declares and does not define: an exclusive fopen
    and a remove are both ISO C and do the same job, so 39 more programs run
    with no preprocessor conditional added anywhere (ADR-0390). What is not
    there yet is still the runtime -- channels, tasks, processes and sockets are
    30 of the 38 that do not run.

  • A seventh target: --target=wasm64-wasi (ADR-0386), WebAssembly's
    memory64. It is admitted on the layout claim alone — the emitted module
    states the target's layout and clang assembles it — because no sysroot for
    it exists to link against. It cost nothing in the compiler's layout rules:
    every one already had the arm an LP64 target needs.

  • A sixth target: --target=wasm32-wasi (ADR-0383), also spelled
    wasm32-unknown-wasi and wasm32-wasip1. The emitted module states
    WebAssembly's layout and triple, and clang assembles it into a .wasm
    object. What this does not yet do is link a program: the runtime does
    not build for the target, and runtime-nonposix says how far away that is —
    two of its four translation units compile, one wants five headers wasi has
    not got, and one wants threads the target does not have.

  • A text-mode editor, tui/apide.pas, in Turbo Pascal's mould and written
    in this language: open, edit, save, and Ctrl-B to compile with the cursor
    landing on the first diagnostic. Build it with
    tui/build.py tools/pascalcc apide.pas ~/bin/apide. A first milestone, and
    it says what it is not — no horizontal scrolling, no mouse, no undo, and a
    column is a byte.

  • PasTerm gained the alternate screen, colour and a reset:
    EnterScreen, LeaveScreen, SetColour and ResetColour, each a string
    the caller writes where it likes, as the five sequences before them are.

Removed

  • --target=x86_64-w64-windows-gnu, and every other spelling of it. The
    compiler emits for five machines and every one of them is POSIX. Windows was
    measured rather than assumed — a program was built and run under wine — and
    then dropped: what remained was a frame-layout change compared across every
    target and seven headers of winsock, for a platform this project does not
    run. The measurements are kept in doc/history.md for anyone who wants to
    take it up, and practical compatibility work is still welcome.

v3.8.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 15:21

The platforms were measured instead of assumed, and one of them was told
no.
--target= admits six machines where it admitted three: a Windows
triple, and both Darwin ones, so a module built on macOS names the machine it
is for rather than relying on clang to override an x86-64 Linux header. A
release now ships a macOS archive beside the two Linux ones. Windows got as
far as a program that runs and was then deferred, on a run rather than a
reading — a trivial program prints its golden under wine and the non-local
goto faults on an alignment this compiler gets wrong for Win64 alone. Minor,
because the command line accepts input it used to refuse and nothing already
accepted changed meaning.

Added

  • Three more targets (ADR-0371, ADR-0372). --target= accepts
    x86_64-w64-mingw32, arm64-apple-macosx and x86_64-apple-macosx, with
    the spellings people write for each, and the emitted module states that
    target's own datalayout and triple — each taken from clang and verified
    byte-identical. Win64 is the first LLP64 target here, a pointer of eight
    bytes with a four-byte C long, which cost one line because AP 6.4.2.7's
    clong and csize already existed for saying "whatever this target's is".
  • A macOS release archive (ADR-0375). A v* tag now produces
    afterschool-pascal-<tag>-arm64-darwin.tar.gz beside the two Linux ones.
    Apple ships no static libc, so that archive cannot be statically linked and
    is not required to be; what is checked instead is a claim that platform can
    answer — that the binary depends on nothing outside /usr/lib and
    /System
    , which otool -L reports and every hosted runner's Homebrew
    makes worth asking.
  • Platform tiers, written down (README). GNU/Linux is the first tier —
    the seed is generated for x86-64, every gate has a job that installs its
    tools, and i386 is built and run at two optimisation levels. macOS (arm64)
    is the second: the whole suite runs there natively on every push and the job
    can fail. Everything else is unsupported, and README says what a
    contributor starts from.
  • Four gates. runtime-nonposix compiles the runtime for a target that is
    not POSIX and holds the result in a catalogue that fails both ways
    (ADR-0369); setjmp-arity compares the _setjmp this compiler emits
    against what each target's C library declares (ADR-0371);
    helper-portability makes ADR-0366's rule mechanical (ADR-0367); and
    foreign-width gained a second claim — a C function declared in more than
    one module must have one representation (ADR-0376).

Changed

  • readstr and writestr are backed by memory, not by a FILE *
    (ADR-0370). §6.7.5.5 asks for an auxiliary text variable backed by memory
    and never for a stream; fmemopen and open_memstream are POSIX and were
    two of the three names that stopped runtime/pasrt.c compiling for a target
    that is not POSIX. The formatting is untouched — the same bytes, handed
    somewhere else — and runtime/pasrt.c's departure from ISO C went from five
    names to three. tmpfile() would have been ISO C and is 88 times slower,
    measured.
  • macOS is a job that can fail (ADR-0368). It was advisory from the first
    run of the port; the whole suite runs there natively on every push now, and
    a macOS failure stops the build. Nine gates still skip, each naming a tool
    the runner has not got, and the job lists them with the reason for each.
  • Every helper is Python 3, and a gate keeps it that way (ADR-0366,
    ADR-0367). The thirty-one shell scripts under test were converted one at a
    time, each proved by byte-identical output from both versions on this tree
    and by its own historical mutation re-run; tools/pascalcc is the one that
    remains, by decision. helper-portability refuses a new shell script, the
    constructs bash 3.2 lacks in the one that is catalogued, and a Python helper
    starting a general-purpose utility to do what the standard library does.
  • The runtime holds one preprocessor conditional (ADR-0373), where it held
    none. _longjmp is what pairs with the _setjmp the emitter writes and
    mingw-w64 declares no such name; no portable spelling exists, longjmp
    after _setjmp working on glibc and restoring on Darwin a mask that was
    never saved. runtime-isoc now catalogues the set of conditionals in both
    directions, so a second one is a decision with a record.

Fixed

  • A task may have helper routines. AP 6.7.8.2 admits a variable declared
    "in that task-declaration or in a block within it", and a procedure or
    function declared inside a task was refused its own parameters and its own
    locals — so recursion inside a task, and any helper at all, could not be
    written (ADR-0365).
  • A task declared inside a task no longer lifts the rule from the one that
    contains it.
    Every statement of the outer body after the inner
    declaration could name a global.
  • writeln(output, x) is accepted inside a task, as writeln(x) always
    was; both write the same required variable.
  • task A; forward; is refused. AP 6.7.8 admits no directive.
  • A task-declaration may stand in a module-block, so a library module can
    export a routine that spawns its own workers. A module-heading is refused
    one, with a diagnostic that says why.
  • A deferred statement of a block no longer runs before the block's tasks
    are joined.
    defer c := nil beside a spawn closed the channel the task
    was still sending on, which AP 6.9.3.12.1 forbids.
  • An integer expression may be sent on a channel of real, and passed to a
    real formal of a task.
    Both were accepted by the front end and then
    refused by the assembler, 6.4.6 c)'s conversion never having been emitted.
  • A failed write to a text file is reported. pas_write_padded called
    fwrite and ignored the result where the primitive beside it checked;
    both go through one emitter now (ADR-0370).
  • Two goldens pinned a platform rather than a program. lib_net_wait's
    fixed which of a buffered line and a socket's reply a round saw first, and
    lib_net's fixed how many writes a closed connection takes to be refused —
    two on Linux, more than a hundred on macOS, where they fit in a send buffer.
    Both were found by macOS after passing on Linux for their whole lives.
  • Four gates were asking about something other than what they said.
    runtime-isoc's fifth strict compile spelled clang literally where the
    other four honoured APASCAL_CLANG; irtest read neither name.opt nor
    AFTERSCHOOL_PASCAL_OPT, so the fixed-point harness compiled at a level no
    case had asked for; runtime-coverage's two totals were written and never
    compared, and had drifted; and tls reported its findings to nowhere.

v3.7.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 15:58

A command is words, not a line — and the boundary answers instead of
stopping.
A path an editor handed the language server could run a shell
command; closing that gave the library PasProcess.Execute, an audit of the
fix the same afternoon closed six more findings, and a project-wide audit
after it found nine foreign scalars bound at the wrong width and nineteen
routines that stopped a program on a name holding chr(0). Beside that, a
TOML library and the project reader rewritten over it. Minor, because the
library and the command line grew and nothing already accepted changed
meaning; a program that met one of the fixed defects met a trap or a wrong
width, and neither is a behaviour to preserve.

Added

  • PasProcess.Deadline — how long any run of an ArgV may take; on expiry
    the child is killed, the pipe is closed so a grandchild cannot hold the
    caller either, and the run is errIO. PasFS.TemporaryDirectory — a
    directory of the program's own, over mkdtemp, for a caller that wants
    several files by names of its choosing (ADR-0363).

  • A command as words, not as a line — PasProcess.Execute and the four
    routines beside it (ExecuteInto, ExecuteBoth, ExecuteLines,
    ExecuteToFile), over an ArgV built with NewArgs, AddArg, ArgsLen
    and DropArgs. The words are carried as words and no shell reads them, so
    an argument holding a space, an apostrophe, a semicolon or a backquote is an
    argument. Run, Capture and CaptureLines are unchanged and remain the
    right answer for a pipeline or a redirection; prefer Execute wherever any
    part of the command came from outside the program (ADR-0362).

  • A TOML library — lib/dialect/pastoml.pas, TOML v1.0.0 whole rather
    than a subset: bare, quoted and dotted keys; basic, literal and both
    multi-line string forms; decimal, hexadecimal, octal and binary integers with
    underscores; floats with inf and nan; all four date-time forms; arrays
    over several lines and with a trailing comma; inline tables; tables; and
    arrays of tables. TomlParse and TomlParseChars answer a TomlResult, and
    TomlPositionOf turns the byte a refusal stopped at into a line and a
    column. TomlPath(doc, 'server.port') is the dotted lookup a configuration
    reader wants; TomlRender writes a document back, and parsing that and
    rendering again reproduces it byte for byte. A date-time is §6.4.3.4's
    own TimeStamp with a nanosecond and a UTC offset beside it, so a program
    hands the result straight to PasTime. An integer is integer, so a
    value outside -maxint..maxint is errRange and never a wrapped number.
    ADR-0360.

  • PasText.RealToStr — the shortest decimal that reads back as a given real,
    which was PasJson's and is now where two formats can reach it. ADR-0359.

  • bin/apconfig — the project-file reader pascalcc build, run and
    test call, written in this language over PasToml and installed beside
    pascalc. APCONFIG overrides where the driver looks for it, as PASCALC
    does for the compiler. ADR-0361.

Fixed

  • Nineteen library routines stopped the program on a name holding
    chr(0)
    -- every path, name, host or service taken by PasFS, PasDir,
    PasEnv, PasIO, PasStream and PasNet reached ADR-0122's trap. Each
    now answers a code (errSyntax, false or nil) through the shared
    PasError.HoldsNul, which is ADR-0363's rule applied to the whole library
    rather than to the two routines it was written for.

  • Nine more foreign scalars bound at the wrong width, and the emitter's own
    slice count
    (ADR-0364): read/write (ssize_t), fflush (a pointer)
    and six OpenSSL bindings (longs and pointers) were int64; now csize
    or clong. A slice's count crossed every foreign call as i64, on i386
    too, where size_t is 32 bits -- it is the target's width now. The new
    foreign-width gate holds both, because no behavioural case can: on a
    host whose spare register is clean the wrong width gives the right answer.

  • PasProcess.Seconds and CpuSeconds answered garbage on i386: time
    and clock were bound as returning int64, and both are long, 32 bits
    there -- the high word was whatever the register held. Bound as clong
    now (AP 6.4.2.7); found by ADR-0363's deadline case, the first to compute
    with Seconds, under the target32 gate on CI.

  • Six findings of the security audit over PasProcess.Execute (ADR-0363),
    four of them older than it: a path holding chr(0) stopped the language
    server on one request, and is now refused as a code; ExecuteToFile
    followed a symbolic link, and the server's scratch files were composed at
    the top of TMPDIR under a guessable name — O_NOFOLLOW now, and a private
    mkdtemp directory removed at exit; no Execute had a deadline; a child
    inherited every open descriptor (FD_CLOEXEC on pipes and sockets, mode
    e on every fopen); three posix_spawn file-action returns went
    unchecked; and a failed fdopen dropped the output silently.

  • A path the language server was given could run a command (ADR-0362).
    lsp/pasls.pas assembled a shell command and wrapped the source path in
    apostrophes; a path holding an apostrophe closed the quoting, so a file named
    a'; touch PWNED; echo '.pas ran touch PWNED when an editor asked for its
    outline — and under MCP the path comes from whatever is driving the model.
    The server now spawns the compiler with an argument vector and quotes
    nothing. tools/pascalcc was never affected: it is bash and uses arrays.
    The new command-injection gate holds both halves — that nothing ran, and
    that such a file still compiles.

  • afterschool-pascal.toml is read as TOML. The driver read it with a
    declared subset written in awk, which got two kinds of legal document
    wrong: a # inside a quoted string ended the value, so output = "build/demo#1" silently built build/demo; and an array was split on every
    comma, so ldflags = ["-Wl,-rpath,/opt/lib"] — one flag — was rejected as
    three malformed strings. Both now read as TOML says, a syntax error names a
    line and a column, and an unknown key or a value of the wrong shape is
    still refused by name. ADR-0361.

  • Writing a NaN through PasJson stopped the program. The guard against a
    value with no decimal spelling asked x <> x, which is false for a NaN on
    this processor — <> on reals is ordered not-equal — so the guard never
    fired and the scan below it read past the end of what writestr had
    written, reporting array index out of bounds against a line of the
    library. It asks not (x = x) now. A program could only reach it by
    computing a NaN, this language having no literal for one. ADR-0359.

v3.6.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 22:46

The library asked the language for something, and got it. Traits landed in
3.5.0 with one client; this release is the one where PasContainer's map keys
itself with a trait and every map call loses two arguments — the payoff the
feature was measured against, and a break for every existing map client, said
below in as many words. Around it: the sanitizers see compiled Pascal for the
first time, an outline calls a task a task, a refused generic instantiation is
reported once instead of a hundred times, and pascalcc new-project writes a
project a program can be started in. The number is minor because the accepted
language and the command line grew and nothing they accept changed meaning; a
library interface changed, and the entry says which.

Changed

  • Every emitted function carries sanitize_address and sanitize_thread
    (ADR-0358). AFTERSCHOOL_PASCAL_CFLAGS=-fsanitize=address now instruments
    the program and not only the runtime — a use-after-free written in Pascal
    is reported with a stack trace where it printed a value — and
    -fsanitize=thread sees a race between two tasks. The attributes are inert
    without the flag. The sanitizers and thread-sanitizer gates refuse to
    sweep until a probe the sanitizer must report is reported, and the corpus
    was clean under both with compiled Pascal instrumented on the day.
  • --dump-symbols reports a task as task (ADR-0357), where it reported
    procedure — a task is started by spawn and cannot be called, so an outline
    naming it a procedure sent a reader to the wrong construct. The language
    server maps the new word beside function, the protocol having no nearer
    kind, and the MCP outline tool writes the word itself. A client written
    against the previous list reports a task as a variable until it learns the
    word.
  • PasContainer's map key implements a trait, and every map call loses two
    arguments
    (ADR-0355). Map(K: Key; V: type; cap: integer) binds the key
    discriminant with trait Key — Hash(k: Self): integer and Same(a: Self; b: Self): boolean — declared in the module's interface. MapPut, MapGet,
    MapHas and MapDelete no longer take a hash and an equality; the client
    writes one impl Key for its key type, before the map type is produced, and
    StrHash/StrEq stay exported as what a string key's implementation calls.
    This breaks every existing map client: the four in this tree lost 34
    argument pairs and gained one implementation block each (three for
    lib_container_key, one per key capacity), with every golden unchanged. The
    module cannot supply an implementation for its own MapKey (ADR-0341), so
    even the ready-made key type takes two lines in each client.

Fixed

  • A refused generic instantiation is reported once (ADR-0356). A schema
    whose type-valued discriminant failed its bound used to produce ^integer,
    and every generic body instantiated against it then reported faults located
    in the library — seven lines for one MapInit, a hundred for a client that
    also put and got — after the one line that was the diagnostic; and a call
    whose instantiation was refused for a category, a bound or an undetermined
    type was followed by unknown function naming the function just refused.
    The type now carries the refusal, no body is checked against it, and the
    second message is gone. Five goldens lost fifteen lines and gained none.

Added

  • A project: pascalcc new-project <name> (ADR-0348), alias new, with
    build, run and test reading what it wrote. The skeleton is src/,
    test/, build/, a .gitignore, a README and afterschool-pascal.toml;
    pascalcc run in the new directory prints Hello, world! without anything
    else being done first.

    afterschool-pascal.toml carries what the compiler cannot infer — which
    source is the program, where the executable goes, the optimisation level, the
    target, extra import paths and the link flags, the last being what a
    program binding PasTls otherwise has to know to pass by hand. It is looked
    for in the current directory and each enclosing one. It lists no modules and
    never will
    : import greet; finds src/greet.pas by name, so adding a
    module is adding a file and there is no build order anywhere. The reader is a
    strict TOML subset — [section], key = "string", key = ["a", "b"],
    comments — and refuses an unknown key or section by line number rather than
    ignoring it.

    A subcommand is recognised as the first argument and nowhere else, so
    pascalcc build.pas still compiles a file of that name.

v3.5.1

Choose a tag to compare

@github-actions github-actions released this 06 Sep 13:36

Nothing about the language changed. The version number here tracks the
accepted language, the diagnostics and the command line, and this release
touches none of them: it is the documentation pass after 3.5.0 plus four dead
symbols removed from the runtime. It is recorded because a release with nothing
in it for a user is worth saying so plainly rather than dressing up.

What the documentation pass found is the part worth reading, and it is in
doc/history.md under The release that checked itself:
three defects that cutting 3.5.0 surfaced and no gate could see, a seed that
named the machine that made it, and two documents claiming an oracle —
AddressSanitizer over compiled Pascal — that has never existed.

Removed

  • Four runtime symbols that nothing called: pas_index_error,
    pas_range_error, pas_disc_error and pas_length_error, the two-word
    forms kept as wrappers when ADR-0293 gave every trap its own position. The
    3.5.0 reseed made them dead — the seed calls only the _at forms — and
    doc/sop.md §7 had said since ADR-0293 that they could go at the next
    reseed.

    The one compatibility note in this release: IR emitted by v3.4.0 or
    earlier
    calls the two-word forms, so such a module will not link against
    this runtime. Anything emitted by 3.5.0 or later is unaffected, and the
    release archive ships the compiler and the runtime together. Recompile the
    source rather than relinking old .ll.

Fixed

  • A trap in a seed-built compiler names its position. The wrappers passed
    none, so a crash in build/bin/pascalc — which procedure-coverage and
    fuzz watch for — reported where nothing. This is a fact about building the
    compiler rather than about using it.

v3.5.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 12:13

The release that finished the memory model and then found out what it had left
open. owned ^T went into the library, which is what ADR-0181 was built for,
and adopting it turned up four ways a borrow could outlive what it borrows that
the rule as written did not see — three of them found by a langspec audit whose
whole purpose is that no oracle here can contradict a reading, and every gate
was green over all three.

Two threads of control became something a program can steer. A task is a
value now, with a name and a type; wait joins one; and the select-statement
waits on several channels at once, takes the first arm that can proceed, and
gives up after a timeout. ThreadSanitizer is the oracle underneath that, and it
is a gate rather than a thing run by hand.

A pointer is not always eight bytes. i386 is admitted, which took seven
layout rules rather than the 3858 offsets that looked like the work, and it
brought clong and csize — the first required identifiers whose type
depends on the target. It also decided something this release states outright:
an i386 this compiler emits for has SSE2, because on an x87 an error condition
this language says it detects goes undetected.

And traits. A trait bounds a schema's type-valued discriminant and a
routine's type parameter, an implementation is written by whoever names the
type, and lib/dialect/passortx.pas is the first client that is not a test —
impl Sortable for Point; once, and Sort(ps) thereafter. Four records
preceded one working feature and each says why the previous was wrong; the
first client found the fifth thing in under an hour, which is now a rule in
doc/sop.md.

Added

  • Waiting for a task, and for whichever of several things comes first
    (ADR-0312, ADR-0313, AP 6.4.17, AP 6.9.3.15). task is a required
    type-identifier — shadowable, like int64, and not a word-symbol — and its
    values are handles, so a task is released by the block that declared it,
    moved by take, and refused as an assignment source like every other handle.
    wait(t) blocks until that one task has finished. The select-statement
    waits on several channels at once and takes the first arm whose operation can
    proceed, with the case-statement's own punctuation:

    select
      receive(x, got): send(out, got);
      receive(y, got): send(out, got);
      after 2000: writeln('gave up waiting')
    end

    A send arm waits for room as a receive arm waits for a value, an after
    arm gives up after that many milliseconds, and an otherwise declines to wait
    at all.

  • A third target: --target=i386-pc-linux-gnu (ADR-0325). Every layout rule
    that wrote 8 now asks PtrSize or WordAlign, so a pointer is four bytes
    where the target says so. The corpus runs against a 32-bit runtime under the
    target32 gate; the one program that cannot is a deliberate 2 GB allocation.

  • clong and csize (ADR-0328), two required identifiers denoting whichever
    integer type a C long and a C size_t fit on the target — int64 on x86-64
    and aarch64, integer on i386. An external declaration naming a C routine
    that takes a long is now writable once rather than per target.

  • A protected parameter may be of an owned-pointer-type (ADR-0318,
    AP 6.4.14.8), which is the read-only borrow: a name for what a variable owns,
    read through, and through which nothing may be released. §6.4.1's exclusion of
    a pointer-type does not reach an owned pointer.

  • A discriminated schema may be written wherever a type-name is required
    (ADR-0324, AP 6.7.3.1.1, AP 6.7.2.1) — a parameter-form and a result-type both
    take Vec(integer) where they took only a name.

  • An array determines a slice parameter's component type (ADR-0316), as a
    slice-type already did, so array of T accepts an actual of an array-type
    without the caller writing the slice.

  • PasSortX, a sort over the element type itself (ADR-0344): the trait
    Sortable with Sort, SortWith, IsSorted and LowerBoundOf over an
    array of T. The trait is declared in the module and every implementation
    is written by the client — impl Sortable for Point; once, and Sort(ps)
    thereafter — which is the only shape separate translation admits and is
    what makes a trait usable from a library at all. SortWith takes the order
    as a procedural parameter instead, for a caller whose order is not the
    type's or whose element type implements nothing. PasSort is unchanged and
    stays: it is conforming Extended Pascal, and its SortIndexed still answers
    for parallel arrays.

  • A type may implement a trait, and a trait may bound a type (ADR-0338 to
    ADR-0341, AP 6.4.7.2, AP 6.7.9, AP 6.7.10). trait Sortable; function Rank(u: Self; v: Self): integer; end; names routines a program supplies, and
    impl Sortable for Point; supplies them — each routine written as a name
    alone, the trait having given the heading. A call selects by the type of its
    first argument, after the ordinary lookup, so two implementations of one
    trait live in one block and a program declaring its own routine of the name
    keeps it. A subrange takes its host's implementation. The bound is written
    where the client writes the type — on a schema's type-valued discriminant,
    Map(K: Sortable; V: type; cap: integer), checked once at the type-denoter —
    and also on a routine's type parameter, T: Sortable type, where a category
    would stand. One bound, not two. Neither trait nor impl is reserved. A
    trait may be declared in a module's interface and exported; an
    implementation belongs to one translation, so it may not stand in a
    procedure or in a module heading.

  • A task may be handed a handle (ADR-0303, AP 6.7.8.1): a formal parameter
    of a task-declaration may be of a handle-type, and the actual is written
    take of a variable of that type — the one position outside an assignment
    where AP 6.4.14.6 admits a move. The variable is emptied before the
    activation commences and the task's block releases what it was given, so a
    socket, a stream or a directory can now be given to a task. A channel is
    still lent and a handle is moved; a handle still cannot be sent through
    a channel.

  • A channel is closed by the release the program wrote (ADR-0302,
    AP 6.4.16.4): release(c), c := nil and c := take(d) each close the
    channel wherever they stand, including inside a task. Before this a task's
    release dropped that task's reference and left the channel open, so a
    pipeline of stages each closing the one downstream of it deadlocked with no
    diagnostic. The release performed by the end of a task's block is unchanged
    and still only drops the reference, which is what a pool of workers needs.

  • A release archive on every tag (ADR-0296). Pushing a v* tag now
    attaches afterschool-pascal-<tag>-x86_64-linux.tar.gz and an
    aarch64-linux one, each with a .sha256, to the GitHub release: the
    cmake --install layout, LICENSE, COPYING.RUNTIME and a short README,
    with pascalc linked statically. clang is still needed at use time.
    tools/release.sh builds and checks the archive, release-archive runs
    both halves under ctest, and tests/checks/install_layout.sh --prefix
    checks an unpacked prefix. APASCAL_STATIC_PASCALC is the CMake option.

  • examples/ (ADR-0295): twelve complete programs of a page each,
    written to be read by someone who knows Turbo Pascal, every one a ctest
    case (example-<name>) with a golden, and swept by the same gates as the
    rest of the corpus. Writing them found seven things — a task cannot close
    the channel downstream of it and a program that tries deadlocks silently,
    a map lookup writes two types the call already knows, four of twelve
    programs collided with a library noun — recorded in the ADR and in
    doc/roadmap.md rather than fixed here.

  • tests/checks/heap_balance.py measures a case that resolves its imports
    by .importpath, and --write refuses to write a catalogue when a case
    did not run instead of striking it in silence.

  • diagnostic-coverage and foreign-layout read nothing when the checkout
    is itself a .claude/worktrees tree, their worktree filter having tested
    the absolute path; both now test the path below the root.

Fixed

  • The committed seed no longer names the machine that generated it
    (ADR-0347). Since ADR-0293 a trap carries its own position, so the source's
    path is a string constant in the emitted module — and seed/refresh.sh
    handed the compiler an absolute one, so seed/*.ll held the reseeding
    machine's home directory and tests/checks/seed_current.sh could pass only
    in the directory that had produced it. Both now translate from the repository
    root with a relative path, and a new seed-portable gate asks the cheap half
    of the question on every push rather than at a tag.

  • Disposing a long owned chain or a deep owned tree costs one frame
    (ADR-0322, ADR-0333, AP 6.4.14.3). The release routine walked the structure
    recursively, so a list or a tree deeper than the stack ended the program in a
    segmentation fault rather than freeing it. Every self-owned field is emptied
    onto a work list threaded through the nodes themselves, so the depth costs no
    stack and no allocation.

  • A block could reach an owner through a procedural parameter (ADR-0326),
    and through a formal bound before its own activation existed (ADR-0332). Both
    were ways for a borrow to outlive what it borrowed that the rule as written
    did not see; both are refused, and the second is one conjunct rather than a
    second rule.

  • dispose of a variable whose domain is a schema found no tuple
    (ADR-0329) where the walk reached one below the outermost, so a discriminated
    component was released against the wrong bounds.

  • PasJson reads a number by rounding once (ADR-0314). The reader
    normalised and t...

Read more

v3.4.0

Choose a tag to compare

@hiroshiyui hiroshiyui released this 01 Sep 14:01

The release that emptied doc/roadmap.md's What would make this easier to
work on
. Three rows closed since 3.3.0 and all three by the same method the
chapter kept recommending to itself -- attempt the row rather than believe it.
Two were built and the third talked the page out of itself.

The fourth warning (ADR-0283) is the first whose answer is a property of
the whole compilation rather than of one routine, and it is a fixed point
rather than a list: protecting one parameter stops its callers' arguments from
being threatened and exposes the next layer. One pass over this tree reports
130 sites and seven passes report zero, having added protected 54 times.

--format --range=L:H (ADR-0284) was recorded as needing a parse to know
where a range's indent begins. It needs no parse: the printer accumulates that
depth as it walks, so the lines before the range are walked without being
written. textDocument/rangeFormatting follows from it.

And the style: gate was tried and declined (ADR-0285). A reformat of this
tree rewrites 42 601 lines and grows the implementation 6.8%, almost all of it
style rather than defect. What the attempt was worth is five layout defects it
found -- every one token-preserving, so every oracle here was green on all
five.

Added

  • pascalc --format --range=L:H (ADR-0284) writes those lines alone, with
    the layout they have in the whole file. doc/roadmap.md had this down as
    needing the printer told where its indent begins, "a question about the
    enclosing structure that only a parse can answer"; the printer accumulates
    that depth itself, so the lines before the range are walked without being
    written and the depth on arrival is the one the whole file would have. A
    boundary inside a construct gets a line break there that the whole file would
    not have, which is what asking for part of a file means. A span it cannot use
    is refused rather than widened to the document.

  • The language server formats a selection. textDocument/rangeFormatting,
    beside the whole-document request. A client's range is a position pair and
    the formatter's unit is a line, so the selection is widened to every line it
    touches and the reply replaces whole lines; a range ending at character 0
    does not reach into that line, so three selected lines come back as three.

  • A var parameter nothing writes through is reported (ADR-0283). The
    fourth warning, and the first whose answer is a property of the whole
    compilation rather than of one routine. 6.7.3.1 spells such a parameter
    protected var, and the advice is exact rather than a guess: 6.5.1 forbids a
    statement to threaten a protected variable-identifier, 6.9.4 lists the six
    ways to threaten one, and the compiler already recorded every one of them --
    so "never threatened" is precisely the condition under which adding the word
    still compiles. A file or a pointer is never advised, 6.4.1 making it
    unprotectable.

    It is not advised where the word would be illegal, which is why it is
    written at the end of the compilation: a routine passed as a procedural
    actual cannot take it, since 6.6.3.6 compares the parameter lists with
    protected in them and the call may be written after the routine; and
    whether an exported routine is passed that way is a question no single
    component can answer.

Changed

  • A program that compiled with three warnings may now print a fourth. As
    in 3.3.0, nothing about what it does changes -- the emitted IR, the exit
    status and the program's own output are untouched -- but a var parameter
    the body never writes through is now remarked on. A build script that treats
    any compiler output as failure is what notices. Every --dump flag still
    suppresses warnings, and the new one is never written for an imported
    component, nor once an error has been reported.

  • 54 parameters of the compiler, the library and the language server are now
    protected var.
    They are the warning's own first finding, and three were
    groups like var l, r, v: str split into protected var l, r: str; var v: str, which had been claiming all three were writable when one was. Nothing
    about what any of them does changes -- protected is a Sema-only property
    and the code generator never reads it -- but a future change that writes
    through one of them is now a compilation error naming the clause.

Fixed

  • Five layout defects in the formatter (ADR-0285), found by pointing it at
    36 real sources rather than at a case someone wrote. Every one preserves the
    token stream, so every oracle here was green on all five. A blank line
    inside a parenthesised list dropped the rest of the list to column zero. A
    comment introducing an else took the indent of the arm above it. ^ was
    glued to whatever preceded it, which is right for a dereference and wrong
    for a pointer-type, printing = ^T as =^T. AP 6.4.13's ! is binary and
    took a space on one side only. And 6.9.2.1's empty statement after a
    case-label lost its space, printing red:;.

  • and then and or else were written straight to standard output by the
    formatter, which adjusted its column counter by hand rather than going
    through its one character sink. The bytes were identical either way, so
    nothing could see it until --range gave the sink something to suppress --
    and then those two word-symbols printed outside the range asked for.