package cinaps

  1. Overview
  2. Docs
Trivial metaprogramming tool

Install

Dune Dependency

Authors

Maintainers

Sources

v0.15.1.tar.gz
sha256=1be18e70f5d8a6b03566c3619b62836a26094fc7208fde46ab7b32ee64116170
sha512=ca53a9da8aa71ce7cddf7e24778e9c4d3f3e5784209da85a5a6b2d5af83cd8ad769fbe3009d2757ebf4a25ca39d76af00ebc693b1b01c3b53c8775ea479123a5

Description

Cinaps is a trivial Metaprogramming tool using the OCaml toplevel. It is based on the same idea as expectation tests. The user write some OCaml code inside special comments and cinaps make sure that what follows is what is printed by the OCaml code.

Published: 12 Feb 2021

README

README.org

* CINAPS - Cinaps Is Not A Preprocessing System

Cinaps is a trivial Metaprogramming tool for OCaml using the OCaml
toplevel.

It is intended for two purposes:
- when you want to include a bit of generated code in a file, but
  writing a proper generator/ppx rewriter is not worth it
- when you have many repeated blocks of similar code in your program,
  to help writing and maintaining them

It is not intended as a general preprocessor, and in particular cannot
only be used to generate static code that is independent of the
system.

** How does it work?

Cinaps is a purely textual tool. It recognizes special syntax of the
form =(*$ <ocaml-code> *)= in the input. =<ocaml-code>= is evaluated
and whatever it prints on the standard output is compared against what
follows in the file until the next =($ ... *)= form, in the same way
that expectation tests works.

A form ending with =$*)= stops the matching and switch back to plain
text mode. In particular the empty form =(*$*)= can be used to mark
the end of a generated block.

If the actual output doesn't match the expected one, cinaps creates a
=.corrected= file containing the actual output, diff the original file
against the actual output and exits with an error code. Other it
simply exits with error code 0.

For instance:

#+begin_src sh
$ cat file.ml
let x = 1
(*$ print_newline ();
    List.iter (fun s -> Printf.printf "let ( %s ) = Pervasives.( %s )\n" s s)
      ["+"; "-"; "*"; "/"] *)
(*$*)
let y = 2

$ cinaps file.ml
---file.ml
+++file.ml.corrected
File "file.ml", line 5, characters 0-1:
  let x = 1
  (*$ print_newline ();
      List.iter (fun s -> Printf.printf "let ( %s ) = Pervasives.( %s )\n" s s)
        ["+"; "-"; "*"; "/"] *)
+|let ( + ) = Pervasives.( + )
+|let ( - ) = Pervasives.( - )
+|let ( * ) = Pervasives.( * )
+|let ( / ) = Pervasives.( / )
  (*$*)
  let y = 2

$ echo $?
1
$ cp file.ml.corrected file.ml
$ cinaps file.ml
$ echo $?
0
#+end_src

You can also pass =-i= to override the file in place in case of
mismatch. For instance you can have a =cinaps= target in your build
system to refresh the files in your project.

** Capturing text from the input

In any form =(*$ ... *)= form, the variable =_last_text_block=
contains the contents of the text between the previous =(*$ ... *)=
form or beginning of file and the current form.

For instance you can use it to write a block of code and copy it to a
second block of code that is similar except for some simple
substitution:

#+begin_src ocaml
(*$*)
let rec power_int32 n p =
  if Int32.equal p 0 then
    Int32.one
  else
    Int32.mul n (power n (Int32.pred p))

(*$ print_string (Str.global_replace (Str.regexp "32") "64" _last_text_block) *)
let rec power_int64 n p =
  if Int64.equal p 0 then
    Int64.one
  else
    Int64.mul n (power n (Int64.pred p))

(*$*)
#+end_src

Now, whenever you modify =power_int32=, you can just run cinaps to update
the =power_int64= version.

** Sharing values across multiple files

The toplevel directive ~#use~ works in CINAPS, and can be used to read in values
from other files. For example,

1. In ~import.cinaps~,

   #+BEGIN_SRC ocaml
     (* -*- mode: tuareg -*- *)
     include StdLabels
     include Printf

     let all_fields = [ "name", "string"; "age", "int" ]
   #+END_SRC

2. In ~foo.ml~,

   #+BEGIN_SRC ocaml
     (*$ #use "import.cinaps";;
              List.iter all_fields ~f:(fun (name, type_) ->
                printf "\n\
                  external get_%s : unit -> %s = \"get_%s\"" name type_ name) *)
     external get_name : unit -> string = "get_name"
     external get_age : unit -> int = "get_age"(*$*)
   #+END_SRC

3. In ~stubs.h~,

   #+BEGIN_SRC C
     /*$ #use "import.cinaps";;
              List.iter all_fields ~f:(fun (name, _) ->
                printf "\n\
                  extern value get_%s(void);" name) */
     extern value get_name(void);
     extern value get_age(void);/*$*/
   #+END_SRC

Etc.

Note that the ~#use~ directive will read in OCaml from files of any extension.
~*.cinaps~ is a safe choice in the presence of jenga and dune, which by default
try to use all ~*.ml~ files in the directory for the executables or library.

** Automatic reformatting of CINAPS output

In files managed by automatic formatting tools such as ocp-indent or
ocamlformat, the code need not come out of CINAPs already formatted correctly.

~cinaps.exe -styler FOO~ uses ~FOO~ to reformat its output, before diffing
against the source file.

Dependencies (4)

  1. base-unix
  2. re >= "1.8.0"
  3. dune >= "2.0.0"
  4. ocaml >= "4.04"

Dev Dependencies

None

Used by (3)

  1. lsp < "1.8.0" | >= "1.11.3"
  2. ocaml-migrate-parsetree >= "2.2.0"
  3. ppxlib >= "0.10.0"

Conflicts

None