This project aims to provide a JavaScript command interpreter for DGtal(a C++ geometry library). This documentation explains the experimental part of the project.
You can click here to read a full report.
-
Clone this repo
-
new a directory in your project path and name it "build"
~your projectPath$ mkdir build
-
go to the "build" directory and run the cmake command
~your projectPath/build$ cmake ../sourceCode
-
run the make command in newly created "build" directory
~your projectPath/build$ make
After these 4 steps, you have already generated all necessary files for this project. Now you can go to the
sourceCode
directory and chose any experiment for testing: -
~your projectPath/sourceCode/experimentName$ node experimentName.js
All the file path configuration are in a CMakeLists.txt
under the sourceCode
directory. If you want to change the structure of this project, just modify this file and rerun the cmake
and make
command.
Our FFI experiments consist of 4 parts. The common goal of these experiments is to use C++ library from within JavaScript by using node-ffi. For each example, I will firstly explain the C++ library that is going to be used, and then explain how to use that library in JavaScript.
The source code to create a library for this experiment is in path "./sourceCode/cpp/BasicUsage.cpp". It contains:
- a global function
add
that adds two Integrate and returns the sum - a class
Rectangle
that contains two private attributes (long and width) and two member functions that calculate its area and perimeter respectively. - a global function
createRectObj
that returns a rectangle object - a global function
createRectRef
that returns a rectangle pointer.
The source code to test a library for this experiment is in path "./sourceCode/js/BasicUsage.js". In this test, we have accomplished to
- Use the function
add
in JavaScript - Create a
Rectangle
object directly in JavaScript - Create a
Rectangle
object in C++ from existing buffer (directly invoke C++ constructor of BasicUsage Library) - Create a
Rectangle
object by using thecreateRectObj
function - Create a
Rectangle
object pointer by using thecreateRectPtr
function - Encapsulate the creation of C++ object into JavaScript function prototype, so that we can use that object with JavaScript syntax.
In order to create a C++ class object with Node FFI, we need to firstly define a structure that has the same attributes in JavaScript code as the class in C++ code. The definition of a structure in JavaScript can be done by requiring the "ref-struct" model of Node. Moreover, if we to want to get the reference of any variable defined in JavaScript, we will need the "ref" module of Node.
const ref = require('ref');//require "ref" module of Node
const Struct = require('ref-struct');//require "ref-struct" module of Node
var PointStruct = Struct({
'x': 'int',
'y': 'int',
});//define a structure
In order to use a C++ class's functions, such as its constructors or member functions, in Node-ffi, the reference of an object created by that class should be passed into these functions as their default parameters.
const RectanglePtrType = ref.refType(RectangleType);//get the reference of a self-define structure
The source code to create a library for this experiment is in path "./sourceCode/cpp/Template.cpp". Its structure is almost the same as the library in BasicUsage experiment expect that it's written in the form of C++ template. We have not rewrite the function add
into template form for simplicity of code.
It's a must to implement all the types that we are going to use lately in this library, which means the template classes or functions can not be used directly through node-ffi. Because without specifying types, template functions or template classes cannot be generated into a library's symbol table and thus cannot be found by node-ffi. As for this experiment, type "int" and type "double" are realized.
Source code for testing this experiment is in path "./sourceCode/js/Template". Similar to what we have done for BasicUsage's test, this experiment has:
- Create a
Rectangle
object directly in JavaScript - Create a
Rectangle
object in C++ from existing buffer (directly invoke C++ constructor of Template Library) - Create a
Rectangle
object by using thecreateRectObj
function - Create a
Rectangle
object pointer by using thecreateRectPtr
function - Encapsulate the creation of C++ object into JavaScript function prototype, so that we can use that object with JavaScript syntax.
The main difference is, this time in the JavaScript file, we have to specific which type (int or double) of the template classes or functions is needed. It's worth noting that, in order to distinguish functions or classes that have the same name in C++ source file, the compiler does something called "name mangling", so that after compilation, each function or class will only have a unique name. In our experiment for example, _Z13createRectObjii
represents the Rectangle<int> createRectObj(int a, int b)
function in source code; _Z13createRectObjdd
represents the Rectangle<double> createRectObj(double a, double b)
.
Source code to create library for Boost experiment is in path "./sourceCode/cpp/Boost.cpp".
Since there are numbers of functions predefined in Boost, we chose only some of them to test if Boost can cooperate with node-ffi. In this experiment, the timer
and progress
of Boost are chosen as for testing.
It's worth noting that we do not expose Boost directly to node-ffi. Instead, we created a wrapper function testBoost
containing the necessary C++ code of Boost for lately using.
The source code for boost timer test is in path "./sourceCode/js/Boost.js". The usage of Boost with FFI is the same as the BasicUsage (like calling a normal global function, calling a global variable...). All we need to do is to "include" the Boost library in the C++ file, and then generate a shared library. After that functions defined in Boost can be used easily from JavaScript.
Source code to create library for Boost experiment is in path "./sourceCode/cpp/2DPoint.cpp".
In the source code, there are two functions, both of which are wrappers that used as the media to invoke DGtal functions.
Point* create2DPoint(int a, int b){
return new Point(a,b);
}
Where Point()
is a built-in constructor of DGtal.
void draw2DPoint(Point p1, Point p2){
Board2D board;
board << p1<<p2;
board.saveSVG("draw2DPoint.svg");
}
Where Board2D
is a built-in class of DGtal.
Code for this test is in path "./sourceCode/cpp/2DPoint.js".
In this test, I've created a point at (3,4) and the other at (1,2) by using the
create2DPoint
function.
After that, I draw these two points with the help of draw2DPoint
.
You can click here
to see the result.
Instead of using a wrapper to invoke DGtal functions, it would be much easier and more straightforward to invoke DGtal functions directly.
Use command $ objdump -S lib2DPointsLib.so |less
, we found out that the symbol of the constructor for a 2 dimension point is _ZN5DGtal11PointVectorILj2EiNSt3__15arrayIiLm2EEEEC1ERKiS6_
.
So, we try to use _ZN5DGtal11PointVectorILj2EiNSt3__15arrayIiLm2EEEEC1ERKiS6_
in the 2DPoint test. It's worth noting
that after name unmangling, we get DGtal::PointVector<2u, int, std::__1::array<int, 2ul> >::PointVector(int const&, int const&)
which means it will take two int references as parameters instead of two int variables.
In order to set the value to an int reference, ref.alloc()
is needed:
let i1 = ref.alloc(ref.types.int, 2);
let i2 = ref.alloc(ref.types.int, 9);
The output of using the DGtal 2D point constructor is exactly the same as using the create2DPoint
wrapper function.
The general process of using SWIG's JavaScript interface generators for Node.js are the following 5 steps:
- Organize all c/c++ logic ( libraries that you want to use) in the form of header files.
- Write a SWIG interface file that indicates which libraries should be used
- Create a binding file binding.gyp
- Run the following commands:
$ swig -c++ -javascript -node mylib.i
//create a wrapper file
$ node-gyp build
// build a node module
- After step 4, a node module containing all functions, variables, classes, etc of your c/c++ libraries will be built.
The source code of this experiment is in path "./swig".
global.h
, rectangle.h
, templateExample.h
and TestBoost.h
are the library files for this experiment.
global.h
contains some simple global functions and global variables. The three rest library files are similar
to the BasicUsage Library
, Template Library
and Boost Library
in FFI experiments, which are already explained.
This file indicates which libraries are needed and some more information telling SWIG how to use these libraries. For example, the types of a template should be specified; global functions, as well as global variables, should be declared with the key word extern
.
%module "mylib"
%{
#include "global.h"
#include "rectangle.h"
#include "TestBoost.h"
#include "templateExample.h"
%}
%include "global.h";
%include "rectangle.h";
%include "TestBoost.h";
%include "templateExample.h"
%template (intRectangle) RectangleT<int>;
%template (floatRectangle) RectangleT<float>;
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
Now, you can run the command $ swig -c++ -javascript -node mylib.i
.
After that, a wrapper file called mylib_wrap.cxx
will be created.
A binding file is used to tell Node.js how to create a node module from a SWIG wrapper (in this example mylib_wrap.cxx
) file.
{
"targets": [
{
"target_name": "mylib",
"sources": [ "mylib_wrap.cxx" ]
}
]
}
Run $ node-gyp configure
and then node-gyp build
. A node module called mylib
will be built in
path ./swig/build/Release/mylib
.
The test file is in path "./swig/test.js". I've tried all the libraries mentioned before in this file. All of them work as expected. The following code snippets will show you how to use such a node module.
-
Require the node module that you generated.
var mylib = require("./build/Release/mylib");
-
Usage of global variables and functions
console.log(mylib.My_variable); console.log(mylib.fact(5));
-
Usage of a class
var rectangle = new mylib.Rectangle(5,6);
-
Usage of template
var intRectangle = new mylib.intRectangle(3,4) console.log(intRectangle.area())
-
Usage of Boost
var boost = new mylib.TestBoost(); boost.boostTimer();
In FFI experiments, I can link the DGtal library into a shared object by configuring the CMakeLists.txt
file.
But in SWIG experiments, I still haven't found an equivalent way to link the DGtal library, and thus the 2DPoint experiment is not yet done. I believe SWIG must have provided some options to do this linking but I don't have enough time to do the research.
- CMake 2.8.6 or later
- 30G disk space
- 6G swap space
-
Download llvm:
`$ cd where-you-want-llvm-to-live` `$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm`
-
Download Clang:
`$ cd where-you-want-llvm-to-live` `$ cd llvm/tools` `$ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang`
-
Download Compile-RT:
`$ cd where-you-want-llvm-to-live` `$ cd llvm/projects` `$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt`
-
Install
`$ cd where you want to build llvm` `$ mkdir build` `$ cd build` `$ cmake -G Unix Makefiles <path to llvm sources>`
-
Make. This will take a very long time
`$ cd where you want to build llvm` `$ make`
As mentioned in the prerequisite, at least 6GB swap space is needed to install LLVM. However, by default, the swap space is the size of memory, which may be less than 6GB. Thus, we need to add swap space manually. The following code shows how to add 4GB to swap space.
$ dd if=/dev/zero of=/swapfile bs=1024 count=4000000
$ mkswap /swapfile
$ swapon /swapfile
If you run out of swap space, you will get an error like this:
The following command can be used to display the AST of a chosen c/c++ file
$ clang -Xclang -ast-dump -fsyntax-only <file_name>
int f(int x) {
int result = (x / 42);
return result;
}
The top-level declaration in a translation unit is always the translation unit declaration
. In this example, our first user-written declaration is the function declaration of “f”. The body of “f” is a compound statement
, whose child nodes are a declaration statement
that declares our result variable, and the return statement.
int a =1;
int function (){
while (a==1){
a++;
};
int b=3;
int b=4;
}
In this example, our deliberately redefined variable b
in a function body is detected as an error.
The while
block corresponds to the WhileStmt
.
I've made notes on some basic symbols of Clang. For more information, you need to visit clang's official site