Comparing version 0.1.0 to 0.2.0
@@ -5,2 +5,31 @@ # overload2 | ||
## [0.2.0] - 2017-06, Mutable Param | ||
### What's New? | ||
Since this section, programmers are allowed to use mutable param. E.g. | ||
```javascript | ||
overload2() | ||
.overload('+', Function, function(/*Array*/ data, callback) { | ||
// ... | ||
}) | ||
.overload(Error, '*', function(err, /*Array*/ rest) { | ||
// ... | ||
}) | ||
``` | ||
For more details, see the testcase named [mutable.js](./test/mutable.js). | ||
__ATTENTION:__ | ||
Before, `'*'` is alias of `overload2.Type.ANY`. And now, it is mapped to any number of arguments of any type. | ||
### Fixed | ||
Next bugs have been fixed in this version: | ||
* Failed to run the default method when no predefined paramlist matched. | ||
In previous version, `TypeError: Cannot read property 'apply' of undefined` will be thrown when overload2 tries to execute the default method. | ||
## [0.1.0] - 2017-06 | ||
@@ -7,0 +36,0 @@ |
@@ -36,2 +36,8 @@ /** | ||
) | ||
.overload( | ||
'*', Date, '*', | ||
function select(some, d, others) { | ||
return d.getDay(); | ||
} | ||
) | ||
; | ||
@@ -55,2 +61,5 @@ | ||
var day = getDay('a', 'b', new Date('2000-1-1'), 'c', 'd'); | ||
assert.equal(day, 6); | ||
console.log('THIS LINE REACHED MEANS EVERYTHING IS OK.'); |
311
overload2.js
/** | ||
* 函数多态的中量级实现。 | ||
* @author youngoat@163.com | ||
* | ||
* 注释中的术语表: | ||
* * 参数 = 定义中的形式参数 | ||
* * 值组 = 多态函数调用时的实际参数表 | ||
*/ | ||
@@ -97,6 +101,24 @@ | ||
// 数据是否包含指定元素。 | ||
// 数组是否包含指定元素。 | ||
, has = function(arr, item) { | ||
return arr.indexOf(item) >= 0; | ||
} | ||
// 数组中的每个元素是否均能通过指定函数的测试。 | ||
, eachMatch = function(arr, fn) { | ||
var matched = true; | ||
for (var i = 0; matched && i < arr.length; i++) { | ||
matched = fn(arr[i], i); | ||
} | ||
return matched; | ||
} | ||
// 数组中任意元素通过指定函数的测试。 | ||
, onceMatch = function(arr, fn) { | ||
var matched = false; | ||
for (var i = 0; !matched && i < arr.length; i++) { | ||
matched = fn(arr[i], i); | ||
} | ||
return matched; | ||
} | ||
; | ||
@@ -182,7 +204,5 @@ | ||
return new Type(function(value) { | ||
var matched = true; | ||
for (var i = 0; i < types.length && matched; i++) { | ||
matched = types[i].match(value); | ||
} | ||
return matched; | ||
return eachMatch(types, function(type) { | ||
return type.match(value); | ||
}); | ||
}); | ||
@@ -195,7 +215,5 @@ }; | ||
return new Type(function(value) { | ||
var matched = false; | ||
for (var i = 0; i < types.length && !matched; i++) { | ||
matched = types[i].match(value); | ||
} | ||
return matched; | ||
return onceMatch(types, function(type) { | ||
return type.match(value); | ||
}); | ||
}); | ||
@@ -258,5 +276,67 @@ }; | ||
this.type = null; | ||
this.minSize = 1; | ||
this.maxSize = 1; | ||
this.nil = false; | ||
this.undef = false; | ||
var setSize = (function(size) { | ||
if (typeof size == 'string') { | ||
// 清除所有空白字符。 | ||
size = size.replace(/\s/g, ''); | ||
if (size == '*' || size == '...') { | ||
this.minSize = 0; | ||
this.maxSize = Infinity; | ||
return true; | ||
} | ||
if (size == '+') { | ||
this.minSize = 1; | ||
this.maxSize = Infinity; | ||
return true; | ||
} | ||
// 区间形式。 | ||
if (/^\{.+\}$/.test(size)) { | ||
size = size.slice(1, -1); | ||
} | ||
if (/^\d+$/.test(size)) { | ||
this.minSize = this.maxSize = parseInt(size); | ||
return true; | ||
} | ||
if (/^,(\d+)$/.test(size)) { | ||
this.minSize = 0; | ||
this.maxSize = parseInt(RegExp.$1); | ||
return true; | ||
} | ||
if (/^(\d+),$/.test(size)) { | ||
this.minSize = parseInt(RegExp.$1); | ||
this.maxSize = Infinity; | ||
return true; | ||
} | ||
if (/^(\d+),(\d+)$/.test(size)) { | ||
this.minSize = parseInt(RegExp.$1); | ||
this.maxSize = parseInt(RegExp.$2); | ||
return true; | ||
} | ||
return false; | ||
} | ||
if (Number.isInteger(size) && size > 0) { | ||
this.minSize = this.maxSize = size; | ||
return true; | ||
} | ||
return false; | ||
}).bind(this); | ||
var type = arguments[0], decos = []; | ||
// --------------------------- | ||
// 处理特殊的参数占位符。 | ||
if (setSize(type)) { | ||
this.type = Type.ANY; | ||
return this; | ||
} | ||
// --------------------------- | ||
// 处理数据类型。 | ||
@@ -279,3 +359,8 @@ | ||
for (i = 1; i < arguments.length; i++) { | ||
decos = decos.concat(arguments[i].trim().split(/\s+/)); | ||
if (typeof arguments[i] == 'string') { | ||
decos = decos.concat(arguments[i].trim().split(/\s+/)); | ||
} | ||
else if (Number.isInteger(arguments[i])) { | ||
decos.push(arguments[i]); | ||
} | ||
} | ||
@@ -285,3 +370,6 @@ for (i = 0; i < decos.length; i++) { | ||
// 修饰符不区分大小写。 | ||
// 尝试作为量化修饰符应用。 | ||
if (setSize(rawDeco)) continue; | ||
// 普通修饰符不区分大小写。 | ||
decos[i] = decos[i].toUpperCase(); | ||
@@ -298,6 +386,6 @@ | ||
// 参数是否可以为 null | ||
this.nil = has(decos, 'null'); | ||
this.nil = has(decos, 'NULL'); | ||
// 参数是否可以为 undefined | ||
this.undef = has(decos, 'undefined'); | ||
this.undef = has(decos, 'UNDEFINED'); | ||
} | ||
@@ -307,3 +395,5 @@ | ||
// 判断值是否合乎参数限定。 | ||
/** | ||
* 判断值是否合乎参数限定。 | ||
*/ | ||
Param.prototype.satisfy = function(value) { | ||
@@ -321,11 +411,16 @@ if (value === null && this.nil) return true; | ||
var params = []; | ||
var minSize = 0; | ||
var maxSize = 0; | ||
for (var i = 0, args; i < arguments.length; i++) { | ||
args = (arguments[i] instanceof Array) ? arguments[i] : [ arguments[i] ]; | ||
params[i] = Param.parse.apply(null, args); | ||
minSize += params[i].minSize; | ||
maxSize += params[i].maxSize; | ||
} | ||
this.length = params.length; | ||
this.item = function(i) { | ||
return params[i]; | ||
}; | ||
// 指定匹配值组的最小和最大长度。 | ||
this.minSize = minSize; | ||
this.maxSize = maxSize; | ||
this.params = params; | ||
} | ||
@@ -336,16 +431,138 @@ | ||
/** | ||
* 判断值组是否合乎参数值限定。 | ||
* 判断值组是否合乎参数定义。 | ||
* @deprecated | ||
*/ | ||
ParamList.prototype.satisfy = function(args) { | ||
// 参数表长度检查。 | ||
if (args.length != this.length) { | ||
if (args.length != this.params.length) { | ||
return false; | ||
} | ||
// 参数类型核对 | ||
var matched = true; | ||
for (var i = 0; matched && i < this.length; i++) { | ||
matched = this.item(i).satisfy(args[i]); | ||
// 参数类型核对。 | ||
return eachMatch(this.params, function(param, index) { | ||
return param.satisfy(args[index]); | ||
}); | ||
} | ||
/** | ||
* 尝试按参数定义解析值组。 | ||
* @return false 匹配失败 | ||
* @return Array 匹配成功,返回与定义一一对应的新值组(因为参数可能是可变长度的) | ||
*/ | ||
ParamList.prototype.parse = function(args) { | ||
// --------------------------- | ||
// 检查值组的长度是否在参数列表定义的区间内。 | ||
if (args.length < this.minSize || this.maxSize < args.length) { | ||
return null; | ||
} | ||
return matched; | ||
// --------------------------- | ||
// 若整个参数组非可变长,则适用简易匹配逻辑。 | ||
if (this.minSize == this.maxSize) { | ||
var matched = eachMatch(this.params, function(param, index) { | ||
return param.satisfy(args[index]); | ||
}); | ||
return matched ? args : null; | ||
} | ||
// --------------------------- | ||
// 否则,适用复杂匹配逻辑。 | ||
// 注意:此函数将被递归调用。 | ||
var matching = function(args, params) { | ||
var newArgs = []; | ||
var argCursor = 0, paramCursor = 0; | ||
for (; argCursor < args.length; argCursor++) { | ||
// 如果所有形式参数匹配成功之后,仍有多余的实参未参加匹配,则认为整个参数表匹配失败。 | ||
if (paramCursor >= params.length) { | ||
return null; | ||
} | ||
// 当前参数。 | ||
var param = params[paramCursor]; | ||
// 当前实参。 | ||
var arg = args[argCursor]; | ||
// --------------------------- | ||
// 如果形式参数对应单个实参(不包括多于 1 的定长),则按简易逻辑处理。 | ||
if (param.minSize == param.maxSize == 1) { | ||
if (!param.satisfy(arg)) { | ||
// 如果实参与形式参数不匹配,则终止后续匹配,整个参数表匹配失败。 | ||
return null; | ||
} | ||
else { | ||
newArgs.push(arg); | ||
paramCursor++; | ||
} | ||
continue; | ||
} | ||
// --------------------------- | ||
// 否则,适用复杂逻辑。 | ||
// 如果剩余实参数量不足以匹配当前形式参数,则匹配失败。 | ||
if (args.length - argCursor < param.minSize) { | ||
return null; | ||
} | ||
// 依次储存当前形式参数匹配的实参。 | ||
var paramArgs = []; | ||
for (; argCursor < args.length && param.satisfy(args[argCursor]); argCursor++) { | ||
paramArgs.push(args[argCursor]); | ||
} | ||
// 如当前形式参数匹配实参个数未达到最小值,则认为当前形式参数匹配失败,整个参数表匹配失败。 | ||
if (paramArgs.length < param.minSize) { | ||
return null; | ||
} | ||
var restParams = params.slice(++paramCursor); | ||
var restArgs = args.slice(argCursor); | ||
// 抵达匹配边界时,若仅匹配了最小数量的实参,或者已是最后一个形式参数,则直接固定。 | ||
if (paramArgs.length == param.minSize || restParams.length == 0) { | ||
newArgs.push(paramArgs); | ||
argCursor--; | ||
} | ||
// 否则,须尝试让贤。 | ||
else { | ||
// 步步让贤,直到让无可让。 | ||
do { | ||
var restNewArgs = matching(restArgs, restParams); | ||
// 剩余参数匹配成功,则拼接匹配结果,整个参数表匹配成功。 | ||
if (restNewArgs != null) { | ||
newArgs.push(paramArgs); | ||
return newArgs.concat(restNewArgs); | ||
} | ||
// 已无余量。 | ||
else if (paramArgs.length == 0) { | ||
break; | ||
} | ||
else { | ||
restArgs.unshift(paramArgs.pop()); | ||
} | ||
} while (paramArgs.length >= param.minSize); | ||
return null; | ||
} | ||
} | ||
for (; newArgs.length < params.length; paramCursor++) { | ||
if (params[paramCursor].minSize == 0) { | ||
newArgs.push([]); | ||
} | ||
else { | ||
return null; | ||
} | ||
} | ||
return newArgs; | ||
}; | ||
return matching(Array.from(args), this.params); | ||
}; | ||
@@ -374,7 +591,9 @@ | ||
if (typeof args[0] == 'number') { | ||
if (args.length > 1) { | ||
throw new ERR.Arguments(2, args.length + 1); | ||
} | ||
this.paramCount = args[0]; | ||
// 支持可变长度参数后,用于代表值组长度的数字,也可以用 Param 实例来表征。 | ||
// 在此我们仍保留 argLength,是出于提高性能的考虑。 | ||
if (typeof args[0] == 'number' && args.length == 1) { | ||
// if (args.length > 1) { | ||
// throw new ERR.Arguments(2, args.length + 1); | ||
// } | ||
this.argLength = args[0]; | ||
} | ||
@@ -399,9 +618,15 @@ else if (args[0] instanceof ParamList) { | ||
Overload.prototype.exec = function(scope, args) { | ||
// 若指定了形参数量,而实参数量不符,则直接跳过。 | ||
if (typeof this.paramCount == 'number' && (args.length != this.paramCount)) { | ||
return false; | ||
// 对于每一个重载实现,值组长度和形参表是互斥的。 | ||
// 或者指定值组长度,或者指定形参表,而不是同时指定。 | ||
// 如指定值组长度,则仅判断长度。 | ||
if (typeof this.argLength == 'number') { | ||
if (args.length != this.argLength) return false; | ||
} | ||
if (this.params && !this.params.satisfy(args)) { | ||
return false; | ||
// 否则,须尝试匹配值组与参数列表。 | ||
else { | ||
// 尝试获取按形式参数列表重整后的值组。 | ||
args = this.params.parse(args); | ||
if (!args) return false; | ||
} | ||
@@ -452,7 +677,13 @@ | ||
if (this._defaultMethod) { | ||
return this.defaultMethod.apply(scope, args); | ||
return this._defaultMethod.apply(scope, args); | ||
} | ||
// 表示没有任何重载形参与实参匹配。 | ||
throw new ERR.Unmatching('Unmatching arguments: ' + args); | ||
var types = []; | ||
for (var i = 0, type; i < args.length; i++) { | ||
type = typeof args[i]; | ||
if (type === 'object') type = args[i].constructor.name; | ||
types.push(type); | ||
} | ||
throw new ERR.Unmatching('Unmatching arguments: ' + types.join(', ')); | ||
}; | ||
@@ -585,3 +816,3 @@ | ||
var TYPE_ALIAS = | ||
{ '*' : Type.ANY | ||
{ '?' : Type.ANY | ||
, 'any' : Type.ANY | ||
@@ -588,0 +819,0 @@ , 'boolean' : Type.BOOLEAN |
{ | ||
"name": "overload2", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Elegant solution for function overloading in JavaScript", | ||
@@ -5,0 +5,0 @@ "main": "overload2.js", |
114
README.md
# overload2 | ||
__Elegant solution for function overloading in JavaScript.__ | ||
[![Build Status](https://travis-ci.org/YounGoat/ecmascript.overload2.svg?branch=master)](https://travis-ci.org/YounGoat/ecmascript.overload2) | ||
[![Coverage Status](https://coveralls.io/repos/github/YounGoat/ecmascript.overload2/badge.svg?branch=master)](https://coveralls.io/github/YounGoat/ecmascript.overload2?branch=master) | ||
[![NPM total downloads](https://img.shields.io/npm/dt/overload2.svg)](https://www.npmjs.com/package/overload2) | ||
[![NPM version](https://img.shields.io/npm/v/overload2.svg)](https://www.npmjs.com/package/overload2) | ||
[![NPM license](https://img.shields.io/npm/l/overload2.svg)](./LICENSE.txt) | ||
[![GitHub stars](https://img.shields.io/github/stars/YounGoat/ecmascript.overload2.svg?style=social&label=Star)](https://github.com/YounGoat/ecmascript.overload2/stargazers) | ||
When you are tired with writing tasteless code to do with arguments, *overload2* will __MAKE THINGS EASY__. | ||
@@ -19,2 +12,3 @@ | ||
* [Datatypes](#datatypes) | ||
* [Mutable Parameter](#mutable) | ||
* [Move Forward](#move-forward) | ||
@@ -33,4 +27,4 @@ * [APIs](#apis) | ||
<a name="get-started"></a> | ||
## Get Started | ||
<a name="get-started"></a> | ||
@@ -67,5 +61,14 @@ Install *overload2* firstly. | ||
.overload( | ||
4, // length of arguments | ||
function four() { console.log('too many arguments'); } | ||
2, // length of arguments | ||
function md(month, date) { | ||
var d = new Date; | ||
return d.setMonth(month - 1), d.setDate(date), d.getDay(); | ||
} | ||
) | ||
.overload( | ||
'*', Date, '*', | ||
function select(some, d, others) { | ||
return d.getDay(); | ||
} | ||
) | ||
; | ||
@@ -82,11 +85,20 @@ | ||
getDay(1, 2, 3, 4); | ||
// four() invoked | ||
getDay(12, 1); | ||
// md() invoked | ||
getDay('foo', 'bar', new Date, 'quz'); | ||
// select() invoked | ||
``` | ||
<a name="datatypes"></a> | ||
## Datatypes | ||
<a name="datatypes"></a> | ||
According to *overload2* , there are different ways to define a datatype. | ||
* [Constructor Function](#constructor-function) | ||
* [Customized Datatype](#customized-datatype) | ||
* [Predefined Datatype](#predefined-datatype) | ||
* [Datatype Alias](#datatype-alias) | ||
* [Create Datatype With Factory Method](#create-datatype-with-factory-method) | ||
### Constructor Function | ||
@@ -151,3 +163,3 @@ | ||
| :--------- | :------------------------------ | | ||
| * | __overload2.Type.ANY__ | | ||
| ? | __overload2.Type.ANY__ | | ||
| any | __overload2.Type.ANY__ | | ||
@@ -177,4 +189,56 @@ | boolean | __overload2.Type.BOOLEAN__ | | ||
<a name="mutable"></a> | ||
## Mutable Parameter | ||
By appending size decorator, we can define mutable parameters. E.g. | ||
```javascript | ||
var add = overload2() | ||
.overload('number *', function(numbers) { | ||
var ret = 0; | ||
numbers.forEach(function(number) { ret += number; }) | ||
return ret; | ||
}) | ||
.overload('boolean {2,3}', function(bools) { | ||
var ret = false; | ||
for (var i = 0; !ret && i < bools.length; i++) { | ||
ret = bools[i]; | ||
} | ||
return ret; | ||
}) | ||
.overload([ Date, '+' ], function(dates) { | ||
var date = dates[0]; | ||
for (var i = 1; i < dates.length; i++) { | ||
if (date < dates[i]) { | ||
date = dates[i]; | ||
} | ||
} | ||
return date; | ||
}) | ||
; | ||
``` | ||
Size decorators look like [repetition in regular expression](http://www.regular-expressions.info/repeat.html). Here are some examples for overload param with size decorators: | ||
```javascript | ||
[ Date, '{2,}'] // takes at least 2 arguments which are instances of Date | ||
'number *' // takes any number (including zero) of arguments of type number | ||
'*' // takes any number (including zero) of arguments of any type | ||
'+' // takes at least one argument | ||
'?' // takes one argument | ||
'{2}' // takes 2 arguments | ||
'{2,4}' // takes 2 to 4 arguments | ||
'{2,}' // takes at least 2 arguments | ||
'{,4{}' // takes no more than 4 arguments | ||
// The braces may be omitted, so the following are also valid. | ||
'2' | ||
'2,4' | ||
'2,' | ||
',4' | ||
``` | ||
<a name="move-forward"></a> | ||
## Move Forward | ||
<a name="move-forward"></a> | ||
@@ -198,4 +262,4 @@ Beyond the basic use, *overload2* is also suitable with more complex and large-scale programs. See the class hierarchy shown below: | ||
<a name="apis"></a> | ||
## APIs | ||
<a name="apis"></a> | ||
@@ -209,4 +273,4 @@ * [overload2()](#api-overload2) | ||
<a name="api-overload2"></a> | ||
### overload2(), Create An Overloaded Function | ||
<a name="api-overload2"></a> | ||
@@ -224,4 +288,4 @@ `overload2` itself is a function, when invoked, it will return an overloded function instance. | ||
<a name="api-class-type"></a> | ||
### class overload2.Type | ||
<a name="api-class-type"></a> | ||
@@ -236,4 +300,4 @@ To define a datatype in context of *overload2*, there are different ways including `overload2.Type`. And all other datatypes will be converted to instances of `overload2.Type` before being used. | ||
<a name="api-class-param"></a> | ||
### class overload2.Param | ||
<a name="api-class-param"></a> | ||
@@ -259,4 +323,4 @@ A Param is made up of a Type and some decorators. Available decorators are: | ||
<a name="api-class-paramlist"></a> | ||
### class overload2.ParamList | ||
<a name="api-class-paramlist"></a> | ||
@@ -272,4 +336,4 @@ * new __overload2.ParamList__( [ Param | Array | String \<param\> [ , ... ] ] ) | ||
<a name="api-class-overload"></a> | ||
### class overload2.Overload | ||
<a name="api-class-overload"></a> | ||
@@ -291,4 +355,4 @@ * new __overload2.Overload__( number <argumentsNumber>, function \<implementation\> ) | ||
<a name="api-class-overloadedfunction"></a> | ||
### class overload2.OverloadedFunction | ||
<a name="api-class-overloadedfunction"></a> | ||
@@ -313,4 +377,4 @@ * new __overload2.OverloadedFunction__() | ||
<a name="examples"></a> | ||
## Examples | ||
<a name="examples"></a> | ||
@@ -343,4 +407,4 @@ * [Basic Usage](./example/basic.js) | ||
<a name="why-overload2"></a> | ||
## Why overload2 | ||
<a name="why-overload2"></a> | ||
@@ -357,4 +421,4 @@ There have been dozens of packages devoted to function overloading in JavaScript, and some of them are really not bad, e.g. | ||
<a name="dependents"></a> | ||
## Honorable Dependents | ||
<a name="dependents"></a> | ||
@@ -361,0 +425,0 @@ Welcome to be the first dependent of *overload2*! |
@@ -14,2 +14,14 @@ var MODULE_REQUIRE | ||
describe('Predefined datatypes', function() { | ||
it('?', function() { | ||
assert(Type.ANY.match(0)); | ||
assert(Type.ANY.match(1)); | ||
assert(Type.ANY.match(null)); | ||
assert(Type.ANY.match(undefined)); | ||
assert(Type.ANY.match('')); | ||
assert(Type.ANY.match(true)); | ||
assert(Type.ANY.match(false)); | ||
assert(Type.ANY.match([])); | ||
assert(Type.ANY.match({})); | ||
}); | ||
it('ANY', function() { | ||
@@ -16,0 +28,0 @@ assert(Type.ANY.match(0)); |
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
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
121916
20
1989
422