npm C++

Neil HaddleyFebruary 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

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

Tests are running

Code passed all of the provided tests

Code passed all of the provided tests

Test results for multiple versions of node

Test results for multiple versions of node

Publishing to npmjs.com

To publish the module to npmjs.com

$ node login

$ node publish

npm 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