eweb1.0 outdir=. outfiles=prog1.erl,prog2.erl,prog3.erl,prog4.erl,prog5.erl,prog6.erl,load_and_go.erl,Makefile,+mkErl.super,+mkErl.ruddles \raggedright \documentstyle[]{report} \begin{document} \begin{center} \vspace{12.0cm} {\Large Erlang Lite\\ or\\ Rolling your own Erlang System} \vspace{1.5cm} # cat mkErl.super | mail thomasl@csd.uu.se Joe Armstrong\\ Computer Science Laboratory\\ Ericsson Telecom AB\\ \verb+joe@cslab.ericsson.se+ \vspace{1.5cm} Literate Erlang Report CS-JA-97-002\\ Started April 1997 \vspace{2.5cm} {\bf Abstract} \end{center} This bookette describes how to "roll your own Erlang system". The standard Erlang development system features a lot of advanced features, automatic code loading, error loggers, a file system etc. It is difficult reading the Erlang book or the documentation which comes with the standard system, to understand how to build an embedded system or how to make a stand-alone system suitable for a specific application. A lot of functionality is built into the standard system. The casual user does not have to worry about {\sl how\/} the error handler and code loader etc. got there in the first place. The more serious user may well be concerned with such problems. At which juncture they will probably discover that exactly how the system bootstraps itself is not well described. \vspace{\fill} \pagebreak \section*{Introduction} This bookette\footnote{Little book} has been written to answer questions like the following: \begin{itemize} \item How do you make a minimal stand-alone Erlang system?. \item How do you write a minimal "hello world program?" \item How does code loading and error handling work? \item How do I interface to standard IO? \item How can I run Erlang in a a unix pipe? \item How can I interface to the unix file system? \end{itemize} The bookette is not just a mass of text. It is also a {\sl program}. You can execute all or part of the bookette. To extract files from the bookette read the section on page \pageref{lit}. \section*{Hello World} Hello world is our first program. We want to write "Hello world" on Standard output. There is a {\sl little\/} semantic problem here. IO in Erlang is written {\sl in\/} Erlang. So how can we do IO from an Erlang program {\sl before\/} the IO code which does the IO has been loaded? The answer is that we {\sl cheat\/} and use the primitive \verb+erlang:display+ which prints its arguments directly on stdout. The reason that won't find much about \verb+erlang:display+ the manuals is that it {\sl totally\/} bypasses all the normal Erlang IO routines. and we don't want you to cheat. Knowing this we can write our first program: <> := -module(prog1). -export([start/1]). start(_) -> erlang:display('hello, world'). >> This writes the magic and hollowed words on stdout using \verb+erlang:display+. We can compile are run this program as follows: \begin{verbatim} unix> mkErl -o erlProg1 -m prog1 -f start -p prog1 Compiling prog1.erl Making jams.c Compiling jams.c Linking Made erlProg1 unix> erlProg1 'hello, world' Finished unix> \end{verbatim} \verb+mkErl+ is a unix command which makes a stand-alone Erlang program. It is evoked with the command: \begin{verbatim} mkErl -o Name -m Mod -f Func -p Prog1 Prog2 Prog3 ... \end{verbatim} The meanings of the command line flags are as follows: \begin{itemize} \item {\bf -o Name} define the name of the stand-alone executable to be {\sl name}. \item {\bf -m Mod} define the startup module to be {\sl Mod}. \item {\bf -f Func} define the startup function to be {\sl Func}. \item {\bf -p Prog1 Prog2, ..} a list of erlang modules which are to be compiled. \end{itemize} When the executable program is started with the command \begin{verbatim} unix> ProgName Arg1 Arg2 .. Argn \end{verbatim} The system will evaluate the Erlang expression: \begin{verbatim} Mod:Func([Arg1, Arg2,..Argn]). \end{verbatim} When this command terminates the system will stop. Note that \verb+Mod+ must be one of \verb+Prog1, ..+. \verb+erlProg1+ is a relatively small program (220KBytes on my Linux 1.2.13 system) and execution of the above program fairly quick 130ms (first time) - thereafter 7ms (133 Mhz Pentium with 32 MBytes memory). Pretty comparable with standard unix tolls like "awk" etc. \section*{Accessing Command Line Arguments} How can we assess command line arguments? With a little modification to our original program we can access the command line arguments: <> := -module(prog2). -export([go/1]). go(Args) -> erlang:display({prog2,called,with,Args}). >> To make a stand-alone executable of this we evaluate the expression: \begin{verbatim} unix> mkErl -o erlProg2 -m prog2 -f go -p prog2 Compiling prog2.erl Making jams.c Compiling jams.c Linking Made erlProg2 unix> erlProg2 a b c d {prog2,called,with,[a,b,c,d]} Finished unix> erlProg2 1 2 3 "abc" a b c d {prog2,called,with,['1','2','3',abc,a,b,c,d]} Finished \end{verbatim} \section*{Accessing stdin and stdout} Out first program opens a {\sl Port\/} which is connected to stdio. <> := -module(prog3). -export([start/1, server/0]). start(_) -> spawn(?MODULE, server, []). server() -> process_flag(trap_exit, true), Port = open_port({fd,0,1},[]), loop(Port). loop(Port) -> receive Any -> erlang:display({got, Any}) end, loop(Port). >> Compile and run: \begin{verbatim} unix> mkErl -o erlProg3 -m prog3 -f start -p prog3 .. unix> erlProg3 abc {got,{<0,1>,{data,[97,98,99,10]}}} 123 {got,{<0,1>,{data,[49,50,51,10]}}} ^C \end{verbatim} Our next example reads an integer from standard input, doubles it and write the answer to standard output. <> := -module(prog4). -export([start/1, server/0]). start(_) -> spawn(?MODULE, server, []). server() -> process_flag(trap_exit, true), Port = open_port({fd,0,1},[]), loop(Port). loop(Port) -> Port ! {self(), {command, "\nenter a number > "}}, receive {Port, {data, L}} -> case catch list_to_integer(first(L)) of {'EXIT', _} -> erlang:display({not_an_integer, L}); Int -> Twice = 2 * Int, Port ! {self(), {command, integer_to_list(Twice)}}, loop(Port) end end. first([_]) -> []; first([H|T]) -> [H|first(T)]. >> Which works as follows: \begin{verbatim} unix> mkErl.super -o erlProg4 -m prog4 -f start -p prog4 ... unix> erlProg4 enter a number > 123 246 enter a number > 345 690 enter a number > 1234123412341234 2468246824682468 enter a number > abc {not_an_integer,[97,98,99,10]} Finished \end{verbatim} Note the {\sl bignum\/} arithmetic. \section*{Reading files} To read files we need to write a C program which runs {\sl outside\/} the Erlang kernel and which talks to the Erlang run-time system through a {\sl Port}. The program \verb+prog5.erl+ uses the linked in driver \verb+efile+ to provide access to the native file system. We will assume that we launch \verb+prog5+ from the command line with one argument, and that this argument is a file name. Our program starts a filer by evaluating \verb+start_filer+ and then requests \verb+FileName+ the filer reads the file and returns a binary representing the file. To display this we convert it to an atom and call \verb+erlang:display+ (Note: this method will only work on small files - since there is an internal limit on the length of file names - this program is to demonstrates {\sl reading\/} files and nothing else, so we will ignore this problem). <> := -module(prog5). -export([start/1]). start([FileName]) -> Str = atom_to_list(FileName), Val = read_file(Str), erlang:display({read,Val}). <> >> \verb+read_file+ sends the sequence of bytes \verb+[15,"FileName",0]+ to the port program and waits to be sent the file (as a binary). The actual protocol (i.e. that we send a \verb+15,...,+ sequence to the Port when we want to read file) is uninteresting here. A detailed description of this protocol can be found in the document {\sl the filer protocol\/}. Note there is {\sl no\/} error handling here. The {\sl purpose\/} of this section is to show how to read files {\sl and nothing else!}. <> := read_file(File) -> Port = open_port({spawn, efile}, [binary]), Port ! {self(),{command,[15,File,[0]]}}, Val = receive {Port,{data,Bin}} -> case binary_to_list(Bin, 1, 1) of [0] -> {_, BinFile} = split_binary(Bin, 1), BinFile; Other -> exit({read_file, File}) end end, Port ! {self(), close}, receive {Port, closed} -> true end, Val. >> Now we can compile and test the program: \begin{verbatim} unix> mkErl -o erlProg5 -m prog5 -f start -p prog5 Compiling prog5.erl Making jams.c Compiling jams.c Linking Made erlProg5 unix> erlProg5 prog5.erl {read,{ok,#Bin}} <--- it worked unix> erlProg5 fooy unix> <--- it failed (no output) \end{verbatim} \section{Loading code} In the previous section we showed how to load a single file. We can extend this and make a simple loader: <> := -module(prog6). -export([start/1]). start(Mods) -> load(Mods), Loaded = erlang:loaded(), erlang:display({loaded, Loaded}). <> >> \verb+load+ is easy: <> += load([Mod|T]) -> load_mod(Mod), load(T); load([]) -> true. <> >> <> := load_module(Mod) -> File = atom_to_list(Mod) ++ ".jam", Bin = read_file(File), erlang:load_module(Mod, Bin). >> Now we can test this \begin{verbatim} mkErl.super -o erlProg6 -m prog6 -f start -p prog6 Compiling prog6.erl Making jams.c Compiling jams.c Linking Made erlProg6 unix> erl -compile prog1 <-- compile prog1 unix> erl -compile prog2 <-- and prog2 gordons 100> erlProg6 prog1 prog2 <-- load them {loaded,[prog2,prog1,prog6]} Finished \end{verbatim} \section*{Load and Go} We have now got sufficiently far that we can write a simple {\sl load\_and\_go\/} program. We will launch it by giving the command \begin{verbatim} unix> load_and_go Prog1 Prog2 ... Progn -go Mod Func Args \end{verbatim} If you understood the previous section, this code is {\sl very\/} easy. <> := -module(load_and_go). -export([start/1]). start(Cmds) -> load_and_go(Cmds). load_and_go(['-go', Mod, Func|Args]) -> apply(Mod, Func, Args); load_and_go([Mod|T]) -> load_module(Mod), load_and_go(T); load_and_go([]) -> []. <> <> >> We compile this with: \begin{verbatim} mkErl.super -o load_and_go -m load_and_go -f start -p load_and_go Compiling load_and_go.erl Making jams.c Compiling jams.c Linking Made load_and_go \end{verbatim} No we can test it by compiling our old friend "hello world". \begin{verbatim} unix> erl -compile prog1 unix> load_and_go prog1 -go prog1 start [] 'hello, world' Finished \end{verbatim} Now the bootstrap is finished. We can start ourself - {\sl hooray}. Amazingly the bootstap is only 34 lines long! We're almost there - hang on in. All we need now is to make the autoloading work. \section*{Installing an error handler} When an undefined function is called the error handler is called, so the {\sl first\/} program we must load is the error handler. A simple version is as follows: \section*{Autoloading code} \section*{Using the terminal driver} \section*{Messages from ERTS} ERTS sends certain standard message to certain defined Erlang processes, the complete list is as follows: \section*{Utilities} \label{lit} To extract all the programs from this file you need to have installed the literate Erlang package \verb+eweb+. Give the shell command: \begin{verbatim} unix> eweb \end{verbatim} This will create a large number of files. The most interesting of which is the following makefile: <> := doc: lite.tex latex lite.tex latex lite.tex lite.tex: lite.lit sh eweb make_erlang.jam: make_erlang.erl erl -compile make_erlang clean: rm -f prog1.erl prog2.erl prog3.erl prog3.erl prog4.erl prog5.erl rm -f prog6.erl load_and_go.erl rm -f prog1.jam prog2.jam prog3.jam prog3.jam prog4.jam prog5.jam rm -f prog6.jam load_and_go.jam rm -f erlProg1 erlProg2 erlProg3 erlProg4 erlProg5 erlProg6 rm -f load_and_go rm -f erlProg1.exe erlProg2.exe erlProg3.exe erlProg4.exe erlProg5.exe rm -f erlProg6.exe load_and_go.exe rm -f lite.tex lite.aux lite.log rm -f jams.c jams.o rm -f lite.dvi rm -f mkErl.super mkErl.ruddles rm -f Makefile >> The command \verb+make clean+ deletes everything (including the Makefile!) apart from \verb+lite.lit+ Go on try it!. The other commands in the make file compile and test the various programs described in the paper. Finally, here are two scripts which make stand-anlone systems on {\sl super\/} and on my home machine {\sl ruddles\/}. <> := <> <> <> >> <> := <> <> <> >> Where: <> := #!/bin/sh ## ## Usage: ## mkErl -o Exec -m Mod -f Func -p Mod*" ## ## Makes a stand-alone ERlang executable called Exec ## ## The command Exec Arg* ## will evaluate the erlang function Mod:Fun([Arg]) ## Mod* is a list of Erlang modules ## example: ## mkErl -o prog1 -m prog1 -f start -p prog1 ## where ## -module(prog1). ## -export([start/1]). ## ## start(_) -> erlang:display('hello, world'). ## ## makes a stand-alone "hello world" program ## which can be executed as follows ## gordons> prog1 ## 'hello, world' ## Finished ## BUGS ## actually makes TWO files Exec which calls Exec.exe ## ## ## For non jam/super systems you make have to fiddle with ## the following lines >> The specific parts are: <> := OBJDIR="/usr/local/otp/releases/otp_jam_sunos5_p2b_5/erts-4.4.2/obj" LIBS="-lm -lsocket -lnsl -lresolv -lcurses -ldl" EMU_OBJS="config.o erl_base.o version.o" DRIVERS="efile_drv.o ttsl_drv.o ddll_drv.o tcp_drv.o" OFILES="$EMU_OBJS $DRIVERS" >> and: <> := OBJDIR="/usr/local/src/otp_jam_r1b/erts-4.4.1/obj" LIBS="-lm -ltermcap" EMU_OBJS="config.o erl_base.o version.o" DRIVERS="efile_drv.o ttsl_drv.o tcp_drv.o" OFILES="$EMU_OBJS $DRIVERS" >> And the common part of the two scripts is: <> := ## ## After this should be the same on all systems ## # # error # usage() { echo "Usage:" echo " mkErl -o Exec -m Mod -f Func -p Progs*" exit 1 } while test $# -ge 1 do case $1 in -m) shift mod=$1 shift ;; -f) shift func=$1 shift ;; -o) shift out=$1 shift ;; -p) shift erl=$@ break ;; *) usage ;; esac done if [ $mod = "" ]; then usage fi if [ $func = "" ]; then usage fi if [ $out = "" ]; then usage fi if [ $erl = "" ]; then usage fi ## ## Compile all the Erlang files ## for i in $erl do echo "Compiling $i.erl" erl -compile $1 done ## ## make jams.c ## jam2c="$OBJDIR/jam2c" for i in $@ do jam2c="$jam2c $i.jam" done echo "Making jams.c" eval $jam2c echo "Compiling jams.c" gcc -c jams.c ## ## link ## load="" for i in $OFILES do load="$load $OBJDIR/$i" done link="gcc -o $out.exe $load jams.o $LIBS" echo "Linking" eval $link ## Now make $out which calls $out.exe echo "#!/bin/sh" >$out echo "exec $out.exe -i $mod -b $func -- \$@" >>$out chmod u+x $out echo "Made $out" >> \end{document}