Incomplete expression in ppx extension - ocaml

I want to try to write my own ppx to allow named arguments in formatting strings:
From Format.printf [%fmt "!(abc) !(qsd)"] to Format.printf "%s %s" abc qsd
When dumping with ppx_tools I want to go from:
{pexp_desc =
Pexp_apply
({pexp_desc = Pexp_ident {txt = Ldot (Lident "Format", "printf")};
pexp_loc_stack = []},
[(Nolabel,
{pexp_desc =
Pexp_extension
({txt = "fmt"},
PStr
[{pstr_desc =
Pstr_eval
({pexp_desc =
Pexp_constant (Pconst_string ("!(abc) !(qsd)", ...));
pexp_loc_stack = []},
...)}]);
pexp_loc_stack = []})]);
pexp_loc_stack = []}
To
{pexp_desc =
Pexp_apply
({pexp_desc = Pexp_ident {txt = Ldot (Lident "Format", "printf")};
pexp_loc_stack = []},
[(Nolabel,
{pexp_desc = Pexp_constant (Pconst_string ("%s %s", ...));
pexp_loc_stack = []});
(Nolabel,
{pexp_desc = Pexp_ident {txt = Lident "abc"}; pexp_loc_stack = []});
(Nolabel,
{pexp_desc = Pexp_ident {txt = Lident "qsd"}; pexp_loc_stack = []})]);
pexp_loc_stack = []}
The ppx extension starts inside a function application so I would just want to specify that what I'm about to create are applications arguments but so far I've not been able to do so:
I get the formatting string (in my example it would be "%s %s") and the arguments to it (e.g. abc and qsd) and try to produce "%s %s" abc qsd but if I use Ast_build.Default.elist fmt args I get ["%s %s"; abc; qsd] and with eapply I get ("%s %s" abc qsd) (almost there but the parenthesis make it wrong).
let expand ~ctxt fmt =
let loc = Expansion_context.Extension.extension_point_loc ctxt in
let fmt, args = parse loc fmt in
Ast_builder.Default.eapply ~loc (* <- Here is where I don't know what to do *)
(Ast_builder.Default.estring ~loc fmt)
(List.map (Ast_builder.Default.evar ~loc) args)
Since it's heavily recommended to use ppxlib to do this kind of things, is there an easy way to achieve what I want? I tried looking for some documentation for it but it's still a work in progress and the few examples I could find transform an expression in another expression while I'm transforming an expression (a string) in an incomplete one.
FULL CODE:
open Ppxlib
(* A format string is a normal string with the special construct !(...) *)
let parse loc string =
let length = String.length string in
let buffer = Buffer.create length in
let rec parse args index =
if index = length then (Buffer.contents buffer, args)
else
match String.unsafe_get string index with
| '!' as c ->
if index = length - 1 || String.unsafe_get string (index + 1) <> '('
then (
(* Simple ! not starting a named argument *)
Buffer.add_char buffer c;
parse args (index + 1))
else
(* We saw !( and need to parse the rest as a named argument *)
let index, var = parse_named_arg (index + 2) in
Buffer.add_string buffer "%s";
parse (var :: args) index
| c ->
Buffer.add_char buffer c;
parse args (index + 1)
and parse_named_arg index =
let var = Buffer.create 8 in
let rec parse_var index =
if index = length then
Location.raise_errorf ~loc
"Reached end of formatting string with !(...) construct not ended"
else
match String.unsafe_get string index with
| ')' -> (index + 1, Buffer.contents var)
| c ->
Buffer.add_char var c;
parse_var (index + 1)
in
parse_var index
in
parse [] 0
let expand ~ctxt fmt =
let loc = Expansion_context.Extension.extension_point_loc ctxt in
let fmt, args = parse loc fmt in
Ast_builder.Default.eapply ~loc
(Ast_builder.Default.estring ~loc fmt)
(List.map (Ast_builder.Default.evar ~loc) args)
let my_extension =
Extension.V3.declare "fmt" Extension.Context.expression
Ast_pattern.(single_expr_payload (estring __))
expand
let rule = Ppxlib.Context_free.Rule.extension my_extension
let () = Driver.register_transformation ~rules:[ rule ] "ppx_fmt_string"

Related

This kind of expression is not allowed as right-hand side of `let rec'

I'm implementing the Raft protocol and my code is as follows:
let rec request_vote_loop: int =
match myState.myRole with
| Follower -> 0
| Leader -> 1
| Candidate ->
let trigger = Domain.spawn(fun _ -> Chan.send c TriggerEvent) in
let request_vote_daemon = Domain.spawn(fun _ ->
let rec loop n =
if n = 0 then 0
else let msg = Chan.recv votes in
match msg with
| (status, id) ->
Domain.join (Array.get !arr id);
if status = 1 then (Array.get votePeers id) := true; (Chan.send c ReceiveVoteEvent); loop (n - 1)
in loop ((Array.length (!peers)) / 2 + 1 - !current_vote)) in
let evt = Chan.recv c in
match evt with
| TimeoutEvent -> myState.myRole <- Follower; 3
| AppendEntriesEvent(_) ->
myState.myRole <- Follower; 4
| ReceiveVoteEvent ->
if !current_vote > (Array.length (!peers) / 2) then
begin current_vote := !current_vote + 1; myState.myRole <- Leader; 3 end
else current_vote := !current_vote + 1; request_vote_loop
| TriggerEvent ->
arr := Array.make (Array.length (!peers)) (Domain.spawn (fun i ->
if (!(Array.get votePeers i)) then 0
else
let conn = Array.get !peers i in
Lwt_main.run
(let+ resp = call_server conn
(RequestVoteArg({
candidateNumber = myState.myPersistentState.id;
term = myState.myPersistentState.currentTerm;
lastlogIndex = (Array.get myState.myPersistentState.logs ((Array.length myState.myPersistentState.logs) - 1)).index;
lastlogTerm = (Array.get myState.myPersistentState.logs ((Array.length myState.myPersistentState.logs) - 1)).term
})) in (match resp with
| Error(s) -> Chan.send votes (0, i); Printf.printf "requestVote: connection failed: %s" s; 1
| Ok(repl, s) ->
(match repl with
| RequestVoteRet(repl) ->
if repl.voteGranted then begin Chan.send votes (1, i); Printf.printf "requestVote: status: %s, currentVote: %d" s !current_vote; 2 end
else
if not (repl.term = (-1l)) then begin myState.myPersistentState.currentTerm <- repl.term; Chan.send votes (0, i);
Printf.printf "requestVote failed because of term: status: %s, currentVote: %d" s !current_vote; 3 end
else Chan.send votes (0, i); Printf.printf "requestVote failed: status: %s" s; 4
| _ -> failwith "Should not reach here" ))))); request_vote_loop
| _ -> failwith "Should not reach here"
in print_endline (Int.to_string request_vote_loop)
But there's an error that "This kind of expression is not allowed as right-hand side of `let rec'", it said my function is of type unit. I don't know what happened...
Thanks in advance.
Your definition starts like this:
let rec request_vote_loop: int = ...
This doesn't define a function, it defines a simple value of type int. The reason is that there are no parameters given.
There's too much code to process (and furthermore it's not self-contained). But I suspect you want to define a function that doesn't take any parameters. The way to do this is to pass () (known as unit) as the parameter:
let rec request_vote_loop () : int = ...
The recursive calls look like this:
request_vote_loop ()
The final call looks like this:
Int.to_string (request_vote_loop ())

OCaml Reading from file and perform some validation

can you help me out, i made this program to get an output from some .txt file like this :
john:3:uk
paul:18:us
#load "str.cma"
let f_test = "/home/test.txt" ;;
(*
Recursive Reading function
*)
let read_lines f_test : string list =
if Sys.file_exists (f_test) then
begin
let ic = open_in f_test in
try
let try_read () =
try Some (input_line ic) with End_of_file -> None in
let rec loop acc = match try_read () with
| Some s -> loop (s :: acc)
| None -> close_in_noerr ic; List.rev acc in
loop []
with e ->
close_in_noerr ic;
[]
end
else
[]
;;
(*Using Records*)
type user =
{
name : string;
age : int;
country : string;
};;
(*
Function to separated info in list
*)
let rec splitinfo ?(sep=":") l = match l with
| [] -> []
| x::xs -> (Str.split (Str.regexp ":") x)::splitinfo xs;;
(*
Function to get users position
*)
let get_user l:user =
let age = int_of_string (List.nth l 1) in
let user_name = List.nth l 0 in
{
name = user_name;
age = age ;
country = List.nth l 2;
};;
(*
Function to check some parameter is valid
*)
let par1 u: int =
if (u.age = 3) then
1
else
0;;
(*
Reporting function
*)
let report_statistics list_users =
let child = ref 0 in
let teenager = ref 0 in
let adult = ref 0 in print_string (" ----- -- Stats -- ----- \n" ) ;
List.iter (
fun user_l -> (
match user_l with
| [] -> print_string("> no user <\n")
| _ ->
let user = get_user user_l in
if (par1 user = 1) then (
print_string (" "^ user.name ^" --> Child \n" ) ;
child := !child + 1;
)
else
print_string (" "^ user.name ^" --> Other \n" );
)
) list_users;
print_string ("------- List ---- ");
print_newline();
print_string ("Child " );
print_int(!child);
print_newline();
print_string ("Teenager ") ;
print_int(!teenager);
print_newline();
print_string ("Adult ");
print_int(!adult);
print_newline();
;;
The program compile but doesn't output any result ...
What am i missing ?
I kept the function to check parameters simple so i can understand it better but can't figure it out why it isn't outputing any result
Can you help me out here ?
Thanks in advance :)
The code as given defines some functions such as read_lines and report_statistics. But there are no calls to these functions.
If there is no other OCaml source involved, this is probably your problem. You need to call the functions.
It is fairly customary to have a "main" function that does the work of an OCaml program, and then (this is key) you have to actually call the main function:
let main () =
(* Call the functions that do the work of the program *)
let () = main ()
I have many times forgotten this last line and then nothing happens when I run the program.

