Search code examples
ocaml

How to convert non-built-in types to string in Ocaml?


I'm trying to write a module that convert something like x:int = if true then 3 else 5 to a string

Here's the code I have so far

module Ast =
struct

type typ = Bool | Int
type var = A | B | C | D | E | F
type exp = Const of int * typ
           | App of string * exp list
           | If of exp * exp * exp
           | And of exp * exp
           | Or of exp * exp
       | Id of var * typ * exp


let rec toString (t) =
    let formatDec1(va,ty,e) = ???
    match t with
    Const(n, _) -> print_int n
      | App(id, [e1; e2]) -> formatter(" " ^ id ^ " ", e1, e2)
      | App(id, [e1]) -> formatter(" " ^ id ^ " ", e1, Const(0, Int))
      | App(id, _) -> formatter(" " ^ id ^ " ", Const(0, Int), Const(0, Int))
      | If(e1, e2, e3) -> formatIf(e1, e2, e3)
      | And(e1, e2) -> formatter(" && ", e1, e2)
      | Or(e1, e2) -> formatter(" || ", e1, e2)
      | Id(va,ty,e) -> formatDecl(va,ty,e)
end

I'm still a beginner in OCaml and couldn't find anything about converting to a string online. Thanks!


Solution

  • You want to pretty print symbolic expressions, with the ml syntax. There are libraries to deal with so called sexp, but I never used them, I cannot comment on their uses, and it's probably not what you want, as it would sidestep the purpose of the exercise.

    I think you're somewhat close with what you already have, but there are some syntactic errors in your code. I'll give you a bunch of advices to help you close the gap.

    First, regarding the OCaml syntax:

    Recall that you don't need to use parentheses for your function argument. The general syntax for a function definition is:

    let fun_name arg1 arg2 arg3 = function_body
    

    if you write

    let fun_name (arg1,arg2,arg3) = ...
    

    you are actually defining a function taking 1 argument, which is itself a tuple of 3 values.

    Within an expression, you can define local values using the let ... in construct, just like when you define top level values (like I did above). If these value definitions aren't inter-dependant, you can use the and keyword between several value definitions.

    For instance,

    let x = 1 
    and y = "a" in
    ...
    

    would define 2 values x and y which may be used in the subsequent code.

    In your code

    The types you have give a little symbolic expression language, with which you can define expressions like the one you give in example:

    "x:int = if true then 3 else 5" would be:

    Id("x", 
       Int, 
       (If (Const (1, Bool), 
            Const (3, Int), 
            Const (5, Int))))
    

    Indeed, you need to go through that value recursively, and combine the returned strings into a bigger string. Alternatively, you could use a string list, and do the string concatenation at the end. Looking at that example, you probably can see how this would work:

    "x" ^ (":" ^ "int") ^ "=" ^ ("if" ^ ("true") ^ "then" ^ ("3") ^ ("5"))
    

    You can see the different sub expressions patterns in the above. I used parentheses to denote them.

    In your toString function, there are 3 undefined values:

    • formatter (a function taking a 3-tuple of type string * exp * exp)
    • formatDec1 (another function taking a 3-tuple of type exp * exp * exp)
    • formatIf (same as formatDec1)

    You used the keyword rec in your toString function definition, indicating that the function is recursive. In my opinion, you don't need the 3 functions above (are they a part of your initial assignment, or did you define them by yourself?), but just the toString function and string concatenation there.

    the App arm of your exp type is very general: you can define application of any number of arguments, so your string conversion code should take that possibility in account. You could use some list function (fold_left comes to mind) to take care of it.