run npm install to generate a package lock
This commit is contained in:
9
node_modules/@weborigami/language/src/runtime/EventTargetMixin.d.ts
generated
vendored
Normal file
9
node_modules/@weborigami/language/src/runtime/EventTargetMixin.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Mixin } from "../../index.ts";
|
||||
|
||||
declare const EventTargetMixin: Mixin<{
|
||||
addEventListener(type: string, listener: EventListener): void;
|
||||
dispatchEvent(event: Event): boolean;
|
||||
removeEventListener(type: string, listener: EventListener): void;
|
||||
}>;
|
||||
|
||||
export default EventTargetMixin;
|
||||
117
node_modules/@weborigami/language/src/runtime/EventTargetMixin.js
generated
vendored
Normal file
117
node_modules/@weborigami/language/src/runtime/EventTargetMixin.js
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
const listenersKey = Symbol("listeners");
|
||||
|
||||
export default function EventTargetMixin(Base) {
|
||||
// Based on https://github.com/piranna/EventTarget.js
|
||||
return class EventTarget extends Base {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this[listenersKey] = {};
|
||||
}
|
||||
|
||||
addEventListener(type, callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listenersOfType = this[listenersKey][type];
|
||||
if (!listenersOfType) {
|
||||
this[listenersKey][type] = [];
|
||||
listenersOfType = this[listenersKey][type];
|
||||
}
|
||||
|
||||
// Don't add the same callback twice.
|
||||
if (listenersOfType.includes(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
listenersOfType.push(callback);
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
if (!(event instanceof Event)) {
|
||||
throw TypeError("Argument to dispatchEvent must be an Event");
|
||||
}
|
||||
|
||||
let stopImmediatePropagation = false;
|
||||
let defaultPrevented = false;
|
||||
|
||||
if (!event.cancelable) {
|
||||
Object.defineProperty(event, "cancelable", {
|
||||
value: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
if (!event.defaultPrevented) {
|
||||
Object.defineProperty(event, "defaultPrevented", {
|
||||
get: () => defaultPrevented,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
// 2023-09-11: Setting isTrusted causes exception on Glitch
|
||||
// if (!event.isTrusted) {
|
||||
// Object.defineProperty(event, "isTrusted", {
|
||||
// value: false,
|
||||
// enumerable: true,
|
||||
// });
|
||||
// }
|
||||
if (!event.target) {
|
||||
Object.defineProperty(event, "target", {
|
||||
value: this,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
if (!event.timeStamp) {
|
||||
Object.defineProperty(event, "timeStamp", {
|
||||
value: new Date().getTime(),
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
event.preventDefault = function () {
|
||||
if (this.cancelable) {
|
||||
defaultPrevented = true;
|
||||
}
|
||||
};
|
||||
event.stopImmediatePropagation = function () {
|
||||
stopImmediatePropagation = true;
|
||||
};
|
||||
event.stopPropagation = function () {
|
||||
// This is a no-op because we don't support event bubbling.
|
||||
};
|
||||
|
||||
const type = event.type;
|
||||
const listenersOfType = this[listenersKey][type] || [];
|
||||
for (const listener of listenersOfType) {
|
||||
if (stopImmediatePropagation) {
|
||||
break;
|
||||
}
|
||||
listener.call(this, event);
|
||||
}
|
||||
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
|
||||
removeEventListener(type, callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listenersOfType = this[listenersKey][type];
|
||||
if (!listenersOfType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove callback from listeners.
|
||||
listenersOfType = listenersOfType.filter(
|
||||
(listener) => listener !== callback
|
||||
);
|
||||
|
||||
// If there are no more listeners for this type, remove the type.
|
||||
if (listenersOfType.length === 0) {
|
||||
delete this[listenersKey][type];
|
||||
} else {
|
||||
this[listenersKey][type] = listenersOfType;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
5
node_modules/@weborigami/language/src/runtime/HandleExtensionsTransform.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/language/src/runtime/HandleExtensionsTransform.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "../../index.ts";
|
||||
|
||||
declare const HandleExtensionsTransform: Mixin<{}>;
|
||||
|
||||
export default HandleExtensionsTransform;
|
||||
17
node_modules/@weborigami/language/src/runtime/HandleExtensionsTransform.js
generated
vendored
Normal file
17
node_modules/@weborigami/language/src/runtime/HandleExtensionsTransform.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { handleExtension } from "./handlers.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
||||
* @typedef {import("../../index.ts").UnpackFunction} FileUnpackFunction
|
||||
*
|
||||
* @param {AsyncTreeConstructor} Base
|
||||
*/
|
||||
export default function HandleExtensionsTransform(Base) {
|
||||
return class FileLoaders extends Base {
|
||||
async get(key) {
|
||||
const value = await super.get(key);
|
||||
return handleExtension(this, value, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
5
node_modules/@weborigami/language/src/runtime/ImportModulesMixin.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/language/src/runtime/ImportModulesMixin.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "../../index.ts";
|
||||
|
||||
declare const ImportModulesMixin: Mixin<{}>;
|
||||
|
||||
export default ImportModulesMixin;
|
||||
58
node_modules/@weborigami/language/src/runtime/ImportModulesMixin.js
generated
vendored
Normal file
58
node_modules/@weborigami/language/src/runtime/ImportModulesMixin.js
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { maybeOrigamiSourceCode } from "./errors.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Constructor<AsyncTree & { dirname: string }>} BaseConstructor
|
||||
* @param {BaseConstructor} Base
|
||||
*/
|
||||
export default function ImportModulesMixin(Base) {
|
||||
return class ImportModules extends Base {
|
||||
async import(...keys) {
|
||||
const filePath = path.join(this.dirname, ...keys);
|
||||
// On Windows, absolute paths must be valid file:// URLs.
|
||||
const fileUrl = pathToFileURL(filePath);
|
||||
const modulePath = fileUrl.href;
|
||||
|
||||
// Try to load the module.
|
||||
let obj;
|
||||
try {
|
||||
obj = await import(modulePath);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code !== "ERR_MODULE_NOT_FOUND") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Does the module exist as a file?
|
||||
try {
|
||||
await fs.stat(filePath);
|
||||
} catch (error) {
|
||||
// File doesn't exist
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Module exists, but we can't load it. Is the error internal?
|
||||
if (maybeOrigamiSourceCode(error.message)) {
|
||||
throw new Error(
|
||||
`Internal Origami error loading ${filePath}\n${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Error may be a syntax error, so we offer that as a hint.
|
||||
const message = `Error loading ${filePath}, possibly due to a syntax error.\n${error.message}`;
|
||||
throw new SyntaxError(message);
|
||||
}
|
||||
|
||||
if ("default" in obj) {
|
||||
// Module with a default export; return that.
|
||||
return obj.default;
|
||||
} else {
|
||||
// Module with multiple named exports. Cast from a module namespace
|
||||
// object to a plain object.
|
||||
return { ...obj };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
5
node_modules/@weborigami/language/src/runtime/InvokeFunctionsTransform.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/language/src/runtime/InvokeFunctionsTransform.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "../../index.ts";
|
||||
|
||||
declare const InvokeFunctionsTransform: Mixin<{}>;
|
||||
|
||||
export default InvokeFunctionsTransform;
|
||||
25
node_modules/@weborigami/language/src/runtime/InvokeFunctionsTransform.js
generated
vendored
Normal file
25
node_modules/@weborigami/language/src/runtime/InvokeFunctionsTransform.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* When using `get` to retrieve a value from a tree, if the value is a
|
||||
* function, invoke it and return the result.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.js").Constructor<AsyncTree>} AsyncTreeConstructor
|
||||
* @param {AsyncTreeConstructor} Base
|
||||
*/
|
||||
export default function InvokeFunctionsTransform(Base) {
|
||||
return class InvokeFunctions extends Base {
|
||||
async get(key) {
|
||||
let value = await super.get(key);
|
||||
if (typeof value === "function") {
|
||||
value = await value.call(this);
|
||||
|
||||
if (Tree.isAsyncTree(value) && !value.parent) {
|
||||
value.parent = this;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
11
node_modules/@weborigami/language/src/runtime/OrigamiFiles.d.ts
generated
vendored
Normal file
11
node_modules/@weborigami/language/src/runtime/OrigamiFiles.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { FileTree } from "@weborigami/async-tree";
|
||||
import EventTargetMixin from "./EventTargetMixin.js";
|
||||
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
||||
import ImportModulesMixin from "./ImportModulesMixin.js";
|
||||
import WatchFilesMixin from "./WatchFilesMixin.js";
|
||||
|
||||
export default class OrigamiFiles extends HandleExtensionsTransform(
|
||||
(
|
||||
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileTree)))
|
||||
)
|
||||
) {}
|
||||
9
node_modules/@weborigami/language/src/runtime/OrigamiFiles.js
generated
vendored
Normal file
9
node_modules/@weborigami/language/src/runtime/OrigamiFiles.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { FileTree } from "@weborigami/async-tree";
|
||||
import EventTargetMixin from "./EventTargetMixin.js";
|
||||
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
||||
import ImportModulesMixin from "./ImportModulesMixin.js";
|
||||
import WatchFilesMixin from "./WatchFilesMixin.js";
|
||||
|
||||
export default class OrigamiFiles extends HandleExtensionsTransform(
|
||||
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileTree)))
|
||||
) {}
|
||||
1
node_modules/@weborigami/language/src/runtime/ReadMe.md
generated
vendored
Normal file
1
node_modules/@weborigami/language/src/runtime/ReadMe.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Modules necessary to evaluate Origami expressions
|
||||
6
node_modules/@weborigami/language/src/runtime/TreeEvent.js
generated
vendored
Normal file
6
node_modules/@weborigami/language/src/runtime/TreeEvent.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export default class TreeEvent extends Event {
|
||||
constructor(type, options = {}) {
|
||||
super(type, options);
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
5
node_modules/@weborigami/language/src/runtime/WatchFilesMixin.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/language/src/runtime/WatchFilesMixin.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "../../index.ts";
|
||||
|
||||
declare const WatchFilesMixin: Mixin<{}>;
|
||||
|
||||
export default WatchFilesMixin;
|
||||
59
node_modules/@weborigami/language/src/runtime/WatchFilesMixin.js
generated
vendored
Normal file
59
node_modules/@weborigami/language/src/runtime/WatchFilesMixin.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import Watcher from "watcher";
|
||||
import TreeEvent from "./TreeEvent.js";
|
||||
|
||||
// Map of paths to trees used by watcher
|
||||
const pathTreeMap = new Map();
|
||||
|
||||
export default function WatchFilesMixin(Base) {
|
||||
return class WatchFiles extends Base {
|
||||
addEventListener(type, listener) {
|
||||
super.addEventListener(type, listener);
|
||||
if (type === "change") {
|
||||
this.watch();
|
||||
}
|
||||
}
|
||||
|
||||
onChange(key) {
|
||||
// Reset cached values.
|
||||
this.subfoldersMap = new Map();
|
||||
this.dispatchEvent(new TreeEvent("change", { key }));
|
||||
}
|
||||
|
||||
async unwatch() {
|
||||
if (!this.watching) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.watcher?.close();
|
||||
this.watching = false;
|
||||
}
|
||||
|
||||
// Turn on watching for the directory.
|
||||
async watch() {
|
||||
if (this.watching) {
|
||||
return;
|
||||
}
|
||||
this.watching = true;
|
||||
|
||||
// Ensure the directory exists.
|
||||
await fs.mkdir(this.dirname, { recursive: true });
|
||||
|
||||
this.watcher = new Watcher(this.dirname, {
|
||||
ignoreInitial: true,
|
||||
persistent: false,
|
||||
recursive: true,
|
||||
});
|
||||
this.watcher.on("all", (event, filePath) => {
|
||||
const key = path.basename(filePath);
|
||||
this.onChange(key);
|
||||
});
|
||||
|
||||
// Add to the list of FileTree instances watching this directory.
|
||||
const treeRefs = pathTreeMap.get(this.dirname) ?? [];
|
||||
treeRefs.push(new WeakRef(this));
|
||||
pathTreeMap.set(this.dirname, treeRefs);
|
||||
}
|
||||
};
|
||||
}
|
||||
19
node_modules/@weborigami/language/src/runtime/codeFragment.js
generated
vendored
Normal file
19
node_modules/@weborigami/language/src/runtime/codeFragment.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export default function codeFragment(location) {
|
||||
const { source, start, end } = location;
|
||||
|
||||
let fragment =
|
||||
start.offset < end.offset
|
||||
? source.text.slice(start.offset, end.offset)
|
||||
: // Use entire source
|
||||
source.text;
|
||||
|
||||
// Replace newlines and whitespace runs with a single space.
|
||||
fragment = fragment.replace(/(\n|\s\s+)+/g, " ");
|
||||
|
||||
// If longer than 80 characters, truncate with an ellipsis.
|
||||
if (fragment.length > 80) {
|
||||
fragment = fragment.slice(0, 80) + "…";
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
104
node_modules/@weborigami/language/src/runtime/errors.js
generated
vendored
Normal file
104
node_modules/@weborigami/language/src/runtime/errors.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Text we look for in an error stack to guess whether a given line represents a
|
||||
|
||||
import { scope as scopeFn, trailingSlash } from "@weborigami/async-tree";
|
||||
import codeFragment from "./codeFragment.js";
|
||||
import { typos } from "./typos.js";
|
||||
|
||||
// function in the Origami source code.
|
||||
const origamiSourceSignals = [
|
||||
"async-tree/src/",
|
||||
"language/src/",
|
||||
"origami/src/",
|
||||
"at Scope.evaluate",
|
||||
];
|
||||
|
||||
export async function builtinReferenceError(tree, builtins, key) {
|
||||
const messages = [
|
||||
`"${key}" is being called as if it were a builtin function, but it's not.`,
|
||||
];
|
||||
// See if the key is in scope (but not as a builtin)
|
||||
const scope = scopeFn(tree);
|
||||
const value = await scope.get(key);
|
||||
if (value === undefined) {
|
||||
const typos = await formatScopeTypos(builtins, key);
|
||||
messages.push(typos);
|
||||
} else {
|
||||
messages.push(`Use "${key}/" instead.`);
|
||||
}
|
||||
const message = messages.join(" ");
|
||||
return new ReferenceError(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an error for display in the console.
|
||||
*
|
||||
* @param {Error} error
|
||||
*/
|
||||
export function formatError(error) {
|
||||
let message;
|
||||
if (error.stack) {
|
||||
// Display the stack only until we reach the Origami source code.
|
||||
message = "";
|
||||
let lines = error.stack.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (maybeOrigamiSourceCode(line)) {
|
||||
break;
|
||||
}
|
||||
if (message) {
|
||||
message += "\n";
|
||||
}
|
||||
message += lines[i];
|
||||
}
|
||||
} else {
|
||||
message = error.toString();
|
||||
}
|
||||
|
||||
// Add location
|
||||
let location = /** @type {any} */ (error).location;
|
||||
if (location) {
|
||||
const fragment = codeFragment(location);
|
||||
let { source, start } = location;
|
||||
|
||||
message += `\nevaluating: ${fragment}`;
|
||||
if (typeof source === "object" && source.url) {
|
||||
message += `\n at ${source.url.href}:${start.line}:${start.column}`;
|
||||
} else if (source.text.includes("\n")) {
|
||||
message += `\n at line ${start.line}, column ${start.column}`;
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
export async function formatScopeTypos(scope, key) {
|
||||
const keys = await scopeTypos(scope, key);
|
||||
// Don't match deprecated keys
|
||||
const filtered = keys.filter((key) => !key.startsWith("@"));
|
||||
if (filtered.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const quoted = filtered.map((key) => `"${key}"`);
|
||||
const list = quoted.join(", ");
|
||||
return `Maybe you meant ${list}?`;
|
||||
}
|
||||
|
||||
export function maybeOrigamiSourceCode(text) {
|
||||
return origamiSourceSignals.some((signal) => text.includes(signal));
|
||||
}
|
||||
|
||||
export async function scopeReferenceError(scope, key) {
|
||||
const messages = [
|
||||
`"${key}" is not in scope.`,
|
||||
await formatScopeTypos(scope, key),
|
||||
];
|
||||
const message = messages.join(" ");
|
||||
return new ReferenceError(message);
|
||||
}
|
||||
|
||||
// Return all possible typos for `key` in scope
|
||||
async function scopeTypos(scope, key) {
|
||||
const scopeKeys = [...(await scope.keys())];
|
||||
const normalizedScopeKeys = scopeKeys.map((key) => trailingSlash.remove(key));
|
||||
const normalizedKey = trailingSlash.remove(key);
|
||||
return typos(normalizedKey, normalizedScopeKeys);
|
||||
}
|
||||
116
node_modules/@weborigami/language/src/runtime/evaluate.js
generated
vendored
Normal file
116
node_modules/@weborigami/language/src/runtime/evaluate.js
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Tree, isUnpackable, scope } from "@weborigami/async-tree";
|
||||
import codeFragment from "./codeFragment.js";
|
||||
import { ops } from "./internal.js";
|
||||
import { codeSymbol, scopeSymbol, sourceSymbol } from "./symbols.js";
|
||||
|
||||
/**
|
||||
* Evaluate the given code and return the result.
|
||||
*
|
||||
* `this` should be the tree used as the context for the evaluation.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {import("../../index.ts").Code} code
|
||||
*/
|
||||
export default async function evaluate(code) {
|
||||
const tree = this;
|
||||
|
||||
if (!(code instanceof Array)) {
|
||||
// Simple scalar; return as is.
|
||||
return code;
|
||||
}
|
||||
|
||||
let evaluated;
|
||||
const unevaluatedFns = [ops.lambda, ops.object, ops.literal];
|
||||
if (unevaluatedFns.includes(code[0])) {
|
||||
// Don't evaluate instructions, use as is.
|
||||
evaluated = code;
|
||||
} else {
|
||||
// Evaluate each instruction in the code.
|
||||
evaluated = await Promise.all(
|
||||
code.map((instruction) => evaluate.call(tree, instruction))
|
||||
);
|
||||
}
|
||||
|
||||
// The head of the array is a function or a tree; the rest are args or keys.
|
||||
let [fn, ...args] = evaluated;
|
||||
|
||||
if (!fn) {
|
||||
// The code wants to invoke something that's couldn't be found in scope.
|
||||
const error = ReferenceError(
|
||||
`${codeFragment(code[0].location)} is not defined`
|
||||
);
|
||||
/** @type {any} */ (error).location = code.location;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (isUnpackable(fn)) {
|
||||
// Unpack the object and use the result as the function or tree.
|
||||
fn = await fn.unpack();
|
||||
}
|
||||
|
||||
if (!Tree.isTreelike(fn)) {
|
||||
const text = fn.toString?.() ?? codeFragment(code[0].location);
|
||||
const error = new TypeError(
|
||||
`Not a callable function or tree: ${text.slice(0, 80)}`
|
||||
);
|
||||
/** @type {any} */ (error).location = code.location;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Execute the function or traverse the tree.
|
||||
let result;
|
||||
try {
|
||||
result =
|
||||
fn instanceof Function
|
||||
? await fn.call(tree, ...args) // Invoke the function
|
||||
: await Tree.traverseOrThrow(fn, ...args); // Traverse the tree.
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (!error.location) {
|
||||
// Attach the location of the code we tried to evaluate.
|
||||
error.location =
|
||||
error.position !== undefined && code[error.position + 1]?.location
|
||||
? // Use location of the argument with the given position (need to
|
||||
// offset by 1 to skip the function).
|
||||
code[error.position + 1]?.location
|
||||
: // Use overall location.
|
||||
code.location;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If the result is a tree, then the default parent of the tree is the current
|
||||
// tree.
|
||||
if (Tree.isAsyncTree(result) && !result.parent) {
|
||||
result.parent = tree;
|
||||
}
|
||||
|
||||
// To aid debugging, add the code to the result.
|
||||
if (Object.isExtensible(result)) {
|
||||
try {
|
||||
if (code.location && !result[sourceSymbol]) {
|
||||
Object.defineProperty(result, sourceSymbol, {
|
||||
value: codeFragment(code.location),
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
if (!result[codeSymbol]) {
|
||||
Object.defineProperty(result, codeSymbol, {
|
||||
value: code,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
if (!result[scopeSymbol]) {
|
||||
Object.defineProperty(result, scopeSymbol, {
|
||||
get() {
|
||||
return scope(this).trees;
|
||||
},
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
} catch (/** @type {any} */ error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
33
node_modules/@weborigami/language/src/runtime/expressionFunction.js
generated
vendored
Normal file
33
node_modules/@weborigami/language/src/runtime/expressionFunction.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
|
||||
import { evaluate } from "./internal.js";
|
||||
|
||||
/**
|
||||
* Given parsed Origami code, return a function that executes that code.
|
||||
*
|
||||
* @param {import("../../index.js").Code} code - parsed Origami expression
|
||||
* @param {string} [name] - optional name of the function
|
||||
*/
|
||||
export function createExpressionFunction(code, name) {
|
||||
/** @this {AsyncTree|null} */
|
||||
async function fn() {
|
||||
return evaluate.call(this, code);
|
||||
}
|
||||
if (name) {
|
||||
Object.defineProperty(fn, "name", { value: name });
|
||||
}
|
||||
fn.code = code;
|
||||
fn.toString = () => code.location.source.text;
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given object is a function that executes an Origami
|
||||
* expression.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is { code: Array }}
|
||||
*/
|
||||
export function isExpressionFunction(obj) {
|
||||
return typeof obj === "function" && obj.code;
|
||||
}
|
||||
120
node_modules/@weborigami/language/src/runtime/expressionObject.js
generated
vendored
Normal file
120
node_modules/@weborigami/language/src/runtime/expressionObject.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
import { extension, ObjectTree, symbols, Tree } from "@weborigami/async-tree";
|
||||
import { handleExtension } from "./handlers.js";
|
||||
import { evaluate, ops } from "./internal.js";
|
||||
|
||||
/**
|
||||
* Given an array of entries with string keys and Origami code values (arrays of
|
||||
* ops and operands), return an object with the same keys defining properties
|
||||
* whose getters evaluate the code.
|
||||
*
|
||||
* The value can take three forms:
|
||||
*
|
||||
* 1. A primitive value (string, etc.). This will be defined directly as an
|
||||
* object property.
|
||||
* 1. An immediate code entry. This will be evaluated during this call and its
|
||||
* result defined as an object property.
|
||||
* 1. A code entry that starts with ops.getter. This will be defined as a
|
||||
* property getter on the object.
|
||||
*
|
||||
* @param {*} entries
|
||||
* @param {import("@weborigami/types").AsyncTree | null} parent
|
||||
*/
|
||||
export default async function expressionObject(entries, parent) {
|
||||
// Create the object and set its parent
|
||||
const object = {};
|
||||
if (parent !== null && !Tree.isAsyncTree(parent)) {
|
||||
throw new TypeError(`Parent must be an AsyncTree or null`);
|
||||
}
|
||||
Object.defineProperty(object, symbols.parent, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: parent,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
let tree;
|
||||
const immediateProperties = [];
|
||||
for (let [key, value] of entries) {
|
||||
// Determine if we need to define a getter or a regular property. If the key
|
||||
// has an extension, we need to define a getter. If the value is code (an
|
||||
// array), we need to define a getter -- but if that code takes the form
|
||||
// [ops.getter, <primitive>], we can define a regular property.
|
||||
let defineProperty;
|
||||
const extname = extension.extname(key);
|
||||
if (extname) {
|
||||
defineProperty = false;
|
||||
} else if (!(value instanceof Array)) {
|
||||
defineProperty = true;
|
||||
} else if (value[0] === ops.getter && !(value[1] instanceof Array)) {
|
||||
defineProperty = true;
|
||||
value = value[1];
|
||||
} else {
|
||||
defineProperty = false;
|
||||
}
|
||||
|
||||
// If the key is wrapped in parentheses, it is not enumerable.
|
||||
let enumerable = true;
|
||||
if (key[0] === "(" && key[key.length - 1] === ")") {
|
||||
key = key.slice(1, -1);
|
||||
enumerable = false;
|
||||
}
|
||||
|
||||
if (defineProperty) {
|
||||
// Define simple property
|
||||
// object[key] = value;
|
||||
Object.defineProperty(object, key, {
|
||||
configurable: true,
|
||||
enumerable,
|
||||
value,
|
||||
writable: true,
|
||||
});
|
||||
} else {
|
||||
// Property getter
|
||||
let code;
|
||||
if (value[0] === ops.getter) {
|
||||
code = value[1];
|
||||
} else {
|
||||
immediateProperties.push(key);
|
||||
code = value;
|
||||
}
|
||||
|
||||
let get;
|
||||
if (extname) {
|
||||
// Key has extension, getter will invoke code then attach unpack method
|
||||
get = async () => {
|
||||
tree ??= new ObjectTree(object);
|
||||
const result = await evaluate.call(tree, code);
|
||||
return handleExtension(tree, result, key);
|
||||
};
|
||||
} else {
|
||||
// No extension, so getter just invokes code.
|
||||
get = async () => {
|
||||
tree ??= new ObjectTree(object);
|
||||
return evaluate.call(tree, code);
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(object, key, {
|
||||
configurable: true,
|
||||
enumerable,
|
||||
get,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate any properties that were declared as immediate: get their value
|
||||
// and overwrite the property getter with the actual value.
|
||||
for (const key of immediateProperties) {
|
||||
const value = await object[key];
|
||||
// @ts-ignore Unclear why TS thinks `object` might be undefined here
|
||||
const enumerable = Object.getOwnPropertyDescriptor(object, key).enumerable;
|
||||
Object.defineProperty(object, key, {
|
||||
configurable: true,
|
||||
enumerable,
|
||||
value,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
24
node_modules/@weborigami/language/src/runtime/functionResultsMap.js
generated
vendored
Normal file
24
node_modules/@weborigami/language/src/runtime/functionResultsMap.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { map, Tree } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* When using `get` to retrieve a value from a tree, if the value is a
|
||||
* function, invoke it and return the result.
|
||||
*/
|
||||
export default function functionResultsMap(treelike) {
|
||||
return map(treelike, {
|
||||
description: "functionResultsMap",
|
||||
|
||||
value: async (sourceValue, sourceKey, tree) => {
|
||||
let resultValue;
|
||||
if (typeof sourceValue === "function") {
|
||||
resultValue = await sourceValue.call(tree);
|
||||
if (Tree.isAsyncTree(resultValue) && !resultValue.parent) {
|
||||
resultValue.parent = tree;
|
||||
}
|
||||
} else {
|
||||
resultValue = sourceValue;
|
||||
}
|
||||
return resultValue;
|
||||
},
|
||||
});
|
||||
}
|
||||
110
node_modules/@weborigami/language/src/runtime/handlers.js
generated
vendored
Normal file
110
node_modules/@weborigami/language/src/runtime/handlers.js
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
box,
|
||||
extension,
|
||||
isPacked,
|
||||
isStringLike,
|
||||
isUnpackable,
|
||||
scope,
|
||||
symbols,
|
||||
trailingSlash,
|
||||
} from "@weborigami/async-tree";
|
||||
|
||||
/** @typedef {import("../../index.ts").ExtensionHandler} ExtensionHandler */
|
||||
|
||||
// Track extensions handlers for a given containing tree.
|
||||
const handlersForContainer = new Map();
|
||||
|
||||
/**
|
||||
* Find an extension handler for a file in the given container.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @param {AsyncTree} parent
|
||||
* @param {string} extension
|
||||
*/
|
||||
export async function getExtensionHandler(parent, extension) {
|
||||
let handlers = handlersForContainer.get(parent);
|
||||
if (handlers) {
|
||||
if (handlers[extension]) {
|
||||
return handlers[extension];
|
||||
}
|
||||
} else {
|
||||
handlers = {};
|
||||
handlersForContainer.set(parent, handlers);
|
||||
}
|
||||
|
||||
const handlerName = `${extension.slice(1)}.handler`;
|
||||
const parentScope = scope(parent);
|
||||
|
||||
/** @type {Promise<ExtensionHandler>} */
|
||||
let handlerPromise = parentScope
|
||||
?.get(handlerName)
|
||||
.then(async (extensionHandler) => {
|
||||
if (isUnpackable(extensionHandler)) {
|
||||
// The extension handler itself needs to be unpacked. E.g., if it's a
|
||||
// buffer containing JavaScript file, we need to unpack it to get its
|
||||
// default export.
|
||||
// @ts-ignore
|
||||
extensionHandler = await extensionHandler.unpack();
|
||||
}
|
||||
// Update cache with actual handler
|
||||
handlers[extension] = extensionHandler;
|
||||
return extensionHandler;
|
||||
});
|
||||
|
||||
// Cache handler even if it's undefined so we don't look it up again
|
||||
handlers[extension] = handlerPromise;
|
||||
|
||||
return handlerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given value is packed (e.g., buffer) and the key is a string-like path
|
||||
* that ends in an extension, search for a handler for that extension and, if
|
||||
* found, attach it to the value.
|
||||
*
|
||||
* @param {import("@weborigami/types").AsyncTree} parent
|
||||
* @param {any} value
|
||||
* @param {any} key
|
||||
*/
|
||||
export async function handleExtension(parent, value, key) {
|
||||
if (isPacked(value) && isStringLike(key) && value.unpack === undefined) {
|
||||
const hasSlash = trailingSlash.has(key);
|
||||
if (hasSlash) {
|
||||
key = trailingSlash.remove(key);
|
||||
}
|
||||
|
||||
// Special case: `.ori.<ext>` extensions are Origami documents.
|
||||
const extname = key.match(/\.ori\.\S+$/)
|
||||
? ".oridocument"
|
||||
: extension.extname(key);
|
||||
if (extname) {
|
||||
const handler = await getExtensionHandler(parent, extname);
|
||||
if (handler) {
|
||||
if (hasSlash && handler.unpack) {
|
||||
// Key like `data.json/` ends in slash -- unpack immediately
|
||||
return handler.unpack(value, { key, parent });
|
||||
}
|
||||
|
||||
// If the value is a primitive, box it so we can attach data to it.
|
||||
value = box(value);
|
||||
|
||||
if (handler.mediaType) {
|
||||
value.mediaType = handler.mediaType;
|
||||
}
|
||||
value[symbols.parent] = parent;
|
||||
|
||||
const unpack = handler.unpack;
|
||||
if (unpack) {
|
||||
// Wrap the unpack function so its only called once per value.
|
||||
let loadPromise;
|
||||
value.unpack = async () => {
|
||||
loadPromise ??= unpack(value, { key, parent });
|
||||
return loadPromise;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
15
node_modules/@weborigami/language/src/runtime/internal.js
generated
vendored
Normal file
15
node_modules/@weborigami/language/src/runtime/internal.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// The runtime includes a number of modules with circular dependencies. This
|
||||
// module exists to explicitly set the loading order for those modules. To
|
||||
// enforce use of this loading order, other modules should only load the modules
|
||||
// below via this module.
|
||||
//
|
||||
// About this pattern: https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
||||
//
|
||||
// Note: to avoid having VS Code auto-sort the imports, keep lines between them.
|
||||
|
||||
export * as ops from "./ops.js";
|
||||
|
||||
export { default as evaluate } from "./evaluate.js";
|
||||
|
||||
export * as expressionFunction from "./expressionFunction.js";
|
||||
43
node_modules/@weborigami/language/src/runtime/mergeTrees.js
generated
vendored
Normal file
43
node_modules/@weborigami/language/src/runtime/mergeTrees.js
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import { isPlainObject, isUnpackable, merge } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* Create a tree that's the result of merging the given trees.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {(Treelike|null)[]} trees
|
||||
*/
|
||||
export default async function mergeTrees(...trees) {
|
||||
// Filter out null or undefined trees.
|
||||
/** @type {Treelike[]}
|
||||
* @ts-ignore */
|
||||
const filtered = trees.filter((tree) => tree);
|
||||
|
||||
if (filtered.length === 1) {
|
||||
// Only one tree, no need to merge.
|
||||
return filtered[0];
|
||||
}
|
||||
|
||||
// Unpack any packed objects.
|
||||
const unpacked = await Promise.all(
|
||||
filtered.map((obj) =>
|
||||
isUnpackable(obj) ? /** @type {any} */ (obj).unpack() : obj
|
||||
)
|
||||
);
|
||||
|
||||
// If all trees are plain objects, return a plain object.
|
||||
if (unpacked.every((tree) => isPlainObject(tree))) {
|
||||
return Object.assign({}, ...unpacked);
|
||||
}
|
||||
|
||||
// If all trees are arrays, return an array.
|
||||
if (unpacked.every((tree) => Array.isArray(tree))) {
|
||||
return unpacked.flat();
|
||||
}
|
||||
|
||||
// Merge the trees.
|
||||
const result = merge(...unpacked);
|
||||
return result;
|
||||
}
|
||||
477
node_modules/@weborigami/language/src/runtime/ops.js
generated
vendored
Normal file
477
node_modules/@weborigami/language/src/runtime/ops.js
generated
vendored
Normal file
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*/
|
||||
|
||||
import {
|
||||
ObjectTree,
|
||||
Tree,
|
||||
isUnpackable,
|
||||
scope as scopeFn,
|
||||
concat as treeConcat,
|
||||
} from "@weborigami/async-tree";
|
||||
import os from "node:os";
|
||||
import { builtinReferenceError, scopeReferenceError } from "./errors.js";
|
||||
import expressionObject from "./expressionObject.js";
|
||||
import { evaluate } from "./internal.js";
|
||||
import mergeTrees from "./mergeTrees.js";
|
||||
import OrigamiFiles from "./OrigamiFiles.js";
|
||||
import { codeSymbol } from "./symbols.js";
|
||||
import taggedTemplate from "./taggedTemplate.js";
|
||||
|
||||
function addOpLabel(op, label) {
|
||||
Object.defineProperty(op, "toString", {
|
||||
value: () => label,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function addition(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
addOpLabel(addition, "«ops.addition»");
|
||||
|
||||
export function bitwiseAnd(a, b) {
|
||||
return a & b;
|
||||
}
|
||||
addOpLabel(bitwiseAnd, "«ops.bitwiseAnd»");
|
||||
|
||||
export function bitwiseNot(a) {
|
||||
return ~a;
|
||||
}
|
||||
addOpLabel(bitwiseNot, "«ops.bitwiseNot»");
|
||||
|
||||
export function bitwiseOr(a, b) {
|
||||
return a | b;
|
||||
}
|
||||
addOpLabel(bitwiseOr, "«ops.bitwiseOr»");
|
||||
|
||||
export function bitwiseXor(a, b) {
|
||||
return a ^ b;
|
||||
}
|
||||
addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
||||
|
||||
/**
|
||||
* Construct an array.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any[]} items
|
||||
*/
|
||||
export async function array(...items) {
|
||||
return items;
|
||||
}
|
||||
addOpLabel(array, "«ops.array»");
|
||||
|
||||
/**
|
||||
* Like ops.scope, but only searches for a builtin at the top of the scope
|
||||
* chain.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export async function builtin(key) {
|
||||
if (!this) {
|
||||
throw new Error("Tried to get the scope of a null or undefined tree.");
|
||||
}
|
||||
|
||||
const builtins = Tree.root(this);
|
||||
const value = await builtins.get(key);
|
||||
if (value === undefined) {
|
||||
throw await builtinReferenceError(this, builtins, key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript comma operator, returns the last argument.
|
||||
*
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
export function comma(...args) {
|
||||
return args.at(-1);
|
||||
}
|
||||
addOpLabel(comma, "«ops.comma»");
|
||||
|
||||
/**
|
||||
* Concatenate the given arguments.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any[]} args
|
||||
*/
|
||||
export async function concat(...args) {
|
||||
return treeConcat.call(this, args);
|
||||
}
|
||||
addOpLabel(concat, "«ops.concat»");
|
||||
|
||||
export async function conditional(condition, truthy, falsy) {
|
||||
return condition ? truthy() : falsy();
|
||||
}
|
||||
|
||||
export function division(a, b) {
|
||||
return a / b;
|
||||
}
|
||||
addOpLabel(division, "«ops.division»");
|
||||
|
||||
export function equal(a, b) {
|
||||
return a == b;
|
||||
}
|
||||
addOpLabel(equal, "«ops.equal»");
|
||||
|
||||
export function exponentiation(a, b) {
|
||||
return a ** b;
|
||||
}
|
||||
addOpLabel(exponentiation, "«ops.exponentiation»");
|
||||
|
||||
/**
|
||||
* Look up the given key as an external reference and cache the value for future
|
||||
* requests.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export async function external(key, cache) {
|
||||
if (key in cache) {
|
||||
return cache[key];
|
||||
}
|
||||
// First save a promise for the value
|
||||
const promise = scope.call(this, key);
|
||||
cache[key] = promise;
|
||||
const value = await promise;
|
||||
// Now update with the actual value
|
||||
cache[key] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This op is only used during parsing. It signals to ops.object that the
|
||||
* "arguments" of the expression should be used to define a property getter.
|
||||
*/
|
||||
export const getter = new String("«ops.getter»");
|
||||
|
||||
export function greaterThan(a, b) {
|
||||
return a > b;
|
||||
}
|
||||
addOpLabel(greaterThan, "«ops.greaterThan»");
|
||||
|
||||
export function greaterThanOrEqual(a, b) {
|
||||
return a >= b;
|
||||
}
|
||||
addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
||||
|
||||
/**
|
||||
* Files tree for the user's home directory.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export async function homeDirectory() {
|
||||
const tree = new OrigamiFiles(os.homedir());
|
||||
tree.parent = this ? Tree.root(this) : null;
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the parent's scope -- i.e., exclude the current tree -- for the given
|
||||
* key.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {*} key
|
||||
*/
|
||||
export async function inherited(key) {
|
||||
if (!this?.parent) {
|
||||
return undefined;
|
||||
}
|
||||
const parentScope = scopeFn(this.parent);
|
||||
return parentScope.get(key);
|
||||
}
|
||||
addOpLabel(inherited, "«ops.inherited»");
|
||||
|
||||
/**
|
||||
* Return a function that will invoke the given code.
|
||||
*
|
||||
* @typedef {import("../../index.ts").Code} Code
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string[]} parameters
|
||||
* @param {Code} code
|
||||
*/
|
||||
export function lambda(parameters, code) {
|
||||
const context = this;
|
||||
|
||||
/** @this {Treelike|null} */
|
||||
async function invoke(...args) {
|
||||
let target;
|
||||
if (parameters.length === 0) {
|
||||
// No parameters
|
||||
target = context;
|
||||
} else {
|
||||
// Add arguments to scope.
|
||||
const ambients = {};
|
||||
for (const parameter of parameters) {
|
||||
ambients[parameter] = args.shift();
|
||||
}
|
||||
Object.defineProperty(ambients, codeSymbol, {
|
||||
value: code,
|
||||
enumerable: false,
|
||||
});
|
||||
const ambientTree = new ObjectTree(ambients);
|
||||
ambientTree.parent = context;
|
||||
target = ambientTree;
|
||||
}
|
||||
|
||||
let result = await evaluate.call(target, code);
|
||||
|
||||
// Bind a function result to the ambients so that it has access to the
|
||||
// parameter values -- i.e., like a closure.
|
||||
if (result instanceof Function) {
|
||||
const resultCode = result.code;
|
||||
result = result.bind(target);
|
||||
if (code) {
|
||||
// Copy over Origami code
|
||||
result.code = resultCode;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// We set the `length` property on the function so that Tree.traverseOrThrow()
|
||||
// will correctly identify how many parameters it wants. This is unorthodox
|
||||
// but doesn't appear to affect other behavior.
|
||||
const fnLength = parameters.length;
|
||||
Object.defineProperty(invoke, "length", {
|
||||
value: fnLength,
|
||||
});
|
||||
|
||||
invoke.code = code;
|
||||
return invoke;
|
||||
}
|
||||
addOpLabel(lambda, "«ops.lambda");
|
||||
|
||||
export function lessThan(a, b) {
|
||||
return a < b;
|
||||
}
|
||||
addOpLabel(lessThan, "«ops.lessThan»");
|
||||
|
||||
export function lessThanOrEqual(a, b) {
|
||||
return a <= b;
|
||||
}
|
||||
addOpLabel(lessThanOrEqual, "«ops.lessThanOrEqual»");
|
||||
|
||||
/**
|
||||
* Return a primitive value
|
||||
*/
|
||||
export async function literal(value) {
|
||||
return value;
|
||||
}
|
||||
addOpLabel(literal, "«ops.literal»");
|
||||
|
||||
/**
|
||||
* Logical AND operator
|
||||
*/
|
||||
export async function logicalAnd(head, ...tail) {
|
||||
if (!head) {
|
||||
return head;
|
||||
}
|
||||
// Evaluate the tail arguments in order, short-circuiting if any are falsy.
|
||||
let lastValue;
|
||||
for (const arg of tail) {
|
||||
lastValue = arg instanceof Function ? await arg() : arg;
|
||||
if (!lastValue) {
|
||||
return lastValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the last value (not `true`)
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical NOT operator
|
||||
*/
|
||||
export async function logicalNot(value) {
|
||||
return !value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical OR operator
|
||||
*/
|
||||
export async function logicalOr(head, ...tail) {
|
||||
if (head) {
|
||||
return head;
|
||||
}
|
||||
|
||||
// Evaluate the tail arguments in order, short-circuiting if any are truthy.
|
||||
let lastValue;
|
||||
for (const arg of tail) {
|
||||
lastValue = arg instanceof Function ? await arg() : arg;
|
||||
if (lastValue) {
|
||||
return lastValue;
|
||||
}
|
||||
}
|
||||
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given trees. If they are all plain objects, return a plain object.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {import("@weborigami/async-tree").Treelike[]} trees
|
||||
*/
|
||||
export async function merge(...trees) {
|
||||
return mergeTrees.call(this, ...trees);
|
||||
}
|
||||
addOpLabel(merge, "«ops.merge»");
|
||||
|
||||
export function multiplication(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
addOpLabel(multiplication, "«ops.multiplication»");
|
||||
|
||||
export function notEqual(a, b) {
|
||||
return a != b;
|
||||
}
|
||||
addOpLabel(notEqual, "«ops.notEqual»");
|
||||
|
||||
export function notStrictEqual(a, b) {
|
||||
return a !== b;
|
||||
}
|
||||
addOpLabel(notStrictEqual, "«ops.notStrictEqual»");
|
||||
|
||||
/**
|
||||
* Nullish coalescing operator
|
||||
*/
|
||||
export async function nullishCoalescing(head, ...tail) {
|
||||
if (head != null) {
|
||||
return head;
|
||||
}
|
||||
|
||||
let lastValue;
|
||||
for (const arg of tail) {
|
||||
lastValue = arg instanceof Function ? await arg() : arg;
|
||||
if (lastValue != null) {
|
||||
return lastValue;
|
||||
}
|
||||
}
|
||||
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an object. The keys will be the same as the given `obj`
|
||||
* parameter's, and the values will be the results of evaluating the
|
||||
* corresponding code values in `obj`.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any[]} entries
|
||||
*/
|
||||
export async function object(...entries) {
|
||||
return expressionObject(entries, this);
|
||||
}
|
||||
addOpLabel(object, "«ops.object»");
|
||||
|
||||
export function remainder(a, b) {
|
||||
return a % b;
|
||||
}
|
||||
addOpLabel(remainder, "«ops.remainder»");
|
||||
|
||||
/**
|
||||
* Files tree for the filesystem root.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export async function rootDirectory(key) {
|
||||
let tree = new OrigamiFiles("/");
|
||||
// We set the builtins as the parent because logically the filesystem root is
|
||||
// outside the project. This ignores the edge case where the project itself is
|
||||
// the root of the filesystem and has a config file.
|
||||
tree.parent = this ? Tree.root(this) : null;
|
||||
return key ? tree.get(key) : tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the given key in the scope for the current tree.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export async function scope(key) {
|
||||
if (!this) {
|
||||
throw new Error("Tried to get the scope of a null or undefined tree.");
|
||||
}
|
||||
const scope = scopeFn(this);
|
||||
const value = await scope.get(key);
|
||||
if (value === undefined && key !== "undefined") {
|
||||
throw await scopeReferenceError(scope, key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
addOpLabel(scope, "«ops.scope»");
|
||||
|
||||
export function shiftLeft(a, b) {
|
||||
return a << b;
|
||||
}
|
||||
addOpLabel(shiftLeft, "«ops.shiftLeft»");
|
||||
|
||||
export function shiftRightSigned(a, b) {
|
||||
return a >> b;
|
||||
}
|
||||
addOpLabel(shiftRightSigned, "«ops.shiftRightSigned»");
|
||||
|
||||
export function shiftRightUnsigned(a, b) {
|
||||
return a >>> b;
|
||||
}
|
||||
addOpLabel(shiftRightUnsigned, "«ops.shiftRightUnsigned»");
|
||||
|
||||
/**
|
||||
* The spread operator is a placeholder during parsing. It should be replaced
|
||||
* with an object merge.
|
||||
*/
|
||||
export function spread(...args) {
|
||||
throw new Error(
|
||||
"Internal error: a spread operation wasn't compiled correctly."
|
||||
);
|
||||
}
|
||||
addOpLabel(spread, "«ops.spread»");
|
||||
|
||||
export function strictEqual(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
addOpLabel(strictEqual, "«ops.strictEqual»");
|
||||
|
||||
export function subtraction(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
addOpLabel(subtraction, "«ops.subtraction»");
|
||||
|
||||
/**
|
||||
* Apply the default tagged template function.
|
||||
*/
|
||||
export function template(strings, ...values) {
|
||||
return taggedTemplate(strings, values);
|
||||
}
|
||||
addOpLabel(template, "«ops.template»");
|
||||
|
||||
/**
|
||||
* Traverse a path of keys through a tree.
|
||||
*/
|
||||
export const traverse = Tree.traverseOrThrow;
|
||||
|
||||
export function unaryMinus(a) {
|
||||
return -a;
|
||||
}
|
||||
addOpLabel(unaryMinus, "«ops.unaryMinus»");
|
||||
|
||||
export function unaryPlus(a) {
|
||||
return +a;
|
||||
}
|
||||
addOpLabel(unaryPlus, "«ops.unaryPlus»");
|
||||
|
||||
/**
|
||||
* If the value is packed but has an unpack method, call it and return that as
|
||||
* the result; otherwise, return the value as is.
|
||||
*
|
||||
* @param {any} value
|
||||
*/
|
||||
export async function unpack(value) {
|
||||
return isUnpackable(value) ? value.unpack() : value;
|
||||
}
|
||||
3
node_modules/@weborigami/language/src/runtime/symbols.js
generated
vendored
Normal file
3
node_modules/@weborigami/language/src/runtime/symbols.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export const codeSymbol = Symbol("code");
|
||||
export const scopeSymbol = Symbol("scope");
|
||||
export const sourceSymbol = Symbol("source");
|
||||
9
node_modules/@weborigami/language/src/runtime/taggedTemplate.js
generated
vendored
Normal file
9
node_modules/@weborigami/language/src/runtime/taggedTemplate.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Default JavaScript tagged template function splices strings and values
|
||||
// together.
|
||||
export default function defaultTemplateJoin(strings, values) {
|
||||
let result = strings[0];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
result += values[i] + strings[i + 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
71
node_modules/@weborigami/language/src/runtime/typos.js
generated
vendored
Normal file
71
node_modules/@weborigami/language/src/runtime/typos.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Returns true if the two strings have a Damerau-Levenshtein distance of 1.
|
||||
* This will be true if the strings differ by a single insertion, deletion,
|
||||
* substitution, or transposition.
|
||||
*
|
||||
* @param {string} s1
|
||||
* @param {string} s2
|
||||
*/
|
||||
export function isTypo(s1, s2) {
|
||||
const length1 = s1.length;
|
||||
const length2 = s2.length;
|
||||
|
||||
// If the strings are identical, distance is 0
|
||||
if (s1 === s2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If length difference is more than 1, distance can't be 1
|
||||
if (Math.abs(length1 - length2) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length1 === length2) {
|
||||
// Check for one substitution
|
||||
let differences = 0;
|
||||
for (let i = 0; i < length1; i++) {
|
||||
if (s1[i] !== s2[i]) {
|
||||
differences++;
|
||||
if (differences > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (differences === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for one transposition
|
||||
for (let i = 0; i < length1 - 1; i++) {
|
||||
if (s1[i] !== s2[i]) {
|
||||
// Check if swapping s1[i] and s1[i+1] matches s2
|
||||
if (s1[i] === s2[i + 1] && s1[i + 1] === s2[i]) {
|
||||
return s1.slice(i + 2) === s2.slice(i + 2);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for one insertion/deletion
|
||||
const longer = length1 > length2 ? s1 : s2;
|
||||
const shorter = length1 > length2 ? s2 : s1;
|
||||
for (let i = 0; i < shorter.length; i++) {
|
||||
if (shorter[i] !== longer[i]) {
|
||||
// If we skip this character, do the rest match?
|
||||
return shorter.slice(i) === longer.slice(i + 1);
|
||||
}
|
||||
}
|
||||
return shorter === longer.slice(0, shorter.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any strings that could be a typo of s
|
||||
*
|
||||
* @param {string} s
|
||||
* @param {string[]} strings
|
||||
*/
|
||||
export function typos(s, strings) {
|
||||
return strings.filter((str) => isTypo(s, str));
|
||||
}
|
||||
Reference in New Issue
Block a user