Ocaml loop for string printf

let string s = "";;
let string s =
for i = 0 to 5 do
Printf.sprintf "%s" s
done;;
I want to printf with string type in loop (ex-string "hi" -> "hihihihihi")
When I use for, It makes string to unit and It doesnt' work.
How to loop print with string type?
There are few ways to do it with a buffer or format, with the right complexity.
First, the more imperative version is probably with a buffer
let string_repeat_n_time s n =
let b = Buffer.create (String.length s * n) in
for i = 1 to n do
Buffer.add_string b s
done;
Buffer.contents b
Buffer are made to handle efficiently repeated addition, so they are the right data structure.
A more functional version would be to use recursion with Format.fprintf (Format is essentially an improved version of Printf)
let string_n_times s n =
let rec repeat ppf n =
if n = 0 then Format.fprintf ppf "%!"
else
Format.fprintf ppf "%s%a" s repeat (n-1) in
Format.asprintf "%a" repeat n ;;
This code is using a Buffer under the hood thus the complexity is the same as before. If we make the buffer explicit, we can have an imperative code that is using the format printer
let string_n_times s n =
let b = Buffer.create (String.length s * n) in
let ppf = Format.formatter_of_buffer b in
for i = 1 to n do
Format.fprintf ppf "%s" s
done;
Format.fprintf ppf "%!" (* flush *);
Buffer.contents b
which can be useful if we are adding something more complex than a string to the buffer.
Something like this:
let string_n_times s n =
let str = ref "" in
for i = 1 to n do
str := !str ^ s
done; !str
let () = print_endline (string_n_times "hi" 5)
Is this what you are trying to accomplish?
let string_n_times s n =
for i = 0 to n do
Printf.printf "%s" s
done
let () = string_n_times "Hi" 5

