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 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:
- The directory containing the Caml executables (c:\caml386\bin) must be included in the executables path.
- The new environment variable camllib has to be set to the directory with the Caml library files (c:\caml386\lib).
- The new environment variable go32tmp has to be set to c:\temp (or whatever is the temporary files directory on your FreeDOS machine).
- The new environment variable go32 has to be set to "driver", followed by the path to the graphics driver that you want to use (this will be needed for working in graphics display mode; cf. below).
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 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 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>
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).
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.
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 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.
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.
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.
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.
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.
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.
Note: To run these programs on a DOS machine, where Caml Light is not installed, you'll have
- to copy the runtime system (camlrun.exe) to that machine,
- to copy the graphics driver (vga.grd) to that machine,
- to set the environment variable go32 to point to the graphics driver;
- 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.
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.
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.
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 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.