Computing: DOS, OS/2 & Windows Programming

Exotic programming languages for DOS - Caml Light and OCaml.

"Caml (Categorical Abstract Machine Language is a multi-paradigm, general-purpose, high-level, functional programming language which is a dialect of the ML programming language family. Caml was developed in France at French Institute for Research in Computer Science and Automation (INRIA) and École normale supérieure (Paris) (ENS). Caml is statically typed, strictly evaluated, and uses automatic memory management. Caml Light is the successor of the original Caml implementation of 1987. OCaml, the main descendant of Caml, adds many features to the language, including an object-oriented programming layer." For further details, cf. the Caml and OCaml articles in Wikipedia.

Installing and running Caml Light.

This part of the tutorial is about the installation of Caml Light for DOS 0.7 on FreeDOS 1.3 RC5; it should apply to MS-DOS 6.22 and other DOS platforms, too.

Caml Light for DOS may be downloaded from caml.inria.fr. The download file cl7pcbin.zip contains the file structure, that I unzipped on my Windows 10. Too big in size for copying the files to a floppy, I created a CDROM ISO to transfer the archive content to my FreeDOS machine. With the drive letter F: referring to my CD-drive and the archive content being stored in f:\cl7, I executed the following commands to "install" Caml Light to C:\caml386 (you should use this directory name):
    mkdir c:\caml386
    f:
    cd cl7
    xcopy *.* c:\caml386 /E /I /H /Q

The screenshot below shows, how I copied the files on my machine.

Caml Light on Free-DOS: Copying the installation files to the harddisk

Caml Light for DOS 0.7 produces 32-bit code, using the go32 extender. You must not worry about this; everything should work out of the box. Provided that the following environment variables are correctly set:

There are several graphics drivers included with Caml Light; you can find them in the c:\caml386\dev directory. The SuperVGA driver vesa_s3.grd would be a nice choice, providing the possibility of a screen resolution of 800×600, and even 1024×768, instead of the standard 640×480 pixels. Unfortunately, this driver is not supported by VMware Workstation. I did not try out the various monitor-specific drivers included; I actually use standard VGA (vga.grd).

I included the configuration settings for Caml Light in a custom batch file (I called it "camlinit.bat"), that I placed in a directory included in my executables path. Here is the script content (note that the environment variable %path0% is specific to my system and set equal to %path% in fdauto.bat):
    @echo off
    set path=%path0%;c:\caml386\bin
    set camllib=c:\caml386\lib
    set go32tmp=c:\temp
    set go32=driver c:\caml386\dev\vga.grd

You can check if the system finds your files by running the commands
    camlinit
    camlc -v

Caml Light on Free-DOS: Display of the runtime, compiler and linker version

Caml Light comes in two flavors: a classical, interactive, toplevel-based system and a standalone, batch-oriented compiler that produces standalone programs, in the spirit of the batch C compilers. The former is good for learning the language and testing programs. The latter integrates more smoothly within programming environments. The generated programs are quite small, and can be used as standalone programs.

The Caml Light toplevel-based system.

To start the Caml Light toplevel-based system, type
    camlinit
    caml

In the following paragraphs, I will give some details about the Caml Light programming language. The screenshots show the execution of the commands in caml. Note that commands must be terminated with two semicolons (;;). To quit caml, use the command quit();;.

Caml variables are really special, compared to those of common programming languages: Variables are not declared as being of a given data type, but their data type is determined by the value or expression that they are assigned to. This typing is very strict, not allowing any mixing of different data types. For example, you cannot assign an integer to a real variable, as you can in most languages. From what is said before, we can deduce that there must be different operators for different data types. The usual operators + - = < etc may be used for integers only. If we want to work with real numbers, these operators must be suffixed by a dot: +. -. =. <. etc. Also, real constants must include a decimal part.

The screenshot below shows some examples of simple arithmetic expressions. In the second example, using the integer operator "-", the float expression "0.5" generates an error. In the third example it's the contrary: Using the float operator "-.", the integer expression 2 generates the error. There are actually two ways to correctly write the subtraction "2 - 0.5" in Caml:
    2.0 -. 0.5
    float_of_int 2 -. 0.5

