index.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. 'use strict'
  2. const genfun = require('genfun')
  3. class Duck extends Function {
  4. // Duck.impl(Foo, [String, Array], { frob (str, arr) { ... }})
  5. impl (target, types, impls) {
  6. if (!impls && !isArray(types)) {
  7. impls = types
  8. types = []
  9. }
  10. if (!impls && this.isDerivable) {
  11. impls = this._defaultImpls
  12. }
  13. if (!impls) {
  14. impls = {}
  15. }
  16. if (typeof target === 'function' && !target.isGenfun) {
  17. target = target.prototype
  18. }
  19. checkImpls(this, target, impls)
  20. checkArgTypes(this, types)
  21. this._constraints.forEach(c => {
  22. if (!c.verify(target, types)) {
  23. throw new Error(`Implementations of ${
  24. this.name || 'this protocol'
  25. } must first implement ${
  26. c.parent.name || 'its constraint protocols defined in opts.where.'
  27. }`)
  28. }
  29. })
  30. this._methodNames.forEach(name => {
  31. defineMethod(this, name, target, types, impls)
  32. })
  33. }
  34. hasImpl (arg, args) {
  35. args = args || []
  36. const fns = this._methodNames
  37. var gf
  38. if (typeof arg === 'function' && !arg.isGenfun) {
  39. arg = arg.prototype
  40. }
  41. args = args.map(arg => {
  42. if (typeof arg === 'function' && !arg.isGenfun) {
  43. return arg.prototype
  44. } else {
  45. return arg
  46. }
  47. })
  48. for (var i = 0; i < fns.length; i++) {
  49. gf = arg[fns[i]]
  50. if (!gf ||
  51. (gf.hasMethod
  52. ? !gf.hasMethod.apply(gf, args)
  53. : typeof gf === 'function')) {
  54. return false
  55. }
  56. }
  57. return true
  58. }
  59. // MyDuck.matches('a', ['this', 'c'])
  60. matches (thisType, argTypes) {
  61. if (!argTypes && isArray(thisType)) {
  62. argTypes = thisType
  63. thisType = 'this'
  64. }
  65. if (!thisType) {
  66. thisType = 'this'
  67. }
  68. if (!argTypes) {
  69. argTypes = []
  70. }
  71. return new Constraint(this, thisType, argTypes)
  72. }
  73. }
  74. Duck.prototype.isDuck = true
  75. Duck.prototype.isProtocol = true
  76. const Protoduck = module.exports = define(['duck'], {
  77. createGenfun: ['duck', _metaCreateGenfun],
  78. addMethod: ['duck', _metaAddMethod]
  79. }, { name: 'Protoduck' })
  80. const noImplFound = module.exports.noImplFound = genfun.noApplicableMethod
  81. module.exports.define = define
  82. function define (types, spec, opts) {
  83. if (!isArray(types)) {
  84. // protocol(spec, opts?) syntax for method-based protocols
  85. opts = spec
  86. spec = types
  87. types = []
  88. }
  89. const duck = function (thisType, argTypes) {
  90. return duck.matches(thisType, argTypes)
  91. }
  92. Object.setPrototypeOf(duck, Duck.prototype)
  93. duck.isDerivable = true
  94. Object.defineProperty(duck, 'name', {
  95. value: (opts && opts.name) || 'Protocol'
  96. })
  97. if (opts && opts.where) {
  98. let where = opts.where
  99. if (!isArray(opts.where)) { where = [opts.where] }
  100. duck._constraints = where.map(w => w.isProtocol // `where: [Foo]`
  101. ? w.matches()
  102. : w
  103. )
  104. } else {
  105. duck._constraints = []
  106. }
  107. duck.isProtocol = true
  108. duck._metaobject = opts && opts.metaobject
  109. duck._types = types
  110. duck._defaultImpls = {}
  111. duck._gfTypes = {}
  112. duck._methodNames = Object.keys(spec)
  113. duck._methodNames.forEach(name => {
  114. checkMethodSpec(duck, name, spec)
  115. })
  116. duck._constraints.forEach(c => c.attach(duck))
  117. return duck
  118. }
  119. function checkMethodSpec (duck, name, spec) {
  120. let gfTypes = spec[name]
  121. if (typeof gfTypes === 'function') {
  122. duck._defaultImpls[name] = gfTypes
  123. gfTypes = [gfTypes]
  124. } if (typeof gfTypes[gfTypes.length - 1] === 'function') {
  125. duck._defaultImpls[name] = gfTypes.pop()
  126. } else {
  127. duck.isDerivable = false
  128. }
  129. duck._gfTypes[name] = gfTypes.map(typeId => {
  130. const idx = duck._types.indexOf(typeId)
  131. if (idx === -1) {
  132. throw new Error(
  133. `type '${
  134. typeId
  135. }' for function '${
  136. name
  137. }' does not match any protocol types (${
  138. duck._types.join(', ')
  139. }).`
  140. )
  141. } else {
  142. return idx
  143. }
  144. })
  145. }
  146. function defineMethod (duck, name, target, types, impls) {
  147. const methodTypes = duck._gfTypes[name].map(function (typeIdx) {
  148. return types[typeIdx]
  149. })
  150. for (let i = methodTypes.length - 1; i >= 0; i--) {
  151. if (methodTypes[i] === undefined) {
  152. methodTypes.pop()
  153. } else {
  154. break
  155. }
  156. }
  157. const useMetaobject = duck._metaobject && duck._metaobject !== Protoduck
  158. // `target` does not necessarily inherit from `Object`
  159. if (!Object.prototype.hasOwnProperty.call(target, name)) {
  160. // Make a genfun if there's nothing there
  161. const gf = useMetaobject
  162. ? duck._metaobject.createGenfun(duck, target, name, null)
  163. : _metaCreateGenfun(duck, target, name, null)
  164. target[name] = gf
  165. } else if (typeof target[name] === 'function' && !target[name].isGenfun) {
  166. // Turn non-gf functions into genfuns
  167. const gf = useMetaobject
  168. ? duck._metaobject.createGenfun(duck, target, name, target[name])
  169. : _metaCreateGenfun(duck, target, name, target[name])
  170. target[name] = gf
  171. }
  172. const fn = impls[name] || duck._defaultImpls[name]
  173. if (fn) { // checkImpls made sure this is safe
  174. useMetaobject
  175. ? duck._metaobject.addMethod(duck, target, name, methodTypes, fn)
  176. : _metaAddMethod(duck, target, name, methodTypes, fn)
  177. }
  178. }
  179. function checkImpls (duck, target, impls) {
  180. duck._methodNames.forEach(function (name) {
  181. if (
  182. !impls[name] &&
  183. !duck._defaultImpls[name] &&
  184. // Existing methods on the target are acceptable defaults.
  185. typeof target[name] !== 'function'
  186. ) {
  187. throw new Error(`Missing implementation for ${
  188. formatMethod(duck, name, duck.name)
  189. }. Make sure the method is present in your ${
  190. duck.name || 'protocol'
  191. } definition. Required methods: ${
  192. duck._methodNames.filter(m => {
  193. return !duck._defaultImpls[m]
  194. }).map(m => formatMethod(duck, m)).join(', ')
  195. }.`)
  196. }
  197. })
  198. Object.keys(impls).forEach(function (name) {
  199. if (duck._methodNames.indexOf(name) === -1) {
  200. throw new Error(
  201. `${name}() was included in the impl, but is not part of ${
  202. duck.name || 'the protocol'
  203. }. Allowed methods: ${
  204. duck._methodNames.map(m => formatMethod(duck, m)).join(', ')
  205. }.`
  206. )
  207. }
  208. })
  209. }
  210. function formatMethod (duck, name, withDuckName) {
  211. return `${
  212. withDuckName && duck.name ? `${duck.name}#` : ''
  213. }${name}(${duck._gfTypes[name].map(n => duck._types[n]).join(', ')})`
  214. }
  215. function checkArgTypes (duck, types) {
  216. var requiredTypes = duck._types
  217. if (types.length > requiredTypes.length) {
  218. throw new Error(
  219. `${
  220. duck.name || 'Protocol'
  221. } expects to be defined across ${
  222. requiredTypes.length
  223. } type${requiredTypes.length > 1 ? 's' : ''}, but ${
  224. types.length
  225. } ${types.length > 1 ? 'were' : 'was'} specified.`
  226. )
  227. }
  228. }
  229. function typeName (obj) {
  230. return (/\[object ([a-zA-Z0-9]+)\]/).exec(({}).toString.call(obj))[1]
  231. }
  232. function installMethodErrorMessage (proto, gf, target, name) {
  233. noImplFound.add([gf], function (gf, thisArg, args) {
  234. let parent = Object.getPrototypeOf(thisArg)
  235. while (parent && parent[name] === gf) {
  236. parent = Object.getPrototypeOf(parent)
  237. }
  238. if (parent && parent[name] && typeof parent[name] === 'function') {
  239. }
  240. var msg = `No ${typeName(thisArg)} impl for ${
  241. proto.name ? `${proto.name}#` : ''
  242. }${name}(${[].map.call(args, typeName).join(', ')}). You must implement ${
  243. proto.name
  244. ? formatMethod(proto, name, true)
  245. : `the protocol ${formatMethod(proto, name)} belongs to`
  246. } in order to call ${typeName(thisArg)}#${name}(${
  247. [].map.call(args, typeName).join(', ')
  248. }).`
  249. const err = new Error(msg)
  250. err.protocol = proto
  251. err.function = gf
  252. err.thisArg = thisArg
  253. err.args = args
  254. err.code = 'ENOIMPL'
  255. throw err
  256. })
  257. }
  258. function isArray (x) {
  259. return Object.prototype.toString.call(x) === '[object Array]'
  260. }
  261. // Metaobject Protocol
  262. Protoduck.impl(Protoduck) // defaults configured by definition
  263. function _metaCreateGenfun (proto, target, name, deflt) {
  264. var gf = genfun({
  265. default: deflt,
  266. name: `${proto.name ? `${proto.name}#` : ''}${name}`
  267. })
  268. installMethodErrorMessage(proto, gf, target, name)
  269. gf.duck = proto
  270. return gf
  271. }
  272. function _metaAddMethod (duck, target, name, methodTypes, fn) {
  273. return target[name].add(methodTypes, fn)
  274. }
  275. // Constraints
  276. class Constraint {
  277. constructor (parent, thisType, argTypes) {
  278. this.parent = parent
  279. this.target = thisType
  280. this.types = argTypes
  281. }
  282. attach (obj) {
  283. this.child = obj
  284. if (this.target === 'this') {
  285. this.thisIdx = 'this'
  286. } else {
  287. const idx = this.child._types.indexOf(this.target)
  288. if (idx === -1) {
  289. this.thisIdx = null
  290. } else {
  291. this.thisIdx = idx
  292. }
  293. }
  294. this.indices = this.types.map(typeId => {
  295. if (typeId === 'this') {
  296. return 'this'
  297. } else {
  298. const idx = this.child._types.indexOf(typeId)
  299. if (idx === -1) {
  300. return null
  301. } else {
  302. return idx
  303. }
  304. }
  305. })
  306. }
  307. verify (target, types) {
  308. const thisType = (
  309. this.thisIdx === 'this' || this.thisIdx == null
  310. )
  311. ? target
  312. : types[this.thisIdx]
  313. const parentTypes = this.indices.map(idx => {
  314. if (idx === 'this') {
  315. return target
  316. } else if (idx === 'this') {
  317. return types[this.thisIdx]
  318. } else if (idx === null) {
  319. return Object
  320. } else {
  321. return types[idx] || Object.prototype
  322. }
  323. })
  324. return this.parent.hasImpl(thisType, parentTypes)
  325. }
  326. }
  327. Constraint.prototype.isConstraint = true