@algebraic/type
Advanced tools
Comparing version
10
fail.js
const fail = Object.assign( | ||
(...args) => { throw args.length > 1 ? | ||
new (args[0])(args[1]) : Error(args[0]); }, | ||
(...args) => { | ||
throw args[0] instanceof Error ? | ||
args[0] : | ||
typeof args[0] === "object" ? | ||
Object.assign(Error(), args[0]) : | ||
args.length > 1 ? | ||
new (args[0])(args[1]) : | ||
Error(args[0]); }, | ||
{ syntax: message => fail(SyntaxError, message) }, | ||
@@ -5,0 +11,0 @@ { type: message => fail(TypeError, message) }); |
279
field.js
@@ -1,233 +0,104 @@ | ||
const { is, fNamed } = require("./declaration"); | ||
const { parameterized } = require("./parameterized"); | ||
const { belongs, parameters } = parameterized; | ||
const union = require("./union"); | ||
const maybe = require("./maybe"); | ||
const or = require("./or"); | ||
const { data } = require("./data"); | ||
const { ftype, string, boolean } = require("./primitive"); | ||
const { isArray } = Array; | ||
const has = hasOwnProperty.call.bind(hasOwnProperty); | ||
const any = require("./any"); | ||
const { IObject } = require("./intrinsics"); | ||
const { has } = IObject; | ||
const f = require("./function-define"); | ||
//const type = require("./type"); | ||
const fail = require("./fail"); | ||
const of = require("./of"); | ||
const private = require("./private"); | ||
const { definition } = require("./type"); | ||
// data.fields are datas themselves, so creating them is a little tricky. You | ||
// can quickly get into infinite recursion as data.fields have data.fields | ||
// which have data.fields, etc. The solution is to have an internal non-data | ||
// representation and only create the data.field representation as necessary | ||
// (lazily). | ||
// | ||
// Here is our "official" representation. Technically this recurses forever, | ||
// as data.field<T>'s init property is a data.field <data.field <T>.init>, | ||
// which itself has an init field that is a | ||
// data.field <data.field <data.field <T>.init >.init>, etc. | ||
const field = parameterized(T => | ||
data `data.field <${T}>` ( | ||
name => string, | ||
definition => field.definition(T) ) ); | ||
module.exports = field; | ||
field.definition = parameterized(function (T) | ||
function Field(options) | ||
{ | ||
const suppliedT = field.definition.supplied(T); | ||
const computedT = field.definition.computed(T); | ||
return union `data.definition<${T}>` ( | ||
supplied => suppliedT, | ||
computed => computedT ); | ||
}); | ||
field.definition.supplied = parameterized(T => | ||
data `field.definition<${T}>.supplied` ( | ||
fallback => maybe (T) ) ); | ||
field.definition.computed = parameterized(T => | ||
data `field.definition<${T}>.computed` ( | ||
compute => ftype, | ||
dependencies => Array ) ); | ||
field.deferred = data `field.deferred` ( | ||
name => string, | ||
computed => [boolean, false], | ||
λdefinition => ftype ); | ||
field.declaration = union `field.declaration` ( | ||
field, | ||
field.deferred ); | ||
field.error = union `field.error` ( | ||
data `missing` ( | ||
name => string ), | ||
data `type` ( | ||
expected => any, | ||
value => any ) ); | ||
field.compile = function (fieldDeclarations) | ||
{ | ||
const partition = require("@climb/partition"); | ||
const compiled = fieldDeclarations.map(fromDeclaration); | ||
const [computed, uncomputed] = | ||
partition(([, [, computed]]) => computed, compiled); | ||
return Object.assign(compiled, { computed, uncomputed }); | ||
return options instanceof Field ? | ||
options : | ||
options instanceof type ? | ||
new Field({ type: options }) : | ||
!(this instanceof Field) ? | ||
new Field(options) : | ||
IObject.assign(this, | ||
{ | ||
default: | ||
has("defaultValue", options) ? | ||
new Default.Value(options.defaultValue) : | ||
definition(options.type).toDefaultValue ? | ||
new Default.Value( | ||
definition(options.type) | ||
.toDefaultValue(options.type)) : | ||
Default.None, | ||
constraint: new Constraint(options.type) | ||
}); | ||
} | ||
// field.declaration | ||
// field.declaration.direct | ||
// field.declaration.shorthand | ||
// | ||
// field.declaration { name, definition } | ||
// property { name, definition: supplied } | ||
// property { name, definition: computed } | ||
module.exports = Field; | ||
const parseShorthand = (function () | ||
Field.prototype.extract = function (forT, name, values) | ||
{ | ||
const fShorthandRegExp = /(?:^\(\[([^\]]+)\]\))|([^=\s]+)\s*=>/; | ||
const present = has(name, values); | ||
return f => | ||
(([, computed, supplied]) => [!!computed, computed || supplied]) | ||
(fShorthandRegExp.exec(f + "")); | ||
})(); | ||
if (!present && this.default === Default.None) | ||
fail.type( | ||
`${toTypeString(forT)} constructor requires field ` + | ||
`${toValueString(name)}.`); | ||
function fromDeclaration(declaration) | ||
{ | ||
const isShorthand = !is (field.declaration, declaration); | ||
const [computed, name, definition] = | ||
isShorthand ? | ||
[...parseShorthand(declaration), declaration([])] : | ||
is (field.deferred, declaration) ? | ||
[declaration.computed, | ||
declaration.name, | ||
declaration.λdefinition([])] : | ||
[is (field.definition.computed, declaration.definition), | ||
declaration.name, | ||
declaration.definition]; | ||
const compiled = is (field.definition, definition) ? | ||
fromDefinition(definition) : | ||
fromShorthandDefinition(computed, definition); | ||
// FIXME: Do computed correctly... | ||
if (!present) | ||
return this.default instanceof Default.Value ? | ||
this.default.value : | ||
this.default.computed(); | ||
return [name, compiled]; | ||
} | ||
const value = values[name]; | ||
function fromDefinition(definition) | ||
{ | ||
const type = parameters(definition)[0]; | ||
if (!this.constraint.has(value)) | ||
fail.type( | ||
`${toTypeString(forT)} constructor passed invalid value` + | ||
` for field ${toValueString(name)}:\n` + | ||
` Expected: type ${toTypeString(this.constraint.type)}\n` + | ||
` Found: ${toValueString(value)} ` + | ||
`of type ${toTypeString(type.of(value))}`); | ||
return is (field.definition.supplied, definition) ? | ||
toSuppliedIC(type, definition) : | ||
toComputedIC(type, definition); | ||
return value; | ||
} | ||
function fromShorthandDefinition(computed, definition) | ||
function Constraint(type) | ||
{ | ||
const tuple = isArray(definition); | ||
if (computed && tuple) | ||
return fromComputedShorthand(...definition); | ||
if (computed && !tuple) | ||
return fail("Failed to parse computed property, you must pass an array"); | ||
/* | ||
if (computed && !tuple) | ||
return toComputedIC( | ||
of(definition), | ||
{ compute:() => definition, dependencies:[] }); | ||
*/ | ||
const type = tuple ? definition[0] : definition; | ||
const fallback = tuple ? | ||
maybe(type).just({ value: definition[1] }) : | ||
maybe(type).nothing; | ||
return toSuppliedIC(type, { fallback }); | ||
this.type = type; | ||
} | ||
const fromComputedShorthand = (function () | ||
Constraint.prototype.has = function (value) | ||
{ | ||
const templateRegExp = require("./templated-regular-expression"); | ||
const { objectRegExp, listRegExp, emptyRegExp } = templateRegExp( | ||
{ | ||
name: /[^,\}\s\)]+/g, | ||
names: /${name}(?:\s*,\s*${name})*/, | ||
objectRegExp: /^\(\s*\{\s*${names}\s*\}\)/, | ||
listRegExp: /^\(?\s*(${names})\s*\)?/, | ||
emptyRegExp: /^\(\s*(\{\s*\})?\s*\)/, | ||
}); | ||
return function fromComputedShorthand(type, shorthand) | ||
{ | ||
const fString = shorthand + ""; | ||
if (emptyRegExp.exec(fString)) | ||
return toComputedIC(type, | ||
{ compute: () => shorthand(), dependencies:[] }); | ||
const object = objectRegExp.exec(fString); | ||
const extracted = (object || listRegExp.exec(fString))[1]; | ||
const dependencies = extracted.split(/\s*,\s*/); | ||
const deduped = Array.from(new Set(dependencies)); | ||
const compute = object ? | ||
values => shorthand(values) : | ||
values => shorthand(...dependencies.map(name => values[name])); | ||
return toComputedIC(type, { compute, dependencies }); | ||
} | ||
})(); | ||
function toSuppliedIC(type, definition) | ||
{ | ||
const { fallback } = definition; | ||
const initializer = fallback === maybe(type).none ? | ||
(provided, name) => has(provided, name) ? | ||
[true, provided[name]] : | ||
[false, field.error.missing({ name })] : | ||
(provided, name) => has(provided, name) ? | ||
[true, provided[name]] : | ||
[true, fallback.value]; | ||
return toIC(false, initializer, type, definition); | ||
return type.has(this.type, value); | ||
} | ||
function toComputedIC(type, definition) | ||
const Default = | ||
{ | ||
const resulted = values => [true, definition.compute(values)]; | ||
None: Symbol("Default.None"), | ||
Value: f.constructible `Default.Value` | ||
(function (f, value) { this.value = value } ), | ||
Computed: f.constructible `Default.Computed` | ||
(function (f, computed) { this.computed = computed }) | ||
}; | ||
return toIC(true, resulted, type, definition); | ||
} | ||
Field.Default = Default; | ||
function toIC(computed, initializer, type, definition) | ||
{ | ||
return [typechecked(type, initializer), computed, type, definition]; | ||
} | ||
const highlighted = ([color]) => string => `${color}${string}\x1b[0m`; | ||
const toTypeString = T => highlighted `\x1b[36m` (type.typename(T)); | ||
const toValueString = value => highlighted `\x1b[35m` ( | ||
value === void(0) ? "undefined" : | ||
value === null ? "null" : | ||
typeof value === "function" ? `[function ${value.name}]` : | ||
// typeof value !== "object" ? JSON.stringify(value, null, 2) : | ||
// of(value) && getKind(of(value)) ? value + "" : | ||
JSON.stringify(value, null, 2)); | ||
function typechecked(expected, initializer) | ||
{ | ||
return (...args) => (([success, value]) => | ||
!success ? [success, value] : | ||
is (expected, value) ? [true, value] : | ||
[false, field.error.type({ expected, value })]) | ||
(initializer(...args)); | ||
} | ||
module.exports.fromCompiled = function ([name, [_, computed, type, values]]) | ||
{ | ||
const definitionT = field.definition(type); | ||
const definition = computed ? | ||
definitionT.computed(values) : | ||
definitionT.supplied(values); | ||
const type = require("./type"); | ||
return field(type)({ name, definition }); | ||
} | ||
// FIXME: UGH. | ||
/* const MISSING = { }; | ||
const defaultValue = private(value, "defaultValue", () => MISSING); | ||
module.exports.toFieldDeclaration = function toFieldDeclaration (declaration) | ||
{ | ||
if (is (field.declaration, declaration)) | ||
return declaration; | ||
this.default = defaultValue !== MISSING ? | ||
new Default.Value(defaultValue) : | ||
Default.None;*/ | ||
const [computed, name] = parseShorthand(declaration); | ||
const λdefinition = declaration; | ||
return field.deferred({ name, computed, λdefinition }); | ||
} |
{ | ||
"name": "@algebraic/type", | ||
"version": "1.0.0-alpha.20", | ||
"version": "1.0.0-alpha.21", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "type.js", |
376
type.js
@@ -1,21 +0,361 @@ | ||
const type = | ||
Error.stackTraceLimit = 10000; | ||
const { IObject, IArray } = require("./intrinsics"); | ||
const { flat } = IArray.prototype; | ||
const { f, constructible } = require("./function-define"); | ||
const { isTaggedCall, tagResolve } = require("./templating"); | ||
const fail = require("./fail"); | ||
const Definition = Symbol("Definition"); | ||
const definition = T => T[Definition]; | ||
const private = require("./private"); | ||
const UseFallbackForEverField = IObject.create(null); | ||
const isObject = value => value && typeof value === "object"; | ||
const isFunction = value => typeof value === "function"; | ||
const type = constructible("type", (_, ...arguments) => | ||
// Case 0: type() -> ERROR | ||
arguments.length < 1 ? | ||
fail (`type() cannot be called with no arguments.`) : | ||
// Case 1: type `[...]` -> ((...body) -> T) | ||
isTaggedCall(arguments) ? | ||
parseBody(tagResolve(...arguments)) : | ||
// Case 2: type (...body) -> T | ||
typeof arguments[0] !== "string" ? | ||
declare("", arguments) : | ||
// Case 3: type (string) -> ((...body) -> T) | ||
arguments.length === 1 ? | ||
parseBody(arguments[0]) : | ||
// Case 4: type (string, ...body) -> T | ||
declare(arguments[0], arguments.slice(1)), | ||
(f, property) => property.inherits(Function.prototype)); | ||
IObject.defineProperty(type, "type", { value: type }); | ||
type.definition = definition; | ||
// FIXME: Generalize this... | ||
type.of = type.of = value => | ||
!value ? type[typeof value] : | ||
IObject.getPrototypeOf(value).constructor instanceof type ? | ||
IObject.getPrototypeOf(value).constructor : | ||
type[typeof value]; | ||
const declare = (name, body, flatBody = flat.call(body)) => | ||
define( | ||
isSumBody(flatBody) ? Sum (name, flatBody) : | ||
isProductBody(flatBody) ? Product (name, flatBody) : | ||
fail (`Could not recognize type declaration.`)); | ||
function parseBody(name) | ||
{ | ||
always: value => [type.of(value), () => value], | ||
any: require("./any"), | ||
array: require("./array"), | ||
of: require("./of"), | ||
...require("./declaration"), | ||
...require("./data"), | ||
union: require("./union"), | ||
...require("./primitive"), | ||
...require("./serialize"), | ||
...require("./deserialize"), | ||
...require("./parameterized"), | ||
maybe: require("./maybe"), | ||
nullable: require("./nullable"), | ||
or: require("./or"), | ||
result: require("./result") | ||
}; | ||
return IObject.assignNonenumerable( | ||
(...body) => declare(name, body), | ||
{ forall: (...rest) => forall(name, ...rest) }); | ||
} | ||
const define = declaration => | ||
constructible(declaration.name, function (T, ...args) | ||
{ | ||
const instantiating = args[0] === instantiate; | ||
const instantiated = this instanceof T; | ||
return instantiating && instantiated ? | ||
this : | ||
!instantiating && instantiated ? | ||
failCannotBeInvokedWithNew(T) : | ||
isTaggedCall(args) ? | ||
annotate(tagResolve(...args), T) : | ||
tryDefaultConstructor(T, args); | ||
}, | ||
(T, property, | ||
TDefinition = new TypeDefinition(T, declaration), | ||
constructors = IObject.values(TDefinition.constructors)) => | ||
[ | ||
declaration.onPrototype && | ||
property.onPrototype(declaration.onPrototype), | ||
property.prototypeOf ( | ||
constructors.length === 1 && | ||
definition(constructors[0]).isUnaryConstructor ? | ||
IObject.setPrototypeOf(T.prototype, type.prototype) : | ||
type.prototype), | ||
declaration.inherits && | ||
property.inherits (declaration.inherits), | ||
property({ name: Definition, value: TDefinition }), | ||
...IObject | ||
.entries(TDefinition.constructors) | ||
.map(([name, constructor]) => property( | ||
{ | ||
name, | ||
enumerable: false, | ||
value: definition(constructor).isUnaryConstructor ? | ||
constructor() : | ||
constructor | ||
})) | ||
]); | ||
module.exports = type; | ||
module.exports.type = type; | ||
function instantiate(T, [public_, private_]) | ||
{ | ||
const instance = | ||
T.prototype instanceof Array ? | ||
IObject.setPrototypeOf([], T.prototype) : | ||
new T(instantiate); | ||
IObject | ||
.entries(private_ || {}) | ||
.map(([name, value]) => private(instance, name, () => value)); | ||
return IObject.freeze(IObject.assign(instance, public_)); | ||
} | ||
function TypeDefinition(T, declaration) | ||
{ | ||
this.name = declaration.name || ""; | ||
this.invocation = declaration.invocation || false; | ||
const constructorDeclarations = declaration.constructors || []; | ||
const hasSingleConstructor = constructorDeclarations.length === 1; | ||
const constructorEntries = | ||
constructorDeclarations | ||
.map((declaration, ID) => | ||
Constructor(hasSingleConstructor, T, ID, declaration)) | ||
.map(constructor => [constructor.name, constructor]); | ||
this.constructors = IObject.fromEntries(constructorEntries); | ||
const count = constructorEntries.length; | ||
this.EveryCID = [(1 << count) - 1]; | ||
const typeKeyedConstructors = | ||
constructorEntries | ||
.filter(([name]) => name.startsWith(".")); | ||
// FIXME: What if *both* of these are present? | ||
this.defaultConstructor = | ||
IObject.has(this.name, this.constructors) ? | ||
this.constructors[this.name] : | ||
typeKeyedConstructors.length > 0 ? | ||
toDefaultedTypeKeyedConstructor(typeKeyedConstructors) : | ||
false; | ||
this.has = declaration.has || ((T, value) => value instanceof T); | ||
this.toDefaultValue = declaration.toDefaultValue; | ||
return IObject.freeze(this); | ||
} | ||
function toDefaultedTypeKeyedConstructor(typeKeyedConstructors) | ||
{ | ||
return function (value) | ||
{ | ||
const T = type.of(value); | ||
const constructor = typeKeyedConstructors | ||
.map(([name, C]) => [name, C, getFields(C)]) | ||
.find(([name, C, fields]) => | ||
fields.length === 1 && | ||
fields[0][1].constraint.type === T); | ||
if (!constructor) | ||
fail (`No matching constructor found for ` + value); | ||
return constructor[1](value); | ||
} | ||
} | ||
function tryDefaultConstructor(T, args) | ||
{ | ||
const { defaultConstructor, constructors, name } = definition(T); | ||
return defaultConstructor ? | ||
defaultConstructor(...args) : | ||
fail( | ||
`${name} has no default constructor.` + | ||
(constructors.length <= 0 ? | ||
"" : | ||
`\nAvailable constructors are:${ | ||
IObject | ||
.values(constructors) | ||
.map(({ name }) => `\n ${name}`)}`)); | ||
} | ||
// FIXME: Do something if constructors.length === 0 | ||
const failCannotBeInvokedWithNew = T => | ||
fail( | ||
`${T.name} cannot be invoked with "new", ` + | ||
`use ${T.name}(...) instead.`); | ||
const primitive = | ||
(name, has = (T, value) => typeof value === name) => | ||
define({ name, has }); | ||
type.bigint = primitive("bigint"); | ||
type.boolean = primitive("boolean"); | ||
type.function = primitive("function"); | ||
type.number = primitive("number"); | ||
type.null = primitive("null"); | ||
type.string = primitive("string"); | ||
type.symbol = primitive("symbol"); | ||
type.undefined = primitive("undefined"); | ||
type.object = primitive("object", (T, value) => value && typeof value === "object"); | ||
const has = (T, ...rest) => | ||
rest.length === 0 ? | ||
value => has(T, value) : | ||
definition(T).has(T, rest[0]); | ||
type.has = has; | ||
type.typename = T => definition(T).name; | ||
function Constructor(hasSingleConstructor, T, ID, declaration) | ||
{ | ||
const { name, preprocess, fields } = declaration; | ||
const initialize = declaration.initialize || ((C, fields) => [fields]); | ||
const hasNamedFields = fields.length === 1 && isObject(fields[0]); | ||
const hasPositionalFields = !hasNamedFields && fields.every(isFunction); | ||
if (!hasNamedFields && !hasPositionalFields) | ||
fail ( | ||
`Unrecognized field declarations for constructor ${name}:` + | ||
JSON.stringify(fields)); | ||
const isUnaryConstructor = hasPositionalFields && fields.length === 0; | ||
const isUnaryType = hasSingleConstructor && isUnaryConstructor; | ||
return f(name, (C, ...values) => | ||
{ | ||
if (isUnaryType) | ||
return T; | ||
const [result, preprocessed] = preprocess ? | ||
preprocess(T, C, values) : | ||
[false, values]; | ||
return result || | ||
instantiate( | ||
T, | ||
initialize(C, process(C, preprocessed))); | ||
}, | ||
(_, property) => | ||
[ | ||
property.prototypeOf (Constructor.prototype), | ||
property({ name: "ID", value: ID }), | ||
property({ name: Definition, value: | ||
{ | ||
name, | ||
initialize, | ||
hasPositionalFields, | ||
isUnaryConstructor, | ||
fieldDefinitions: IObject | ||
.entries(hasNamedFields ? fields[0] : fields) | ||
} }) | ||
]); | ||
} | ||
function process(C, preprocessed) | ||
{ | ||
const { hasPositionalFields } = definition(C); | ||
if (!hasPositionalFields && preprocessed.length > 1) | ||
fail ( | ||
`Too many arguments passed to ${C.name}.\n` + | ||
`${C.name} is a record constructor and thus expects ` + | ||
`one object argument.`); | ||
const fields = getFields(C); | ||
if (hasPositionalFields && preprocessed.length > fields.length) | ||
fail ( | ||
`Too many arguments passed to ${C.name}.\n` + | ||
`${C.name} is a positional constructor that expects no more than` + | ||
`${fields.length} arguments.`); | ||
const flattened = | ||
preprocessed.length <= 0 ? | ||
UseFallbackForEverField : | ||
hasPositionalFields ? | ||
preprocessed : | ||
preprocessed[0]; | ||
return IObject | ||
.fromEntries(fields | ||
.map(([name, field]) => | ||
[name, field.extract(C, name, flattened)])); | ||
} | ||
function getFields(C) | ||
{ | ||
return private(C, "fields", () => | ||
C[Definition].fieldDefinitions | ||
.map(([name, f]) => [name, Field(f())])); | ||
} | ||
function annotate(annotation, T) | ||
{ | ||
if (annotation === "?") | ||
return Optional.of(T); | ||
if (annotation === "[]") | ||
return List.of(T); | ||
if (annotation === "=") | ||
return defaultValue => Field({ type: T, defaultValue }); | ||
fail (`Unrecognized annotation: ${annotation} on type ${T}`); | ||
} | ||
type.fallback = function fallback(toDefaultValue) | ||
{ | ||
return f `fallback` ( | ||
(f, ...args) => toDefaultValue(...args), | ||
(_, property) => property.prototypeOf (fallback.prototype)); | ||
} | ||
const { isProductBody, Product } = require("./types/product"); | ||
const { isSumBody, Sum, caseof } = require("./types/sum"); | ||
type.caseof = caseof; | ||
const Field = require("./field"); | ||
const forall = require("./types/forall"); | ||
const Optional = require("./types/optional"); | ||
type.Optional = Optional; | ||
const List = require("./types/list"); | ||
type.List = List; | ||
/* | ||
IObject.assign(type, | ||
IObject.fromEntries( | ||
require("./primitives") | ||
.map(([name, has]) => | ||
type(new TypeDefinition({ name, has }))))); | ||
IObject.assign( | ||
type, | ||
IObject.fromEntries(... | ||
["bigint", "boolean", "function", "number", "string", "symbol", "undefined"] | ||
.map(name => [name, primitive]))); | ||
*/ | ||
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
48924
45.09%1332
52.58%1
Infinity%