Caml Light on Free-DOS: caml.exe - Floating point number arithmetic

Caml Light includes real string variables (as a difference with arrays of characters in C, for examples). The strict Caml typing system makes it necessary to distinguish between character and string literals. Thus, you have to use back quotes (not single quotes, as used in English!) for characters, and double quotes for strings. To concatenate two strings, the concatenation operator ^ is used. To compare two strings, the string compare functions eq_string neq_string, lt_string, le_string, gt_string, ge_string are available. There are lots of other build-in string functions. In the examples on the screenshot, you can see how to get the substring and the length of a string variable. These examples also show how variable assignment is done in Caml Light. General form:
    let <variable> = <expression>

Caml Light on Free-DOS: caml.exe - Build-in string functions

The screenshot below shows function definitions using the function keyword. General form:
    function x -> f(x)
where f(x) is some expression involving x (you may use any other variable instead, of course).

The first three examples show how functions may be used "directly" (without assigning them to a variable) in Caml Light: just enclose the function definition between brackets. Note the order of evaluation of the terms of an expression (and the usage of parentheses to change it) in examples 2 and 3. The other examples show how a function may be assigned to a variable, and then be used the same way as are build-in functions. Note that, when defining a function this way, caml displays the function's data types (all integers in the examples).

Caml Light on Free-DOS: caml.exe - User defined functions [1]

The screenshot below shows two alternate ways for function definitions. General form:
    let <function-name> x = f(x)
    let <function-name>(x) = f(x)
where f(x) is some expression involving x (you may use any other variable instead, of course). The first of these forms may appear somewhat irritating; in fact, it's just a shorter form of the second one, that actually corresponds to the way that we define functions in mathematics.

Caml Light on Free-DOS: caml.exe - User defined functions [2]

The last example above shows the definition of local variables in Caml Light, using the keyword in. The variable "y" (with value 2*3=6) is local to the expression "square y", thus the statement let y = 2*3 in square y;; returns the square of 6 = 36. However, if after this statement, we enter the statement square y;; we get the error message The value identifier is unbound, which means that the variable is not defined (here, at the top-level, where the statement is executed).

As a further example, lets write a custom function to calculate the absolute value of a floating point number. It could look like this:
    let abs(x) = if x >. 0.0 then x else -. x;;

The screenshot shows the usage of this function with different argument values. With "5", the result is an error, as 5 is an integer. With "-5.0", the result is an error because of the integer - operator (I suppose?). With "abs -. 5.0" the result is a somewhat irritating error message; in fact, we have to include "-. 5.0" within parentheses.

Caml Light on Free-DOS: caml.exe - User defined functions [3]

Caml Light includes several other data types like boolean, arrays, records, vectors, lists, and streams (these latter ones being a Caml Light extension to the ML standard). It includes a huge number of build-in functions and comes with several ready-to-use libraries. A further description of the Caml Light language is outside the scope of this tutorial. If you want to learn this language and use it on DOS, the book The Caml Light system release 0.74 - Documentation and user's manual by Xavier Leroy (1997) is what you'll need. You can find a copy in PDF format on the Internet. Another book, that you can find on the Internet, is Functional Programming Using Caml Light by Michel Mauny (1995).

If you wonder what the classic "Hello World" looks like in Caml Light, here is the code of my hello.ml:
    print_string "Hello World!";
    print_newline()
    ;;

We can run this program in caml by loading the file content at the system's top-level using the command include. This is nothing else than interpreting the statements in the file, just as if they would have been entered from standard input (the keyboard). The screenshot shows the result.

Caml Light on Free-DOS: caml.exe - 'Hello World' run from an ML source file

The Caml Light compiler.

