Update website

This commit is contained in:
Andrew Morris
2023-02-21 16:54:40 +11:00
parent 964802a6fb
commit 7fafbb839c
5 changed files with 192 additions and 38 deletions

View File

@@ -221,6 +221,52 @@ const files: Record<string, string | nil> = {
return res;
}
`),
"examples/idGenerationError.ts": blockTrim(`
export default function main() {
let nextId = 1;
function generateId() {
const result = nextId;
nextId++;
return result;
}
return [
generateId(),
generateId(),
generateId(),
];
}
`),
"examples/idGeneration.ts": blockTrim(`
export default function main() {
let idGen = new IdGenerator();
return [
idGen.generate(),
idGen.generate(),
idGen.generate(),
];
}
class IdGenerator {
nextId: number;
constructor() {
this.nextId = 1;
}
generate() {
const result = this.nextId;
this.nextId++;
return result;
}
}
`),
};
export default files;

View File

@@ -22,25 +22,8 @@
<div class="display-title">Outcome</div>
<div id="outcome">"The playground is loading"</div>
<!--
<div class="display-title">Stats</div>
<div id="stats"><div class="table-wrap"><table
>
<tr>
<td>Steps</td><td></td><td id="steps"></td><td></td>
</tr>
<tr>
<td>Step Limit</td><td class="clickable" id="steps-dec">-</td><td id="stepLimit">100000</td><td class="clickable" id="steps-inc">+</td>
</tr>
<tr>
<td>Characters</td><td></td><td id="chars"></td><td></td>
</tr>
</table
></div></div>
<div class="display-title">Notes</div>
<div id="notes"></div>
-->
<div class="display-title">Diagnostics</div>
<div id="diagnostics"></div>
<div class="display-title">Assembly</div>
<div id="vsm">@main = function() {

View File

@@ -4,7 +4,12 @@ import files from "./files.ts";
import assert from "./helpers/assert.ts";
import nil from "./helpers/nil.ts";
import notNil from "./helpers/notNil.ts";
import VslibPool, { Job } from "./vslib/VslibPool.ts";
import VslibPool, {
CompilerOutput,
Diagnostic,
Job,
RunResult,
} from "./vslib/VslibPool.ts";
function domQuery<T = HTMLElement>(query: string): T {
return <T> <unknown> notNil(document.querySelector(query) ?? nil);
@@ -17,6 +22,7 @@ const filePreviousEl = domQuery("#file-previous");
const fileNextEl = domQuery("#file-next");
const outcomeEl = domQuery("#outcome");
const vsmEl = domQuery("#vsm");
const diagnosticsEl = domQuery("#diagnostics");
for (const filename of Object.keys(files)) {
const option = document.createElement("option");
@@ -109,8 +115,8 @@ editorEl.innerHTML = "";
timerId = setTimeout(handleUpdate, 100);
});
let compileJob: Job<string> | nil = nil;
let runJob: Job<string> | nil = nil;
let compileJob: Job<CompilerOutput> | nil = nil;
let runJob: Job<RunResult> | nil = nil;
let updateId = 0;
function handleUpdate() {
@@ -124,10 +130,70 @@ editorEl.innerHTML = "";
compileJob = vslibPool.compile(source);
runJob = vslibPool.run(source);
renderJob(compileJob, vsmEl);
renderJob(runJob, outcomeEl);
renderJob(
compileJob,
vsmEl,
(el, compilerOutput) => {
el.textContent = compilerOutput.assembly.join("\n");
},
);
function renderJob(job: Job<string>, el: HTMLElement) {
renderJob(
runJob,
outcomeEl,
(el, runResult) => {
if ("Ok" in runResult.output) {
el.textContent = runResult.output.Ok;
} else if ("Err" in runResult.output) {
el.textContent = runResult.output.Err;
} else {
never(runResult.output);
}
diagnosticsEl.innerHTML = "";
for (const diagnostic of runResult.diagnostics) {
const diagnosticEl = document.createElement("div");
diagnosticEl.classList.add(
"diagnostic",
toKebabCase(diagnostic.level),
);
const { line, col } = toLineCol(source, diagnostic.span.start);
diagnosticEl.textContent = `${line}:${col}: ${diagnostic.message}`;
diagnosticsEl.appendChild(diagnosticEl);
}
monaco.editor.setModelMarkers(
model,
"valuescript",
runResult.diagnostics.map((diagnostic) => {
const { line, col } = toLineCol(source, diagnostic.span.start);
const { line: endLine, col: endCol } = toLineCol(
source,
diagnostic.span.end,
);
return {
severity: toMonacoSeverity(diagnostic.level),
startLineNumber: line,
startColumn: col,
endLineNumber: endLine,
endColumn: endCol,
message: diagnostic.message,
};
}),
);
},
);
function renderJob<T>(
job: Job<T>,
el: HTMLElement,
apply: (el: HTMLElement, jobResult: T) => void,
) {
const startTime = Date.now();
const loadingInterval = setInterval(() => {
@@ -140,7 +206,7 @@ editorEl.innerHTML = "";
(async () => {
try {
el.textContent = await job.wait();
apply(el, await job.wait());
el.classList.remove("error");
} catch (err) {
if (!(err instanceof Error)) {
@@ -158,4 +224,34 @@ editorEl.innerHTML = "";
})();
}
}
function toMonacoSeverity(level: Diagnostic["level"]): any {
switch (level) {
case "Error":
return monaco.MarkerSeverity.Error;
case "InternalError":
return monaco.MarkerSeverity.Error;
case "Lint":
return monaco.MarkerSeverity.Warning;
case "CompilerDebug":
return monaco.MarkerSeverity.Info;
}
}
})();
function never(_: never): never {
throw new Error("This should not happen");
}
function toKebabCase(str: string): string {
// account for leading capital letters
str = str.replace(/^[A-Z]/, (match) => match.toLowerCase());
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
}
function toLineCol(str: string, index: number): { line: number; col: number } {
const lines = str.slice(0, index).split("\n");
return { line: lines.length, col: lines[lines.length - 1].length + 1 };
}

View File

@@ -96,41 +96,41 @@ a, a:visited {
white-space: normal;
}
#display #notes {
#display #diagnostics {
padding: 0;
display: flex;
flex-direction: column;
}
#display .note {
#display .diagnostic {
padding: 0.5em 1.5em;
}
#notes > .note:not(:first-child) {
#diagnostics > .diagnostic:not(:first-child) {
border-top: 1px solid black;
}
.note.info {
.diagnostic.info {
background-color: hsla(240, 100%, 50%, 0.1);
}
.note.warn {
.diagnostic.warn, .diagnostic.lint {
background-color: hsla(30, 100%, 50%, 0.1);
}
.note.error {
.diagnostic.error {
background-color: hsla(0, 100%, 50%, 0.1);
}
#display .note .note {
#display .diagnostic .diagnostic {
border: 1px solid black;
}
#display .note .note:first-child {
#display .diagnostic .diagnostic:first-child {
margin-top: 0.5em;
}
#display .note .note:not(:first-child) {
#display .diagnostic .diagnostic:not(:first-child) {
border-top: 0;
}

View File

@@ -36,20 +36,49 @@ const workerUrl = URL.createObjectURL(
new Blob([workerScript], { type: "application/javascript" }),
);
export type Diagnostic = {
level: "Lint" | "Error" | "InternalError" | "CompilerDebug";
message: string;
span: {
start: number;
end: number;
ctxt: number;
};
};
export type CompilerOutput = {
diagnostics: Diagnostic[];
assembly: string[];
};
export type RunResult = {
diagnostics: Diagnostic[];
output:
| { Ok: string }
| { Err: string };
};
export type Job<T> = {
wait: () => Promise<T>;
cancel: () => void;
};
export function mapJob<U, V>(job: Job<U>, f: (x: U) => V): Job<V> {
return {
wait: () => job.wait().then(f),
cancel: job.cancel,
};
}
export default class VslibPool {
#pool = new valuescript.WorkerPool(workerUrl);
run(source: string) {
return this.#Job("run", [source]) as Job<string>;
return this.#Job("run", [source]) as Job<RunResult>;
}
compile(source: string) {
return this.#Job("compile", [source]) as Job<string>;
return this.#Job("compile", [source]) as Job<CompilerOutput>;
}
#Job(method: string, args: unknown[]) {
@@ -70,7 +99,7 @@ export default class VslibPool {
worker.onmessage = (evt) => {
if ("ok" in evt.data) {
resolve(evt.data.ok);
resolve(JSON.parse(evt.data.ok));
} else if ("err" in evt.data) {
if (evt.data.err instanceof Error) {
reject(evt.data.err);