deleight
Version:
A library with 9 modules for writing more expressive web applications with traditional HTML, CSS and JavaScript.
599 lines (598 loc) • 19.9 kB
JavaScript
;
/**
* Exports the {@link Action} type along with many useful {@link Step}
* types which are used to create 'live' functions.
*
* Actions are very expressive, more controllable and mutable almost
* without any performance penalty. The flow is easy to track as it is
* declarative. You can create new step types which interpret your code
* at speed.
*
* Actions can be used for:
*
* 1. regular code - You can use Actions to implement common patterns
* like function chaining and passing the same arguments to multiple functions.
*
* 2. declarative programming - You can build up code for the same action in
* multiple places, allowing you to be more declarative in how you write code.
* This is a powerful feature for reactivity.
*
* 3. implementing other libraries that will generate code on the fly - You can
* create and modify actions easily without parsing and compiling from
* string (unlike Function constructor). This allows you to maintain a consistently
* good performance throughout your code.
*
* 4. custom DSLs - You can write your own steps to interpret code however you
* like. for example your own markup language
*
* 5. etc - You can probably do other cool things with Actions that we havent yet
* to mention or envisage.
*
* Actions can be used in any JavaScript environment as it has no
* platform dependencies. It is of course performant and lightweight.
*
* Currently this module has not been fully tested.
*
* @module
*
*/
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SetStep = exports.Getr = exports.G = exports.getr = exports.g = exports.GetStep = exports.One = exports.O = exports.one = exports.o = exports.OneStep = exports.Many = exports.M = exports.many = exports.m = exports.ManyStep = exports.Args = exports.A = exports.args = exports.a = exports.ArgsStep = exports.f = exports.func = exports.F = exports.Func = exports.FuncStep = exports.Pipe = exports.P = exports.pipe = exports.p = exports.PipeStep = exports.Withr = exports.W = exports.withr = exports.w = exports.WithStep = exports.fn = exports.FunctionValue = exports.Run = exports.R = exports.run = exports.r = exports.defineSteps = exports.Step = exports.$ = exports.Closer = exports.action = exports.Action = exports.isAsyncGenerator = exports.isGenerator = void 0;
exports.template = exports.StepTemplate = exports.e = exports.est = exports.E = exports.Est = exports.EarlyStep = exports.Nullr = exports.N = exports.nullr = exports.n = exports.NullStep = exports.Delr = exports.D = exports.delr = exports.d = exports.DeleteStep = exports.Callr = exports.C = exports.callr = exports.c = exports.CallStep = exports.Setr = exports.S = exports.setr = exports.s = void 0;
const deep_js_1 = require("../object/member/deep/deep.js");
function isGenerator(value) {
return value && value[Symbol.iterator]?.() === value;
}
exports.isGenerator = isGenerator;
function isAsyncGenerator(value) {
return value && value[Symbol.asyncIterator]?.() === value;
}
exports.isAsyncGenerator = isAsyncGenerator;
class Action {
constructor(values, arrayScope) {
this.values = values;
if (arrayScope)
this.arrayScope = arrayScope;
}
getValues(scope, ...args) {
return Reflect.has(this.values, Symbol.iterator) ? this.values[Symbol.iterator]() : Object.values(this.values);
}
getScope(...args) {
return (this.arrayScope ? [] : {});
}
start(scope, ...args) {
const env = {
values: this.getValues(scope, ...args),
scope, args
};
// nb: First item must be a runner.
return { index: 0, runner: env.values.next().value, env };
}
*genWith(scope, ...args) {
const run = this.start(scope, ...args);
if (run) {
yield* run.runner.run(run.env);
}
}
*gen(...args) {
return this.genWith(this.getScope(...args), ...args);
}
callWith(scope, ...args) {
for (let value of this.genWith(scope, ...args)) {
return value;
}
}
call(...args) {
return this.callWith(this.getScope(...args), ...args);
}
}
exports.Action = Action;
function action(code) {
return new Action(code);
}
exports.action = action;
class Closer {
constructor(runner) {
this.runner = runner;
}
}
exports.Closer = Closer;
/**
* Used directly to end the last step or called with a step to
* end that step.
*
* @example
*
*/
function $(runner) {
return new Closer(runner);
}
exports.$ = $;
/**
* Interpretes values and returns replacement values.
*
* An action is simply an array of different values. Steps are some
* of these values that provide interpretations for the values following
* them (up until the next termination step or the end of the action).
*
* A simple policy is adopted to resolve situations when a step encounters
* another while collecting the values it runs with:
*
* 1. If the next step has a `priority` of `0`, the current step, along with any others it is nested
* within, finish immediately.
*
* 2. If instead the next step has a priority of `1`, it will be started and any values
* it `yield`s will be collected by the preceding step as part of the values it
* uses to run.
*
* To force a step to stop collecting values, esplicitly terminate with the `$`
* function value. If the step we want to stop has nested steps which are still
* collecting values, simply call the `$` function with the step.
*
* @example
*
*
*/
class Step {
constructor(priority = 0) {
this.priority = priority;
}
/**
* Collect all the values used in the step and pass to
* {@link Step#runWith}. Then replace the collected values
* with those returned by {@link Step#runWith}.
*
* This method concludes by starting the next step, if there is
* one. Not starting the next step can cause the action to end
* prematurely. This should be noted if overriding this method in
* a subclass. It is best to override {@link Step#runWith} if you
* just want to implement the step's own behaviour.
*
* @example
*
*
* @param env
* @param parent
*/
*run(env, parent) {
yield* this.runWith(env, this.getValues(env));
const endBy = this.endBy;
delete this.endBy;
if ((endBy instanceof Closer && endBy.runner !== this) || endBy instanceof Step) {
if (parent)
return endBy; // parent receives it like a normal value
else if (endBy instanceof Step)
yield* endBy.run(env);
}
}
*getValue(value, env) {
if (value instanceof Closer || (value instanceof Step && !value.priority)) {
if (value instanceof Step || value.runner !== this)
return value;
else
return $;
}
else if (value instanceof Step) {
// run and yield this runner and every higher priority runners the run yields.
// then return whatever finished the runner (except '$' which disappears);
const newStatements = value.run(env, this);
let newStatement, newResult;
const newEnv = { values: newStatements, scope: env.scope, args: env.args };
while (!(newStatement = newStatements.next()).done) {
newResult = yield* this.getValue(newStatement.value, newEnv);
if (newResult !== undefined)
newStatements.return(newResult);
}
if (newStatement.value !== undefined)
return newStatement.value;
}
else if (value === $) {
return $;
}
else {
yield value;
}
}
*getValues(env) {
let item, endBy;
while (!(item = env.values.next()).done) {
endBy = yield* this.getValue(item.value, env);
if (endBy !== undefined) {
if (endBy !== $)
this.endBy = endBy;
return;
}
}
}
/**
* Default execution is to call all function values in parallel and/or
* yield any generators. The final result from calling the functions will
* also be yield*ed if it is not undefined. All other values, along with
* non-generator function return values are used to build the args for
* the next function encountered. Will also yield* generators returned by
* fuctions.
*
* @example
*
*
* @param env
* @param values
*/
*runWith(env, values) {
yield* values;
}
}
exports.Step = Step;
function defineSteps(type) {
const runner = new type(), // normal lower priority for next value
runner1 = new type(1); // force higher priority
return [runner1, runner1, runner, runner];
}
exports.defineSteps = defineSteps;
_a = defineSteps(Step), exports.r = _a[0], exports.run = _a[1], exports.R = _a[2], exports.Run = _a[3]; // literal runners
class FunctionValue {
}
exports.FunctionValue = FunctionValue;
/**
* Call to wrap functions to use them as literal arguments in WithStep
* and PipeStep which interpret functions specially.
*
* @param f
* @returns
*/
function fn(f) {
const f2 = new FunctionValue();
f2.value = f;
return f2;
}
exports.fn = fn;
/**
* WithStep executes by calling any function values with the
* scope and main args as the first 2 arguments followed by any
* further arguments built within the step.
*
* Where a generator is encountered, either among the values or as a function
* return value, it will immediately be yielded and 'forgotten'.
*
* The final result from calling the functions will be yield*ed
* if it is not undefined.
*
* All yielded values effectively replace the step
* and all its input values in the list of values used in the rest of the action
*
* All other values are used to build the args for
* the next function encountered.
*
* @example
*
*
*/
class WithStep extends Step {
*runWith(env, values) {
let args = [], result;
for (let value of values) {
if (isGenerator(value))
yield* value;
else if (value instanceof Function) {
result = value(env.scope, env.args, ...args);
if (isGenerator(result))
result = yield* result;
if (result !== undefined)
args.push(result);
}
else if (value !== undefined) {
args.push(value instanceof FunctionValue ? value.value : value);
}
}
if (result !== undefined)
yield result;
}
}
exports.WithStep = WithStep;
_b = defineSteps(WithStep), exports.w = _b[0], exports.withr = _b[1], exports.W = _b[2], exports.Withr = _b[3];
/**
* The pipe step used to interprete values as chained functions (or extra
* arguments to them).
*
* @example
*
*/
class PipeStep extends Step {
*runWith(env, values) {
let args = env.args, result;
for (let value of values) {
if (value instanceof Function) {
args = [result = value(...args)];
}
else if (value !== undefined && args instanceof Array) {
args.push(value instanceof FunctionValue ? value.value : value);
}
}
if (result)
yield result;
}
}
exports.PipeStep = PipeStep;
_c = defineSteps(PipeStep), exports.p = _c[0], exports.pipe = _c[1], exports.P = _c[2], exports.Pipe = _c[3];
/**
* A special step which enables a user to create
* on-the-fly steps by initializing with an interpreter
* function.
*
* @example
*
*
*/
class FuncStep extends Step {
constructor(interpreter, priority = 0) {
super(priority);
this.interpreter = interpreter;
}
*runWith(env, values) {
const result = this.interpreter(values, env);
if (result !== undefined) {
for (let value of result)
yield value;
}
}
}
exports.FuncStep = FuncStep;
function Func(interpreter) {
return new FuncStep(interpreter, 0);
}
exports.Func = Func;
exports.F = Func;
function func(interpreter) {
return new FuncStep(interpreter, 1);
}
exports.func = func;
exports.f = Func;
/**
* Values are fetched from the action arguments during this step.
*
* @example
*
*/
class ArgsStep extends Step {
*runWith(env, values) {
let count = 0;
const args = Array.from(env.args);
for (let value of values) {
yield args[value];
count++;
}
if (count === 0)
for (let arg of env.args)
yield arg;
}
}
exports.ArgsStep = ArgsStep;
_d = defineSteps(ArgsStep), exports.a = _d[0], exports.args = _d[1], exports.A = _d[2], exports.Args = _d[3];
/**
* A single action value (iterable) is spread into multiple action values
* during this step.
*
* @example
*
*/
class ManyStep extends Step {
*runWith(env, values) {
for (let value of values) {
if (Reflect.has(value, Symbol.iterator))
yield* value;
else if (typeof value === 'object') {
for (let key of Reflect.ownKeys(value)) {
yield [key, value[key]];
}
}
}
}
}
exports.ManyStep = ManyStep;
_e = defineSteps(ManyStep), exports.m = _e[0], exports.many = _e[1], exports.M = _e[2], exports.Many = _e[3];
/**
* Multiple action values are collected into a single value (iterable)
* during this step.
*
* @example
*/
class OneStep extends Step {
*runWith(env, values) {
yield values;
}
}
exports.OneStep = OneStep;
_f = defineSteps(OneStep), exports.o = _f[0], exports.one = _f[1], exports.O = _f[2], exports.One = _f[3];
/**
* Properties are fetched from the scope or specified objects during
* this step. The step can fetch properties at any depth. Also the
* scope itself can be returned.
*
* @example
*
*/
class GetStep extends Step {
*runWith(env, values) {
let count = 0, key = '';
for (let value of values) {
if (count === 0)
key = value; // set the key to get
else
yield (0, deep_js_1.get)(value, key); // get from value object
count++;
}
if (count === 0)
yield env.scope; // get the scope
else if (count === 1)
yield (0, deep_js_1.get)(env.scope, key); // get from the scope
}
}
exports.GetStep = GetStep;
_g = defineSteps(GetStep), exports.g = _g[0], exports.getr = _g[1], exports.G = _g[2], exports.Getr = _g[3];
/**
* Properties are set on the scope or specified objects during
* this step. The step can set properties at any depth. Also the
* scope itself can be set
*
* @example
*
*/
class SetStep extends Step {
*runWith(env, values) {
let count = 0, key = '', valueToSet;
let value;
for (value of values) {
if (count === 0)
key = value; // set the key
else if (count === 1)
valueToSet = value; // set the value
else
yield (0, deep_js_1.set)(value, key, valueToSet); // set on value object
count++;
}
if (count === 1)
yield env.scope = key; // we just wanted to set the scope. the key is actually the object.
else if (count === 2)
yield (0, deep_js_1.set)(env.scope, key, valueToSet); // set on scope.
}
}
exports.SetStep = SetStep;
_h = defineSteps(SetStep), exports.s = _h[0], exports.setr = _h[1], exports.S = _h[2], exports.Setr = _h[3];
/**
* Methods are called on the scope or specified objects during
* this step. The step can call methods at any depth.
*
* @example
*
*/
class CallStep extends Step {
*runWith(env, values) {
let count = 0, key = '', args = [];
let value;
for (value of values) {
if (count === 0)
key = value; // set the key
else if (count === 1)
args = value; // set the args
else
yield (0, deep_js_1.call)(value, key, ...args); // call on value object
count++;
}
if (count === 1)
yield (0, deep_js_1.call)(env.scope, key); // call on scope with no args.
else if (count === 2)
yield (0, deep_js_1.call)(env.scope, key, ...args); // call on scope with args.
}
}
exports.CallStep = CallStep;
_j = defineSteps(CallStep), exports.c = _j[0], exports.callr = _j[1], exports.C = _j[2], exports.Callr = _j[3];
/**
* Properties are deleted from the scope or specified objects during
* this step. The step can delete properties at any depth. Also the
* scope itself can be deleted
*
* @example
*
*/
class DeleteStep extends Step {
*runWith(env, values) {
let count = 0, key = '';
for (let value of values) {
if (count === 0)
key = value; // set the key to delete
else
(0, deep_js_1.del)(value, key); // delete from value object
count++;
}
if (count === 0)
delete env.scope; // delete the scope
else if (count === 1)
(0, deep_js_1.del)(env.scope, key); // delete from the scope
}
}
exports.DeleteStep = DeleteStep;
_k = defineSteps(DeleteStep), exports.d = _k[0], exports.delr = _k[1], exports.D = _k[2], exports.Delr = _k[3];
/**
* Used to silence the values yielded by nested steps by simply doing
* nothing. This allows us to perform 'side-effect' operations from
* within another step which yield values we don't want to send back to the
* previous step.
*
* @example
*
*/
class NullStep extends Step {
*runWith(env, values) {
}
}
exports.NullStep = NullStep;
_l = defineSteps(NullStep), exports.n = _l[0], exports.nullr = _l[1], exports.N = _l[2], exports.Nullr = _l[3];
/**
* A special step which joins multiple steps so that the values
* of earlier steps are built from the values from the subsequent ones.
*
* It is used to alias a chain of steps that would have had to be
* written out every time.
*
* @example
*
*
*/
class EarlyStep extends Step {
constructor(values, priority = 0) {
super(priority);
this.values = values;
}
*runWith(env, values) {
const ef = action(chain(this.values, values));
yield* ef.genWith(env.scope, ...env.args);
}
}
exports.EarlyStep = EarlyStep;
function* chain(...args) {
for (let it of args)
yield* it;
}
function Est(values) {
return new EarlyStep(values, 0);
}
exports.Est = Est;
exports.E = Est;
function est(values) {
return new EarlyStep(values, 1);
}
exports.est = est;
exports.e = est;
/**
* Allows more flexibility than EarlyStep in where new values
* are placed. However this means we must use static arrays to hold the
* values in the template. To use more transient iterables here,
* please implement your own.
*
*/
class StepTemplate {
constructor(values, map) {
this.values = values;
this.map = map;
}
run(values, priority = 0) {
const erStatements = [...this.values];
let index;
for (let key of Reflect.ownKeys(values)) {
index = this.map[key];
if (index === $) {
erStatements.push(values[key]);
}
else {
erStatements.splice(index, 0, values[key]);
}
}
return new EarlyStep(erStatements, priority);
}
}
exports.StepTemplate = StepTemplate;
function template(values, map) {
return new StepTemplate(values, map);
}
exports.template = template;