The Caml Light compiler is called camlc and the way it works is somewhat complicated. In fact, there are "normal" source files (file extension .ml) and interface source files (file extension .mli) as input and an object file (file extension .zo) and a compiled interface file (file extension .zi) as output. The .ml files are ML modules (that contain the program logic, define private data types, etc); the .mli files are module interfaces (that declare exported global identifiers, define public data types, etc). For simple applications, there is no need to create a special interface. In this case, the compilation of <filename>.ml produces the files <filename>.zo (object code of the module) and <filename>.zi (corresponding to an interface that exports everything that is defined in the implementation of <filename>.ml).

On DOS, the compiler is invoked by the command
    camlc -o <executable-filename>.exe <source-filename.ml
in the case of our simple "Hello World" program:
    camlc -o hello.exe hello.ml
what creates the files hello.zo, hello.zi and hello.exe, as shown on the screenshot below.

Caml Light on Free-DOS: camlc.exe - Compiling and running a simple 'Hello World' program

The executable produced (hello.exe) may be run as such, as you can see on the screenshot. However, this file is not DOS executable code of hello.ml. If you try to run it on another DOS machine (I tried on my MS-DOS 6.22), the program will not work (I got the output: C). Why is this? The Caml Light compiler actually produces bytecode, that is intended to be executed by a virtual machine (program camlrun.exe). However, on DOS, if the name of the compiler output file (more exactly the linker output file, because the module, the interface, and possibly additional libraries are linked together in order to create a standalone executable) is specified with the .exe file extension, camlc.exe creates a file that is composed of two parts: a small DOS executable code part, that runs the Caml Light runtime system (camlrun.exe), that actually executes the program bytecode, included in the second part of the file. Thus, if we want to run hello.exe on our MS-DOS 6.22 machine, we'll have to copy camlrun.exe to that machine.

The program colwheel.ml, that you can find in the examples\colwheel directory of your Caml Light installation, compiles with a series of warnings, but well creates the executable colwheel.exe. The screenshot below shows the output of the program.

Caml Light on Free-DOS: Running the program colwheel.exe (obtained by compiling colwheel.ml)

The compilation of the program spir.ml, that you can find in the examples\spirals directory of your Caml Light installation, also creates a working executable, however with an "unpretty" display, due to the relatively poor screen resolution provided by vga.grd.

Caml Light on Free-DOS: Running the program spirals.exe (obtained by compiling spir.ml)

On the github page of AdrienC21, you can find several Caml fractal examples. The Mandelbrot and Julia programs compile correctly, but produce a disrupted output, with just "junk" in the first screen lines. Also, executing these programs will result in the DOS command line screen no more correctly functioning, and all you can do will be to use CTRL+ALT+DEL to reboot. The third fractals program, contained in the ZIP archive, does not compile, the floor function not being known by Caml Light 0.7.

I have modified the Mandelbrot and Julia programs in order to make them work on my FreeDOS machine. Here is the download link to the source code. Please, note that these programs are modifications of the original programs by AdrienC21, and thereby are distributed under the MIT license (cf. license file included with the download archive).