How to convert strings to Num.num in OCaml?

I'm looking for a function that converts a string representation of a decimal number into a Num.num. There is a Num.num_of_string function but, sadly, it only works for valid integers.
I'm asking before reimplementing one such function.
You'll have to do it yourself, I think. I did it once, here's what I did :
let num_of_string =
let open Num in
let code_0 = Char.code '0' in
let num10 = Int 10 in
fun s ->
try num_of_string s
with Failure _ ->
let r = ref (Int 0) in
let pos_dot = ref (-1) in
String.iteri (fun i c ->
if c = '.' then pos_dot := String.length s - i
else
r := add_num (mult_num num10 !r) (num_of_int (Char.code c - code_0))
) s;
assert (!pos_dot <> -1);
div_num !r (power_num num10 (num_of_int !pos_dot))

Trying to get first word from character list

I have a character list [#"h", #"i", #" ", #"h", #"i"] which I want to get the first word from this (the first character sequence before each space).
I've written a function which gives me this warning:
stdIn:13.1-13.42 Warning: type vars not generalized because of value
restriction are instantiated to dummy types (X1,X2,...)
Here is my code:
fun next [] = ([], [])
| next (hd::tl) = if(not(ord(hd) >= 97 andalso ord(hd) <= 122)) then ([], (hd::tl))
else
let
fun getword [] = [] | getword (hd::tl) = if(ord(hd) >= 97 andalso ord(hd) <= 122) then [hd]#getword tl else [];
in
next (getword (hd::tl))
end;
EDIT:
Expected input and output
next [#"h", #"i", #" ", #"h", #"i"] => ([#"h", #"i"], [#" ", #"h", #"i"])
Can anybody help me with this solution? Thanks!
This functionality already exists within the standard library:
val nexts = String.tokens Char.isSpace
val nexts_test = nexts "hi hi hi" = ["hi", "hi", "hi"]
But if you were to build such a function anyway, it seems that you return ([], []) sometimes and a single list at other times. Normally in a recursive function, you can build the result by doing e.g. c :: recursive_f cs, but this is assuming your function returns a single list. If, instead, it returns a tuple, you suddenly have to unpack this tuple using e.g. pattern matching in a let-expression:
let val (x, y) = recursive_f cs
in (c :: x, y + ...) end
Or you could use an extra argument inside a helper function (since the extra argument would change the type of the function) to store the word you're extracting, instead. A consequence of doing that is that you end up with the word in reverse and have to reverse it back when you're done recursing.
fun isLegal c = ord c >= 97 andalso ord c <= 122 (* Only lowercase ASCII letters *)
(* But why not use one of the following:
fun isLegal c = Char.isAlpha c
fun isLegal c = not (Char.isSpace c) *)
fun next input =
let fun extract (c::cs) word =
if isLegal c
then extract cs (c::word)
else (rev word, c::cs)
| extract [] word = (rev word, [])
in extract input [] end
val next_test_1 =
let val (w, r) = next (explode "hello world")
in (implode w, implode r) = ("hello", " world")
end
val next_test_2 = next [] = ([], [])