πŸ“œ ⬆️ ⬇️

OCaml and PHP - esoteric for your convenience

NB: reading this topic, it is desirable not to eat - you can choke on surprise.
NB: less valuable pieces of code had to be put out to the pastin, due to the fact that the Habr is cutting the post. Follow the links in the text.

Many attribute OCaml to marginal and even esoteric languages. Perhaps they are right, although many people disagree with them. For me, acquaintance with him began six months ago, when I once again wanted to learn something new and I thought that at least one functional language should be mastered. From a variety of languages, I chose Objective Caml. The language conquered me with human syntax and the idea: there are all the functional joys of life, but if you want an imperative style and OOP, take them, I have them! It turned out that the developers were well aware that different tools needed different tools. Three days of reading the manual for C ++ and Perl programmers and I already could read the code and write the hello world. At this, my acquaintance with the language ended, because learning a language not on a real task is a silly thing.

I returned to OCaml a couple of weeks ago, when there was one interesting task. I looked at it and realized that, in principle, it can be completely solved in PHP, or I can write, as I like, a PHP extension in C ++, but a functional language would fit much more. So, we have the main program in PHP, from which we want to call OCaml functions, getting from this an obvious profit and uncomplicated joy of the idiot and Michurin in one bottle. In this article I will give just a simple code to demonstrate the principle - without clever optimizations and all that will make our code much faster, but will worsen its readability. In addition, I tried to separate the wrappers for PHP and OCaml as much as possible, so data conversion is not directly done by PHP ← β†’ OCaml, but by the way PHP ← β†’ C ← β†’ OCaml, which additionally reduces speed. But the principle will be so much clearer, and with the optimization, I hope, everyone will cope.
We define the task that we solve as follows: it is necessary to take an array of structures from PHP, filter it in some way, for example, by some value, and then return the remaining array.

Ocaml


Let's get started First, we write our OCaml-part, which will do all the work. We define in the file ocamlpart.ml data types with which we will work. Let it be some chemical groups:
  1. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  2. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  3. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  4. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  5. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  6. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  7. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  8. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  9. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;
  10. type group_position = UndefPos | LeftPos | RightPos ;; type cycle_type = UndefCycle | NoneCycle | AliphaticCycle | AromaticCycle | HeteroCycle ;; type group_type = OrgGroup | InorgGroup | NeighbourGroup of int ;; type group = { name : string ; position : group_position ; cycle : cycle_type ; grouptype : group_type ; link : int ; } ;;

')
Please note that we have an analogue of the C structure ( group ), analogs of enum ( group_position , cycle_type ) and a hybrid type, which can be either an enum or an arbitrary number ( group_type ). Now add a function to filter:
  1. let filter_org = List . filter ( fun r -> if r . grouptype = OrgGroup then true else false )

This function will filter the list of groups by the value of the grouptype field - in fact, this is an analogue of what in PHP is called the array_filter function.
Finally, add the registration of the filter function for use in C:
  1. let _ =
  2. Callback . register "filter_organic" filter_org ;
  3. ;;

This block will be executed upon initialization of the OCaml part from C, providing us with a remarkable nominal callback.
Now we need to write a wrapper for this.

C / C ++


First of all, we will prepare a common cpart.h file, which will contain common parts for OCaml and PHP wrappers:
  1. #include <vector>
  2. typedef enum _group_position {
  3. UndefPos = 0 ,
  4. LeftPos,
  5. Rightpos
  6. } group_position ;
  7. typedef enum _cycle_type {
  8. UndefCycle = 0 ,
  9. NoneCycle
  10. AliphaticCycle
  11. AromaticCycle
  12. Heterocycle
  13. } cycle_type ;
  14. typedef enum _group_pos {
  15. OrgGroup = - 2 ,
  16. InorgGroup = - 1
  17. } group_pos ;
  18. typedef struct _group {
  19. char * name ;
  20. group_position position ;
  21. cycle_type cycle ;
  22. int group_type ;
  23. int link ;
  24. } group ;
  25. void init_ocaml ( ) ;
  26. std :: vector < group > filter_org ( std :: vector < group > g ) ;

