npm C++
Neil Haddley • February 26, 2021
Create and publish an npm module using C++.
Recursion
When a function is called, the computer must "remember" the place it was called from... so that it can return to that location with the result once the call is complete. Typically, this information is saved on the call stack... For tail calls, there is no need to remember the caller...
https://en.wikipedia.org/wiki/Tail_call
Some interpreters (and compilers) eliminate the stack frame creation and destruction work when they recognize tail recursion.
C++
Create a folder for the new node module.
$ mkdir haddley-factorial-cc
Navigate to the new folder
$ cd haddley-factorial-cc
Create a package.json file
$ npm init -y
Mocha and Chai
Automated testing is added using mocha and chai.
$ npm i --save-dev mocha
$ npm i --save-dev chai
$ mkdir test

npm test
package.json
This is the final package.json file
Github actions
Github actions ensure that testing is performed using multiple versions of node.

Tests are running

Code passed all of the provided tests

Test results for multiple versions of node
Publishing to npmjs.com
To publish the module to npmjs.com
$ node login
$ node publish

npm publish
addon.c
TEXT
1#include <assert.h> 2#include <node_api.h> 3#include <stdio.h> 4 5double fac(int n) 6{ 7 if (n < 2) 8 return 1; 9 return n * fac(n - 1); 10} 11 12double fac_tr_aux(int n, double acc) 13{ 14 if (n < 2) 15 return acc; 16 return fac_tr_aux(n - 1, n * acc); 17} 18 19double fac_tr(int n) 20{ 21 return fac_tr_aux(n, 1); 22} 23 24double fac_it(int n) 25{ 26 double acc = 1; 27 28 for (int i = n; i > 1; i--) 29 { 30 acc = acc * i; 31 } 32 33 return acc; 34} 35 36static napi_value Factorial_aux(napi_env env, napi_callback_info info, double (*funcp)(int)) 37{ 38 napi_status status; 39 40 size_t argc = 1; 41 napi_value args[1]; 42 status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); 43 assert(status == napi_ok); 44 45 if (argc < 1) 46 { 47 napi_throw_type_error(env, NULL, "Wrong number of arguments"); 48 return NULL; 49 } 50 51 napi_valuetype valuetype0; 52 status = napi_typeof(env, args[0], &valuetype0); 53 assert(status == napi_ok); 54 55 if (valuetype0 != napi_number) 56 { 57 napi_throw_type_error(env, NULL, "Wrong arguments"); 58 return NULL; 59 } 60 61 double value0; 62 status = napi_get_value_double(env, args[0], &value0); 63 assert(status == napi_ok); 64 65 napi_value result; 66 67 double res = funcp(value0); 68 69 status = napi_create_double(env, res, &result); 70 assert(status == napi_ok); 71 72 return result; 73} 74 75static napi_value Factorial(napi_env env, napi_callback_info info) 76{ 77 return Factorial_aux(env, info, &fac); 78} 79 80static napi_value Factorial_tr(napi_env env, napi_callback_info info) 81{ 82 return Factorial_aux(env, info, &fac_tr); 83} 84 85static napi_value Factorial_it(napi_env env, napi_callback_info info) 86{ 87 return Factorial_aux(env, info, &fac_it); 88} 89 90#define DECLARE_NAPI_METHOD(name, func) \ 91 { \ 92 name, 0, func, 0, 0, 0, napi_default, 0 \ 93 } 94 95napi_value Init(napi_env env, napi_value exports) 96{ 97 napi_status status; 98 napi_property_descriptor desc[] = { 99 DECLARE_NAPI_METHOD("factorial", Factorial), 100 DECLARE_NAPI_METHOD("factorial_tr", Factorial_tr), 101 DECLARE_NAPI_METHOD("factorial_it", Factorial_it)}; 102 103 status = napi_define_properties(env, exports, 3, desc); 104 105 assert(status == napi_ok); 106 107 return exports; 108} 109 110NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
addon.js
JAVASCRIPT
1const addon = require('bindings')('addon.node') 2 3exports.factorial = addon.factorial 4 5exports.factorial_tr = addon.factorial_tr 6 7exports.factorial_it = addon.factorial_it
binding.gyp
JAVASCRIPT
1{ 2 "targets": [ 3 { 4 "target_name": "addon", 5 "sources": [ "addon.c" ] 6 } 7 ] 8}
testtest.js
JAVASCRIPT
1var module = require('../addon') 2var expect = require('chai').expect; 3 4describe('#factorial()', function () { 5 6 7 // test recursive function 8 it('should calculate factorial of 9 using recursion', function () { 9 // add an assertion 10 expect(module.factorial(9)).to.equal(362880); 11 }) 12 13 // test tail recursion version 14 it('should calculate factorial of 9 using tail recursion', function () { 15 // add an assertion 16 expect(module.factorial_tr(9)).to.equal(362880); 17 }) 18 19 // test iteration version 20 it('should calculate factorial of 9 using iteration', function () { 21 // add an assertion 22 expect(module.factorial_it(9)).to.equal(362880); 23 }) 24 25 // test tail recursion version 26 it('should calculate factorial of 170 using recursion', function () { 27 // add an assertion 28 expect(module.factorial(170)).to.equal(7.257415615307994e+306); 29 }) 30 31 // test tail recursion version 32 it('should calculate factorial of 170 using tail recursion', function () { 33 // add an assertion 34 expect(module.factorial_tr(170)).to.equal(7.257415615308004e+306); 35 }) 36 37 // test iteration version 38 it('should calculate factorial of 170 using iteration', function () { 39 // add an assertion 40 expect(module.factorial_it(170)).to.equal(7.257415615308004e+306); 41 }) 42 43 // test tail recursion version 44 it('should calculate factorial of 171 using recursion', function () { 45 // add an assertion 46 expect(module.factorial(171)).to.equal(Infinity); 47 }) 48 49 // test tail recursion version 50 it('should calculate factorial of 171 using tail recursion', function () { 51 // add an assertion 52 expect(module.factorial_tr(171)).to.equal(Infinity); 53 }) 54 55 // test iteration version 56 it('should calculate factorial of 171 using iteration', function () { 57 // add an assertion 58 expect(module.factorial_it(171)).to.equal(Infinity); 59 }) 60 61 // test recursive function 62 it('should calculate factorial of 200000 using recursion', function () { 63 this.timeout(10000); 64 // add an assertion 65 expect(module.factorial(200000)).to.equal(Infinity); 66 }) 67 68 // test recursive function 69 it('should calculate factorial of 800000 using tail recursion', function () { 70 this.timeout(10000); 71 // add an assertion 72 expect(module.factorial_tr(800000)).to.equal(Infinity); 73 }) 74 75 // test recursive function 76 it('should calculate factorial of 1600000 using iteration', function () { 77 this.timeout(10000); 78 // add an assertion 79 expect(module.factorial_it(1600000)).to.equal(Infinity); 80 }) 81 82 83})
package.json
JAVASCRIPT
1{ 2 "name": "haddley-factorial-cc", 3 "version": "1.0.0", 4 "description": "", 5 "main": "addon.js", 6 "directories": { 7 "test": "test" 8 }, 9 "scripts": { 10 "test": "mocha" 11 }, 12 "repository": { 13 "type": "git", 14 "url": "git+https://github.com/Haddley/haddley-factorial-cc.git" 15 }, 16 "keywords": [], 17 "author": "", 18 "license": "ISC", 19 "bugs": { 20 "url": "https://github.com/Haddley/haddley-factorial-cc/issues" 21 }, 22 "homepage": "https://github.com/Haddley/haddley-factorial-cc#readme", 23 "dependencies": { 24 "bindings": "~1.2.1" 25 }, 26 "devDependencies": { 27 "chai": "^4.3.0", 28 "mocha": "^8.3.0" 29 } 30}
node.js.yml
JAVASCRIPT
1# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 4name: Node.js CI 5 6on: 7 push: 8 branches: [ main ] 9 pull_request: 10 branches: [ main ] 11 12jobs: 13 build: 14 15 runs-on: ubuntu-latest 16 17 strategy: 18 matrix: 19 node-version: [10.x, 12.x, 14.x, 15.x] 20 # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 22 steps: 23 - uses: actions/checkout@v2 24 - name: Use Node.js ${{ matrix.node-version }} 25 uses: actions/setup-node@v1 26 with: 27 node-version: ${{ matrix.node-version }} 28 - run: npm ci 29 - run: npm run build --if-present 30 - run: npm test