The program mandel.ml is a Caml Light implementation of the Mandelbrot Set.
    (* Mandelbrot Set in Caml Light for DOS *)
    (* Original program by AdrienC21; https://github.com/AdrienC21/fractals-caml *)
    (* Modifications for Caml Light for DOS 0.7 by allu, September 2024 *)
    (*Define complex operations*)

    type complexe == float*float;;
    let re ((x,y):complexe)=x;;
    let im ((x,y):complexe)=y;;
    let norm ((x,y):complexe)=sqrt(x *. x +. y *. y);;
    let mult_complexe ((a,b):complexe) ((c,d):complexe)=((a *. c -. b *. d,a *. d +. b *. c):complexe);;
    let add_complexe ((a,b):complexe) ((c,d):complexe)=((a +. c,b +. d):complexe);;
    let sub_complexe ((a,b):complexe) ((c,d):complexe)=((a -. c,b -. d):complexe);;
    let scal_complexe a ((c,d):complexe)=((a *. c,a *. d):complexe);;
    let carre_complexe ((a,b):complexe)=mult_complexe (a,b) (a,b);;
    let sqrt_complexe ((a,b):complexe)=let aux=sqrt(0.5 *. (a +. (norm (a,b)))) in
        ((aux,b /. (2. *. aux)):complexe);;
    let f z c=add_complexe (carre_complexe z) c;; (*recursive sequence*)
    let maxiter=1600;; (*accuracy*)
    (*sequence is bounded ?*)
    let rec defn z n c=match n with
        n when n>maxiter -> maxiter
        |n when (norm z) > 2. -> n
        |n -> (defn (f z c) (n+1) c);;
    let round x = if (x >= 0.) then int_of_float x
        else int_of_float ( x -. 1.0);;
    # open "graphics";;
    open_graph "";;
    (*Attribute a color to a specific coordinates given by the complex c*)
    let zone c= let n=(defn (0.,0.) 0 c) in match (n mod 8) with
        |0 -> black
        |1 -> red
        |2 -> green
        |3 -> blue
        |4 -> yellow
        |5 -> cyan
        |6 -> magenta
        |7 -> black;;
    let abso=float_of_int(size_x()) and ord=float_of_int(size_y());;
    let drawMandel (xmin,xmax,ymin,ymax)= let pasabs=((xmax -. xmin) /. abso) and pasord=((ymax -. ymin) /. ord) and c=ref ((xmin,ymin):complexe) in
    for i=1 to round(abso) do
        for j=1 to round(ord) do
            set_color (zone !c);
            plot i j;
            c:= add_complexe (!c) (pasabs,0.)
        done;
        c:= sub_complexe (!c) ((float_of_int(round(ord)) *. pasabs),0.);
        c:= add_complexe (!c) (0.,pasord)
    done;;
    try
        drawMandel (-.2.,2.,-.2.,1.);
        (* Loop until ESC or q is pressed... *)
        while true do
            let e = wait_next_event [Key_pressed] in
            if e.keypressed then begin
                match e.key with
                    `q` | `Q` | `\027` ->
                        raise Exit
            end
        done
        with Exit ->
            close_graph()
    ;;
    exit 0;;

The screenshot shows the program output. Hit the ESC key (or q) to terminate the program.

Caml Light on Free-DOS: Running a Caml Light Mandelbrot Set program

The program julia.ml is a Caml Light implementation of common Julia Sets.
    (* Julia Set in Caml Light for DOS *)
    (* Original program by AdrienC21; https://github.com/AdrienC21/fractals-caml *)
    (* Modifications for Caml Light for DOS 0.7 by allu, September 2024 *)

    #open "graphics";;
    open_graph "";;
    clear_graph ();;
    let diverge_julia a b cr ci n =
        let x = ref a in
            let y = ref b in
                let xtemp=ref 0. in
                    let ytemp=ref 0. in
                        let k = ref 0 in
                            while ((!x *. !x+. !y *. !y) < 4.)&&(!k<n) do
                                xtemp:=(!x)*. (!x)-. (!y)*. (!y)+. cr;
                                ytemp:=2. *. (!x) *.(!y)+. ci;
                                x:= !xtemp;
                                y:= !ytemp;
                                k:= !k+1;
                            done;
                            (!k);;
    let julia cr ci n d =
        clear_graph ();
        let topx=size_x() and topy=size_y() and k = ref 0 in
            for x=(-topx/2) to (topx/2) do
                for y=(-topy/2) to (topy/2) do
                    k:=((diverge_julia ((float_of_int x) /. d) ((float_of_int y) /. d) cr ci n)*10);
                    set_color (rgb (255-(!k)/2) (255-(!k)/2) (255-(!k)));
                    plot (x+(topx/2)) (y+(topy/2));
                done;
            done;;
    try
        julia (-0.3380) (-0.6230) 150 300.;
        while true do
            (* Loop until key pressed: 1 - 5: draw Julia Set; ESC or q: terminate program *)
            let e = wait_next_event [Key_pressed] in
            if e.keypressed then begin
                match e.key with
                    `q` | `Q` | `\027` -> raise Exit
                    |`1` -> julia (-0.181) (-0.667) 150 400.
                    |`2` -> julia (0.285) (0.01) 300 300.
                    |`3` -> julia (-0.7927) (0.1609) 500 300.
                    |`4` -> julia (0.32) (0.043) 150 300.
                    |`5` -> julia (-0.3380) (-0.6230) 150 300.
            end
        done
        with Exit ->
            close_graph()
    ;;
    exit 0;;

The program draws one Julia Set, then waits for a key to be pressed. Use the number keys 1 to 5 in order to display further Julia sets; use ESC (or q) to terminate the program. The screenshot shows two of the Julia Sets created.

Caml Light on Free-DOS: Running a Caml Light Julia Set program [1]
Caml Light on Free-DOS: Running a Caml Light Julia Set program [2]

Note: To run these programs on a DOS machine, where Caml Light is not installed, you'll have

  1. to copy the runtime system (camlrun.exe) to that machine,
  2. to copy the graphics driver (vga.grd) to that machine,
  3. to set the environment variable go32 to point to the graphics driver;
  4. maybe, you should also set the environment variable go32tmp (?).

Installing and running OCaml.

This part of the tutorial is about the installation of OCaml 1.0 for DOS on FreeDOS 1.3 RC5; it should apply to MS-DOS 6.22 and other DOS platforms, too.

OCaml 1.0 for DOS may be downloaded from caml.inria.fr. This version of OCaml is not an official release but a port of OCaml 1.0 to DOS, that we owe to a inria.fr user contribution. The download file is a self-extracting archive, that I transferred to my FreeDOS machine using a CDROM image. To install the software, copy the file ocaml-1.00-msdos.exe to C:\. Running it, will create the installation directory c:\ocaml and extract all files to there.

The screenshot shows the installation directory with the files and folders created from the self-extracting archive.

OCaml on Free-DOS: OCaml installation directory

As before for Caml Light, I created a custom batch file (I called it ocmlinit.bat), containing the commands to set up the OCaml environment. Here is its content (%path0% is an environment variable specific to my system and set equal to %path% in fdauto.bat):
    @echo off
    set path=%path0%;c:\ocaml\bin
    set camllib=c:\ocaml\lib
    set dos16m=:4M

As Caml Light, OCaml comes in two flavors: a classical, interactive, toplevel-based system (ocaml.exe), and a standalone, batch-oriented compiler (ocamlc.exe) that produces standalone programs, in the spirit of the batch C compilers. The former is good for learning the language and testing programs. The latter integrates more smoothly within programming environments. The generated programs are quite small, and can be used as standalone programs.

The screenshot below shows the execution of some statements in ocaml.

OCaml on Free-DOS - ocaml.exe: Executing some OCaml statements

The last line shows the function used on Caml Light to quit the toplevel-based system. It seems not to work with OCaml. In fact, I did not find any way to properly quit ocaml (?). Performing CTRL+C twice results in an error of the memory manager JemmEx; hitting the ESC key then terminates ocaml.

OCaml on Free-DOS - ocaml.exe: Quitting the system with CTRL+C

Compilation is done in a similar way as for Caml Light, with creation of an object code file (file extension .cmo), a compiled interface file (file extension .cmi), and an executable; specifying the file extension .exe for the compilation (linkage) output file, will create a program that may be run on DOS, provided that the OCaml runtime is present (cf. Caml Light for explanations). Here is the command to compile our "Hello World" program from above:
    ocamlc -o hello.exe hello.ml

The screenshot shows the compilation, the files created and the successful execution of hello.exe.

OCaml on Free-DOS - ocamlc.exe: Compiling and running a simple 'Hello World' program

OCaml 1.0 seems to run correctly on FreeDOS. No idea, however, how far you can use it to create working OCaml programs. In fact, I did not succeed to compile any of the program samples, that I tried out (I admit that I did not really try hard...), neither the examples included with Caml Light, nor OCaml code that I found on the Internet. The problem is obvious: OCaml 1.0 is a really old version, thus it's not surprising that the code that you find on the Internet produces compilation errors. In order to seriously use OCaml 1.0 for DOS, a reference manual of this version of the language would be needed...


If you find this text helpful, please, support me and this website by signing my guestbook.