Heroes / Esempi / shapes

examples/shapes/

Un programma diviso in moduli su più livelli di cartelle, per verificare come vengono risolti i percorsi.

I file usano lo stesso modulo da cartelle a profondità diverse. Il programma stampa un valore per lo scale nella cartella principale e un altro per quello nella cartella vicina. Se il compilatore scegliesse il modulo sbagliato, l’output cambierebbe e il test fallirebbe.

Eseguilo

$ heroes run examples/shapes/main.hero
12
14
####
###
###
10
3

exit 0 · 7 righe

Il programma

# Six modules in three directories — the first program in this corpus whose
# files do not all sit beside each other (M-package-layout, panel 099).
#
# **What it is FOR is the line `use geom/point`, written three times from
# three different depths and meaning the same file every time.** A `use` path
# starts at the directory of the file you compile, so `render/ascii.hero`
# reaches the point module by exactly the spelling `main.hero` uses, and so
# does `geom/area.hero` — which sits in the SAME directory as its target and
# still names it from the root. That last one is the rule's real bite: there
# is no shorter spelling for a neighbour, and there is no `..` for anything
# else, because a module above the root belongs to a different program.
#
# The qualifier is the path's last part: `use geom/point` binds `point`, so
# every call below reads `point.something`. The directory is not part of the
# name a call writes, so two modules can end in the same word — and then a
# FILE that wants both renames one with `as`, which is what happens below.
# (These lines said *"last parts are unique across a program"* until panel 100
# repealed that rule the same day, hours after they were written.)
use geom/point
use geom/area
use render/ascii
use render/scale as near_scale
use scale

function main()
    corner = point.Point(x: 0, y: 0)
    far = point.Point(x: 4, y: 3)
    print(area.rect(a: corner, b: far))
    print(area.perimeter(a: corner, b: far))
    print(ascii.bar(width: point.span(from: corner, to: far)))
    print(ascii.frame(width: 3, height: 2))

    # The two `scale`s in one file, which is what `as` is for: `use scale`
    # takes the root's and `use render/scale as near_scale` renames the
    # neighbour, because a file may not bind one name twice (panel 100). The
    # compiler refuses the version without `as` on the second `use` line and
    # offers the rename there — measured while writing this program.
    print(scale.factor())
    print(near_scale.factor())

test "the area and the perimeter of the 3-4-5 rectangle"
    origin = point.Point(x: 0, y: 0)
    tip = point.Point(x: 4, y: 3)
    assert area.rect(a: origin, b: tip) == 12
    assert area.perimeter(a: origin, b: tip) == 14

test "a bare use is the root's, and the neighbour needs its path plus a rename"
    # The rule's sharp edge, asserted rather than described: both modules are
    # live in this file and they answer differently.
    assert scale.factor() == 10
    assert near_scale.factor() == 3
examples/shapes/main.hero, 51 righe
geom/area.hero · 27 righe
# What a pair of corners measures.
#
# **`use geom/point` and not `use point`** — and `point.hero` is the file
# right next to this one. A `use` path is read from the directory of the
# file you compile, so a neighbour has no shorter spelling than a stranger;
# the reward is that this line means the same thing wherever it is copied
# to, which is what `render/ascii.hero` does with it.
use geom/point

function rect(a: point.Point, b: point.Point) -> i64
    return point.span(from: a, to: b) * height(a: a, b: b)

function perimeter(a: point.Point, b: point.Point) -> i64
    return 2 * (point.span(from: a, to: b) + height(a: a, b: b))

# The other side, measured the same way the first one is — through the
# module that owns the idea of a distance rather than by subtracting here.
function height(a: point.Point, b: point.Point) -> i64
    swapped_a = point.Point(x: a.y, y: a.x)
    swapped_b = point.Point(x: b.y, y: b.x)
    return point.span(from: swapped_a, to: swapped_b)

test "a rectangle of no width has no area and a perimeter twice its height"
    a = point.Point(x: 2, y: 0)
    b = point.Point(x: 2, y: 5)
    assert rect(a: a, b: b) == 0
    assert perimeter(a: a, b: b) == 10
