Is there example code for parsing function call expression from string?

I have a program which allows user to enter some function alike order such as : “scaled(side=buy, amount=100, orderCount=30)”, and the string will be parsed and map to a similar function call, is there anyway of doing it? I found there is a lexer and parser in Ocaml and read articles about simple math interpreter, but still not able to figure out a way to execute a function call. Or, is there a simple way of doing this without using lexer and parser?

To extend the idea of creating a lexer and parser for simple math interpreters, you want to add function application to your parse tree, having some structure such as:

module StringMap = Map.Make(String)
type t =
| Ident of string
| Int of int
| App of string * t StringMap.t

With your example scaled(side=buy, amount=100, orderCount=30) being parsed as

App("scaled", StringMap.of_list [
  "side", Ident "buy";
  "amount", Int 100;
  "orderCount", Int 30
])

(things are of course much more fun if function application operates on an expression rather than just named functions)

The evaluator is then something along the lines of:

(* Real logic; in OCaml *)

type side = Buy | Sell
let scaled side amount orderCount = (* Your logic here, perhaps returning a number *)

(* Interpreter for user-entered function calls *)
let fail () =
  failwith "This is not the kind of error handling you were looking for"

let int_of_t = function
| Int i -> i
| _ -> fail ()

let rec evaluate = function
| App("scaled", params) ->
    let kind = match StringMap.find "side" params with
    | Ident "buy" -> Buy
    | Ident "sell" -> Sell
    | _ -> fail ()
    in
    let amount = int_of_t (StringMap.find "amount" params) in
    let orderCount = int_of_t (StringMap.find "orderCount" params) in
    Int (scaled kind amount orderCount)
| _ ->
    fail ()

with obviously some actual error handling (perhaps even some type checking…) and, especially if there are lots of functions, some meta-programming of the evaluator. In particular, if you have a vast number of such functions, that’s the kind of thing where ppx can be very useful, generating the interpreter automatically from the function code itself.

Whether that counts as simple is subjective :slightly_smiling_face: Many years ago, I went through a phase where every system seemed to end up with at least a type checker and usually with parametric polymorphism… these days, I find it’s quicker turn the problem around and embed whatever I’m doing directly in OCaml instead :grin: Which does sort of answer the second part of your question - there isn’t a magic/simple way of doing this without parsing the string, indeed, other than using OCaml (or another embeddable language - it’s years since I actually used it, but I’ve had systems in the past in OCaml which included SpiderMonkey and permitted user-scripting via JavaScript, but where all the logic being called was OCaml functions provided using SpiderMonkey’s FFI).

Thanks! @dra27, I will make a tiny program to test the idea!

Here’s another approach that you may find interesting, which consists of using the Angstrom parser combinator library.

I’m pretty noobish at this though. For simplicity, you can exclusively use the >>= operator.

Here’s how you can think of it:

I describe the fragments that I’m parsing, to then either use or ignore what comes out of this description via >>=

Each of those descriptions has a my_thing Angstrom.t type

open Printf
module A = Angstrom

let ( >>= ) = A.( >>= )

type scaled =
  { side: string
  ; amount: int
  ; order_count: int
  }

type operation =
  | NoOp
  | Scaled of { side: string
              ; amount: int
              ; order_count: int
              }
  [@@warning "-37"]

let dbl_quoted : string A.t =
  A.char '"'                        >>= fun _ ->
  A.take_while1 (fun c -> c <> '"') >>= fun str ->
  A.char '"'                        >>= fun _ ->
  A.return str

let digit = A.take_while1 (function
    | '0' .. '9' -> true
    | _ -> false)

let scaled_parser : scaled A.t =
  A.string "side="       >>= fun _ ->
  dbl_quoted             >>= fun side ->
  A.char ','             >>= fun _ ->
  A.string "amount="     >>= fun _ ->
  digit                  >>= fun amount ->
  A.char ','             >>= fun _ ->
  A.string "orderCount=" >>= fun _ ->
  digit                  >>= fun order_count ->
  let amount = int_of_string amount in
  let order_count = int_of_string order_count in
  A.return {side; amount; order_count}

let operation_parser : operation A.t =
  A.string "scaled"  >>= fun _ ->
  A.char '('         >>= fun _ ->
  scaled_parser      >>= fun params ->
  A.char ')'         >>= fun _ ->
  A.return (Scaled { side = params.side
                   ; amount = params.amount
                   ; order_count = params.order_count
                   }
           )

let eval = function
  | NoOp -> printf "NoOp..."
  | Scaled {side; amount; order_count} ->
      printf
        "Ready to call internal scale fn with: (%s, %d, %d)\n"
        side amount order_count

let () =
let input = {|scaled(side="buy",amount=100,orderCount=30)|} in
let parsed = A.parse_string ~consume:Prefix
               operation_parser input
in
match parsed with
| Ok operation ->
    eval operation
| Error err ->
    prerr_endline "I failed to parse the input :(";
    prerr_endline "Maybe use `A.peek` to debug your parser.";
    prerr_endline "---";
    prerr_endline err
$ dune exec --display=quiet ./bin/main.exe 
Ready to call internal scale fn with: (buy, 100, 30)

I like parser combinators. To me, they are simpler than implementing lexing and parsing separately. I would consider adding a bunch of tests around them, to prevent some hair pulling if something goes wrong much later as you refactor :slight_smile:

Of course there are examples here but I’m afraid that those will look overwhelming (especially something involving lexers and parsers).

So Angstrom is probably a good suggestion. But if you want lexers and and parsers have a look Pl Zoo.

Or you can try the Astring where the parse_env example does something very close to what you are trying to achieve.

The simplest approach (if you can get away with it) is the split and trim aproach. Split the string at ( to get the function name, split at the ) to get the key-value pairs, split on , then split on =

Also just for fun I’m adding a small (untested) Menhir parser which shold parse a list of such calls separated by ;

%{
type name = string
type value = string
type arg = name * value

type fn = name * arg list
%}

%token <string> VAR
%token LPAREN   "("
%token RPAREN   ")"
%token COMMA    ","
%token EQUAL    "="
%token SEMI     ";"
%token EOF

%start calls
%type <fn list> calls

%%

calls:
  | xs = separated_list(";", call) EOF              { xs }

call:
  | x = VAR "(" args = separated_list("," arg) ")"  { x, args }

arg:
  | n = VAR "=" v = VAR                             { n, v }

%%

Writing the lexer is left as an exersize

Oh, I found this solution is pretty straight forward and fit into my brain. Love it!