You’re being a little too liberal with semicolons. Only 2 out of 7 in your example are actually necessary:
if true then begin
if false then begin
print_int 2;
print_newline ()
end
else begin
print_int 3;
print_newline ()
end
end
In OCaml, we typically use ; only when sequencing two expressions when the first one has type unit. In your case, we need them to split the adjacent print_* applications. We could also do this with let bindings (see explanation here), but it’s a nice bit of “syntax sugar”.
The OCaml parser will – unfortunately, in my opinion – also accept a trailing ; after an expression, but this is not necessary or generally encouraged. In your case, the semicolon inside end; else begin is problematic because it does not come after an expression: it’s in the middle of an if ... then ... else expression where the parser does not expect to find one.