examples/shapes/geom/area.hero, 27 righe
geom/point.hero · 25 righe
# A point, and the one measurement everything else is built from.
#
# This module names nothing: it is the leaf of the program's little graph,
# which is why it is the one every other file reaches for. It sits in
# `geom/` and is spelled `geom/point` from every file that uses it,
# including the one beside it.

record Point
    x: i64
    y: i64

# The distance along x, never negative. `span` rather than `width` because a
# pair of points has no orientation until somebody gives it one.
function span(from: Point, to: Point) -> i64
    dx = to.x - from.x

    if dx < 0
        return 0 - dx
    return dx

test "a span is the same in both directions"
    a = Point(x: 1, y: 0)
    b = Point(x: 5, y: 0)
    assert span(from: a, to: b) == 4
    assert span(from: b, to: a) == 4
examples/shapes/geom/point.hero, 25 righe
render/ascii.hero · 39 righe
# Drawing, in a different directory from the geometry it draws.
#
# The `use` line below is character for character the one in `main.hero` and
# in `geom/area.hero`. Three files at two depths, one spelling: that is the
# whole of what the root base buys, and it is why copying an import from a
# neighbouring file is the correct move here rather than the classic mistake.
use geom/point

# `use scale` — and there is a `render/scale.hero` BESIDE this file. This line
# reaches the ROOT's `scale.hero`, because every `use` starts at the directory
# of the file you compile. `scale.hero`'s own doc is where that is explained.
use scale

function bar(width: i64) -> str
    origin = point.Point(x: 0, y: 0)
    tip = point.Point(x: width, y: 0)
    return "#".repeat(point.span(from: origin, to: tip).to_u64().must())

function frame(width: i64, height: i64) -> str
    rows: [str] @= [bar(width: width)]
    at: i64 @= 1

    while at < height
        rows @ rows.push(bar(width: width))
        at @ at + 1

    return rows.join("\n")

test "a bar is as wide as the span it is given, and a frame repeats it"
    assert bar(width: 3) == "###"
    assert bar(width: 0) == ""
    assert frame(width: 2, height: 2) == "##\n##"

# The root's factor, reached from a subdirectory by a bare `use`.
function scaled(width: i64) -> i64
    return width * scale.factor()

test "the bare use reached the root, so the factor is ten"
    assert scaled(width: 2) == 20
examples/shapes/render/ascii.hero, 39 righe
render/scale.hero · 16 righe
# The neighbour that is NOT reached by a bare `use scale`.
#
# `render/ascii.hero` sits beside this file and says `use scale`; that line
# names the root's `scale.hero`, not this one. To reach this module a file
# writes `use render/scale`, from anywhere in the program, because a path
# starts at the directory of the file you compile.
#
# `main.hero` does exactly that, so both modules are live and the program's
# output tells them apart: 10 from the root, 3 from here. If the resolution
# ever flips, `main.expected` changes and the corpus goes red.

function factor() -> i64
    return 3

test "the neighbour's scale is three"
    assert factor() == 3
examples/shapes/render/scale.hero, 16 righe
scale.hero · 23 righe
# A root module with a name a subdirectory also uses — deliberately.
#
# **This file exists to pin the sharp edge of the rule, in a program that
# runs** (panels 099 R1, 100 R5, 101 R5). `render/ascii.hero` says
# `use scale`, and there is a `render/scale.hero` sitting right beside it.
# The `use` reaches THIS file, because every `use` starts at the directory of
# the file you compile — never beside the file that writes the line.
#
# It is correct and it is silent: no diagnostic fires, because the program is
# exactly what the rule says it is. The loud half exists too — delete this
# file and the same line becomes `unknown_module`, naming the directory it
# looked in — so the compiler teaches the rule the moment it can. What it
# cannot do is guess which file you meant, and a refusal here would leave a
# file inside `render/` with no way at all to name the root's `scale`.
#
# The number is 10 rather than 1 so a wrong resolution changes the program's
# output instead of hiding inside it.

function factor() -> i64
    return 10

test "the root's scale is ten"
    assert factor() == 10
examples/shapes/scale.hero, 23 righe

impronta sha256 2a475dcc47cb · 7 file · index.json

Successivo: todo. Tutti gli esempi. ← Precedente: readings

Eseguine uno: i quattro comandi. Impara il linguaggio, oppure leggi la specifica completa in un solo file.