Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ What it was asked matters, because "can this be run" has two halves here.
Twenty-two of the twenty-nine have no `main` at all. Six of the remaining
seven have one and want the filesystem, which a page does not have, so `needs`
records the capabilities they asked for. That leaves exactly one example this
page can start, and Run is off for the other twenty-eight with the reason
beside it, rather than letting somebody press it and be refused.
page can start through `main`. In the picker that example says
`Run program`; a library or a program needing filesystem capabilities
says `Run tests` instead and names why its `main` cannot
start in the tab. Every shown example therefore has a real primary action
rather than a disabled Run button.

The picker shows fourteen of the twenty-nine, and that list is in
`tools/artifact.mjs` because both tools need it. The corpus is not a menu:
Expand Down
55 changes: 44 additions & 11 deletions assets/play.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const GUTTER = document.getElementById("gutter");
const CONTROLS = document.querySelector(".controls");
const CONSOLE = document.querySelector(".console");
const VERBS = Array.from(document.querySelectorAll("[data-verb]"));
const EXAMPLE_ACTION = document.getElementById("example-action");

const encoder = new TextEncoder();
const decoder = new TextDecoder();
Expand Down Expand Up @@ -123,11 +124,10 @@ function running(yes) {
}
}

// Most of the corpus cannot be started here, for two different reasons, and
// pressing Run to find that out is a bad way to be told. Twenty-one are
// libraries with no `main` to enter through. Six more have one and want the
// filesystem, which this page does not have, so they would answer with a list
// of capabilities rather than with anything about the program.
// Most of the corpus cannot start a `main` here, for two different reasons.
// Some are libraries with no entry point; others have one and want filesystem
// capabilities this page deliberately does not hold. They do have tests, so
// the primary action runs those instead of becoming a disabled dead end.
//
// `examples/index.json` records both answers from the pinned artifact, so the
// button is off before it is reached for, and the note says which of the two
Expand All @@ -143,6 +143,36 @@ function thisIsRunnable(yes, why = "") {
if (ready) running(false);
}

function configureExampleAction(entry) {
if (!entry) {
EXAMPLE_ACTION.dataset.verb = "deed_run";
EXAMPLE_ACTION.textContent = "Run program";
thisIsRunnable(true);
return;
}

if (entry.runs && entry.needs.length === 0) {
EXAMPLE_ACTION.dataset.verb = "deed_run";
EXAMPLE_ACTION.textContent = "Run program";
thisIsRunnable(true, "Program example. Its `main` runs in this tab.");
return;
}

if (entry.tests > 0) {
EXAMPLE_ACTION.dataset.verb = "deed_test";
EXAMPLE_ACTION.textContent = "Run tests";
const reason = entry.runs
? `Its \`main\` needs ${entry.needs.join(", ")}, which this page cannot provide.`
: "Library example: there is no `main` to start.";
thisIsRunnable(true, `${reason} The button runs its tests in this tab.`);
return;
}

EXAMPLE_ACTION.dataset.verb = "deed_run";
EXAMPLE_ACTION.textContent = "Cannot run here";
thisIsRunnable(false, whyNot(entry));
}