We defined type mappings from OCaml and declared two functions β€” to initialize OCaml and the main effective wrapper function, which will filter the data array.
In fact, we will have more functions, but only these we plan to show in the future for PHP. Now we will write a file ccamlpart.cc with a wrapper for Ocaml. Headlines first:
  1. #include "cpart.h"
  2. #ifdef __cplusplus
  3. extern "C"
  4. {
  5. #endif
  6. #include <caml / mlvalues.h>
  7. #include <caml / alloc.h>
  8. #include <caml / callback.h>
  9. #include <caml / fail.h>
  10. #include <caml / memory.h>
  11. #ifdef __cplusplus
  12. }
  13. #endif
  14. #include <string.h>

Since we use C ++ with its vector, and we have to compile the code as a plus, we don’t forget about extern for standard OCaml functions. Now we define auxiliary functions for type conversion between OCaml and C ( here ).
Here, we needed a special function to convert the hybrid OCaml-type group_type - its enum-values ​​are stored as simple integers, but the NeighbourGroup is already a structure with one field.
You should also look at three macros: CAMLparamX, CAMLlocalX and CAMLreturn *. The first one is used for the correct operation of the garbage collector, and takes as input all OCaml variables passed to the function. The second is used to declare local OCaml variables. And the third macro is again necessary not only for returning the value, but also for the correct operation of the garbage collector.
And finally, our actual functions for PHP:
  1. void init_ocaml ( )
  2. {
  3. char * argv [ 1 ] ;
  4. argv [ 0 ] = NULL ;
  5. caml_main ( argv ) ;
  6. }
  7. std :: vector < group > filter_org ( std :: vector < group > g )
  8. {
  9. CAMLparam0 ( ) ;
  10. static value * closure_f = NULL ;
  11. if ( closure_f == NULL )
  12. closure_f = caml_named_value ( "filter_organic" ) ;
  13. CAMLlocal3 ( cli, cons, cb_res ) ;
  14. cli = Val_emptylist ;
  15. for ( std :: vector < group > :: iterator i = g. begin ( ) ; i ! = g. end ( ) ; ++ i )
  16. {
  17. cons = caml_alloc ( 2 , 0 ) ;
  18. Store_field ( cons, 0 , camlgroup_of_group ( & ( * i ) ) ) ;
  19. Store_field ( cons, 1 , cli ) ;
  20. cli = cons ;
  21. }
  22. cb_res = caml_callback ( * closure_f, cli ) ;
  23. std :: vector < group > result ;
  24. while ( cb_res ! = Val_emptylist )
  25. {
  26. result. push_back ( group_of_camlgroup ( Field ( cb_res, 0 ) ) ) ;
  27. cb_res = Field ( cb_res, 1 ) ;
  28. }
  29. return result ;
  30. }

We simply construct an OCaml-list from a vector: it is arranged literally according to all canons - a structure in which the first field is an element and the second is a pointer to the continuation of the list. Then we feed this OCaml list and turn the result back into a vector.

Let's put all this together in a static library with a makefile:
  1. ocamlpart . o : ocamlpart . ml
  2. ocamlopt - g - output - obj $ ^ - o $ @
  3. ccamlpart . o : ccamlpart . cc
  4. g ++ - g - c - o $ @ - I "` ocamlc -where` " $ ^
  5. libchempart . a : ocamlpart . o ccamlpart . o
  6. ar rcs $ @ $ ^
  7. all : libchempart . a
  8. clean :
  9. rm - f *. o *. a *. cmi *. cmx


Php


