Skip to content

Commit

Permalink
Add a parse function to the library
Browse files Browse the repository at this point in the history
Closes #4
Closes #8 (supersedes)
  • Loading branch information
Avaq committed May 8, 2017
1 parent 27df967 commit 9ef3c31
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 44 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DOCTEST = node_modules/.bin/doctest --module commonjs --prefix .
ESLINT = node_modules/.bin/eslint --config node_modules/sanctuary-style/eslint-es3.json --env es3
MOCHA = node_modules/.bin/mocha --reporter dot --ui tdd
NPM = npm
Expand Down Expand Up @@ -34,6 +35,7 @@ lint:
--env node \
--global suite \
--global test \
--rule 'max-len: [off]' \
-- test/index.js
$(REMEMBER_BOWER) $(shell pwd)

Expand All @@ -51,3 +53,8 @@ setup:
.PHONY: test
test:
$(MOCHA) -- test/index.js
ifeq ($(shell node --version | cut -d . -f 1),v6)
$(DOCTEST) -- index.js
else
@echo '[WARN] Doctests are only run in Node v6.x.x (current version is $(shell node --version))' >&2
endif
142 changes: 99 additions & 43 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,38 @@
//. purpose: the [`typeof`][1] operator and [`Object.prototype.toString`][2].
//. Each has pros and cons, but neither supports user-defined types.
//.
//. This package specifies an [algorithm][3] for deriving a _type identifier_
//. from any JavaScript value, and exports an implementation of the algorithm.
//. Authors of algebraic data types may follow this specification in order to
//. make their data types compatible with the algorithm.
//. This package exports a function which derives a _type identifier_ from any
//. JavaScript value, and a [specification][3] for customizing the type
//. identifier of a value.
//.
//. ### Algorithm
//. ### Specification
//.
//. 1. Take any JavaScript value `x`.
//. For a type to be compatible with the algorithm:
//.
//. 2. If `x` is `null` or `undefined`, go to step 6.
//.
//. 3. If `x.constructor` evaluates to `null` or `undefined`, go to step 6.
//.
//. 4. If `x.constructor.prototype === x`, go to step 6. This check prevents a
//. prototype object from being considered a member of its associated type.
//. - every member of the type MUST have a `constructor` property pointing
//. to an object known as the _type representative_;
//.
//. 5. If `typeof x.constructor['@@type']` evaluates to `'string'`, return
//. the value of `x.constructor['@@type']`.
//. - the type representative MUST have a `@@type` property;
//.
//. 6. Return the [`Object.prototype.toString`][2] representation of `x`
//. without the leading `'[object '` and trailing `']'`.
//. - the type representative's `@@type` property (the _type identifier_)
//. MUST be a string primitive and SHOULD have format:
//. `'<namespace>/<name>[@<version>]'`, where:
//.
//. ### Compatibility
//. - The `namespace` MUST consist of one or more characters, and SHOULD
//. equal the npm package name which defines the type (including
//. [scope][4] where appropriate); and
//.
//. For an algebraic data type to be compatible with the [algorithm][3]:
//. - the `name` MUST consist of one or more characters, and SHOULD be
//. the unique name of the type; and
//.
//. - every member of the type must have a `constructor` property pointing
//. to an object known as the _type representative_;
//. - the `version` MUST be a numeric value consisting of one or more
//. digits, and it SHOULD represent the version of the type.
//.
//. - the type representative must have a `@@type` property; and
//. - If the property does not conform to the format specified above, it is
//. assumed that the entire string represents the _name_ of the type, and
//. _namespace_ will be `null`.
//.
//. - the type representative's `@@type` property (the _type identifier_)
//. must be a string primitive, ideally `'<npm-package-name>/<type-name>'`.
//. - If the version is not given, it is assumed to be `0`.
//.
//. For example:
//.
Expand Down Expand Up @@ -81,24 +80,6 @@
//. return {constructor: IdentityTypeRep, value: x};
//. }
//. ```
//.
//. ### Usage
//.
//. ```javascript
//. var Identity = require('my-package').Identity;
//. var type = require('sanctuary-type-identifiers');
//.
//. type(null); // => 'Null'
//. type(true); // => 'Boolean'
//. type([1, 2, 3]); // => 'Array'
//. type(Identity); // => 'Function'
//. type(Identity(0)); // => 'my-package/Identity'
//. ```
//.
//.
//. [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
//. [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
//. [3]: #algorithm