function lines(text) {
return text.split("\n").filter((line) => line.trim() !== "");
}
Expand Down Expand Up @@ -492,7 +522,7 @@ const check = () => run("deed_check").then(paint);
SOURCE.addEventListener("input", () => {
// Whatever was known about the example is now known about a different
// program.
if (!runnable) thisIsRunnable(true);
configureExampleAction(null);
drawGutter(SOURCE.value);
schedule(paint, 150);
schedule(check, 500);
Expand Down Expand Up @@ -651,7 +681,7 @@ async function loadFromLink() {
// tests, and the summary under the picker is the comment at the top of the
// file rather than a description written here.
//
// The picker shows twelve of the twenty-eight. The rest are the corpus doing
// The picker shows fourteen of the twenty-nine. The rest are the corpus doing
// its other job, one language feature at a time so the compiler's tests have
// something to read, and a menu of those is a menu of somebody else's test
// suite. `shown` in the index carries the choice and its order; every file is
Expand All @@ -678,15 +708,18 @@ async function loadExamples() {
.sort((a, b) => a.shown - b.shown)) {
const option = document.createElement("option");
option.value = entry.file;
option.textContent = entry.file.replace(/\.deed$/, "");
const name = entry.file.replace(/\.deed$/, "");
option.textContent = entry.runs && entry.needs.length === 0
? `${name} · runs here`
: `${name} · tests`;
EXAMPLE.append(option);
}
EXAMPLE.disabled = false;

EXAMPLE.addEventListener("change", async () => {
const file = EXAMPLE.value;
if (!file) {
thisIsRunnable(true);
configureExampleAction(null);
return;
}
const entry = summaries.get(file);
Expand All @@ -696,8 +729,8 @@ async function loadExamples() {
const response = await fetch(`../examples/${encodeURIComponent(file)}`);
SOURCE.value = await response.text();
marked = new Map();
thisIsRunnable(entry ? entry.runs && entry.needs.length === 0 : true, whyNot(entry));
paint();
configureExampleAction(entry);
schedule(paint, 150);
});
}

Expand Down
59 changes: 59 additions & 0 deletions assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,65 @@ button {
min-width: calc(19ch + 1.6rem + 2px);
}

#example-action {
min-width: calc(16ch + 1.6rem + 2px);
}

@media (max-width: 42rem) {
body.fits header.site h1 {
font-size: 1.55rem;
}

body.fits header.site h2,
body.fits main > h1,
body.fits main > .lede {
display: none;
}

body.fits nav.site {
margin-bottom: 0.5rem;
}

body.fits .status {
margin-bottom: 0.5rem;
}

body.fits .controls {
display: grid;
gap: 0.5rem;
width: 100%;
min-width: 0;
}

body.fits .picker {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
width: 100%;
min-width: 0;
justify-content: stretch;
}

body.fits .picker select {
width: 100%;
min-width: 0;
max-width: 100%;
}

body.fits .verbs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
width: 100%;
min-width: 0;
}

body.fits .verbs button,
body.fits #share,
body.fits #example-action {
width: 100%;
min-width: 0;
}
}

button:hover:not(:disabled),
select:hover:not(:disabled) {
border-color: var(--black);
Expand Down
2 changes: 1 addition & 1 deletion play/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ <h1>Playground</h1>

<div class="verbs">
<button data-verb="deed_check" disabled>Check</button>
<button data-verb="deed_run" disabled>Run</button>
<button id="example-action" data-verb="deed_run" disabled>Run program</button>
<button data-verb="deed_test" disabled>Test</button>
<button data-verb="deed_fmt" disabled>Format</button>
<button id="stop" type="button" disabled>Stop</button>
Expand Down
20 changes: 19 additions & 1 deletion tools/check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ if (deed) {
}
}

// The picker's dozen. `shown` is the position in `SHOWN` and -1 for the rest,
// The picker's fourteen. `shown` is the position in `SHOWN` and -1 for the rest,
// so a file leaving the corpus takes its place in the picker with it and says
// so here rather than shortening the menu quietly.
for (const file of SHOWN) {
Expand All @@ -318,6 +318,24 @@ for (const entry of index.examples) {
}
}

// A selected example always gets one honest primary action. A program with no
// external capability runs its main; everything else shown in the picker must
// have tests for the dynamic `Run tests` action. Without this, a newly shown
// library with no tests silently brings back the disabled Run dead end.
const shownEntries = index.examples.filter((entry) => entry.shown >= 0);
if (!shownEntries.some((entry) => entry.runs && entry.needs.length === 0)) {
complain("examples/index.json", "the picker has no program whose main runs in the browser");
}
for (const entry of shownEntries) {
const mainRunsHere = entry.runs && entry.needs.length === 0;
if (!mainRunsHere && entry.tests < 1) {
complain(
"examples/index.json",
`${entry.file} is shown but has neither a browser-runnable main nor tests`,
);
}
}

// This site names one release, and the pin is which one. Everything else that
// says a version is prose: the filenames on the install page, the `deed 0.2.2`
// under each `--version`, and the version a share link carries.
Expand Down