intertype
Advanced tools
Comparing version 0.0.4 to 0.101.4
@@ -15,2 +15,6 @@ (function() { | ||
super(); | ||
if (ref === null) { | ||
this.message = message; | ||
return void 0; | ||
} | ||
this.message = `${ref} (${this.constructor.name}) ${message}`; | ||
@@ -80,2 +84,16 @@ this.ref = ref; | ||
this.Intertype_user_error = class Intertype_user_error extends this.Intertype_error { | ||
constructor(message) { | ||
super(null, message); | ||
} | ||
}; | ||
this.Intertype_validation_error = class Intertype_validation_error extends this.Intertype_error { | ||
constructor(ref, state, report) { | ||
super(ref, `not a valid ${state.hedgerow}; failing tests: ${report}`); | ||
} | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
@@ -82,0 +100,0 @@ this.Intertype_ETEMPTBD = class Intertype_ETEMPTBD extends this.Intertype_error {}; |
(function() { | ||
'use strict'; | ||
var E, GUY, H, L, PMATCH, debug, ref, rpr, | ||
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }, | ||
indexOf = [].indexOf; | ||
var E, GUY, H, L, debug, rpr; | ||
@@ -21,4 +19,2 @@ //########################################################################################################### | ||
PMATCH = require('picomatch'); | ||
//=========================================================================================================== | ||
@@ -32,3 +28,3 @@ this.defaults = { | ||
//=========================================================================================================== | ||
ref = this.Intertype_hedges = (function() { | ||
this.Intertype_hedges = (function() { | ||
class Intertype_hedges extends GUY.props.Strict_owner { | ||
@@ -38,65 +34,10 @@ //--------------------------------------------------------------------------------------------------------- | ||
super(); | ||
//--------------------------------------------------------------------------------------------------------- | ||
this._combine = this._combine.bind(this); | ||
this.cfg = {...L.defaults.combinator_cfg, ...cfg}; | ||
// @hedgepaths = new GUY.props.Strict_owner() | ||
// for groupname from @_get_groupnames() | ||
// compiled_hedges = @_compile_hedges groupname, @constructor.hedges | ||
// hedgepaths = @get_hedgepaths compiled_hedges | ||
// @hedgepaths[ groupname ] = @_reduce_hedgepaths hedgepaths | ||
return void 0; | ||
} | ||
_combine(terms) { | ||
var _, i, len, ref1, results, v, x; | ||
boundMethodCheck(this, ref); | ||
ref1 = combinate(terms); | ||
results = []; | ||
for (i = 0, len = ref1.length; i < len; i++) { | ||
x = ref1[i]; | ||
results.push((function() { | ||
var results1; | ||
results1 = []; | ||
for (_ in x) { | ||
v = x[_]; | ||
results1.push(v); | ||
} | ||
return results1; | ||
})()); | ||
} | ||
return results; | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_compile_hedges(groupname, hedges) { | ||
var R, catchall_group, hedge, i, j, len, len1, ref1, target, termgroup; | ||
R = []; | ||
catchall_group = this.constructor.catchall_group; | ||
for (i = 0, len = hedges.length; i < len; i++) { | ||
hedge = hedges[i]; | ||
if (indexOf.call(hedge.groups, catchall_group) < 0) { | ||
if (indexOf.call(hedge.groups, groupname) < 0) { | ||
continue; | ||
} | ||
} | ||
target = []; | ||
R.push(target); | ||
ref1 = hedge.terms; | ||
for (j = 0, len1 = ref1.length; j < len1; j++) { | ||
termgroup = ref1[j]; | ||
// continue if termgroup? and @_has_conflicting_hedge_matchers | ||
if (Array.isArray(termgroup)) { | ||
target.splice(target.length - 1, 0, ...(this.get_hedgepaths(termgroup))); | ||
} else { | ||
target.push(termgroup); | ||
} | ||
} | ||
} | ||
return R; | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
get_hedgepaths(compiled_hedges) { | ||
var R, hedgematch, i, idx, ref1, x; | ||
throw new Error("not implemented: get_hedgepaths()"); | ||
var R, hedgematch, i, idx, ref, x; | ||
throw new E.Intertype_ETEMPTBD('^intertype.hedges@1^', "not implemented: get_hedgepaths()"); | ||
if ((hedgematch = this.cfg.hedgematch) == null) { | ||
@@ -106,7 +47,7 @@ return []; | ||
R = (function() { | ||
var i, len, ref1, results; | ||
ref1 = this._combine(compiled_hedges); | ||
var i, len, ref, results; | ||
ref = this._combine(compiled_hedges); | ||
results = []; | ||
for (i = 0, len = ref1.length; i < len; i++) { | ||
x = ref1[i]; | ||
for (i = 0, len = ref.length; i < len; i++) { | ||
x = ref[i]; | ||
results.push(x.flat()); | ||
@@ -117,3 +58,3 @@ } | ||
if (hedgematch !== '*') { | ||
for (idx = i = ref1 = R.length - 1; i >= 0; idx = i += -1) { | ||
for (idx = i = ref = R.length - 1; i >= 0; idx = i += -1) { | ||
if (!this._match_hedgepath(R[idx], hedgematch)) { | ||
@@ -133,109 +74,47 @@ delete R[idx]; | ||
_get_groupnames() { | ||
var h; | ||
return GUY.lft.freeze(new Set(((function() { | ||
var i, len, ref1, results; | ||
ref1 = this.constructor.hedges; | ||
results = []; | ||
for (i = 0, len = ref1.length; i < len; i++) { | ||
h = ref1[i]; | ||
results.push(h.groups); | ||
} | ||
return results; | ||
}).call(this)).flat())); | ||
} | ||
}; | ||
//--------------------------------------------------------------------------------------------------------- | ||
Intertype_hedges.catchall_group = 'other'; | ||
Intertype_hedges.hedges = GUY.lft.freeze([]); | ||
//--------------------------------------------------------------------------------------------------------- | ||
Intertype_hedges.hedges = GUY.lft.freeze([ | ||
{ | ||
terms: [null, | ||
'optional'], | ||
groups: ['other'] | ||
}, | ||
{ | ||
terms: [null, | ||
[[null, | ||
'empty', | ||
'nonempty'], | ||
['list_of', | ||
'set_of'], | ||
[null, | ||
'optional']]], | ||
groups: ['other'] | ||
}, | ||
{ | ||
terms: [null, | ||
'empty', | ||
'nonempty'], | ||
groups: ['collection'] | ||
}, | ||
{ | ||
terms: [null, | ||
'positive0', | ||
'positive1', | ||
'negative0', | ||
'negative1'], | ||
groups: ['number'] | ||
} | ||
]); | ||
// #--------------------------------------------------------------------------------------------------------- | ||
// @groups_of_groups: | ||
// collection: [ ] | ||
//--------------------------------------------------------------------------------------------------------- | ||
/* TAINT tack onto prototype as hidden */ | ||
Intertype_hedges.prototype._hedgemethods = GUY.lft.freeze(new GUY.props.Strict_owner({ | ||
Intertype_hedges.prototype._hedgemethods = new GUY.props.Strict_owner({ | ||
freeze: true, | ||
target: { | ||
optional: function(x) { | ||
if (x == null) { | ||
// debug GUY.trm.reverse GUY.trm.yellow '^optional@453^', rpr x, @ | ||
return H.signals.true_and_break; | ||
if (x != null) { | ||
return true; | ||
} else { | ||
return H.signals.return_true; | ||
} | ||
return true; | ||
}, | ||
//....................................................................................................... | ||
/* TAINT use `length` or `size` or custom method */ | ||
empty: function(x) { | ||
return (H.size_of(x, null)) === 0; | ||
or: function(x) { | ||
return x === true; | ||
}, | ||
nonempty: function(x) { | ||
return (H.size_of(x, null)) !== 0; | ||
of: function(x) { | ||
return H.signals.element_mode; | ||
}, | ||
//....................................................................................................... | ||
list_of: function(x) { | ||
if (!Array.isArray(x)) { | ||
return H.signals.false_and_break; | ||
} | ||
return H.signals.process_list_elements; | ||
empty: function(x) { | ||
var R; | ||
return ((R = H.size_of(x, null)) != null) && R === 0; | ||
}, | ||
set_of: function(x) { | ||
if (!(x instanceof Set)) { | ||
return H.signals.false_and_break; | ||
} | ||
return H.signals.process_set_elements; | ||
nonempty: function(x) { | ||
var R; | ||
return ((R = H.size_of(x, null)) != null) && R !== 0; | ||
}, | ||
//....................................................................................................... | ||
positive0: function(x) { | ||
// debug GUY.trm.reverse GUY.trm.yellow '^positive0@453^', rpr x | ||
return x >= 0; | ||
return (x === +2e308) || ((Number.isFinite(x)) && (x >= 0)); | ||
}, | ||
positive1: function(x) { | ||
return x > 0; | ||
return (x === +2e308) || ((Number.isFinite(x)) && (x > 0)); | ||
}, | ||
negative0: function(x) { | ||
return x <= 0; | ||
return (x === -2e308) || ((Number.isFinite(x)) && (x <= 0)); | ||
}, | ||
negative1: function(x) { | ||
return x < 0; | ||
return (x === -2e308) || ((Number.isFinite(x)) && (x < 0)); | ||
} | ||
} | ||
})); | ||
}); | ||
@@ -242,0 +121,0 @@ return Intertype_hedges; |
(function() { | ||
'use strict'; | ||
var E, GUY, misfit, notavalue, | ||
var E, GUY, Intertype_abc, debug, help, idf, info, misfit, notavalue, rpr, to_width, urge, warn, width_of, | ||
indexOf = [].indexOf; | ||
@@ -9,2 +9,6 @@ | ||
({debug, info, warn, urge, help} = GUY.trm.get_loggers('INTERTYPE')); | ||
({rpr} = GUY.trm); | ||
misfit = Symbol('misfit'); | ||
@@ -16,5 +20,30 @@ | ||
/* | ||
js_type_of = ( x ) => ( ( Object::toString.call x ).slice 8, -1 ).toLowerCase().replace /\s+/g, '' | ||
*/ | ||
({to_width, width_of} = require('to-width')); | ||
/* TAINT unify with symbols in `hedges` */ | ||
this.misfit = Symbol('misfit'); | ||
//........................................................................................................... | ||
this.constructor_of_generators = ((function*() { | ||
return (yield 42); | ||
})()).constructor; | ||
/* see https://github.com/davidmarkclements/rfdc */ | ||
this.deep_copy = (require('rfdc'))({ | ||
proto: true, | ||
circles: false | ||
}); | ||
this.nameit = function(name, f) { | ||
return Object.defineProperty(f, 'name', { | ||
value: name | ||
}); | ||
}; | ||
idf = function(x) { | ||
return x/* IDentity Function */; | ||
}; | ||
this.equals = this.nameit('equals', require('../deps/jkroso-equals')); | ||
//=========================================================================================================== | ||
@@ -24,4 +53,4 @@ // TYPE_OF FLAVORS | ||
this.domenic_denicola_device = (x) => { | ||
var ref, ref1; | ||
return (ref = x != null ? (ref1 = x.constructor) != null ? ref1.name : void 0 : void 0) != null ? ref : './.'; | ||
var ref1, ref2; | ||
return (ref1 = x != null ? (ref2 = x.constructor) != null ? ref2.name : void 0 : void 0) != null ? ref1 : './.'; | ||
}; | ||
@@ -38,3 +67,3 @@ | ||
this.js_type_of = (x) => { | ||
return ((Object.prototype.toString.call(x)).slice(8, -1)).toLowerCase().replace(/\s+/g, ''); | ||
return Object.prototype.toString.call(x); | ||
}; | ||
@@ -105,6 +134,5 @@ | ||
target: { | ||
true_and_break: Symbol('true_and_break'), | ||
false_and_break: Symbol('false_and_break'), | ||
process_list_elements: Symbol('process_list_elements'), | ||
process_set_elements: Symbol('process_set_elements'), | ||
return_true: Symbol('return_true'), | ||
advance: Symbol('advance'), | ||
// element_mode: Symbol 'element_mode' | ||
nothing: Symbol('nothing') | ||
@@ -183,9 +211,457 @@ } | ||
//=========================================================================================================== | ||
// INTERNAL TYPES | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.constructor_of_generators = ((function*() { | ||
return (yield 42); | ||
})()).constructor; | ||
this.types = new (require('intertype-legacy')).Intertype(); | ||
this.defaults = {}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('deep_boolean', function(x) { | ||
return x === 'deep' || x === false || x === true; | ||
}); | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('Type_cfg_constructor_cfg', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa.nonempty_text x.name": function(x) { | ||
return this.isa.nonempty_text(x.name); | ||
}, | ||
// "@isa.deep_boolean x.copy": ( x ) -> @isa.boolean x.copy | ||
// "@isa.boolean x.seal": ( x ) -> @isa.boolean x.seal | ||
"@isa.deep_boolean x.freeze": function(x) { | ||
return this.isa.deep_boolean(x.freeze); | ||
}, | ||
"@isa.boolean x.extras": function(x) { | ||
return this.isa.boolean(x.extras); | ||
}, | ||
"if extras is false, default must be an object": function(x) { | ||
return x.extras || (this.isa.object(x.default)); | ||
}, | ||
"@isa_optional.function x.create": function(x) { | ||
return this.isa_optional.function(x.create); | ||
}, | ||
/* TAINT might want to check for existence of `$`-prefixed keys in case of `( not x.test? )` */ | ||
/* TAINT should validate values of `$`-prefixed keys are either function or non-empty strings */ | ||
"x.test is an optional function or non-empty list of functions": function(x) { | ||
if (x.test == null) { | ||
return true; | ||
} | ||
if (this.isa.function(x.test)) { | ||
return true; | ||
} | ||
if (!this.isa_list_of.function(x.test)) { | ||
return false; | ||
} | ||
if (x.test.length === 0) { | ||
return false; | ||
} | ||
return true; | ||
}, | ||
"x.groups is deprecated": function(x) { | ||
return x.groups == null; | ||
}, | ||
"@isa.boolean x.collection": function(x) { | ||
return this.isa.boolean(x.collection); | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.Type_cfg_constructor_cfg = { | ||
name: null, | ||
test: null, | ||
/* `default` omitted on purpose */ | ||
create: null, | ||
// copy: false | ||
// seal: false | ||
freeze: false, | ||
extras: true, | ||
collection: false | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('Type_factory_type_dsc', { | ||
tests: { | ||
//......................................................................................................... | ||
/* for later / under consideration */ | ||
// "@isa.deep_boolean x.copy": ( x ) -> @isa.boolean x.copy # refers to result of `type.create()` | ||
// "@isa.boolean x.seal": ( x ) -> @isa.boolean x.seal # refers to result of `type.create()` | ||
// "@isa.boolean x.oneshot": ( x ) -> @isa.boolean x.oneshot # refers to result of `type.create()` | ||
// "@isa.deep_boolean x.freeze": ( x ) -> @isa.deep_boolean x.freeze # refers to result of `type.create()` | ||
//......................................................................................................... | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa.nonempty_text x.name": function(x) { | ||
return this.isa.nonempty_text(x.name); | ||
}, | ||
"@isa.nonempty_text x.typename": function(x) { | ||
return this.isa.nonempty_text(x.typename); | ||
}, | ||
"@isa.boolean x.collection": function(x) { | ||
return this.isa.boolean(x.collection); | ||
}, | ||
"@isa.function x.isa": function(x) { | ||
return this.isa.function(x.isa); | ||
}, | ||
"@isa optional list.of.function x.fields": function(x) { | ||
if (!this.isa.list(x.fields)) { | ||
return true; | ||
} | ||
return this.isa_list_of.function(x.fields); | ||
}, | ||
"@isa.boolean x.extras": function(x) { | ||
return this.isa.boolean(x.extras); // refers to result of `type.create()` | ||
}, | ||
"if extras is false, default must be an object": function(x) { | ||
return x.extras || (this.isa.object(x.default)); | ||
}, | ||
"@isa_optional.function x.create": function(x) { | ||
return this.isa_optional.function(x.create); | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.Type_factory_type_dsc = { | ||
name: null, | ||
typename: null, | ||
isa: null, | ||
fields: null, | ||
collection: false, | ||
/* `default` omitted on purpose */ | ||
create: null, // refers to result of `type.create()` | ||
// copy: false # refers to result of `type.create()` | ||
// seal: false # refers to result of `type.create()` | ||
freeze: false, // refers to result of `type.create()` | ||
extras: true // refers to result of `type.create()` | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('Intertype_iterable', function(x) { | ||
return (x != null) && (x[Symbol.iterator] != null); | ||
}); | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('Intertype_constructor_cfg', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa_optional.nonempty_text x.sep": function(x) { | ||
return this.isa_optional.nonempty_text(x.sep); | ||
}, | ||
"@isa.boolean x.errors": function(x) { | ||
return this.isa.boolean(x.errors); | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.Intertype_constructor_cfg = { | ||
sep: '.', | ||
errors: true | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('intertype_color', function(x) { | ||
if (this.isa.function(x)) { | ||
return true; | ||
} | ||
if (this.isa.boolean(x)) { | ||
return true; | ||
} | ||
if (!this.isa.nonempty_text(x)) { | ||
return false; | ||
} | ||
if (!this.isa.function(GUY.trm[x])) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('intertype_state_report_colors', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa.intertype_color x.ref": function(x) { | ||
return this.isa.intertype_color(x.ref); | ||
}, | ||
"@isa.intertype_color x.value": function(x) { | ||
return this.isa.intertype_color(x.value); | ||
}, | ||
"@isa.intertype_color x.true": function(x) { | ||
return this.isa.intertype_color(x.true); | ||
}, | ||
"@isa.intertype_color x.false": function(x) { | ||
return this.isa.intertype_color(x.false); | ||
}, | ||
"@isa.intertype_color x.hedge": function(x) { | ||
return this.isa.intertype_color(x.hedge); | ||
}, | ||
"@isa.intertype_color x.verb": function(x) { | ||
return this.isa.intertype_color(x.verb); | ||
}, | ||
"@isa.intertype_color x.arrow": function(x) { | ||
return this.isa.intertype_color(x.arrow); | ||
}, | ||
"@isa.intertype_color x.error": function(x) { | ||
return this.isa.intertype_color(x.error); | ||
}, | ||
"@isa.intertype_color x.reverse": function(x) { | ||
return this.isa.intertype_color(x.reverse); | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.intertype_state_report_colors = GUY.lft.freeze({ | ||
ref: 'grey', | ||
value: 'lime', | ||
true: 'green', | ||
false: 'red', | ||
hedge: 'blue', | ||
verb: 'gold', | ||
arrow: 'white', | ||
error: 'red', | ||
reverse: 'reverse' | ||
}); | ||
//........................................................................................................... | ||
this.defaults.intertype_state_report_no_colors = GUY.lft.freeze({ | ||
ref: idf, | ||
value: idf, | ||
true: idf, | ||
false: idf, | ||
hedge: idf, | ||
verb: idf, | ||
arrow: idf, | ||
error: idf, | ||
reverse: idf | ||
}); | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.types.declare('intertype_get_state_report_cfg', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"x.format in [ 'all', 'failing', 'short' ]": function(x) { | ||
var ref1; | ||
return (ref1 = x.format) === 'all' || ref1 === 'failing' || ref1 === 'short'; | ||
}, | ||
"@isa.boolean x.refs": function(x) { | ||
return this.isa.boolean(x.refs); | ||
}, | ||
"@isa_optional.positive_integer x.width": function(x) { | ||
return this.isa_optional.positive_integer(x.width); | ||
}, | ||
"( @isa.boolean x.colors ) or ( @isa.intertype_state_report_colors )": function(x) { | ||
return (this.isa.boolean(x.colors)) || this.isa.intertype_state_report_colors; | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.intertype_get_state_report_cfg = { | ||
colors: this.defaults.intertype_state_report_colors, | ||
format: 'failing', | ||
width: null, | ||
refs: false | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.defaults.Intertype_state = { | ||
method: null, | ||
verb: null, | ||
isa_depth: 0, | ||
hedgerow: null, | ||
hedges: null, | ||
hedgeresults: null, | ||
x: misfit, | ||
result: null, | ||
error: null, | ||
extra_keys: null, | ||
data: null | ||
}; | ||
//=========================================================================================================== | ||
//----------------------------------------------------------------------------------------------------------- | ||
Intertype_abc = class Intertype_abc extends GUY.props.Strict_owner {}; | ||
//=========================================================================================================== | ||
//----------------------------------------------------------------------------------------------------------- | ||
// @defaults = GUY.lft.freeze @defaults | ||
this.Intertype_abc = Intertype_abc; | ||
//=========================================================================================================== | ||
//----------------------------------------------------------------------------------------------------------- | ||
this._get_state_report_colors = function(colors) { | ||
var R, color, purpose; | ||
if (colors === true) { | ||
return this.defaults.intertype_state_report_colors; | ||
} | ||
if (colors === false) { | ||
return this.defaults.intertype_state_report_no_colors; | ||
} | ||
R = {}; | ||
for (purpose in colors) { | ||
color = colors[purpose]; | ||
if (this.types.isa.function(color)) { | ||
continue; | ||
} | ||
switch (color) { | ||
case true: | ||
R[purpose] = GUY.trm[this.defaults.intertype_state_report_colors[color]].bind(GUY.trm); | ||
break; | ||
case false: | ||
R[purpose] = idf; | ||
break; | ||
default: | ||
R[purpose] = GUY.trm[color].bind(GUY.trm); | ||
} | ||
} | ||
return R; | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
this.get_state_report = function(hub, cfg) { | ||
var C, R, TTY, arrow_field, first_hidx, hedge, hidx, i, j, last_hidx, level, push_error_row, push_value_row, r, ref, ref1, ref2, ref3, ref4, sep, truth, value, value_r, verb_field, widths; | ||
this.types.validate.intertype_get_state_report_cfg((cfg = {...this.defaults.intertype_get_state_report_cfg, ...cfg})); | ||
C = this._get_state_report_colors(cfg.colors); | ||
//......................................................................................................... | ||
TTY = require('node:tty'); | ||
truth = function(b, r) { | ||
return C.reverse(b ? C.true(" T ") : C.false(" F ")); | ||
}; | ||
first_hidx = 0; | ||
last_hidx = hub.state.hedgeresults.length - 1; | ||
//......................................................................................................... | ||
R = []; | ||
sep = ''; | ||
widths = (function() { | ||
var lw, ref1; | ||
lw = (ref1 = cfg.width) != null ? ref1 : (TTY.isatty(process.stdout.fd)) ? process.stdout.columns : 100; | ||
widths = {}; | ||
widths.line = lw; | ||
lw -= widths.ref = cfg.refs ? 5 : 0; | ||
lw -= widths.verb = 10; | ||
lw -= widths.truth = 3; | ||
lw -= widths.hedgerow = Math.floor(lw / 3); | ||
lw -= widths.value = lw; | ||
return widths; | ||
})(); | ||
//......................................................................................................... | ||
switch (cfg.format) { | ||
case 'all': | ||
null; | ||
break; | ||
case 'failing': | ||
case 'short': | ||
if (hub.state.result === true) { | ||
return null; | ||
} | ||
first_hidx = last_hidx; | ||
while (first_hidx > 0) { | ||
if ((hub.state.hedgeresults[first_hidx - 1].at(-1)) !== false) { | ||
break; | ||
} | ||
first_hidx--; | ||
} | ||
first_hidx = Math.min(first_hidx, last_hidx); | ||
break; | ||
default: | ||
throw new E.Intertype_internal_error('^intertype.get_state_report@1^', `unknown format ${rpr(format)}`); | ||
} | ||
//......................................................................................................... | ||
switch (cfg.format) { | ||
case 'short': | ||
verb_field = C.reverse(C.verb(` ${hub.state.verb} `)); | ||
arrow_field = C.reverse(C.arrow(" ◀ ")); | ||
break; | ||
default: | ||
verb_field = C.reverse(C.verb(to_width(hub.state.verb, widths.verb, { | ||
align: 'center' | ||
}))); | ||
arrow_field = null; | ||
} | ||
//......................................................................................................... | ||
push_value_row = function(ref, level, hedge, value, r) { | ||
var dent; | ||
dent = ' '.repeat(level); | ||
if (cfg.refs) { | ||
R.push(C.reverse(C.ref(to_width(ref != null ? ref : '', widths.ref)))); | ||
} | ||
R.push(truth(r, r != null ? r.toString() : void 0)); | ||
R.push(verb_field); | ||
R.push(C.reverse(C.hedge(to_width(' ' + dent + hedge, widths.hedgerow)))); | ||
R.push(C.reverse(C.value(to_width(' ' + rpr(value), widths.value)))); | ||
R.push('\n'); | ||
return null; | ||
}; | ||
//......................................................................................................... | ||
push_error_row = function(error = null) { | ||
var error_r; | ||
if (error == null) { | ||
return null; | ||
} | ||
if (error instanceof Error) { | ||
error_r = ` Error: ${error.message.trim()}`; | ||
} else { | ||
error_r = ` Error: ${error.toString()}`; | ||
} | ||
R.push(C.reverse(C.error(to_width(error_r, widths.line)))); | ||
return R.push('\n'); | ||
}; | ||
//......................................................................................................... | ||
switch (cfg.format) { | ||
//....................................................................................................... | ||
case 'all': | ||
case 'failing': | ||
for (hidx = i = ref1 = first_hidx, ref2 = last_hidx; (ref1 <= ref2 ? i <= ref2 : i >= ref2); hidx = ref1 <= ref2 ? ++i : --i) { | ||
[ref, level, hedge, value, r] = hub.state.hedgeresults[hidx]; | ||
push_value_row(ref, level, hedge, value, r); | ||
} | ||
//..................................................................................................... | ||
if (hub.state.hedgeresults.length > 1) { | ||
push_value_row(null, 0, hub.state.hedgerow, hub.state.x, hub.state.result); | ||
} | ||
push_error_row(hub.state.error); | ||
break; | ||
//....................................................................................................... | ||
case 'short': | ||
for (hidx = j = ref3 = first_hidx, ref4 = last_hidx; (ref3 <= ref4 ? j <= ref4 : j >= ref4); hidx = ref3 <= ref4 ? ++j : --j) { | ||
[ref, level, hedge, value, r] = hub.state.hedgeresults[hidx]; | ||
value_r = rpr(value); | ||
if ((width_of(value_r)) > 50) { | ||
value_r = to_width(value_r, 50); | ||
} | ||
R.push('' + (truth(r)) + verb_field + (C.reverse(C.hedge(` ${hedge} `))) + (C.reverse(C.value(` ${value_r} `)))); | ||
} | ||
sep = arrow_field; | ||
break; | ||
default: | ||
throw new E.Intertype_internal_error('^intertype.get_state_report@2^', `unknown format ${rpr(format)}`); | ||
} | ||
//......................................................................................................... | ||
R = R.join(sep); | ||
if ((cfg.format === 'short') && (cfg.colors === false)) { | ||
R = R.trim(); | ||
R = R.replace(/\x20{2,}/g, ' '); | ||
} | ||
return R; | ||
}; | ||
}).call(this); | ||
//# sourceMappingURL=helpers.js.map |
659
lib/main.js
(function() { | ||
'use strict'; | ||
var E, GUY, H, HEDGES, ITYP, Intertype_abc, debug, rpr, to_width, types, warn, | ||
var DECLARATIONS, E, GUY, H, HEDGES, Intertype, Type_factory, debug, help, info, rpr, to_width, urge, warn, | ||
splice = [].splice; | ||
@@ -9,3 +9,3 @@ | ||
({debug, warn} = GUY.trm.get_loggers('INTERTYPE')); | ||
({debug, info, warn, urge, help} = GUY.trm.get_loggers('INTERTYPE')); | ||
@@ -21,143 +21,76 @@ ({rpr} = GUY.trm); | ||
ITYP = this; | ||
DECLARATIONS = require('./declarations'); | ||
types = new (require('intertype-legacy')).Intertype(); | ||
({Type_factory} = require('./type-factory')); | ||
this.defaults = {}; | ||
({to_width} = require('to-width')); | ||
//----------------------------------------------------------------------------------------------------------- | ||
types.declare('Type_cfg_constructor_cfg', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa.nonempty_text x.name": function(x) { | ||
return this.isa.nonempty_text(x.name); | ||
}, | ||
"( @isa.function x.test ) or ( @isa_list_of.function x.test )": function(x) { | ||
return (this.isa.function(x.test)) || (this.isa_list_of.function(x.test)); | ||
}, | ||
"x.groups is a nonempty text or a nonempty list of nonempty texts": function(x) { | ||
if (this.isa.nonempty_text(x.groups)) { | ||
return true; | ||
} | ||
if (!this.isa.list(x.groups)) { | ||
return false; | ||
} | ||
return x.groups.every((e) => { | ||
return (this.isa.nonempty_text(e)) && !/[\s,]/.test(e); | ||
}); | ||
Intertype = (function() { | ||
//=========================================================================================================== | ||
class Intertype extends H.Intertype_abc { | ||
//--------------------------------------------------------------------------------------------------------- | ||
constructor(cfg) { | ||
super(); | ||
GUY.props.hide(this, 'cfg', {...H.defaults.Intertype_constructor_cfg, ...cfg}); | ||
H.types.validate.Intertype_constructor_cfg(this.cfg); | ||
//....................................................................................................... | ||
GUY.props.hide(this, '_hedges', new HEDGES.Intertype_hedges()); | ||
GUY.props.hide(this, '_collections', new Set()); | ||
GUY.props.hide(this, '_signals', H.signals); | ||
// GUY.props.hide @, 'isa', new GUY.props.Strict_owner { reset: false, } | ||
GUY.props.hide(this, 'isa', new Proxy({}, this._get_hedge_base_proxy_cfg(this, '_isa'))); | ||
GUY.props.hide(this, 'validate', new Proxy({}, this._get_hedge_base_proxy_cfg(this, '_validate'))); | ||
GUY.props.hide(this, 'create', new Proxy({}, this._get_hedge_base_proxy_cfg(this, '_create'))); | ||
GUY.props.hide(this, 'type_factory', new Type_factory(this)); | ||
//....................................................................................................... | ||
/* TAINT squeezing this in here for the moment, pending reformulation of `isa` &c to make them callable: */ | ||
GUY.props.hide(this, 'declare', new Proxy(this._declare.bind(this), { | ||
get: (_, name) => { | ||
return (...P) => { | ||
return this._declare(name, ...P); | ||
}; | ||
} | ||
})); | ||
//....................................................................................................... | ||
GUY.props.hide(this, 'registry', GUY.props.Strict_owner.create({ | ||
oneshot: true | ||
})); | ||
// GUY.props.hide @, 'types', H.types | ||
this._initialize_state(); | ||
//....................................................................................................... | ||
this._register_hedges(); | ||
DECLARATIONS._provisional_declare_basic_types(this); | ||
return void 0; | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.Type_cfg_constructor_cfg = { | ||
groups: 'other', | ||
name: null, | ||
test: null | ||
}; | ||
//----------------------------------------------------------------------------------------------------------- | ||
types.declare('Intertype_constructor_cfg', { | ||
tests: { | ||
"@isa.object x": function(x) { | ||
return this.isa.object(x); | ||
}, | ||
"@isa_optional.nonempty_text x.sep": function(x) { | ||
return this.isa_optional.nonempty_text(x.sep); | ||
//--------------------------------------------------------------------------------------------------------- | ||
_initialize_state(cfg) { | ||
/* TAINT should use deep copy of default object */ | ||
return this.state = { | ||
...H.defaults.Intertype_state, | ||
hedgeresults: [], | ||
...cfg | ||
}; | ||
} | ||
} | ||
}); | ||
//........................................................................................................... | ||
this.defaults.Intertype_constructor_cfg = { | ||
sep: '.' | ||
}; | ||
// #----------------------------------------------------------------------------------------------------------- | ||
// types.declare 'Intertype_walk_hedgepaths_cfg', tests: | ||
// "@isa.object x": ( x ) -> @isa.object x | ||
// "@isa_optional.nonempty_text x.sep": ( x ) -> @isa_optional.nonempty_text x.sep | ||
// "@isa_optional.function x.evaluate": ( x ) -> @isa_optional.function x.evaluate | ||
// ### TAINT omitted other settings for `GUY.props.tree()` ### | ||
// #........................................................................................................... | ||
// @defaults.Intertype_walk_hedgepaths_cfg = | ||
// sep: @defaults.Intertype_constructor_cfg.sep | ||
// evaluate: ({ owner, key, value, }) -> | ||
// return 'take' if ( types.type_of value ) is 'function' | ||
// return 'take' unless GUY.props.has_any_keys value | ||
// return 'descend' | ||
//=========================================================================================================== | ||
Intertype_abc = class Intertype_abc extends GUY.props.Strict_owner {}; | ||
//=========================================================================================================== | ||
this.Type_cfg = class Type_cfg extends Intertype_abc { | ||
//--------------------------------------------------------------------------------------------------------- | ||
constructor(hub, cfg) { | ||
var _test, f, k, v; | ||
/* TAINT ensure type_cfg does not contain `type`, `name` */ | ||
super(); | ||
GUY.props.hide(this, 'hub', hub); | ||
cfg = {...ITYP.defaults.Type_cfg_constructor_cfg, ...cfg}; | ||
cfg.groups = this._compile_groups(cfg.groups); | ||
types.validate.Type_cfg_constructor_cfg(cfg); | ||
if (types.isa.list(cfg.test)) { | ||
_test = (function() { | ||
var i, len, ref, results; | ||
ref = cfg.test; | ||
results = []; | ||
for (i = 0, len = ref.length; i < len; i++) { | ||
f = ref[i]; | ||
results.push(f.bind(hub)); | ||
} | ||
return results; | ||
})(); | ||
cfg.test = (x) => { | ||
return _test.every(function(f) { | ||
return f(x); | ||
}); | ||
}; | ||
} else { | ||
cfg.test = cfg.test.bind(hub); | ||
//--------------------------------------------------------------------------------------------------------- | ||
_register_hedges() { | ||
var hedge, isa, ref1; | ||
ref1 = this._hedges._hedgemethods; | ||
for (hedge in ref1) { | ||
isa = ref1[hedge]; | ||
((hedge, isa) => { | ||
return this.declare(hedge, {isa}); | ||
})(hedge, isa); | ||
} | ||
return null; | ||
} | ||
if (cfg.isa_collection && (cfg.size == null)) { | ||
cfg.size = 'length'; | ||
} | ||
if (cfg.size == null) { | ||
cfg.size = null; | ||
} | ||
for (k in cfg) { | ||
v = cfg[k]; | ||
this[k] = v; | ||
} | ||
return GUY.lft.freeze(this); | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_compile_groups(groups) { | ||
var R; | ||
warn(GUY.trm.reverse("^_compile_groups@1^ should validate groups")); | ||
R = (types.isa.text(groups)) ? groups.split(/\s*,\s*/) : groups; | ||
// for group in R | ||
// continue if GUY.props.has @hub._hedges.hedgepaths, group | ||
// throw new E.Intertype_ETEMPTBD '^intertype/Type_cfg^', "unknown hedge group #{rpr group}" | ||
return R; | ||
} | ||
}; | ||
//=========================================================================================================== | ||
this.Intertype = (function() { | ||
class Intertype extends Intertype_abc { | ||
//--------------------------------------------------------------------------------------------------------- | ||
constructor(cfg) { | ||
var _hedgebuffer, base_proxy_cfg, count, group, ref, sub_proxy_cfg; | ||
super(); | ||
//....................................................................................................... | ||
/* TAINT ideally would put this stuff elsewhere */ | ||
base_proxy_cfg = { | ||
/* TAINT ideally would put this stuff elsewhere */ | ||
_get_hedge_base_proxy_cfg(self, method_name) { | ||
return { | ||
// _method_name = method_name | ||
// _method_name = "_#{method_name}" unless _method_name.startsWith '_' | ||
//....................................................................................................... | ||
get: (target, key) => { | ||
@@ -168,107 +101,104 @@ var R, f; | ||
} | ||
_hedgebuffer.length = 0; | ||
_hedgebuffer.push('_isa'); | ||
_hedgebuffer.push(key); | ||
if (key === 'constructor') { | ||
return target.constructor; | ||
} | ||
if (key === 'toString') { | ||
return target.toString; | ||
} | ||
if (key === 'call') { | ||
return target.call; | ||
} | ||
if (key === 'apply') { | ||
return target.apply; | ||
} | ||
//................................................................................................... | ||
self._initialize_state(); | ||
self.state.method = method_name; | ||
self.state.verb = method_name.slice(1); | ||
self.state.hedges = [key]; | ||
self.state.hedgerow = key; | ||
//................................................................................................... | ||
if (key === 'of' || key === 'or') { | ||
throw new E.Intertype_ETEMPTBD('^intertype.base_proxy@2^', `hedgerow cannot start with \`${key}\`, must be preceeded by hedge`); | ||
} | ||
if ((GUY.props.get(this.registry, key, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.base_proxy@3^', `unknown hedge or type ${rpr(key)}`); | ||
} | ||
if ((R = GUY.props.get(target, key, H.signals.nothing)) !== H.signals.nothing) { | ||
//................................................................................................... | ||
return R; | ||
} | ||
f = { | ||
[`${key}`]: (function(x) { | ||
praise('^878-1^', rpr(x)); | ||
return 'something'; | ||
}) | ||
}[key]; | ||
return target[key] = new Proxy(f, sub_proxy_cfg); | ||
//................................................................................................... | ||
/* TAINT code below never used? */ | ||
if (method_name === '_create') { | ||
f = H.nameit(key, function(cfg = null) { | ||
return self[self.state.method](key, cfg); | ||
}); | ||
} else { | ||
f = H.nameit(key, function(...P) { | ||
return self[self.state.method](...P); | ||
}); | ||
} | ||
GUY.props.hide(target, key, R = new Proxy(f, this._get_hedge_sub_proxy_cfg(self))); | ||
return R; | ||
} | ||
}; | ||
//....................................................................................................... | ||
count = 0; | ||
sub_proxy_cfg = { | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_get_hedge_sub_proxy_cfg(self) { | ||
return { | ||
get: (target, key) => { | ||
var R, f; | ||
var R, f, type_dsc; | ||
if (key === Symbol.toStringTag) { | ||
// debug '^878-2^', target, rpr key | ||
// process.exit 111 if count++ > 100 | ||
return void 0; | ||
} | ||
_hedgebuffer.push(key); | ||
if (key === 'constructor') { | ||
return target.constructor; | ||
} | ||
if (key === 'toString') { | ||
return target.toString; | ||
} | ||
if (key === 'call') { | ||
return target.call; | ||
} | ||
if (key === 'apply') { | ||
return target.apply; | ||
} | ||
self.state.hedges.push(key); | ||
self.state.hedgerow = self.state.hedges.join(self.cfg.sep); | ||
if ((R = GUY.props.get(target, key, H.signals.nothing)) !== H.signals.nothing) { | ||
return R; | ||
} | ||
f = { | ||
[`${key}`]: function(x) { | ||
var method_name; | ||
method_name = _hedgebuffer.shift(); | ||
debug('^878-3^', {method_name, _hedgebuffer}); | ||
return this[method_name](..._hedgebuffer, x); | ||
} | ||
}[key]; | ||
debug('^878-4^', f); | ||
return target[key] = new Proxy(f, sub_proxy_cfg); | ||
//................................................................................................... | ||
if ((type_dsc = GUY.props.get(this.registry, key, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.base_proxy@4^', `unknown hedge or type ${rpr(key)}`); | ||
} | ||
//................................................................................................... | ||
/* check for preceding type being iterable when building hedgerow with `of`: */ | ||
if ((key === 'of') && (!this._collections.has(target.name))) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.sub_proxy@5^', `expected type before \`of\` to be a collection, got ${rpr(target.name)}`); | ||
} | ||
//................................................................................................... | ||
f = H.nameit(key, function(x) { | ||
return self[self.state.method](...self.state.hedges, x); | ||
}); | ||
GUY.props.hide(target, key, R = new Proxy(f, this._get_hedge_sub_proxy_cfg(self))); | ||
return R; | ||
} | ||
}; | ||
//....................................................................................................... | ||
GUY.props.hide(this, 'cfg', {...ITYP.defaults.Intertype_constructor_cfg, ...cfg}); | ||
GUY.props.hide(this, '_hedges', new HEDGES.Intertype_hedges()); | ||
// GUY.props.hide @, 'isa', new GUY.props.Strict_owner { reset: false, } | ||
GUY.props.hide(this, 'isa', new Proxy({}, base_proxy_cfg)); | ||
GUY.props.hide(this, 'validate', new GUY.props.Strict_owner({ | ||
reset: false | ||
})); | ||
GUY.props.hide(this, 'declare', new Proxy(this._declare.bind(this), { | ||
get: (_, type) => { | ||
return (cfg) => { | ||
return this._declare.call(this, type, cfg); | ||
}; | ||
} | ||
})); | ||
GUY.props.hide(this, 'registry', new GUY.props.Strict_owner({ | ||
reset: false | ||
})); | ||
GUY.props.hide(this, 'types', types); | ||
GUY.props.hide(this, 'groups', {}); | ||
// GUY.props.hide @, '_hedgebuffer', [] | ||
_hedgebuffer = []; | ||
ref = this._hedges._get_groupnames(); | ||
//....................................................................................................... | ||
for (group of ref) { | ||
this.groups[group] = new Set(); | ||
((group) => { | ||
return this.declare(group, { | ||
groups: group, | ||
test: (x) => { | ||
var R; | ||
R = this.groups[group].has(this.type_of(x)); | ||
return this._protocol_isa({ | ||
term: group, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: R | ||
}); | ||
} | ||
}); | ||
})(group); | ||
} | ||
GUY.lft.freeze(this.groups); | ||
//....................................................................................................... | ||
return void 0; | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_declare(type, type_cfg) { | ||
var group, i, len, ref; | ||
type_cfg = { | ||
...type_cfg, | ||
name: type | ||
}; | ||
type_cfg = new ITYP.Type_cfg(this, type_cfg); | ||
this.registry[type] = type_cfg; | ||
this.isa[type] = type_cfg.test; | ||
this.validate[type] = (x) => { | ||
return this._validate(type, x); | ||
}; | ||
ref = type_cfg.groups; | ||
for (i = 0, len = ref.length; i < len; i++) { | ||
group = ref[i]; | ||
this._add_type_to_group(group, type); | ||
_declare(...P) { | ||
/* TAINT handling of arguments here shimmed while we have not yet nailed down the exact calling | ||
convention for this method. */ | ||
var dsc; | ||
dsc = this.type_factory.create_type(...P); | ||
this.registry[dsc.typename] = dsc; | ||
/* TAINT need not call _get_hedge_sub_proxy_cfg() twice? */ | ||
this.isa[dsc.typename] = new Proxy(dsc, this._get_hedge_sub_proxy_cfg(this)); | ||
this.validate[dsc.typename] = new Proxy(dsc, this._get_hedge_sub_proxy_cfg(this)); | ||
if (dsc.collection) { | ||
this._collections.add(dsc.typename); | ||
} | ||
@@ -279,4 +209,8 @@ return null; | ||
//--------------------------------------------------------------------------------------------------------- | ||
_add_type_to_group(group, type) { | ||
this.groups[group].add(type); | ||
_validate_hedgerow(hedgerow) { | ||
var ref1, ref2, xr; | ||
if (((ref1 = hedgerow[0]) === 'of' || ref1 === 'or') || ((ref2 = hedgerow[hedgerow.length - 1]) === 'of' || ref2 === 'or')) { | ||
xr = rpr(hedgerow.join(this.cfg.sep)); | ||
throw new E.Intertype_ETEMPTBD('^intertype.validate_hedgerow@6^', `hedgerow cannot begin or end with \`of\` or \`or\`, must be surrounded by hedges, got ${xr}`); | ||
} | ||
return null; | ||
@@ -287,130 +221,161 @@ } | ||
_isa(...hedges) { | ||
var R, e, hedge, hedge_idx, i, len, ref, tail_hedges, type, typetest, verdict, x; | ||
ref = hedges, [...hedges] = ref, [type, x] = splice.call(hedges, -2); | ||
for (hedge_idx = i = 0, len = hedges.length; i < len; hedge_idx = ++i) { | ||
var R, error, ref1, x; | ||
ref1 = hedges, [...hedges] = ref1, [x] = splice.call(hedges, -1); | ||
this.state.isa_depth++; | ||
R = false; | ||
try { | ||
R = this.state.result = this._inner_isa(...hedges, x); | ||
} catch (error1) { | ||
error = error1; | ||
if (this.cfg.errors || error instanceof E.Intertype_error) { | ||
throw error; | ||
} | ||
this.state.error = error; | ||
} | ||
this.state.isa_depth--; | ||
return this.state.result = R; | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_inner_isa(...hedges) { | ||
var R, advance, element, error, hedge, hedge_idx, is_terminal, last_hedge_idx, ref1, result, tail_hedges, type_dsc, x; | ||
ref1 = hedges, [...hedges] = ref1, [x] = splice.call(hedges, -1); | ||
this._validate_hedgerow(hedges); | ||
hedge_idx = -1; | ||
last_hedge_idx = hedges.length - 1; | ||
advance = false; | ||
is_terminal = false; | ||
R = true; | ||
while (true) { | ||
//....................................................................................................... | ||
hedge_idx++; | ||
if (hedge_idx > last_hedge_idx) { | ||
return R; // exit point | ||
} | ||
hedge = hedges[hedge_idx]; | ||
switch (R = this._test_hedge(hedge, x)) { | ||
case true: | ||
null; | ||
break; | ||
case H.signals.true_and_break: | ||
return true; | ||
case H.signals.false_and_break: | ||
is_terminal = (hedges[hedge_idx + 1] === 'or') || (hedge_idx === last_hedge_idx); | ||
//..................................................................................................... | ||
if (advance) { | ||
if (is_terminal) { // exit point | ||
return false; | ||
case false: | ||
return false; | ||
case H.signals.process_list_elements: | ||
case H.signals.process_set_elements: | ||
} | ||
if (hedge !== 'or') { | ||
continue; | ||
} | ||
} | ||
advance = false; | ||
//..................................................................................................... | ||
switch (hedge) { | ||
//................................................................................................... | ||
case 'of': | ||
this.push_hedgeresult(['▲ii1', this.state.isa_depth, 'of', x, true]); | ||
tail_hedges = hedges.slice(hedge_idx + 1); | ||
for (e of x) { | ||
if (!this._isa(...tail_hedges, type, e)) { | ||
return false; | ||
try { | ||
for (element of x) { | ||
if ((this._isa(...tail_hedges, element)) === false) { // exit point | ||
// return ( false ) if ( @_inner_isa tail_hedges..., element ) is false # exit point | ||
return false; | ||
} | ||
} | ||
} catch (error1) { | ||
error = error1; | ||
if (!((error.name === 'TypeError') && (error.message === 'x is not iterable'))) { | ||
throw error; | ||
} | ||
throw new E.Intertype_ETEMPTBD('^intertype.isa@7^', `\`of\` must be preceded by collection name, got ${rpr(hedges[hedge_idx - 1])}`); | ||
} | ||
return true; // exit point | ||
//................................................................................................... | ||
case 'or': | ||
this.push_hedgeresult(['▲ii2', this.state.isa_depth, 'or', x, true]); | ||
R = true; | ||
continue; | ||
} | ||
//..................................................................................................... | ||
if ((type_dsc = GUY.props.get(this.registry, hedge, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.isa@8^', `unknown hedge or type ${rpr(hedge)}`); | ||
} | ||
//..................................................................................................... | ||
// @push_hedgeresult hedgeresult = [ '▲ii3', @state.isa_depth, type_dsc.name, x, ] | ||
result = type_dsc.call(this, x); | ||
// hedgeresult.push result | ||
switch (result) { | ||
case H.signals.return_true: | ||
return true; | ||
default: | ||
throw new E.Intertype_ETEMPTBD('^intertype@1^', `illegal return value from \`_test_hedge()\`: ${rpr(type)}`); | ||
case false: | ||
advance = true; | ||
R = false; | ||
continue; | ||
case true: | ||
if (is_terminal) { | ||
return true; | ||
} | ||
continue; | ||
} | ||
//..................................................................................................... | ||
throw new E.Intertype_internal_error('^intertype.isa@9^', `unexpected return value from hedgemethod for hedge ${rpr(hedge)}: ${rpr(R)}`); | ||
} | ||
//....................................................................................................... | ||
if ((typetest = GUY.props.get(this.isa, type, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype@1^', `unknown type ${rpr(type)}`); | ||
return R; // exit point | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_validate(...hedges) { | ||
var ref1, state_report, x; | ||
ref1 = hedges, [...hedges] = ref1, [x] = splice.call(hedges, -1); | ||
if (this._isa(...hedges, x)) { | ||
return x; | ||
} | ||
verdict = typetest(x); | ||
return this._protocol_isa({ | ||
term: type, | ||
x, | ||
value: H.signals.nothing, | ||
verdict | ||
console.log(GUY.trm.reverse(GUY.trm.red("\n Validation Failure "))); | ||
console.log((this.get_state_report({ | ||
format: 'failing' | ||
})).trim()); | ||
console.log(GUY.trm.reverse(GUY.trm.red(" Validation Failure \n"))); | ||
state_report = this.get_state_report({ | ||
format: 'short', | ||
colors: false, | ||
width: 500 | ||
}); | ||
throw new E.Intertype_validation_error('^intertype.validate@3^', this.state, state_report); | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_test_hedge(hedge, x) { | ||
var R, hedgetest; | ||
if ((hedgetest = GUY.props.get(this._hedges._hedgemethods, hedge, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype@1^', `unknown hedge ${rpr(hedge)}`); | ||
_create(type, cfg) { | ||
var R, create, t, type_dsc; | ||
create = null; | ||
//....................................................................................................... | ||
if ((type_dsc = GUY.props.get(this.registry, type, null)) == null) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.create@11^', `unknown type ${rpr(type)}`); | ||
} | ||
//....................................................................................................... | ||
switch (R = hedgetest.call(this, x)) { | ||
case H.signals.true_and_break: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: R | ||
}); | ||
case H.signals.false_and_break: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: R | ||
}); | ||
case false: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: false | ||
}); | ||
case true: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: true | ||
}); | ||
case H.signals.process_list_elements: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: R | ||
}); | ||
case H.signals.process_set_elements: | ||
return this._protocol_isa({ | ||
term: hedge, | ||
x, | ||
value: H.signals.nothing, | ||
verdict: R | ||
}); | ||
/* Try to get `create` method, or, should that fail, the `default` value. Throw error when neither | ||
`create` nor `default` are given: */ | ||
if ((create = GUY.props.get(type_dsc, 'create', null)) === null) { | ||
if ((R = GUY.props.get(type_dsc, 'default', H.signals.nothing)) === H.signals.nothing) { | ||
throw new E.Intertype_ETEMPTBD('^intertype.create@12^', `type ${rpr(type)} does not have a \`default\` value or a \`create()\` method`); | ||
} | ||
} else { | ||
/* If `create` is given, call it to obtain default value: */ | ||
//....................................................................................................... | ||
R = create.call(this, cfg); | ||
} | ||
//....................................................................................................... | ||
throw new E.Intertype_internal_error('^intertype@1^', `unexpected return value from hedgemethod for hedge ${rpr(hedge)}: ${rpr(R)}`); | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_protocol_isa({term, x, value, verdict}) { | ||
var groups, ref, src, test, type_cfg; | ||
if ((type_cfg = GUY.props.get(this.registry, term, null)) != null) { | ||
groups = (ref = type_cfg.groups) != null ? ref : null; | ||
if ((test = GUY.props.get(type_cfg, 'test', null)) != null) { | ||
src = GUY.src.slug_from_simple_function({ | ||
function: test, | ||
fallback: '???' | ||
}); | ||
if ((create == null) && (cfg != null)) { | ||
if ((t = H.js_type_of(R)) === '[object Object]' || t === '[object Array]') { | ||
R = Object.assign(H.deep_copy(R), cfg); | ||
} else { | ||
src = null; | ||
R = cfg; | ||
} | ||
} else { | ||
groups = null; | ||
src = null; | ||
R = H.deep_copy(R); | ||
} | ||
debug(GUY.trm.gold('^_protocol_isa@1^', {term, groups, x, value, verdict, src})); | ||
return verdict; | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
_validate(...hedges) { | ||
var qtype, ref, type, x, xr; | ||
ref = hedges, [...hedges] = ref, [type, x] = splice.call(hedges, -2); | ||
debug('^4534^', {hedges, type, x}); | ||
debug('^4534^', this._isa(...hedges, type, x)); | ||
if (this._isa(...hedges, type, x)) { | ||
return true; | ||
//....................................................................................................... | ||
if (type_dsc.freeze === true) { | ||
R = Object.freeze(R); | ||
} else if (type_dsc.freeze === 'deep') { | ||
R = GUY.lft.freeze(H.deep_copy(R)); | ||
} | ||
qtype = [...hedges, type].join(this.cfg.sep); | ||
xr = to_width(rpr(x), 100); | ||
throw new E.Intertype_ETEMPTBD('^intertype@1^', `not a valid ${qtype}`); | ||
//....................................................................................................... | ||
return this._validate(type, R); | ||
} | ||
@@ -422,10 +387,29 @@ | ||
//----------------------------------------------------------------------------------------------------------- | ||
_walk_hedgepaths(cfg) { | ||
throw new Error("^_walk_hedgepaths@1^ not implemented"); | ||
_split_hedgerow_text(hedgerow) { | ||
return hedgerow.split(this.cfg.sep); | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
get_state_report(cfg) { | ||
return H.get_state_report(this, cfg); | ||
} | ||
//--------------------------------------------------------------------------------------------------------- | ||
push_hedgeresult(hedgeresult) { | ||
/* [ ref, level, hedge, value, r, ] = hedgeresult */ | ||
var hedge, level, r, ref, value; | ||
[ref, level, hedge, value, r] = hedgeresult; | ||
H.types.validate.nonempty_text(ref); | ||
// H.types.validate.cardinal level | ||
H.types.validate.nonempty_text(hedge); | ||
H.types.validate.boolean(r); | ||
this.state.hedgeresults.push(hedgeresult); | ||
return hedgeresult.at(-1); | ||
} | ||
}; | ||
//--------------------------------------------------------------------------------------------------------- | ||
Intertype.prototype.equals = H.equals; | ||
Intertype.prototype.type_of = H.type_of; | ||
@@ -439,11 +423,18 @@ | ||
// cfg = { ITYP.defaults.Intertype_walk_hedgepaths_cfg..., cfg..., } | ||
// yield from GUY.props.walk_tree @isa, cfg | ||
// return null | ||
// #----------------------------------------------------------------------------------------------------------- | ||
// _walk_hedgepaths: ( cfg ) -> | ||
// throw new Error "^intertype._walk_hedgepaths@9^ not implemented" | ||
// # cfg = { H.defaults.Intertype_walk_hedgepaths_cfg..., cfg..., } | ||
// # yield from GUY.props.walk_tree @isa, cfg | ||
// # return null | ||
//########################################################################################################### | ||
this.defaults = GUY.lft.freeze(this.defaults); | ||
this.Type_factory = Type_factory; | ||
this.Intertype = Intertype; | ||
this.Intertype_user_error = E.Intertype_user_error; | ||
}).call(this); | ||
//# sourceMappingURL=main.js.map |
{ | ||
"name": "intertype", | ||
"version": "0.0.4", | ||
"version": "0.101.4", | ||
"description": "A JavaScript typechecker", | ||
@@ -17,9 +17,5 @@ "main": "lib/main.js", | ||
"dependencies": { | ||
"acorn": "^8.7.1", | ||
"acorn-loose": "^8.3.0", | ||
"acorn-walk": "^8.2.0", | ||
"astring": "^1.8.3", | ||
"guy": "10.0.0", | ||
"guy": "11.6.0", | ||
"intertype-legacy": "^7.7.1", | ||
"picomatch": "^2.3.1", | ||
"rfdc": "^1.3.0", | ||
"to-width": "^1.2.0" | ||
@@ -26,0 +22,0 @@ }, |
629
README.md
# InterType | ||
# ▞ InterType | ||
@@ -11,5 +11,15 @@ A JavaScript type checker with helpers to implement own types and do object shape validation. | ||
- [InterType](#intertype) | ||
- [Hedges](#hedges) | ||
- [Type Declarations](#type-declarations) | ||
- [Quick Links](#quick-links) | ||
- [▞ InterType](#%E2%96%9E-intertype) | ||
- [Motivation](#motivation) | ||
- [Contracts of Type Tests and the Verbs `isa`, `validate`](#contracts-of-type-tests-and-the-verbs-isa-validate) | ||
- [Type Tests](#type-tests) | ||
- [`isa`](#isa) | ||
- [`validate`](#validate) | ||
- [Hedgerows](#hedgerows) | ||
- [Diagram](#diagram) | ||
- [xxx](#xxx) | ||
- [Intertype `state` Property](#intertype-state-property) | ||
- [Intertype `create`](#intertype-create) | ||
- [Intertype `equals()`](#intertype-equals) | ||
- [To Do](#to-do) | ||
@@ -20,8 +30,289 @@ - [Is Done](#is-done) | ||
## Quick Links | ||
# InterType | ||
* [Type Declarations](README-declare.md) | ||
## Hedges | ||
# ▞ InterType | ||
## Motivation | ||
* structural typing | ||
* inspired by [Clojure spec](https://typedclojure.org)[https://www.youtube.com/watch?v=B_Farscj0hY] | ||
* most of the time used to perform a 'lower bounds' check of Plain Old Dictionaries (objects), i.e. objects | ||
must satisfy all key/constraint checks of a given type declaration, but object may have additional | ||
key/value pairs | ||
## Contracts of Type Tests and the Verbs `isa`, `validate` | ||
### Type Tests | ||
* A type test (TT) is a function that accepts a single argument and returns a boolean. | ||
* TTs should not normally throw errors; however, that can sometimes be inconvenient to implement. For this | ||
reason, InterType implements 'exception-guarding' which entails that should a type tester inadvertently | ||
cause an exception, that exception will be silently caught and stored in the `state.error` property of the | ||
`Intertype` instance; the return value of the call will be set `false`. The following types `nevah` and | ||
`oops` are almost equivalent in that they return `false` for any input; however, immediately after using | ||
`oops`, the suppressed error may be accessed through `types.state.error` property: | ||
```coffee | ||
{ Intertype | ||
Intertype_user_error } = require 'intertype' | ||
types = new Intertype { errors: false, } | ||
types.declare.nevah ( x ) -> false | ||
types.declare.oops ( x ) -> throw new Error 'oops' | ||
types.declare.oops_anyway ( x ) -> throw new Intertype_user_error 'oops' | ||
types.isa.oops 42 # `false` | ||
types.state.error? # `true` (i.e. `types.state.error` contains error) | ||
types.isa.nevah 42 # `false` | ||
types.state.error? # `false` (i.e. no error, all OK) | ||
types.isa.oops_anyway 42 # !!! throws an error | ||
``` | ||
Because silently suppressed errors can be tricky to debug and checking for `state.error` is easily | ||
forgotten (and should not normally be necessary), exception-guarding is an opt-in (as shown above, use | ||
`errors: false`) | ||
* Users may always construct type testers whose intentional errors will not be silently caught by deriving | ||
their errors from `Intertype_user_error` | ||
* A type test must be *idempotent* and is therefore only allowed to look at, but not to touch (modify) | ||
values; IOW, it must be a pure method in the sense of functional programming. Yes, in theory one could | ||
write an `isa` method that 'fixes' a list by transforming, adding or deleting some elements (conveniently | ||
so, while one has to iterate over list elements anyway), but that would most certainly violate most user's | ||
basic assumptions—a type check is not supposed to return with a triumphant 'does the value have the proper | ||
shape? Well yes, now it does!'. A type check is not a repair order. InterType does not check for `isa` | ||
method being pure because that is deemed (far) too computationally expensive. | ||
### `isa` | ||
* However, when called in the context of a hedgerow as in `isa.collection.of.type x`, an exception may be | ||
thrown, e.g. when a type name is undeclared or `of` is preceded by a non-iterable type name (cf the | ||
non-sensical `isa.integer.of.integer 42`). This is not the type test, this is the verb `isa` complaining | ||
about a malformed chain of type tests. | ||
* It is not allowed to use a name in an `isa` (or `validate` or `create`) hedgerow without that name being | ||
`declare`d prior to that. | ||
## `validate` | ||
* `validate` is a verb that performs an `isa` test; should that return `false`, an exception is thrown; if | ||
it returns `true`, *the tested value* will be returned. | ||
* convenient for writing postconditions, as in `f = ( a, b ) -> validate.integer a * b`. | ||
## Hedgerows | ||
* simplest form: test for a value; preferred form is to use property accessor syntax (a.k.a. 'dot | ||
notation'), e.g. `isa.integer x` (equivalent to `isa[ 'integer' ] x`) | ||
* these accessors are called 'hedges' | ||
* hedges can be combined in so-called 'hedgerows', e.g. `isa.positive0.integer x` tests whether `x >= 0` | ||
* hedgerows can be arbitrarily long, e.g. `isa.optional.nonempty.list_of.optional.negative1.integer x` | ||
* whether one wants lomng hedgerows or not is a matter of taste, but it will very probably be more | ||
systematic and more readable to define meaningful intermediate types instead of using log hedgerows: | ||
```coffee | ||
declare.xy_count { test: ( ( x ) -> @isa.optional.set_of.negative1.integer x ), } | ||
declare.maybe_xy_counts { test: ( ( x ) -> @isa.optional.nonempty.list_of.xy_count x ), } | ||
... | ||
validate.maybe_xy_counts some_value | ||
``` | ||
* in order to satisfy a hedgerow constraint, the value given must satisfy all individual terms, in the order | ||
given. In other words, a hedgerow is a notation for a series of terms connected by logical conjunctions, | ||
`a.b.c x ⇔ ( a x ) ∧ ( b x ) ∧ ( c x )` (in detail, `list_of` and `set_of` introduce a complication). | ||
To re-use the slightly convoluted example from above, one can understand what | ||
`isa.optional.nonempty.list_of.optional.negative1.integer x` means by rewriting it in pseudo-code along | ||
the following lines: | ||
```coffee | ||
test: ( x ) -> | ||
return false unless isa.optional x # `optional x` is satisfied if `x` is `undefined` or `null`, otherwise go on | ||
return false unless isa.nonempty x # `nonempty x` is true if `x` contains at least one element | ||
return false unless isa.list x # `list_of...` tests whether `x` is a list, ... | ||
for each e in x: # ... and then applies the rest of the hedgerow to each of its elements: | ||
return false unless isa.optional e # this `optional` clause is run against each element, so list may have `null` elements | ||
return false unless isa.negative1 e # `negative1 x` tests for `x < 0` | ||
return false unless isa.integer e # `true` for whole numbers; uses `Number.isInteger()` | ||
return true | ||
``` | ||
* hedgerows will be evaluated in a 'short-circuited' manner like JavaScript logical operators; this means | ||
that tests will only be performed up to the point where the result is definitely known. For example, if in | ||
`z = ( a or b )` the left sub-expression has been found to be `true`, we already know that the outcome can | ||
only be `true` as well, so we don't have to compute `b`. In `isa.optional.text x` we find that `x` is | ||
`undefined` or `null` we are already done and can (and must) skip the test for `text`. Conversely, if we'd | ||
find that `a` is `false` the second part of the disjunction could still be `true`, so we cannot | ||
short-circuit but must evaluate the second part as well, and the same goes for `isa.optional.text x` if | ||
`x?` (i.e. `x != null` or, even more explicitly, (JS) `( x !== null ) and ( x !== undefined )`). | ||
* a hedgerow may contain one or more `or` hedges that signify logical disjunction, e.g. | ||
`isa.integer.or.nonempty.text.or.boolean x`. In this case, we partition the hedgerow into its constituent | ||
terms: `( integer x ) ∨ ( nonempty.text ) ∨ ( boolean x )` and evaluate by walking through each | ||
sub-hedgerow until it is either satisfied (which is when we can break the loop) or dissatisfied; in that | ||
case, we jump forward to the next sub-hedgerow to repeat the same; when there are no more sub-hedgerows | ||
left, the very last test then determines the result for the entire row. | ||
### Diagram | ||
``` | ||
isa.text | ||
text is text isnt text | ||
return true ATOERF¹ | ||
⊙ return true return false | ||
¹ATOERF: Advance To OR, Else Return False | ||
``` | ||
``` | ||
isa.text.or.optional.list_of.positive1.integer | ||
text is text isnt text | ||
return true ATOERF¹ | ||
or ———————————————————————— | ||
integer is integer isnt integer | ||
next ATOERF¹ | ||
⊙ return true return false | ||
¹ATOERF: Advance To OR, Else Return False | ||
``` | ||
``` | ||
isa.text.or.optional.list_of.positive1.integer | ||
text is text isnt text | ||
return true ATOERF¹ | ||
or ———————————————————————— | ||
optional not x? x? | ||
return true next | ||
list_of: list is list isnt list | ||
switch to EM² ATOERF¹ | ||
positive1 e > 0 not ( e > 0 ) | ||
next ATOERF¹ | ||
integer is integer isnt integer | ||
next ATOERF¹ | ||
⊙ return true return false | ||
¹ATOERF: Advance To OR, Else Return False | ||
²EM: Elements Mode | ||
``` | ||
Schema for `isa.negative1.integer.or.optional.empty.text -42` (`true`): Both `isa.negative1 -42` and | ||
`isa.integer -42` evaluate to `true`; since these terms are implicitly connected with `and`, we must | ||
evaluate them all to ensure no `false` term occurs; this is what the single triangle ▼ signifies, 'continue | ||
with next'. When we reach the `or` clause, though, we can short-circuit (▼▼▼) the evaluation and return `true`: | ||
| FALSE | isa | TRUE | | ||
| ------: | :-------: | :----- | | ||
| | negative1 | ▼ | | ||
| | integer | ▼ | | ||
| ───────── | OR | ▼▼▼────── | | ||
| | optional | | | ||
| | empty | | | ||
| | text | | | ||
| ═════════ | ═════════ | ═════════ | | ||
| | -42 | TRUE | | ||
Schema for `isa.negative1.integer.or.optional.empty.text 'meep'` (`false`): `'meep'` cannot satisfy | ||
`negative1` since it is not numeric, so the entire clause fails. We can again short-circuit, but *only up to | ||
the next or-clause*, symbolized by ▼▼. The next term is `optional`; since all values (including `null` and | ||
`undefined`) satisfy this constraint, we go to the next term, `empty`; since `'meep'.length` is `4`, this | ||
term fails, so we have to ▼▼ advance to the end of the current clause which coincides with the end of the | ||
hedgerow, meaning we can return `false`: | ||
| FALSE | isa | TRUE | | ||
| ------: | :-------: | :----- | | ||
| ▼▼ | negative1 | | | ||
| | integer | | | ||
| ────────▼ | OR | ───────── | | ||
| | optional | ▼ | | ||
| ▼▼ | empty | | | ||
| | text | | | ||
| ═════════ | ═════════ | ═════════ | | ||
| FALSE | 'meep' | | | ||
Schema for `isa.negative1.integer.or.optional.empty.text -42` (`true`): `null` is not negative (and, of | ||
course, not positive either) so we can ▼▼ advance to the next 'gate'; there, `null` does fulfill `optional` | ||
(like any value) but with a 'special effect': `isa.optional null` and `isa.optional undefined` cause a | ||
global short-circuit, meaning we can return `true` right away and ignore any number of other constraints: | ||
| FALSE | isa | TRUE | | ||
| ------: | :-------: | :----- | | ||
| ▼▼ | negative1 | | | ||
| | integer | | | ||
| ────────▼ | OR | ───────── | | ||
| | optional | ▼▼▼ | | ||
| | empty | | | ||
| | text | | | ||
| ═════════ | ═════════ | ═════════ | | ||
| | null | TRUE | | ||
This short-circuiting behavior of `optional` when seeing a nullish value is peculiar to `optional`; it is | ||
similar to there only being a single empty exemplar of collections (strings, lists, sets) except applying to | ||
all types: `( isa.empty.text a ) == ( isa.empty.text b )` entails `a == b == ''`; likewise, `( | ||
isa.optional.$TYPE_A a ) == ( isa.optional.$TYPE_B b )` in conjunction with `( a == null )` implies `a == | ||
b`, so as soon as we learn that `a == null` and a value has an `optional` allowance, no other constraint has | ||
to be considered. | ||
**Note** on `optional`: The types `optional.integer` and `optional.text` have `{ null, undefined }` as | ||
intersection of their domains, meaning that in the case of their disjunction—`isa.optional.integer.or.text`, | ||
`isa.integer.or.optional.text` and so on—are indistinguishable: all variations will, among (infinitely many) | ||
other values accept all of `null`, `undefined`, `1`, `42`, `'x'`, `'foobar'` and so on. Because of this one | ||
may want to restrict oneself to only allow `optional` as the *first* hedge, avoiding constructs like | ||
`isa.integer.or.optional.text` as a matter of style. | ||
Schema for `isa.nonempty.text.or.list_of.nonempty.text [ 'helo', 'world', ]` (`true`): `nonempty` gives | ||
`true` for `[ 'helo', 'world', ]`, but since this is a `list` rather than a `text`, the first clause fails | ||
nonetheless. Next up is `list_of`, which first calls `isa.list [ 'helo', 'world', ]`; that being true, it | ||
then switches to element mode, meaning that rather than applaying the remaining tests against the argument | ||
passed in (the list), they will be applied to each *element* of the collection; this is here symbolized by | ||
`∈ nonempty` and `∈ text`; in total, four tests will be performed: `isa.nonempty 'helo'`, `isa.text 'helo'`, | ||
`isa.nonempty 'world'`, and `isa.text 'world'`, all of which return `true`, which leads to the entire | ||
compound type being satisfied: | ||
| FALSE | isa | TRUE | | ||
| ------: | :-------: | :----- | | ||
| | nonempty | ▼ | | ||
| ▼▼ | text | | | ||
| ────────▼ | OR | ───────── | | ||
| | list_of | ▼ | | ||
| | ∈ nonempty | ▼ | | ||
| | ∈ text | ▼ | | ||
| ═════════ | ═════════ | ═════════ | | ||
| | [ 'helo', 'world', ] | TRUE | | ||
Observe that in a compound type, once the mode has been switched to element testing mode `∈`, there's no | ||
going back, so `isa.list_of.text.or.integer` is fundamentally different from `isa.integer.or.list_of.text`: | ||
the first will be true for all lists that contain nothing but strings and integer numbers, the second will | ||
be true for all values that are either an integer or a list of zero or more strings. This is a shortcoming | ||
of the current algorithm and may be fixed in the future; currently, there's no way to write `( | ||
isa.set_of.text x ) or ( isa.list_of.text x )` in a single go. Should you need such a type, it will probably | ||
be best to give the type a name, as in | ||
```coffee | ||
declare.set_or_list test: ( x ) -> ( isa.set_of.text x ) or ( isa.list_of.text x ) | ||
... | ||
isa.nonempty.set_or_list [ 'a', 'b', ] # true | ||
isa.set_or_list.or.integer 123 # true | ||
``` | ||
### xxx | ||
``` | ||
types.isa.integer 42 | ||
@@ -66,40 +357,115 @@ types.isa.even.integer -42 | ||
## Intertype `state` Property | ||
## Type Declarations | ||
* `Intertype` instances have a `state` property | ||
* the shape and initial value of `types.state` is: | ||
```coffee | ||
'use strict' | ||
```coffee | ||
types.state = { | ||
method: null | ||
hedges: [] | ||
extra_keys: null | ||
error: null | ||
data: null } | ||
``` | ||
types = new ( require '../../../apps/intertype' ).Intertype() | ||
{ isa | ||
declare } = types | ||
log = console.log | ||
* The initial value of `types.state` is resumed right before a top-level `isa` or `validate` test is | ||
performed. | ||
declare 'xy_quantity', test: [ | ||
( x ) -> @isa.object x | ||
( x ) -> @isa.float x.value | ||
( x ) -> @isa.nonempty.text x.unit | ||
] | ||
* The fields of `types.state` are used as follows: | ||
# can use simplified form: | ||
* When chained methods on `isa` and `validate` are called (as in `isa.integer x`, `validate.integer x`), | ||
`method` will be set to the name of method to be invoked (here `'_isa'` or `'_validate'`). | ||
declare.xy_quantity, | ||
* As each hedge in a hedgerow is encountered, its name is pushed into the `types.state.hedges` list and | ||
its associated test is performed. | ||
* type testing methods are allowed to set or manipulate the `types.state.data` value; this can be used as a | ||
side channel e.g. to cache intermediate and ancillary results from an expensive testing method | ||
* should an `isa` method cause an error with an `Intertype` instance with an `errors: false` setting, | ||
`state.error` will contain that error to enable applications to query for `types.state.error?` when an | ||
`isa` test has failed. Errors that are thrown instead of being silenced are *not* recorded in | ||
`state.error`. | ||
* `state.data` is a place for the user's type-checking methods to store intermediate data in. It is never | ||
touched by InterType methods except for being reset each time a top-level `isa` or `validate` is | ||
performed. It can be used to store expensive computational results that are necessitated by | ||
type-checking procedures; for example, one might have a type that is a potentially long list of either | ||
functions or names identifying functions. When checking that the list validates, one has to iterate over | ||
the list and check for all elements being either a function or a name. Knowing that names will have to | ||
be replaced by functions later on, on could have the `isa` method cache all those to-be-postprocessed | ||
items: | ||
```coffee | ||
( x ) -> | ||
@state.data ?= {} | ||
@state.data.name_indexes ?= [] | ||
for idx, element in x | ||
switch @type_of element | ||
when 'function' | ||
continue | ||
when 'text' | ||
@state.data.name_indexes.push { idx, element, } | ||
else | ||
return false | ||
... | ||
... | ||
``` | ||
## Intertype `create` | ||
* returns deep copy (structural clone) of `default` member of type declaration | ||
* in the case of objects, uses `Object.assign()` to apply optional `cfg` | ||
* all types can (and maybe should) have a `default` value: | ||
* `types.create.text()` returns `''` | ||
* `types.create.integer()` and `types.create.float()` return `0` | ||
* `types.create.object()` returns `{}` | ||
* `types.create.list()` returns `[]` | ||
* and so on | ||
* no implicit type coercion | ||
* `types.create.quantity()` (for which see below) has default `{ value: 0, unit: null, }` which does | ||
not satisfy the constraint `isa.nonempty.text x.unit`, so causes an error | ||
* but `types.create.quantity { unit: 'km', }` works because `Object.assign { value: 0, unit: null, }, { | ||
unit: 'km', }` gives `{ value: 0, unit: 'km', }` which does satisfy all constraints of `quantity` | ||
```coffee | ||
types.declare.quantity | ||
test: [ | ||
( x ) -> @isa.object x | ||
( x ) -> @isa.float x.value | ||
( x ) -> @isa.nonempty.text x.unit | ||
( x ) -> @isa.object x | ||
( x ) -> @isa.float x.value | ||
( x ) -> @isa.nonempty.text x.unit | ||
] | ||
default: | ||
value: 0 | ||
unit: null | ||
``` | ||
* `create()` is of great help when writing functions with a configuration object (here always called `cfg`). | ||
Where in earlier versions of this library one had to write: | ||
log '^1-1^', isa.xy_quantity null | ||
log '^1-1^', isa.xy_quantity 42 | ||
log '^1-1^', isa.xy_quantity { value: 42, unit: 'm', } | ||
``` | ||
```coffee | ||
f = ( cfg ) -> | ||
types.validate.foobar ( cfg = { defaults.foobar..., cfg..., } ) | ||
... | ||
``` | ||
now one can write more succinctly: | ||
```coffee | ||
f = ( cfg ) -> | ||
cfg = types.create.foobar cfg | ||
... | ||
``` | ||
and have reference to defaults, assignment from structured value and validation all wrapped up inside | ||
one call to a single method. | ||
## Intertype `equals()` | ||
* a 'deep equals' implementation (see [`jseq`](https://github.com/loveencounterflow/jseq), gleaned from | ||
[`jkroso/equals`](https://github.com/jkroso/equals)) | ||
## To Do | ||
* **[–]** make hedgepaths configurable—**hedges need an opt-in** | ||
* using depth (length) of hedgepath; default depth is 0 | ||
* using *wildcard hedgepath pattern* (provided by [`picomatch`]()https://github.com/micromatch/picomatch) | ||
* both at instantiation time for all builtins and declaration time for the type being declared | ||
* **[–]** allow to filter out builtin types | ||
@@ -111,9 +477,4 @@ * **[–]** implement sum types (a.k.a. tagged union, variant, variant record, choice type, discriminated | ||
between standard types and user-defined types | ||
* **[–]** "a group is a set of types. A group's `groups` property is itself, so group `collection` is | ||
groupmember of group `collection`, meaning there are tests for `isa.collection`, `isa.empty.collection` | ||
and so on." | ||
* **[–]** eliminate hedgepaths that end in a hedge instead of in a type (or group). So we don't allow to | ||
test for `empty x`, only for `empty.collection x`, `empty.any x` &c | ||
* **[–]** special types: | ||
* groups | ||
* <del>groups</del> | ||
* hedges | ||
@@ -125,19 +486,8 @@ * existential / quantified: | ||
* **[–]** allow to derive types, including derivation of defaults | ||
* **[–]** provide methods for the ubiquitous `validate.$TYPE ( cfg = { defaults.$TYPE..., cfg..., } )` as | ||
`cfg = types.get_defaults.$TYPE cfg` | ||
* **[–]** add `defaults` parameter to `declare` | ||
* **[–]** make it so that type declarations can be queried / viewed / checked by user, especially `defaults` | ||
must be retrievable so they can be referenced from new type declarations | ||
* **[–]** rename group `number` to `real`? to avoid conflict with JS `Number` and to clarify that this does | ||
not cover imaginary, complex numbers. Observe we now have `BigInt`s | ||
pre-generating literally hundreds of hedgpath chains | ||
* **[–]** fix naming of type test functions (always `test`, should be name of type) | ||
* **[–]** use 'auto-vivification' for hedgepaths as outlined in | ||
[`hengist/dev/intertype`](https://github.com/loveencounterflow/hengist/blob/40ec7b9cec3afc72c389a0d2889d4bab7babc893/dev/intertype/src/_ng.test.coffee#L813) | ||
* <del>**[–]** how to finalize hedges?</del> | ||
* <del>**[–]** demand to declare types with hedgepaths? `types.declare.empty.list`? `types.declare 'empty', | ||
'list'`?</del> | ||
* <del>**[–]** possible to 'auto-vivify' hedgepaths?</del> | ||
* <del>**[–]** scrap hedgepaths, replace by `isa.$TYPE x, cfg` API? or `isa.$TYPE P..., x` where P may be any | ||
number of modifiers as in `isa.list 'optional', 'empty', x`</del> | ||
* **[–]** numeric types: | ||
* **[–]** rename `number` to `real`? to avoid conflict with JS `Number` and to clarify that this does not | ||
cover imaginary, complex numbers. Observe we now have `BigInt`s pre-generating literally hundreds of | ||
hedgpath chains | ||
* **[–]** consider `float` (includes `infinity`) vs `ffloat` ('**f**inite' float, excludes `infinity`) | ||
(longer name, more restricted) | ||
* **[–]** salvage | ||
@@ -148,3 +498,122 @@ * from [farewell-commit of generated | ||
Islands](https://github.com/loveencounterflow/gaps-and-islands) | ||
* **[–]** implement `or` as in `types.isa.integer.or.text 'x'` | ||
* **[–]** consider to turn all hedges into strict owners | ||
* **[–]** can we generate random data based on a type declaration (like [Clojure `spec`] | ||
does)[https://youtu.be/B_Farscj0hY?t=1562] | ||
* **[–]** use sets not arrays when testing for extraneous keys in `Type_cfg.constructor()` | ||
* **[–]** offer a way to collect all errors in validation ('slow fail') instead of bailing out on first | ||
error ('fast fail') ([see HN post](https://news.ycombinator.com/item?id=32179856#32180458)) | ||
* **[–]** <del>make it so that type declarations can be queried / viewed / checked by user, especially | ||
`defaults` must be retrievable so they can be referenced from new type declarations</del> <ins>offer API | ||
to retrieve, review, print, document type declarations</ins> | ||
* **[–]** try to find a way to treat hedges, types equally—there shouldn't be any (apparent at least) | ||
difference since in a hedgerow like `isa.nonempty.text.or.optional.integer x` the types and hedges proper | ||
both appear all over the place | ||
* **[–]** in principle then, any combination of hedges proper and types becomes allowable; one could also | ||
say: hedges become types and types are chainable, as in `validate.empty.text x` isnt categorically | ||
different from `validate.text.integer x`, even if the latter reads non-sensically and can only ever fail | ||
(because there's no overlap between text values and integer values). Could/should then rule out | ||
non-sensical hedgerows by other means (i.e. a grammar that states what can go where) | ||
* **[–]** make the name of the disjunction—by default, `'or'`—configurable | ||
* **[–]** allow to configure that `optional` shall only applay to `null`; additionaly or alternatively, | ||
offer `nullable` as a hedge for the same purpose | ||
* **[–]** consider to change `list_of`, `set_of` into `list.of`, `set.of`, allow for all collections | ||
(`text.of`, `object.of`, `map.of`) | ||
* **[–]** implement `last_of()`, `first_of()` | ||
* **[–]** try to centralize hedgerow validation; happens in several places now | ||
* **[–]** implement aliases | ||
* **[–]** implement `isa`, `validate`, `create` as functions that accept hedgerow, value (i.e. can say both | ||
`isa.list.of.integer []` and `isa 'list', 'of', 'integer', []`, maybe `isa.list.of' 'integer', []`, too) | ||
* **[–]** currently `isa` &c call instance method `_isa` &c; make it so that `isa` calls `super()` and | ||
define effective `isa()` in base class `Intertype_xxxxx extends Intertype_abc`, `Intertype extends | ||
Intertype_xxxxx`. | ||
* **[–]** re-consider `seal` | ||
* **[–]** implement type dependencies (both explicit and implicit), e.g. `codepoint` depends on `text` while | ||
`codepointid` depends on `integer` | ||
* **[–]** clarify distinction between `container` and `collection` or remove one of them | ||
* **[–]** `helpers.dep_copy()` should allow circular objects where necessary | ||
* **[–]** `structuredClone()` throws an exception when encountering a function (and other things) | ||
* **[–]** fix (probably) related bug [metteur#1867d3a6535c4d1f12ccc55d359fc6ff681a16e6](https://github.com/loveencounterflow/metteur/tree/1867d3a6535c4d1f12ccc55d359fc6ff681a16e6) | ||
``` | ||
Validation Failure | ||
F create mtr_new_template.rpr:optional.function { template: 'the answers are ❰...answer❱.', open: '❰', close: '❱', rpr: null } | ||
F create mtr_new_template Symbol(misfit) | ||
Error: ^intertype/validate@1567^ not a valid boolean (violates 'x is true or false'): Symbol(return_true) | ||
Validation Failure | ||
``` | ||
* **[–]** in addition to single-`$`-prefixed keys, allow double-`$`-prefixed keys to allow arbitrary names | ||
for arbitrary conditions; these should probably always use functions as values: | ||
```coffee | ||
declare.foobar | ||
$: 'object' | ||
$number: 'optional.float' | ||
$string: 'optional.nonempty.text' | ||
$$either_number_or_string: ( x ) -> | ||
( x.number? or x.string? ) and not ( x.number? and x.string? ) | ||
``` | ||
* **[–]** implement 'checks', i.e. helpers to test for conditions like 'object has keys that conform to this | ||
pattern' &c (?) | ||
* **[–]** turn `Type_cfg` instances into functions | ||
* **[–]** document that `isa.optional.t x` is just a convenient way to write `isa.null.or.undefined.or.t x`, | ||
which explains why a hedgerow can be short-circuited as soon as `not x?` has been found to be `true` | ||
* **[–]** implement `examine`, a non-throwing equivalent to `validate`, which returns the test clauses up to | ||
the point of failure or `null`. Variant: call it `fails`, returns `false` where `isa` had returned `true`, | ||
non-empty list of tests otherwise: | ||
```coffee | ||
if tests = fails.foobar x # lists are truthy in JS | ||
log tests.at -1 # print info about failed test | ||
``` | ||
* **[–]** based on the above, provide nicely formatted error reports so users don't have to | ||
* **[–]** implement `create` with hedges such that one can write things like | ||
`create.nonempty.list.of.integer size: 5`; in this case, the `create` method of type `integer` should be | ||
called with argument `{ hedges: [ 'nonempty', 'integer', ], size: 5, }` | ||
* **[–]** wrap all hedges and type testers to check for arity `1`; mabe this can be done in | ||
`_get_hedge_base_proxy_cfg` once for all | ||
* **[–]** rule out use of names with `cfg.sep` (`.`) (generally, check for name being a valid JS identifier; | ||
likewise, `sep` should be restricted to non-identifier characters) | ||
* **[–]** consider to offer faster mode where all hegerows must get pre-declared instead of being | ||
auto-vivified on-the-fly | ||
* **[–]** rename `extras` in type descriptions to `open`? Or indeed create type `noxtra` similar to `empty`, | ||
`nonempty`: `isa.noxtra.foo x` (or `isa.foo.noxtra x`?) is `true` when `isa.foo x` is `true`, the | ||
declaration of type `foo` enumerates fieldnames, and no fields except these are found in `Object.keys x`. | ||
* **[–]** <del>allow `validate` to take an extra parameter: either string (with placeholders for data?) or | ||
function to be called in case of validation failure; this to make throwing more meaningful errors than | ||
standard validation errors easier</del> <ins>probably not possible due to existence of rest parameter in | ||
`_validate: ( hedges..., type, x ) ->`; instead, recommend these patterns:</ins> | ||
```coffee | ||
### provide custom value in case of postcondition failure: ### | ||
plus_1 = ( a, b ) -> | ||
R = a + b | ||
return try validate.float.or.bigint R catch error | ||
0 | ||
### throw custom error in case of postcondition failure: ### | ||
plus_2 = ( a, b ) -> | ||
R = a + b | ||
return try validate.float.or.bigint R catch error | ||
throw new Error "these values can not be added: a: #{rpr a}, b: #{rpr b}" | ||
``` | ||
* **[–]** implement configuration to specifiy whether validation errors should output tracing message and | ||
whether to include tracing in `stderr` or print to console or both | ||
* **[–]** must use deep copies when deriving values from defaults in `create()` | ||
* **[–]** `get_state_report()` may report single line even with `format: 'failing'` when test has succeeded, | ||
should return `null` | ||
* **[–]** change `default` to `defaults` (as in, 'field defaults') to avoid clash with JS reserved word. | ||
Alternative: <del>`paragon`</del> <ins>`template`</ins> | ||
* **[–]** do not use `$`-prefixed fieldnames, define fields in `fields` sub-object | ||
* **[–]** allow list as enumeration of allowed values as in `color: [ 'red', 'green', 'blue', ]` | ||
* **[–]** Implement a demo type `quantity` that allows inputs like `ms: 42`, `km: 3, m: 800` with | ||
normalization | ||
* **[–]** make sure key properties of `Intertype` instances are hidden to avoid terminal flooding on output | ||
## Is Done | ||
@@ -159,1 +628,53 @@ | ||
implement the same for `declare` as in `declare.my_type cfg` | ||
* **[+]** use 'auto-vivification' for hedgepaths as outlined in | ||
[`hengist/dev/intertype`](https://github.com/loveencounterflow/hengist/blob/40ec7b9cec3afc72c389a0d2889d4bab7babc893/dev/intertype/src/_ng.test.coffee#L813) | ||
* <del>**[–]** how to finalize hedges?</del> | ||
* <del>**[–]** demand to declare types with hedgepaths? `types.declare.empty.list`? `types.declare 'empty', | ||
'list'`?</del> | ||
* <del>**[–]** possible to 'auto-vivify' hedgepaths?</del> | ||
* <del>**[–]** scrap hedgepaths, replace by `isa.$TYPE x, cfg` API? or `isa.$TYPE P..., x` where P may be any | ||
number of modifiers as in `isa.list 'optional', 'empty', x`</del> | ||
* **[+]** fix naming of type test functions (always `test`, should be name of type) | ||
* **[+]** add `default` parameter to `declare` | ||
* **[+]** implement `create()` | ||
* **[+]** provide methods for the ubiquitous `validate.$TYPE ( cfg = { defaults.$TYPE..., cfg..., } )` as | ||
`cfg = types.get_defaults.$TYPE cfg` | ||
* **[+]** implement | ||
* **[+]** declarative freezing | ||
* <del>**[–]** declarative sealing</del> | ||
* **[+]** declarative validation of absence of extraneous (enumerable) properties | ||
* **[+]** declarative object creation with class declaration property `create` (must be function) | ||
* **[+]** <del>make hedgepaths configurable—**hedges need an opt-in**</del> | ||
* <del>using depth (length) of hedgepath; default depth is 0</del> | ||
* <del>using *wildcard hedgepath pattern* (provided by [`picomatch`]()https://github.com/micromatch/picomatch)</del> | ||
* <del>both at instantiation time for all builtins and declaration time for the type being declared</del> | ||
* **[+]** <del>eliminate hedgepaths that end in a hedge instead of in a type (or group). So we don't allow to | ||
test for `empty x`, only for `empty.collection x`, `empty.any x` &c</del> | ||
* **[+]** flatten type entries in registry to be simple `Type_cfg` instances | ||
* **[+]** <del>"a group is a set of types. A group's `groups` property is itself, so group `collection` is | ||
groupmember of group `collection`, meaning there are tests for `isa.collection`, `isa.empty.collection` | ||
and so on."</del> | ||
* **[+]** <del>reconsider role of groups in type declarations</del> <ins>removed groups altogether, keeping | ||
boolean property `collection`</ins> | ||
* **[+]** <del>change `collection` to `iterable`, b/c their distinguishing mark is that they can be iterated | ||
over by virtue of `x[ Symbol.iterator ]` returning a function</del> <ins>make `collection` a boolean | ||
property of type configuration, implement type `iterable`</ins> | ||
* **[+]** implement 'exception-guarding' where we catch exceptions thrown by type testers when so configured | ||
(with `errors: false`) and return `false` instead, recording the error in `state`. When `errors: 'throw'` | ||
is set, errors will be thrown as normally | ||
* **[+]** allow users to access `Intertype_user_error` class so they can throw errors that are exempt from | ||
exception-guarding | ||
* **[+]** implement using the cfg directly for tests against object (struct) properties. Keys can be | ||
anything but if they start with a `$` dollar sign the refer to the keys of the struct being described; | ||
<del>`$` refers to the struct itself;</del> string values name an existing type. These additions make | ||
declarations highly declarative and aid in providing automatic features (e.g. implicit type dependency): | ||
```coffee | ||
declare.quantity | ||
$: 'object' # this could be implicit, judging by the use of any `$`-prefixed key | ||
$value: 'float' | ||
$unit: 'nonempty.text' | ||
default: | ||
value: 0 | ||
unit: null | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
323605
4
27
1840
674
+ Addedrfdc@^1.3.0
+ Addedguy@11.6.0(transitive)
+ Addedrfdc@1.4.1(transitive)
- Removedacorn@^8.7.1
- Removedacorn-loose@^8.3.0
- Removedacorn-walk@^8.2.0
- Removedastring@^1.8.3
- Removedpicomatch@^2.3.1
- Removedguy@10.0.0(transitive)
- Removedpicomatch@2.3.1(transitive)
Updatedguy@11.6.0