Now, finally, write a PHP extension. To do this, we will create a separate folder php in the folder with the project - the extension will create its Makefile, overwriting the one we used above, so we separate one from the other. Create a cphppart.h file in it. In the file, we declared the standard module functions, the Chemlib class, and the global function get_group_of_caml. We are again forced to use std :: vector, so do not forget about extern for PHP inclusions. Now in the cphppart.cc file we implement the extension directly. For a start, the standard information about the extension, class and its functions (see here ). Then the module initialization function β€” it is in it that the class is registered in PHP, and in addition to the methods it also receives properties:
  1. PHP_MINIT_FUNCTION ( chemlib )
  2. {
  3. init_ocaml ( ) ;
  4. zend_class_entry chemlib_ce ;
  5. INIT_CLASS_ENTRY ( chemlib_ce, PHP_CHEMLIB_CLASS_NAME, chemlib_class_functions ) ;
  6. chemlib_class_entry = zend_register_internal_class ( & chemlib_ce TSRMLS_CC ) ;
  7. zend_declare_property_string ( chemlib_class_entry, ( char * ) "name" , 4 , ( char * ) "" , ZEND_ACC_PUBLIC TSRMLS_CC ) ;
  8. zend_declare_property_long ( chemlib_class_entry, ( char * ) "position" , 8 , 0 , ZEND_ACC_PUBLIC TSRMLS_CC ) ;
  9. zend_declare_property_long ( chemlib_class_entry, ( char * ) "cycle" , 5 , 0 , ZEND_ACC_PUBLIC TSRMLS_CC ) ;
  10. zend_declare_property_long ( chemlib_class_entry, ( char * ) "group_type" , 10 , 0 , ZEND_ACC_PUBLIC TSRMLS_CC ) ;
  11. zend_declare_property_long ( chemlib_class_entry, ( char * ) "link" , 4 , 0 , ZEND_ACC_PUBLIC TSRMLS_CC ) ;
  12. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "UndefPos" , 8 , 0 TSRMLS_CC ) ;
  13. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "LeftPos" , 7 , 1 TSRMLS_CC ) ;
  14. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "RightPos" , 8 , 2 TSRMLS_CC ) ;
  15. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "UndefCycle" , 10 , 0 TSRMLS_CC ) ;
  16. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "NoneCycle" , 9 , 1 TSRMLS_CC ) ;
  17. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "AliphaticCycle" , 14 , 2 TSRMLS_CC ) ;
  18. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "AromaticCycle" , 13 , 3 TSRMLS_CC ) ;
  19. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "HeteroCycle" , 11 , 4 TSRMLS_CC ) ;
  20. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "OrgGroup" , 8 , - 2 TSRMLS_CC ) ;
  21. zend_declare_class_constant_long ( chemlib_class_entry, ( char * ) "InorgGroup" , 10 , - 1 TSRMLS_CC ) ;
  22. } ;

As you can see, we set the class with all the same properties as in C for the group structure, in addition, we declared convenient constants of the Chemlib :: OrgGroup type. Add default functions, functionality that everyone can fill to taste ( here ). Season with auxiliary internal functions for converting a group between C and PHP:
  1. zval * phpgroup_of_group ( group * gr )
  2. {
  3. zval * res ;
  4. ALLOC_INIT_ZVAL ( res ) ;
  5. object_init_ex ( res, chemlib_class_entry ) ;
  6. zend_update_property_string ( Z_OBJCE_P ( res ) , res, ( char * ) "name" , 4 , gr - > name TSRMLS_CC ) ;
  7. zend_update_property_long ( Z_OBJCE_P ( res ) , res, ( char * ) "position" , 8 , gr - > position TSRMLS_CC ) ;
  8. zend_update_property_long ( Z_OBJCE_P ( res ) , res, ( char * ) "cycle" , 5 , gr - > cycle TSRMLS_CC ) ;
  9. zend_update_property_long ( Z_OBJCE_P ( res ) , res, ( char * ) "group_type" , 10 , gr - > group_type TSRMLS_CC ) ;
  10. zend_update_property_long ( Z_OBJCE_P ( res ) , res, ( char * ) "link" , 4 , gr - > link TSRMLS_CC ) ;
  11. return res ;
  12. }
  13. group group_of_phpgroup ( zval * gr )
  14. {
  15. group res, def ;
  16. zval * x = zend_read_property ( chemlib_class_entry, gr, ( char * ) "name" , 4 , 1 TSRMLS_CC ) ;
  17. if ( Z_TYPE_P ( x ) ! = IS_STRING )
  18. return def ;
  19. res. name = estrdup ( Z_STRVAL_P ( x ) ) ;
  20. x = zend_read_property ( chemlib_class_entry, gr, ( char * ) "position" , 8 , 1 TSRMLS_CC ) ;
  21. if ( Z_TYPE_P ( x ) ! = IS_LONG )
  22. return def ;
  23. res. position = ( group_position ) Z_LVAL_P ( x ) ;
  24. x = zend_read_property ( chemlib_class_entry, gr, ( char * ) "cycle" , 5 , 1 TSRMLS_CC ) ;
  25. if ( Z_TYPE_P ( x ) ! = IS_LONG )
  26. return def ; li style = "font-weight: n

Source: https://habr.com/ru/post/98185/


All Articles