intertype
Advanced tools
Comparing version
209
lib/main.js
(function() { | ||
'use strict'; | ||
var E, Intertype, Intertype_minimal, WG, _TMP_basetype_names, _TMP_basetype_names_matcher, _TMP_isa_minimal_type/* TAINT unfortunate choice of name */, _TMP_minimal_types, __type_of, _isa, basetypes, debug, deepmerge, default_declarations, default_types, hide, nameit, rpr, set, types, walk_prefixes; | ||
var E, Intertype, Intertype_minimal, WG, _TMP_basetype_names, _TMP_basetype_names_matcher, _TMP_isa_minimal_type/* TAINT unfortunate choice of name */, _TMP_minimal_types, __type_of, _isa, basetypes, debug, deepmerge, default_declarations, default_types, hide, nameit, rpr, set, types, walk_prefixes, | ||
modulo = function(a, b) { return (+a % (b = +b) + b) % b; }; | ||
@@ -98,2 +99,14 @@ //=========================================================================================================== | ||
return x instanceof Set; | ||
}, | ||
nan: (x) => { | ||
return Number.isNaN(x); | ||
}, | ||
finite: (x) => { | ||
return Number.isFinite(x); | ||
}, | ||
/* TAINT make sure no non-numbers slip through */integer: (x) => { | ||
return Number.isInteger(x); | ||
}, | ||
/* TAINT make sure no non-numbers slip through */safeinteger: (x) => { | ||
return Number.isSafeInteger(x); | ||
} | ||
@@ -123,3 +136,3 @@ }; | ||
//----------------------------------------------------------------------------------------------------------- | ||
default_declarations = { | ||
/* TAINT make sure no non-numbers slip through */ default_declarations = { | ||
basetype: { | ||
@@ -197,2 +210,188 @@ test: _isa.basetype | ||
} | ||
}, | ||
nan: { | ||
test: _isa.nan, | ||
template: 0/0 | ||
}, | ||
finite: { | ||
test: _isa.finite, | ||
template: 0 | ||
}, | ||
integer: { | ||
test: _isa.integer, | ||
template: 0 | ||
}, | ||
safeinteger: { | ||
test: _isa.safeinteger, | ||
template: 0 | ||
}, | ||
//......................................................................................................... | ||
'empty': { | ||
role: 'qualifier' | ||
}, | ||
'nonempty': { | ||
role: 'qualifier' | ||
}, | ||
'empty.list': function(x) { | ||
return (this.isa.list(x)) && (x.length === 0); | ||
}, | ||
'empty.text': function(x) { | ||
return (this.isa.text(x)) && (x.length === 0); | ||
}, | ||
'empty.set': function(x) { | ||
return (this.isa.set(x)) && (x.size === 0); | ||
}, | ||
'empty.object': function(x) { | ||
return (this.isa.object(x)) && ((Object.keys(x)).length === 0); | ||
}, | ||
'nonempty.list': function(x) { | ||
return (this.isa.list(x)) && (x.length > 0); | ||
}, | ||
'nonempty.text': function(x) { | ||
return (this.isa.text(x)) && (x.length > 0); | ||
}, | ||
'nonempty.set': function(x) { | ||
return (this.isa.set(x)) && (x.size > 0); | ||
}, | ||
'nonempty.object': function(x) { | ||
return (this.isa.object(x)) && ((Object.keys(x)).length > 0); | ||
}, | ||
//......................................................................................................... | ||
'positive': { | ||
role: 'qualifier' | ||
}, | ||
'negative': { | ||
role: 'qualifier' | ||
}, | ||
'posnaught': { | ||
role: 'qualifier' | ||
}, | ||
'negnaught': { | ||
role: 'qualifier' | ||
}, | ||
'positive.float': function(x) { | ||
return (this.isa.float(x)) && (x > 0); | ||
}, | ||
'positive.integer': function(x) { | ||
return (this.isa.integer(x)) && (x > 0); | ||
}, | ||
'positive.infinity': function(x) { | ||
return (this.isa.infinity(x)) && (x > 0); | ||
}, | ||
'negative.float': function(x) { | ||
return (this.isa.float(x)) && (x < 0); | ||
}, | ||
'negative.integer': function(x) { | ||
return (this.isa.integer(x)) && (x < 0); | ||
}, | ||
'negative.infinity': function(x) { | ||
return (this.isa.infinity(x)) && (x < 0); | ||
}, | ||
'posnaught.float': function(x) { | ||
return (this.isa.float(x)) && (x >= 0); | ||
}, | ||
'posnaught.integer': function(x) { | ||
return (this.isa.integer(x)) && (x >= 0); | ||
}, | ||
'posnaught.infinity': function(x) { | ||
return (this.isa.infinity(x)) && (x >= 0); | ||
}, | ||
'negnaught.float': function(x) { | ||
return (this.isa.float(x)) && (x <= 0); | ||
}, | ||
'negnaught.integer': function(x) { | ||
return (this.isa.integer(x)) && (x <= 0); | ||
}, | ||
'negnaught.infinity': function(x) { | ||
return (this.isa.infinity(x)) && (x <= 0); | ||
}, | ||
'cardinal': 'posnaught.integer', | ||
//......................................................................................................... | ||
'frozen': { | ||
role: 'qualifier' | ||
}, | ||
'sealed': { | ||
role: 'qualifier' | ||
}, | ||
'extensible': { | ||
role: 'qualifier' | ||
}, | ||
'frozen.list': function(x) { | ||
return (this.isa.list(x)) && (Object.isFrozen(x)); | ||
}, | ||
'sealed.list': function(x) { | ||
return (this.isa.list(x)) && (Object.isSealed(x)); | ||
}, | ||
'extensible.list': function(x) { | ||
return (this.isa.list(x)) && (Object.isExtensible(x)); | ||
}, | ||
'frozen.object': function(x) { | ||
return (this.isa.object(x)) && (Object.isFrozen(x)); | ||
}, | ||
'sealed.object': function(x) { | ||
return (this.isa.object(x)) && (Object.isSealed(x)); | ||
}, | ||
'extensible.object': function(x) { | ||
return (this.isa.object(x)) && (Object.isExtensible(x)); | ||
}, | ||
//......................................................................................................... | ||
'odd': { | ||
role: 'qualifier' | ||
}, | ||
'even': { | ||
role: 'qualifier' | ||
}, | ||
'odd.positive': { | ||
role: 'qualifier' | ||
}, | ||
'odd.negative': { | ||
role: 'qualifier' | ||
}, | ||
'odd.posnaught': { | ||
role: 'qualifier' | ||
}, | ||
'odd.negnaught': { | ||
role: 'qualifier' | ||
}, | ||
'even.positive': { | ||
role: 'qualifier' | ||
}, | ||
'even.negative': { | ||
role: 'qualifier' | ||
}, | ||
'even.posnaught': { | ||
role: 'qualifier' | ||
}, | ||
'even.negnaught': { | ||
role: 'qualifier' | ||
}, | ||
'odd.integer': function(x) { | ||
return (this.isa.integer(x)) && (modulo(x, 2)) === 1; | ||
}, | ||
'even.integer': function(x) { | ||
return (this.isa.integer(x)) && (modulo(x, 2)) === 0; | ||
}, | ||
'odd.positive.integer': function(x) { | ||
return (this.isa.positive.integer(x)) && (this.isa.odd.integer(x)); | ||
}, | ||
'even.positive.integer': function(x) { | ||
return (this.isa.positive.integer(x)) && (this.isa.even.integer(x)); | ||
}, | ||
'odd.negative.integer': function(x) { | ||
return (this.isa.negative.integer(x)) && (this.isa.odd.integer(x)); | ||
}, | ||
'even.negative.integer': function(x) { | ||
return (this.isa.negative.integer(x)) && (this.isa.even.integer(x)); | ||
}, | ||
'odd.posnaught.integer': function(x) { | ||
return (this.isa.posnaught.integer(x)) && (this.isa.odd.integer(x)); | ||
}, | ||
'even.posnaught.integer': function(x) { | ||
return (this.isa.posnaught.integer(x)) && (this.isa.even.integer(x)); | ||
}, | ||
'odd.negnaught.integer': function(x) { | ||
return (this.isa.negnaught.integer(x)) && (this.isa.odd.integer(x)); | ||
}, | ||
'even.negnaught.integer': function(x) { | ||
return (this.isa.negnaught.integer(x)) && (this.isa.even.integer(x)); | ||
} | ||
@@ -532,2 +731,3 @@ }; | ||
//..................................................................................................... | ||
/* deal with sum types (tagged unions, variants) */ | ||
case role === 'qualifier': | ||
@@ -546,2 +746,3 @@ return nameit(method_name, function(x) { | ||
//..................................................................................................... | ||
/* deal with product types (records) */ | ||
case role === 'basetype' || role === 'usertype': | ||
@@ -837,4 +1038,5 @@ return nameit(method_name, function(x) { | ||
(() => { | ||
var create, isa, type_of, validate; | ||
var create, isa, testing, type_of, validate; | ||
({isa, validate, create, type_of} = types); | ||
testing = {_isa}; | ||
return module.exports = { | ||
@@ -851,2 +1053,3 @@ Intertype, | ||
walk_prefixes, | ||
testing, | ||
__type_of | ||
@@ -853,0 +1056,0 @@ }; |
{ | ||
"name": "intertype", | ||
"version": "201.1.0", | ||
"version": "202.0.0", | ||
"description": "A JavaScript typechecker", | ||
@@ -5,0 +5,0 @@ "main": "lib/main.js", |
108
README.md
@@ -17,2 +17,3 @@ | ||
- [`evaluate` methods](#evaluate-methods) | ||
- [Sum Types (Variants) and Product Types (Records)](#sum-types-variants-and-product-types-records) | ||
- [Declaration Values (Test Method, Type Name, Object)](#declaration-values-test-method-type-name-object) | ||
@@ -136,2 +137,45 @@ - [Namespaces and Object Fields](#namespaces-and-object-fields) | ||
## Sum Types (Variants) and Product Types (Records) | ||
* Variants can be defined with or without a *qualifier*, a syntactic element that precedes a type name | ||
* Variants *without* qualifiers ('unqualified variants') can be defined by using a logical disjunction | ||
(`or`, `||`) in the test method. For example, one could declare a type `boolordeep` like this: | ||
```coffee | ||
declarations: | ||
boolordeep: ( x ) -> ( @isa.boolean x ) or ( x is 'deep' ) | ||
``` | ||
which will be satisfied by any one of the three values `true`, `false`, and (the string) `'deep'`, to the | ||
exclusion of any other values. | ||
* '*Qualified* variants' do use an explicit qualifier that is meant to be used in conjunction with other | ||
types, for example: | ||
```coffee | ||
declarations: | ||
nonempty: { role: 'qualifier', } | ||
'nonempty.list': ( x ) -> ( @isa.list x ) and ( x.length isnt 0 ) | ||
'nonempty.set': ( x ) -> ( @isa.set x ) and ( x.size isnt 0 ) | ||
``` | ||
* Having declared the above types—the qualifier `nonempty` with its two branch types `nonempty.list` and | ||
`nonempty.set`—we can now test for any of: | ||
* `isa.nonempty.list x`: whether `x` is a `list` with non-zero `x.length` | ||
* `isa.nonempty.set x`: whether `x` is a `set` with non-zero `x.size` | ||
* `isa.nonempty x`: whether `x` satisifes either `isa.nonempty.list x` or `isa.nonempty.set x` | ||
* In other words, the qualifier is what becomes the variant—`isa.nonempty.list` and `isa.nonempty.set` are | ||
ordinary tpes (though conceivably one could declare them as unqualified variants, as shown above) | ||
* 'Product types' or 'records', on the other hand, are types that mandate the presence not of alternatives, | ||
but of named fields each of which must satisfy its own test method for the record to be valid | ||
* Clarification of terminology: | ||
* an explicit *qualifier* is what goes in front of a *qualified typename*; | ||
* a *variant* is a type that has several equivalent alternatives to choose from; | ||
* an *implicit variant* is a type that has several equivalent alternatives to choose from that are however | ||
not formally declared to the InterType API but inside a test method that contains disjunctions (`foo: ( | ||
x ) -> ( @isa.a x ) or ( @isa.b x )`) | ||
* an *explicit variant* comes about by using a (chain of) qualifier(s) as a typename; so when we have | ||
declared `empty` as a `qualifier` and set up tests for both `empty.text` and `empty.list`, then the | ||
explicit variant `empty` can be tested for with `isa.empty x`, which will return `true` for exactly the | ||
values `''` and `[]`. | ||
### Declaration Values (Test Method, Type Name, Object) | ||
@@ -247,3 +291,2 @@ | ||
* **[–]** implement `fields` | ||
* **[–]** in `_compile_declaration_object()`, add validation for return value | ||
@@ -258,3 +301,2 @@ * **[–]** implement using `optional` in a declarations, as in `{ foo: 'optional.text', }` | ||
* **[–]** test `create()` method for the recursive case | ||
* **[–]** test that incorrect templates are rejected | ||
* **[–]** acquire deep-freezing method | ||
@@ -270,2 +312,5 @@ * **[–]** use deep-freezing for declaration | ||
* **[–]** there's `template` but it's a function | ||
* **[–]** `evaluate.cardinal Infinity` returns `{ cardinal: false }`; `evaluate.posnaught.integer Infinity` | ||
returns `{ 'posnaught.integer': false }`; in both cases, more details would be elucidating (`Infinity` | ||
satisfies `posnaught` but not `integer`) | ||
* **[–]** consider to enrich result of `evaluate` methods with length-limited `rpr()` and type of values | ||
@@ -277,27 +322,24 @@ encountered | ||
implemented 'qualifiers' | ||
* **[–]** consider to implement `nonempty.text()`, `nonempty.list()`, `empty.text()`, `empty.list()`; here, | ||
`empty` and `nonempty` are not types names of an object with fields, and the names after the dots are not | ||
field names; also, `isa.nonempty x` does not necessarily have to make sense so either it shouldn't be a | ||
function or, if it is one, it should throw an error when called, something that has always been ruled out | ||
so far | ||
* **[–]** `nonempty` etc. could be autogenerated: go through each enumerable property `T` of | ||
`isa.nonempty` and add a test `( @isa[ T ] x ) and ( @isa.nonempty[ T ] x` | ||
* **[–]** need a term for the 'sub-methods' that get attached as props to the 'target methods'(??), e.g. | ||
after `isa.quantity()` has been set 'sub-methods' `isa.quantity.q()`, `isa.quantity.u()` will be set as | ||
properties of their 'target' `isa.quantity`; the current terminology is unfortunate and obfuscates more | ||
than it elicits | ||
* **[–]** clarify difference between basetypes and meta/quasitype `optional`, provide a type for the union | ||
of both | ||
* **[–]** Type roles: | ||
* `basetype` | ||
* `optional` | ||
* `usertype` | ||
* `qualifier` | ||
* `negation` (?) | ||
* **[–]** implement 'qualifiers' (as in `nonempty.text`) that look a lot like object fields but have a | ||
different `isa` method implementation | ||
* **[–]** qualifiers should be distinguished from `optional` which is and remains a prefix that can before | ||
any other legal (known, declared) (fully quylified) type name, so `isa.optional.nonempty.text x` may be | ||
legal but `isa.nonempty.optional.text x` won't | ||
* **[–]** `nonempty` etc. could be autogenerated: go through each enumerable property `T` of | ||
`isa.nonempty` and add a test `( @isa[ T ] x ) and ( @isa.nonempty[ T ] x` | ||
* **[–]** need a term for the 'sub-methods' that get attached as props to the 'target methods'(??), e.g. | ||
after `isa.quantity()` has been set 'sub-methods' `isa.quantity.q()`, `isa.quantity.u()` will be set as | ||
properties of their 'target' `isa.quantity`; the current terminology is unfortunate and obfuscates more | ||
than it elicits | ||
* **[–]** clarify difference between basetypes and meta/quasitype `optional`, provide a type for the union | ||
of both | ||
* **[–]** Type roles: | ||
* `basetype` | ||
* `optional` | ||
* `usertype` | ||
* `qualifier` | ||
* `negation` (?) | ||
* **[–]** in addition to setting `role`, allow users to set `{ test: '@qualifier' }` which then can be | ||
shortened to `empty: '@qualifier'` in dotted field enumerations (maybe really the preferred way to | ||
differentiate qualifier trees from nested records) | ||
* **[–]** implement method to supply all types that are present in `_isa` but missing from | ||
`default_declarations` | ||
* **[–]** consider to relegate private module exports to sub-key `testing` or similar | ||
## Is Done | ||
@@ -401,2 +443,16 @@ | ||
* **[+]** test that a declaration with fields defaults to `{ test: 'object', }` | ||
* **[+]** implement `fields` | ||
* **[+]** test that incorrect templates are rejected | ||
* **[+]** consider to implement `nonempty.text()`, `nonempty.list()`, `empty.text()`, `empty.list()`; here, | ||
`empty` and `nonempty` are not types names of an object with fields, and the names after the dots are not | ||
field names; also, `isa.nonempty x` does not necessarily have to make sense so either it shouldn't be a | ||
* **[+]** implement 'qualifiers' (as in `nonempty.text`) that look a lot like object fields but have a | ||
different `isa` method implementation | ||
* **[+]** qualifiers should be distinguished from `optional` which is and remains a prefix that can before | ||
any other legal (known, declared) (fully quylified) type name, so `isa.optional.nonempty.text x` may be | ||
legal but `isa.nonempty.optional.text x` won't | ||
function or, if it is one, it should throw an error when called, something that has always been ruled out | ||
so far | ||
* **[+]** use prototypes of test methods `throws()` &c for new version of `guy-test` | ||
* **[+]** use prototype of set equality for `equals()` implementation in `webguy` | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
192414
16.22%1174
20.91%452
14.14%