(function(f) {

Expand All @@ -119,7 +100,46 @@
// $$type :: String
var $$type = '@@type';

// type :: Any -> String
// RPARSE :: RegExp
var RPARSE = /^([^]+)\/([^]+?)(?:@(\d+))?$/;

// TypeIdentifier :: (Nullable String, String, Number)
// -> TypeIdentifier
function TypeIdentifier(namespace, name, version) {
return {namespace: namespace, name: name, version: version};
}

TypeIdentifier[$$type] = 'sanctuary-type-identifiers/TypeIdentifier@1';

//. ### Usage
//.
//. `const {parse, type} = require('sanctuary-type-identifiers')`
//.
//. ```javascript
//. > parse(type([1, 2, 3]));
//. {namespace: null, name: 'Array', version: 0}
//. ```
//.
//. ### API

//# type :: Any -> String
//.
//. Takes any value and produces a String which represents its type. If the
//. value conforms to the [specification][3], the custom type representation
//. is returned.
//.
//. ```javascript
//. > type(null);
//. 'Null'
//.
//. > type(true);
//. 'Boolean'
//.
//. > function Identity(x) { this.value = x; }
//. > Identity['@@type'] = 'my-package/Identity@1';
//. > type(new Identity(0));
//. 'my-package/Identity@1'
//. ```
function type(x) {
return x != null &&
x.constructor != null &&
Expand All @@ -129,6 +149,42 @@
Object.prototype.toString.call(x).slice('[object '.length, -']'.length);
}

return type;
//# parse :: String -> TypeIdentifier
//.
//. Takes any String and parses it according to the [specification][3],
//. returning a TypeIdentifier: An object with a `namespace`, a `name` and
//. a `version` field.
//.
//. ```javascript
//. > parse('my-package/List@2');
//. {namespace: 'my-package', name: 'List', version: 2}
//.
//. > parse('nonsense!');
//. {namespace: null, name: 'nonsense!', version: 0}
//.
//. > function Identity(x) { this.value = x; }
//. > Identity['@@type'] = 'my-package/Identity@1';
//. > parse(type(new Identity(0)));
//. {namespace: 'my-package', name: 'Identity', version: 1}
//. ```
function parse(s) {
var parsed = RPARSE.exec(s);
if (parsed == null) return TypeIdentifier(null, s, 0);
return TypeIdentifier(
parsed[1] == null ? null : parsed[1],
parsed[2],
parsed[3] == null ? 0 : Number(parsed[3])
);
}

return {
type: type,
parse: parse
};

}));

//. [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
//. [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
//. [3]: #specification
//. [4]: https://docs.npmjs.com/misc/scope
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {},
"devDependencies": {
"doctest": "^0.12.0",
"eslint": "3.19.x",
"mocha": "3.2.x",
"remember-bower": "0.1.x",
Expand Down
23 changes: 22 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ var assert = require('assert');

var Z = require('sanctuary-type-classes');

var type = require('..');
var id = require('..');
var type = id.type;
var parse = id.parse;


function eq(actual, expected) {
Expand Down Expand Up @@ -33,6 +35,10 @@ function Just(x) {
return {constructor: MaybeTypeRep, isNothing: false, isJust: true, value: x};
}

function TypeIdentifier(namespace, name, version) {
return {namespace: namespace, name: name, version: version};
}


test('type', function() {
eq(type(null), 'Null');
Expand All @@ -55,3 +61,18 @@ test('type', function() {
eq(type(new Number(0)), 'Number');
eq(type(new String('')), 'String');
});

test('parse', function() {
eq(parse('package/Type'), TypeIdentifier('package', 'Type', 0));
eq(parse('package/Type/X'), TypeIdentifier('package/Type', 'X', 0));
eq(parse('@scope/package/Type'), TypeIdentifier('@scope/package', 'Type', 0));
eq(parse(''), TypeIdentifier(null, '', 0));
eq(parse('/Type'), TypeIdentifier(null, '/Type', 0));
eq(parse('@0'), TypeIdentifier(null, '@0', 0));
eq(parse('foo/\n@1'), TypeIdentifier('foo', '\n', 1));
eq(parse('Type@1'), TypeIdentifier(null, 'Type@1', 0));
eq(parse('package/Type@1'), TypeIdentifier('package', 'Type', 1));
eq(parse('package/Type@999'), TypeIdentifier('package', 'Type', 999));
eq(parse('package/Type@X'), TypeIdentifier('package', 'Type@X', 0));
eq(parse('package////@3@2@1@1'), TypeIdentifier('package///', '@3@2@1', 1));
});

0 comments on commit 9ef3c31

Please sign in to comment.