mirror of
https://github.com/voltrevo/ValueScript.git
synced 2026-04-18 03:00:27 -04:00
Get things working using monaco-webpack-build
This commit is contained in:
170
website/src/playground/files.ts
Normal file
170
website/src/playground/files.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import nil from "./helpers/nil.ts";
|
||||
|
||||
function blockTrim(text: string) {
|
||||
let lines = text.split("\n");
|
||||
|
||||
while (lines.length > 0 && /^ *$/.test(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
while (lines.length > 0 && /^ *$/.test(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
let minIndent = Infinity;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = line.match(/^ */);
|
||||
|
||||
if (match === null || match[0].length >= minIndent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
minIndent = match[0].length;
|
||||
}
|
||||
|
||||
lines = lines.map((line) => line.slice(minIndent));
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
const files: Record<string, string | nil> = {
|
||||
"tutorial/hello.ts": blockTrim(`
|
||||
// Welcome to the ValueScript playground!
|
||||
//
|
||||
// This playground also acts as a tutorial by describing a variety of
|
||||
// examples. Please go ahead and make edits to the code, you should see
|
||||
// the results in real-time!
|
||||
//
|
||||
// Keeping with tradition, here is the hello world program.
|
||||
|
||||
export default function main() {
|
||||
return "Hello world!";
|
||||
}
|
||||
|
||||
// When you're ready, click the next arrow ('>') above to continue.
|
||||
`),
|
||||
|
||||
"tutorial/valueSemantics.ts": blockTrim(`
|
||||
export default function main() {
|
||||
const leftBowl = ['apple', 'mango'];
|
||||
|
||||
let rightBowl = leftBowl;
|
||||
rightBowl.push('peach');
|
||||
|
||||
return {
|
||||
leftBowl,
|
||||
rightBowl,
|
||||
};
|
||||
}
|
||||
|
||||
// In TypeScript, leftBowl also contains 'peach':
|
||||
//
|
||||
// {
|
||||
// leftBowl: ['apple', 'mango', 'peach'],
|
||||
// rightBowl: ['apple', 'mango', 'peach'],
|
||||
// }
|
||||
//
|
||||
// This is because TypeScript interprets the code to mean that leftBowl and
|
||||
// rightBowl are the same object, and that object changes.
|
||||
//
|
||||
// In ValueScript, objects do not change, but variables do. Pushing onto
|
||||
// rightBowl is interpreted as a change to the rightBowl variable itself,
|
||||
// not the data it points to. rightBowl points to some new data, which may
|
||||
// reference the old data, but only as a performance optimization.
|
||||
`),
|
||||
|
||||
"tutorial/factorial.ts": blockTrim(`
|
||||
export default function main() {
|
||||
return factorial(5);
|
||||
}
|
||||
|
||||
function factorial(n: number): number {
|
||||
if (n === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return n * factorial(n - 1);
|
||||
}
|
||||
`),
|
||||
|
||||
"tutorial/binaryTree.ts": blockTrim(`
|
||||
export default function main() {
|
||||
let tree = BinaryTree();
|
||||
|
||||
tree.insert(2);
|
||||
tree.insert(5);
|
||||
tree.insert(1);
|
||||
|
||||
const treeSnapshot = tree;
|
||||
|
||||
tree.insert(3);
|
||||
tree.insert(4);
|
||||
|
||||
return [treeSnapshot.toArray(), tree.toArray()];
|
||||
}
|
||||
|
||||
function BinaryTree() {
|
||||
type BinaryTree = {
|
||||
data: {
|
||||
value?: number,
|
||||
left?: BinaryTree,
|
||||
right?: BinaryTree,
|
||||
},
|
||||
insert(this: BinaryTree, newValue: number): void,
|
||||
toArray(this: BinaryTree): number[],
|
||||
};
|
||||
|
||||
let tree: BinaryTree = {
|
||||
data: {},
|
||||
insert: function(newValue) {
|
||||
if (this.data.value === undefined) {
|
||||
this.data.value = newValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue < this.data.value) {
|
||||
this.data.left ??= BinaryTree();
|
||||
this.data.left.insert(newValue);
|
||||
} else {
|
||||
this.data.right ??= BinaryTree();
|
||||
this.data.right.insert(newValue);
|
||||
}
|
||||
},
|
||||
toArray: function() {
|
||||
let res: number[] = [];
|
||||
|
||||
if (this.data.left) {
|
||||
res = cat(res, this.data.left.toArray());
|
||||
}
|
||||
|
||||
if (this.data.value !== undefined) {
|
||||
res.push(this.data.value);
|
||||
}
|
||||
|
||||
if (this.data.right) {
|
||||
res = cat(res, this.data.right.toArray());
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function cat(left: number[], right: number[]) {
|
||||
for (let i = 0; i < right.length; i++) {
|
||||
left.push(right[i]);
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
`),
|
||||
};
|
||||
|
||||
export default files;
|
||||
8
website/src/playground/helpers/assert.ts
Normal file
8
website/src/playground/helpers/assert.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default function assert(
|
||||
value: boolean,
|
||||
msg = "value was not true",
|
||||
): asserts value {
|
||||
if (value !== true) {
|
||||
throw new Error(`Assertion failed: ${msg}`);
|
||||
}
|
||||
}
|
||||
4
website/src/playground/helpers/nil.ts
Normal file
4
website/src/playground/helpers/nil.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
const nil = undefined;
|
||||
type nil = undefined;
|
||||
|
||||
export default nil;
|
||||
9
website/src/playground/helpers/notNil.ts
Normal file
9
website/src/playground/helpers/notNil.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import nil from "./nil.ts";
|
||||
|
||||
export default function notNil<T>(value: T | nil): T {
|
||||
if (value === nil) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1 +1,107 @@
|
||||
import * as monaco from "https://esm.sh/monaco-editor@0.34.1";
|
||||
import monacoPromise from "./monacoPromise.ts";
|
||||
|
||||
import files from "./files.ts";
|
||||
import assert from "./helpers/assert.ts";
|
||||
import nil from "./helpers/nil.ts";
|
||||
import notNil from "./helpers/notNil.ts";
|
||||
|
||||
function domQuery<T = HTMLElement>(query: string): T {
|
||||
return <T> <unknown> notNil(document.querySelector(query) ?? nil);
|
||||
}
|
||||
|
||||
const editorEl = domQuery("#editor");
|
||||
|
||||
const selectEl = domQuery<HTMLSelectElement>("#file-location select");
|
||||
const filePreviousEl = domQuery("#file-previous");
|
||||
const fileNextEl = domQuery("#file-next");
|
||||
|
||||
for (const filename of Object.keys(files)) {
|
||||
const option = document.createElement("option");
|
||||
option.textContent = filename;
|
||||
selectEl.appendChild(option);
|
||||
}
|
||||
|
||||
let currentFile = "";
|
||||
|
||||
editorEl.innerHTML = "";
|
||||
|
||||
monacoPromise.then((monaco) => {
|
||||
const editor = monaco.editor.create(editorEl, {
|
||||
theme: "vs-dark",
|
||||
value: "",
|
||||
language: "typescript",
|
||||
});
|
||||
|
||||
setTimeout(() => changeFile(location.hash.slice(1)));
|
||||
|
||||
globalThis.addEventListener("hashchange", () => {
|
||||
changeFile(location.hash.slice(1));
|
||||
});
|
||||
|
||||
globalThis.addEventListener("resize", () => editor.layout());
|
||||
|
||||
const model = notNil(editor.getModel() ?? nil);
|
||||
|
||||
model.updateOptions({ tabSize: 2, insertSpaces: true });
|
||||
|
||||
function changeFile(newFile: string) {
|
||||
if (currentFile === "") {
|
||||
currentFile = Object.keys(files)[0];
|
||||
} else if (newFile === currentFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newFile === "") {
|
||||
newFile = Object.keys(files)[0];
|
||||
}
|
||||
|
||||
const fileIdx = Object.keys(files).indexOf(newFile);
|
||||
|
||||
if (fileIdx !== -1) {
|
||||
currentFile = newFile;
|
||||
}
|
||||
|
||||
location.hash = currentFile;
|
||||
selectEl.selectedIndex = fileIdx;
|
||||
|
||||
const content = files[currentFile];
|
||||
assert(content !== nil);
|
||||
|
||||
model.setValue(content);
|
||||
}
|
||||
|
||||
selectEl.addEventListener("change", () => {
|
||||
changeFile(selectEl.value);
|
||||
});
|
||||
|
||||
const moveFileIndex = (change: number) => () => {
|
||||
const filenames = Object.keys(files);
|
||||
let idx = filenames.indexOf(currentFile);
|
||||
|
||||
if (idx === -1) {
|
||||
throw new Error("This should not happen");
|
||||
}
|
||||
|
||||
idx += change;
|
||||
idx = Math.max(idx, 0);
|
||||
idx = Math.min(idx, filenames.length - 1);
|
||||
|
||||
changeFile(filenames[idx]);
|
||||
};
|
||||
|
||||
filePreviousEl.addEventListener("click", moveFileIndex(-1));
|
||||
fileNextEl.addEventListener("click", moveFileIndex(1));
|
||||
|
||||
let timerId: undefined | number = undefined;
|
||||
|
||||
model.onDidChangeContent(() => {
|
||||
files[currentFile] = model.getValue();
|
||||
clearTimeout(timerId);
|
||||
|
||||
timerId = setTimeout(handleUpdate, 200);
|
||||
});
|
||||
|
||||
function handleUpdate() {
|
||||
console.log("TODO: handle update");
|
||||
}
|
||||
});
|
||||
|
||||
27
website/src/playground/monacoPromise.ts
Normal file
27
website/src/playground/monacoPromise.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/// <reference types="https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts" />
|
||||
|
||||
import * as MonacoImport from "https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts";
|
||||
// export * from "https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts";
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = "/monaco.bundle.js";
|
||||
document.head.append(script);
|
||||
|
||||
const monacoPromise = new Promise<typeof MonacoImport>((resolve, reject) => {
|
||||
script.onload = () => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const monaco = (globalThis as any).monaco;
|
||||
|
||||
if (monaco === undefined) {
|
||||
throw new Error("Missing monaco definition");
|
||||
}
|
||||
|
||||
resolve(monaco);
|
||||
};
|
||||
|
||||
script.onerror = (evt) => {
|
||||
reject(new Error(evt.toString()));
|
||||
};
|
||||
});
|
||||
|
||||
export default monacoPromise;
|
||||
3
website/src/playground/static/.gitignore
vendored
Normal file
3
website/src/playground/static/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.bundle.js
|
||||
*.bundle.js.*
|
||||
*.ttf
|
||||
Reference in New Issue
Block a user