Libero Libero 2.32


Libero Documentation

  • Introduction to Libero
  • Summary
  • Terms and Disclaimer
  • Introduction
  • How It All Started
  • Learning To Use Libero
  • The Coke Machine Example
  • Example of Using a Telephone
  • Serious Stuff: Events and Names
  • Example of Controlling a Telephone
  • Source Code For Phone.c
  • Example of a C/C++ Comment Stripper
  • Example of Parsing An Arithmetic Expression
  • Dialogs For Dummies
  • Explain It Like I'm 5 Years Old
  • A Bit Of Animation
  • Frequently Asked Questions
  • Installing Libero
  • Getting Your Hands On Libero
  • Availability and Distribution
  • Documentation
  • Deinstalling Libero
  • What You Will Need
  • Installing Libero for UNIX
  • Installing the MS Windows Kit
  • Installing the MS-DOS Kit
  • Installing the 32-bit MS-DOS Kit
  • Installing the OS/2 Kit
  • Installing the VAX/VMS Kit
  • Installing the Examples
  • Configuring and Testing Libero
  • Using Libero
  • Running The lr Program
  • The Dialog File Syntax
  • Libero Options
  • Using Exceptions
  • Using The Defaults State
  • Using Sub-Dialogs
  • Using Super-States
  • Using Templates
  • Using Get-External-Event
  • Efficiency
  • Efficiency of The Generated Code
  • Efficiency of The Dialog
  • Care and Feeding of Dialogs
  • Style - Good, Bad, and Ugly
  • Choosing Names
  • When To Use Libero
  • Libero Examples
  • Libero 'install' Script for UNIX
  • COBOL Picture Parser
  • Expression Parser
  • Configuration Management System
  • Multithreaded Agent For VAX/ACMS
  • E-mail Robot - ERBOT
  • HTML Preprocessor
  • Complete Examples Archive
  • Libero's Languages
  • Choosing A Language
  • Standard Public Names
  • Contributed Schemas
  • The Awk Language
  • The C Language
  • The C++ Language
  • The COBOL Language
  • The GNU Borne-Again Shell Language
  • The Java Language
  • The Microsoft Setup Basic Language
  • The MS Visual Basic Language
  • The Perl Language
  • The Rexx Language
  • The UNIX C Shell Language
  • The UNIX Bourne Shell Language
  • The UNIX Korn Shell Language
  • The 80x86 Assembler Language
  • Libero Technical Reference
  • A First Look At Code Generation
  • The Schema
  • Read-Only Variables
  • Schema Commands
  • Notes
  • Syntax Of Expressions
  • Syntax Of Logical Conditions
  • Logical Operators
  • Standard Tests
  • Control Variables
  • Standard :do Conditions
  • Extra Variables In A :do Loop
  • :do while condition
  • :do event
  • :do event local
  • :do state
  • :do module
  • :do action
  • :do nextst
  • :do vector
  • :do overflow
  • :do stubs [filename]...
  • General Rules for Code Generation
  • Generating Module Stubs
  • Generating COBOL Code
  • The State Machine Algorithm
  • Compiled-In Limitations
  • Quick Reference - Libero Schema Language
  • General
  • Read-Only Variables
  • Schema Commands
  • Expressions
  • Logical Conditions
  • Control Variables
  • Standard :do Conditions

  • Introduction to Libero

    Summary

    Terms and Disclaimer

    This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    Introduction

    "Laziness

    The quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labour-saving programs that other people will find useful, and document what you wrote so you don't have to answer so many questions about it. Hence the first great virtue of a programmer."

    Larry Wall [Programming Perl, Larry Wall and Randal. L. Schwartz, 1992 O'Reilly & Associates, Inc., page 426.]

    How It All Started

    In 1982, as a student, I got into games programming as a way of making some money. I skipped the build-it-yourself-from-an- old-TV-screen-and-a-hex-keypad phase of people computers, and jumped straight into the world of ready-to-go colour and sound. My first PC had 5K of RAM, and 25x22 colour video. It could beep in three-part harmony. My first published work was an article that explained how to tweak the video output to 30x33 (hi-resolution!). Soon I was writing sprite painters, assembly libraries for sound and graphics, even languages to replace the built-in Basic. Anything to let me write my games faster and better. Before I knew what had hit me, I was hooked into the cycle of writing tools to improve the world I lived in.

    I haven't written a game for a long time, but I've continued writing tools. Libero is one of the best - it's simple, clean, portable, and has hit the mark so often that I feel it's unfair to keep it for myself.

    The ideas behind Libero evolved in Brussels, Belgium during the 1980's and 90's. I worked with Leif Svalgaard, on what we would now call a CASE tool - ETK - that let COBOL programmers produce clean, portable code instead of the mess we generally saw. One of the key techniques we used was a programming method based on finite state machines. Historically, the first real programs that used finite state machines were compilers. In 1967, Peter Naur describes a new way of using FSMs (which he refers to as a Turing machine approach) in a compiler, and shows how they simplify error checking. He goes on to say:

    "The above description has stressed the checking aspect of the Turing machine approach. However, an equally important aspect is the ease with which arbitrary actions may be specified. By using this approach it is usually possible to avoid tests in individual actions to a surprisingly high degree. This in our experience is a very effective way of reducing the bulk and execution time of the translator algorithm."

    [Annual Review in Automatic Programming, "Design Of The Gier Algol Compiler", Ed. Richard Goodman; 1964 Pergamon Press, page 77.]

    ETK provides an interactive editor that you use to describe the logic of the program as a FSM. This approach encourages you to think about the complete problem. You describe everything that can happen, and how the program should react. The end- result looks a little like a flow-chart, but has more arrows, and fewer different kinds of boxes. The value of this approach is that you can abstract a complex problem using the restricted semantics of a FSM. In the same way that a While statement is less powerful but more useful than a Goto, a FSM is less powerful but more useful than a structured programming approach for describing complex problems. Leif Svalgaard once said: "the issue is not one of power, but coping with the human difficulty in understanding complex structures".

    Now, in a conventional state machine, these boxes are given numbers, and the programmer builds some tables that encode the arrows. To make such a table by hand - or, just as bad, encode the table directly in the logic of the program, using GOTOs - is a Bad Thing, since the result is near to impossible to maintain. The neat part of our solution was that we took the textual description (called a 'dialog'), and generated the mystical tables directly as COBOL code. This is a Good Thing, since the original dialog is easy to change, and suddenly becomes excellent (and always accurate) documentation. In 1992 I began working as a consultant, and found that I wanted to use these techniques in my work. I was writing in C, so I threw together a code generator that could output C code using the same dialog methodology I was used to. I called this tool 'Libero' after the guy who runs around the sidelines at the football/soccer pitch doing all the dirty work. My first serious job was a bunch of TCP/IP clients and servers. It was nice to come back to the place three years later and find that the guy maintaining my work had only found one bug, and was happy to go into the programs. He said that the dialogs made them easy to understand and modify.

    This kind of experience convinced me that there was real value in this tool. I've written lots of cute programs that are useful for my own needs, but that just don't hack it in the real world. Libero is different.

    The first public release of Libero (1.7) generated C code using an external model, a kind of script called a 'schema'. I had a couple of schemas; one for ANSI C code, and one for multithreaded DEC/VMS code. I released version 2.0 onto the Net after adding schemas for UNIX scripts. This release gave me a lot of feedback, and I rewrote the code-generator again to make it much more generic. The current version 2.11 has a lot more language schemas and a lot fewer bugs, plus a front-end for MS- Windows.

    I hope to continue in both these directions. Libero has become a tool that lets you switch between languages at ease. The first step is to accept the state-machine as a valid method for program development. This takes a little effort, but I hope that the examples which follow will help convince you. The second step is to see that this method is language-independent. You can write a program in C, then recode it in Perl without changing its design.

    A hypothetical development team might write mainframe business applications in COBOL, tools in Perl, servers in C, batch scripts in Korn shell, Web clients in Java. Yet instead of a team fragmented into specialists in each domain, you would have a team that enjoys a common design technique and who can quickly take on each other's work. In practice, you also get a certain communality of programming style, which disturbs language purists. However, I like it when - say - a COBOL programmer that knows only Libero take a C program written with Libero and says: "But it looks like COBOL!". Of course neither the Libero+C nor Libero+COBOL programs look anything like 'COBOL'.

    Learning To Use Libero

    I've not found a quick way to convey the true nature of state machine programming. In my experience, it invariably takes a few days of practice. After three or four days' exposure to the idea there is a near-audible 'click!' and something in the brain connects all the pieces together and you go 'Oh, it's like THAT!'. Maybe people cultured on event-driven programming will get it faster.

    The Coke Machine Example

    The coke machine (once awake and gently humming) sits against the wall, waiting. There is a theory about this humming business, notably that coke machines have terrible memories, and can't remember the words. A second school of thought says that the coke machines are humming because they're thinking about all the terrible things they're going to do to humans with crow bars. A third school of thought reckons that the coke machines have already done something, and are humming because they thought it was really funny. Like all coke machines, it's very good at waiting, due to Natural Selection. All the impatient proto-coke machines ran off screaming 'I can't take it anymore' and were crunched underfoot by brontosaurii back in the mists of prehistory.

    A punter arrives with a handful of loose change. The coke-machine (covered by the modern and tasteful metal grid that serves both as a defensive covering and as an attraction for coke machines of the opposite sex) moves into 'attention' mode. When it hears the clink of tasty cash, rather that the ominous clunk of a heavy crow-bar, it relaxes into 'ready mode'. The machine is now ready to handle any eventuality, so long as it includes coinage.

    As the last coin falls and the punter presses the button labelled 'healthie waye sparkling spring water', the machine considers the possibilities. Should it play dead, and just take the punter's money? Or maybe it can substitute a can of 30% sucrose 'sweeto-sap'? Finally it rumbles once, then again for added effect, then excretes a can of water. It munches the coins, then settles back against the wall, quietly humming again.

    Just to prove that you can describe any sequence of events, however improbable, with a state machine, here goes. (The observant reader will have skipped to the next section by now, realising that this one is not worth the paper it's printed on. If you're reading this on-line, then this section is not worth the phosphor it's been digitised, rasterised, and sprayed by an electronic beam gun onto.) Act 1, scene 1 has our protagonist against the wall, trying to remember the words of the song. Our coke-machine hero is normally OK; if something went terribly wrong with the rehearsals (e.g. a dinosaur got sucked through a five-dimensional space-time vortex stretched between the middle terrasic to five minutes into rehersals; the dinosaur a little off balance after the trip puts down its left foot, and 'crunch'...), it might be Dead, in which case we have to call the whole thing off. We'll call this scene 'Should Be Gently Humming'...

    Should-Be-Gently-Humming:
        (--) Ok                         -> Something-Happened
              + Wait-For-A-Punter
        (--) Dead                       ->
              + Stop-The-Programme
    

    The coke machine has been waiting, perhaps for days, for a punter to come along. When the footprints fade in from the distance (a coke machine's ears are always against the ground, which explains why you rarely find coke machines on wet or dirty surfaces), the machine hears either a cashy 'clink' or a more sinister 'clunk':

    Something-Happened:
        (--) Clink                      -> Before-Cooperating
              + Accept-Punter's-Cash
              + Wait-For-Punter's-Choice
              + Shall-We-Cooperate?
        (--) Clunk                      ->
              + Exit-Stage-Left-Running
              + Stop-The-Programme
    

    The advent of a 'Clunk' tells the machine to beat it, smartly. If a 'Clink' happened, the machine goes on to think about a game plan for the coming few minutes. It accepts whatever spare change the punter has to spare, then pounces, metaphorically:

    Before-Cooperating:
        (--) Ok                         -> Cooperate
              + Consider-Punter's-Choice
        (--) Nasty                      -> Let's-Be-Nasty
              + Consider-Punter's-Choice
        (--) Play-Dead                  -> Something-Happened
              + Switch-Off-All-Lights
              + Wait-Until-Punter-Has-Left
              + Switch-On-The-Lights
              + Wait-For-A-Punter
    

    If it's in a good mood, the machine ejects more or less the drink that the punter chose:

    Cooperate:
        (--) Coke                       -> Something-Happened
              + Eject-Can-Of-Coke
              + Wait-For-A-Punter
        (--) Spring-Water               -> Something-Happened
              + Eject-Can-Of-Spring-Water
              + Wait-For-A-Punter
        (--) Juice                      -> Something-Happened
              + Eject-Can-Of-Juice
              + Wait-For-A-Punter
        (--) Sweeto-Sap                 -> Something-Happened
              + Eject-Can-Of-Sweeto-Sap
              + Wait-For-A-Punter
        (--) Empty                      ->
              + Switch-Off-All-Lights
              + Stop-The-Programme
    

    If the coke machine's pretty weak internal moral system failed, it supplies the opposite of whatever the punter wanted:

    Let's-Be-Nasty:
        (--) Coke                       -> Something-Happened
              + Eject-Can-Of-Juice
              + Wait-For-A-Punter
        (--) Spring-Water               -> Something-Happened
              + Eject-Can-Of-Sweeto-Sap
              + Wait-For-A-Punter
        (--) Juice                      -> Something-Happened
              + Eject-Can-Of-Coke
              + Wait-For-A-Punter
        (--) Sweeto-Sap                 -> Something-Happened
              + Eject-Can-Of-Spring-Water
              + Wait-For-A-Punter
        (--) Empty                      ->
              + Switch-Off-All-Lights
              + Stop-The-Programme
    

    This diagram is a little complex for a first example. Remember however, that we are modelling a rich behaviour pattern that took several hundreds of millions of years to evolve. Real software is usually developed much faster, and should therefore be much simpler, only it isn't.

    Example of Using a Telephone

    In a more technical example, we'll model the steps we take in using making a telephone call. By convention, the first state is After-Init. Another convention is to use Terminate-The-Program to halt the dialog.

    After initialisation (I would recommend a good coffee or a decent beer, depending on the time of day), we pick-up the handset, and listen to the dialing tone.

    After-Init:
        (--) Ok                          -> Want-Dialing-Tone
              + Pick-Up-Telephone-Handset
              + Listen-For-Dialing-Tone
        (--) Error                       ->
              + Terminate-The-Program
    

    This leads us to the state Want-Dialing-Tone. Here we handle each of the possible events produced by Listen-For-Dialing-Tone. Let's look at these in detail:

    We give each of these events a short snappy name and list them in a reasonable order. Like a 'case' statement, the actual order makes no difference to the machine, but helps the person reading the dialog. Typically we put the most frequent or expected events first, with the more bizarre ones at the end. Each event provokes one or more actions that correspond to modules of code. A module can be a function, procedure, subroutine, paragraph, etc., depending on the programming language. A module is where you do the real work.

    Want-Dialing-Tone:
        (--) Ok                          -> Want-Ringing-Tone
              + Dial-Required-Number
              + Listen-For-Ringing-Tone
        (--) Silent                      -> Want-Dialing-Tone
              + Put-Down-Telephone-Handset
              + Pick-Up-Telephone-Handset
              + Listen-For-Dialing-Tone
        (--) Voices                      ->
              + Apologise-Telephone-Busy
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Modem                       ->
              + Put-Down-Telephone-Handset
              + Apologise-Cutting-Modem
              + Terminate-The-Program
    

    Here we are waiting for a ringing tone. We can reuse some of the event names - Ok, Silence, Voices - from the previous state. This makes the generated code smaller, and is nice for the reader, since there are fewer names to remember:

    Want-Ringing-Tone:
        (--) Ok                          -> Want-Answer
              + Listen-For-Answer
        (--) Silence                     -> Want-Dialing-Tone
              + Put-Down-Telephone-Handset
              + Pick-Up-Telephone-Handset
              + Listen-For-Dialing-Tone
        (--) Engaged                     -> After-Engaged
              + Put-Down-Telephone-Handset
              + Consider-Trying-Again
        (--) Voices                      -> Want-Dialing-Tone
              + Complain-Crossed-Connection
              + Put-Down-Telephone-Handset
              + Pick-Up-Telephone-Handset
              + Listen-For-Dialing-Tone
    

    You get the picture. The remaining states map out the rest of the conversation. The next state has an event, Doorbell, that is not produced by any previous module, but that can happen at any time. We call this an exception event, and we add the handling for it in the state where it can happen:

    Want-Answer:
        (--) Ok                          ->
              + Have-Conversation
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Wrong-Number                ->
              + Apologise-Wrong-Number
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Impatient                   ->
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Answering-Machine           -> Have-Answering-Machine
              + Consider-Leaving-Message
        (--) Modem-Or-Fax
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Doorbell                    ->
              + End-Conversation-Quickly
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
    
    After-Engaged:
        (--) Ok                          -> Want-Ringing-Tone
              + Dial-Required-Number
              + Listen-For-Ringing-Tone
        (--) Impatient                   ->
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
    
    Have-Answering-Machine:
        (--) Message                     ->
              + Leave-The-Message
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
        (--) Impatient                   ->
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
    

    The Defaults state is special; we never come here explicitly. Rather, the state holds events that are implicitly valid in any other state. Here we say that whenever the Doorbell event strikes, we put the phone down and beat it doorwards. Note that the Doorbell event is handled explicitly in Want-Answer - the action in that state is a little different from the other states.

    Defaults:
        (--) Doorbell                    ->
              + Put-Down-Telephone-Handset
              + Terminate-The-Program
    

    Serious Stuff: Events and Names

    Now I want to show how an action module 'produces' an event. Libero provides a standard variable called The-Next-Event. An event like 'Ok' is actually called Ok-Event in the program (Libero tacks-on '-Event' for you). This is how you would set The-Next-Event in various languages:

    Libero applies this rule: at least one of the action modules for an event must supply a value for The-Next-Event. If no value for The-Next-Event is supplied, the dialog halts with some kind of error message.

    The names of events and action modules can take various forms, depending on the language and your preferences. For example, the C/C++ code could take any of these forms:

    Personally I prefer the first style, which is why it's the default.

     

    Example of Controlling a Telephone

    This next example is one that a correspondent at a telephone company proposed. It's a design for a simple but dedicated subprogram (we'll call it 'Rover') that hangs-around on the corner of our telephone central. Rover springs into action (presumably woofing and waggling a shaggy tail) when we pick-up the handset, or when a call comes in for us. The dialog loops around and around, each time driven by an 'incoming message'. This message is one of these events:

    Here is the dialog. Rover waits for anything to happen, then leaps:

    After-Init:
        (--) Ok                           -> Idle
              + Wait-For-Incoming-Message
        (--) Error                        ->
              + Terminate-The-Program
    

    The two possible events at this stage are Offhook (coming from the local phone), and Request (from a remote phone). We call this state 'Idle'. Some people get confused at this point, and point out that Rover is exactly not idle, so why the name. Why not call the very first state 'Idle'? I answer so: the name of a state reflects the state we were in when the event happened. Thus, when Rover was idle, the only two events that could happen were Offhook and Request.

    Idle:
        (--) Offhook                      -> Dialing-First
              + Start-Dial-Tone
              + Reset-Dialed-Number
              + Wait-For-Incoming-Message
        (--) Request                      -> Ringing
              + Start-Ringing-Local
              + Wait-For-Incoming-Message
    

    The next two states show what happens when we dial the first digit. Rover stops with the dial tone, and slurps up the digits we press, one by one. I distinguish Dialing-First from Dialing-Next to ensure that Rover does a Stop-Local-Tones the first time only:

    Dialing-First:
        (--) Digit                        -> Dialing-Next
              + Stop-Local-Tones
              + Have-Dialed-Digit
              + Wait-For-Incoming-Message
        (--) Whole-Number                 -> Seizing
              + Stop-Local-Tones
              + Send-Outgoing-Request
              + Wait-For-Incoming-Message
        (--) Onhook                       -> Idle
              + Stop-Local-Tones
              + Wait-For-Incoming-Message
    

    Rover doesn't bother handling the Onhook event here, since the Defaults state handles it just fine:

    Dialing-Next:
        (--) Digit                        -> Dialing-Next
              + Have-Dialed-Digit
              + Wait-For-Incoming-Message
        (--) Whole-Number                 -> Seizing
              + Stop-Local-Tones
              + Send-Outgoing-Request
              + Wait-For-Incoming-Message
    

    Okay, we just tried to 'seize' the remote phone. This is either accepted (and the remote phone starts ringing) or rejected for various reasons:

    Seizing:
        (--) Accept                       -> Ringing
              + Start-Ringing-Remote
              + Wait-For-Incoming-Message
        (--) Busy                         -> Seize-Failed
              + Start-Busy-Tone
              + Wait-For-Incoming-Message
        (--) Reject                       -> Seize-Failed
              + Start-Reject-Tone
              + Wait-For-Incoming-Message
    
    Seize-Failed:
        (--) Onhook                       -> Idle
              + Stop-Local-Tones
              + Wait-For-Incoming-Message
    

    The next three states show how Rover handles a ringing phone (at either end) and the eventual Talking, if any. I combined the handling for the local and remote phones into two states; I could also write this more clearly but verbosely as four states (Ringing-Local, Ringing-Remote, Talking-Local, Talking-Remote).

    Ringing:
        !  Events from local phone
        (--) Offhook                      -> Talking
              + Stop-Ringing-Local
              + Wait-For-Incoming-Message
        (--) Onhook                       -> Idle
              + Stop-Ringing-Remote
              + Wait-For-Incoming-Message
        !  Event from remote phone
        (--) Answer                       -> Talking
              + Stop-Ringing-Remote
              + Wait-For-Incoming-Message
    
    Talking:
        !  Event from local phone
        (--) Onhook                       -> Idle
              + Send-Outgoing-Release
              + Wait-For-Incoming-Message
        !  Event from remote phone
        (--) Release                      -> Released
              + Start-Released-Tone
              + Wait-For-Incoming-Message
    
    Released:
        (--) Onhook                       -> Idle
              + Stop-Local-Tones
              + Wait-For-Incoming-Message
    

    The Defaults state handles the Onhook event in those states where there is no local tone. In those states (like Released), Rover silences the local phone, then loops back to the Idle state:

    Defaults:
        (--) Onhook                       -> Idle
              + Wait-For-Incoming-Message
    

    Source Code For Phone.c

    When I take the above dialog (called phone.l), and give it to Libero, this is what happens:

     C:\CTOOLS\LIBERO>lr phone
     LIBERO v2.11 (c) 1991-96 by Pieter A. Hintjens
     lr I: processing 'phone.l'...
     lr I: creating skeleton program phone.c...
     lr I: building phone.d...
     lr I: building phone.i...
     lr I: Building stub for have dialed digit
     lr I: Building stub for reset dialed number
     lr I: Building stub for send outgoing release
     lr I: Building stub for send outgoing request
     lr I: Building stub for start busy tone
     lr I: Building stub for start dial tone
     lr I: Building stub for start released tone
     lr I: Building stub for start ringing local
     lr I: Building stub for start ringing remote
     lr I: Building stub for stop local tones
     lr I: Building stub for stop ringing local
     lr I: Building stub for stop ringing remote
     lr I: Building stub for wait for incoming message
     C:\CTOOLS\LIBERO>
    

    Libero assumes that I want to make a program called phone.c. Since this file does not exist, it creates a skeleton program for me. This only happens the first time. Once the program is created, Libero will add missing pieces to it, at the end, but will never change it again otherwise.

    I get two other files as well, phone.d and phone.i. These are the data and interpreter for my dialog. I need these files to compile phone.c. Each time I run Libero, it recreates these two files. If I was working in another language, I would get different files, with different extensions. The default language is C. Here is the skeleton program that Libero gave me, more or less:

    /*=========================================================================*
     *                                                                         *
     *  phone.c     description...                                             *
     *                                                                         *
     *  Written:    95/07/29    Your Name                                      *
     *  Revised:    95/07/29                                                   *
     *                                                                         *
     *  Skeleton generated by LIBERO 2.10 on 29 Jul, 1995, 11:50.              *
     *                                                                         *
     *=========================================================================*/
    
    #include "prelude.h"                    /*  Public definitions             */
    #include "phone.d"                      /*  Include dialog data            */
    
    /*- Function prototypes ---------------------------------------------------*/
    
    /*- Global variables used in this source file only ------------------------*/
    
    static int
        feedback;                           /*  Feedback for calling program   */
    
    static char
        *some_param;                        /*  Example of parameter           */
    
    /********************************   M A I N   ******************************/
    
    int my_funct (char *p_some_param)
    {
        feedback = 0;                       /*  No errors so far               */
        some_param = p_some_param;          /*  Local copy of parameters       */
    
    #   include "phone.i"                   /*  Include dialog interpreter     */
    }
    
    /************************   INITIALISE THE PROGRAM   ***********************/
    
    MODULE initialise_the_program (void)
    {
        the_next_event = ok_event;
    }
    
    /**************************   GET EXTERNAL EVENT   *************************/
    
    MODULE get_external_event (void)
    {
    }
    
    /************************   TERMINATE THE PROGRAM   ***********************/
    
    MODULE terminate_the_program (void)
    {
        the_next_event = terminate_event;
    }
    
    /**************************   HAVE DIALED DIGIT   **************************/
    
    MODULE have_dialed_digit (void)
    {
    }
    
    /************************   RESET DIALED NUMBER   ************************/
    
    MODULE reset_dialed_number (void)
    {
    }
     ... etc.
    

    I discuss the details of Libero+C programming elsewhere. Some rules apply whatever language you use:

    The basic C program consists of a number of standard pieces:

    Example of a C/C++ Comment Stripper

    One of the classic uses for state machines is in scanning and parsing text - e.g. source programs. People usually do this with tools like lex and yacc, or using languages like Awk and Perl that are good at pattern matching. When I explain that Libero uses a state-machine model, some people think that I am asking them to use Libero in place of the honoured tools I just mentioned. The next example shows why this is not the case.

    Here I present a C/C++ comment stripper based on an example by Robert Martin (who makes a finite-state machine code generator for C++). In C and C++, comments are either enclosed between '/*' and '*/', or begin with '//' until the end of the line. This program removes all comments from a C or C++ source program, with some checking for errors like an unmatched '/*':

    !
    !   C/C++ comment stripper dialog
    !
    !   Written:    95/03/30  Pieter Hintjens <ph@imatix.com>
    !   Revised:    96/05/15  Frans Janssens <frans@sebb.bel.alcatel.be>
    !
    -source=stripper.c
    -schema=lrschema.c
    
    After-Init:
        (--) Ok                         -> Outside-Comment
              + Get-Char-From-Input
        (--) Error                      ->
              + Terminate-The-Program
    
    Outside-Comment:
        (--) Quote                      -> Skip-Quote
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Slash                      -> After-Slash
              + Get-Char-From-Input
        (--) Star                       -> Outside-Comment
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) NewLine                    -> Outside-Comment
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Other                      -> Outside-Comment
              + Put-Char-To-Output
              + Get-Char-From-Input
    
    Skip-Quote:
        (--) Quote                      -> Outside-Comment
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Slash                      -> Skip-Quote
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Star                       -> Skip-Quote
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) NewLine                    -> Skip-Quote
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Other                      -> Skip-Quote
              + Put-Char-To-Output
              + Get-Char-From-Input
    
    After-Slash:
        (--) Quote                      -> Outside-Comment
              + Put-Slash-To-Output
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Slash                      -> Skip-Eoln
              + Get-Char-From-Input
        (--) Star                       -> Skip-Comment
              + Start-New-Comment
              + Get-Char-From-Input
        (--) NewLine                    -> Outside-Comment
              + Put-Slash-To-Output
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Other                      -> Outside-Comment
              + Put-Slash-To-Output
              + Put-Char-To-Output
              + Get-Char-From-Input
    
    Skip-Eoln:
        (--) Quote                      -> Skip-Eoln
              + Get-Char-From-Input
        (--) Slash                      -> Skip-Eoln
              + Get-Char-From-Input
        (--) Star                       -> Skip-Eoln
              + Get-Char-From-Input
        (--) NewLine                    -> Outside-Comment
              + Put-Char-To-Output
              + Get-Char-From-Input
        (--) Other                      -> Skip-Eoln
              + Get-Char-From-Input
    
    Skip-Comment:
        (--) Quote                      -> Skip-Comment
              + Get-Char-From-Input
        (--) Slash                      -> After-Comment-Slash
              + Get-Char-From-Input
        (--) Star                       -> After-Comment-Star
              + Get-Char-From-Input
        (--) NewLine                    -> Skip-Comment
              + Get-Char-From-Input
        (--) Other                      -> Skip-Comment
              + Get-Char-From-Input
        (--) End-Of-File                ->
              + Signal-Unterminated-Comment
              + Terminate-The-Program
    
    After-Comment-Star:
        (--) Quote                      -> Skip-Comment
              + Get-Char-From-Input
        (--) Slash                      -> Outside-Comment
              + Other-If-Nested-Comment
              + Get-Char-From-Input
        (--) Star                       -> After-Comment-Star
              + Get-Char-From-Input
        (--) NewLine                    -> Skip-Comment
              + Get-Char-From-Input
        (--) Other                      -> Skip-Comment
              + Get-Char-From-Input
        (--) End-Of-File                ->
              + Signal-Unterminated-Comment
              + Terminate-The-Program
    
    After-Comment-Slash:
        (--) Quote                      -> Skip-Comment
              + Get-Char-From-Input
        (--) Slash                      -> Skip-Comment
              + Get-Char-From-Input
        (--) Star                       -> Skip-Comment
              + Start-New-Comment
              + Get-Char-From-Input
        (--) NewLine                    -> Skip-Comment
              + Get-Char-From-Input
        (--) Other                      -> Skip-Comment
              + Get-Char-From-Input
        (--) End-Of-File                ->
              + Signal-Unterminated-Comment
              + Terminate-The-Program
    
    Defaults:
        (--) Exception                  ->
              + Terminate-The-Program
        (--) End-Of-File                ->
              + Terminate-The-Program
    

    This is what Libero generates when I compile the dialog:

    /*===========================================================================*
     *                                                                           *
     *  stripper.c  description...                                               *
     *                                                                           *
     *  Written:    96/12/31    Pieter Hintjens                   *
     *  Revised:    96/12/31                                                     *
     *                                                                           *
     *  Skeleton generated by LIBERO 2.20 on 31 Dec, 1996, 14:00.                *
     *===========================================================================*/
    
    #include "prelude.h"                    /*  Universal include file           */
    #include "stripper.d"                   /*  Include dialog data              */
    
    /*- Function prototypes -----------------------------------------------------*/
    
    /*- Global variables used in this source file only --------------------------*/
    
    static int
        feedback;                           /*  Feedback for calling program     */
    
    static char
        *some_param;                        /*  Example of parameter             */
    
    /********************************   M A I N   ********************************/
    
    int my_funct (char *p_some_param)
    {
        feedback = 0;                       /*  No errors so far                 */
        some_param = p_some_param;          /*  Local copy of parameters         */
    
    #   include "stripper.i"                /*  Include dialog interpreter       */
    }
    
    /*************************   INITIALISE THE PROGRAM   ************************/
    
    MODULE initialise_the_program (void)
    {
        the_next_event = ok_event;
    }
    
    /***************************   GET EXTERNAL EVENT   **************************/
    
    MODULE get_external_event (void)
    {
    }
    
    /*************************   TERMINATE THE PROGRAM    ************************/
    
    MODULE terminate_the_program (void)
    {
        the_next_event = terminate_event;
    }
    
    /**************************   GET CHAR FROM INPUT   **************************/
    
    MODULE get_char_from_input (void)
    {
    }
    
    /***************************   PUT CHAR TO OUTPUT   **************************/
    
    MODULE put_char_to_output (void)
    {
    }
    
    /**************************   PUT SLASH TO OUTPUT   **************************/
    
    MODULE put_slash_to_output (void)
    {
    }
    
    /***************************   START NEW COMMENT   ***************************/
    
    MODULE start_new_comment (void)
    {
    }
    
    /**********************   SIGNAL UNTERMINATED COMMENT   **********************/
    
    MODULE signal_unterminated_comment (void)
    {
    }
    
    /************************   OTHER IF NESTED COMMENT   ************************/
    
    MODULE other_if_nested_comment (void)
    {
    }
    

    This is the completed comment-stripper program:

    /*=========================================================================*
     *                                                                         *
     *  stripper.c  C/C++ comment stripper                                     *
     *                                                                         *
     *  Written:    96/12/31    Pieter Hintjens                   *
     *  Revised:    96/12/31                                                   *
     *                                                                         *
     *=========================================================================*/
    
    #include "prelude.h"                    /*  Standard include file          */
    #include "stripper.d"                   /*  Include dialog data            */
    
    
    /*- Global variables used in this source file only ------------------------*/
    
    static int
        thisch,                             /*  Current character              */
        comment_level,                      /*  Comment nesting level          */
        feedback;                           /*  Feedback for calling program   */
    
    int main (void)
    {
        feedback = 0;                       /*  No errors so far               */
    #   include "stripper.i"                /*  Include dialog interpreter     */
    }
    
    /************************   INITIALISE THE PROGRAM   ***********************/
    
    MODULE initialise_the_program (void)
    {
        the_next_event = ok_event;
    }
    
    /*************************   GET CHAR FROM INPUT   *************************/
    
    MODULE get_char_from_input (void)
    {
        thisch = getchar ();
        switch (thisch)
          {
            case '"':   the_next_event = quote_event;         break;
            case '/':   the_next_event = slash_event;         break;
            case '*':   the_next_event = star_event;          break;
            case '\n':  the_next_event = newline_event;       break;
            case EOF:   the_next_event = end_of_file_event;   break;
            default:    the_next_event = other_event;
          }
    }
    
    /**************************   PUT CHAR TO OUTPUT   *************************/
    
    MODULE put_char_to_output (void)
    {
        putchar (thisch);
    }
    
    /*************************   PUT SLASH TO OUTPUT   *************************/
    
    MODULE put_slash_to_output (void)
    {
        putchar ('/');
    }
    
    /**************************   START NEW COMMENT   **************************/
    
    MODULE start_new_comment (void)
    {
        comment_level++;                    /*  We have a new comment          */
    }
    
    /***********************   OTHER IF NESTED COMMENT   ***********************/
    
    MODULE other_if_nested_comment (void)
    {
        comment_level--;                    /*  We've finished a comment       */
        if (comment_level > 0)              /*    but maybe it was nested...   */
            raise_exception (other_event);
    }
    
    /*********************   SIGNAL UNTERMINATED COMMENT   *********************/
    
    MODULE signal_unterminated_comment (void)
    {
        fprintf (stderr, "stripper: unterminated comment\n");
        feedback = 1;                       /*  Return code for main program   */
    }
    
    /***************************   GET EXTERNAL EVENT   ************************/
    
    MODULE get_external_event (void)
    {
    }
    
    /************************   TERMINATE THE PROGRAM   ************************/
    
    MODULE terminate_the_program (void)
    {
        the_next_event = terminate_event;
    }
    

    The program works and is easily understood. However, because the dialog is really too low-level, it makes the problem seem more complex than it actually is. In fact, this is a good example of the kind of problem that I would never use Libero for. I would write this as a straight C program.

    Moral: Libero is not worth the overhead on trivial problems. By contrast, when you are wrestling with a really complex problem, Libero lets you break it down into smaller, simpler pieces, and keep the central logic in a single place.

    Second moral: simplicity can be deceptive. It often happens that the first stab at a problem produces a simple solution, as here. However, when the solution meets reality, reality often gets its own way and imposes all kind of nasty complexities that mess-up the clean solution. A dialog is very good at handling such things. So, it can be better to start-out on the right footing and use a dialog from the start.

    Example of Parsing An Arithmetic Expression

    The next example is for an arithmetic expression evaluator that was originally proposed by Leif Svalgaard. The program works out the value of a string like '2-SQR(2)*1.414'. The program translates easily into most of the languages I've worked with so far, from assembler to Basic to C and COBOL.

    Here I describe the program lrcalc.c, which is a subroutine that I use in Libero. You can find the source for this program along with the other sources for Libero in the lrsrc.zip archive. There are also versions of this program in various languages in the examples archive.

    Lrcalc chops the expression into tokens, each representing an operand (numbers) or operator. The operators are classified as:

    There are various ways of parsing an expression like this; lrcalc combines two basic techniques: push-down stacks for the operands and operators, and states to indicate how tokens are handled. Each state accepts specific tokens and rejects others. For instance, at the start of the expression, an operator like '*' is not valid. When a state accepts an operand or operator, it adds it to the appropriate stack. When a state stacks an operator, it evaluates any previous operators that have the same, or higher priority. '*' and '/' have a higher priority than '+' and '-'.

    Operators like '(' and ')' are placeholders that group parts of the expression together with a higher priority. Operators like '*', '/', '+', and '-' are binary operators that take two values off the operand stack, do their work, and place the result back on the stack.

    The program basically takes tokens one by one, stacks and evaluates them according to the priority rules, until it reaches the end of the expression. To make this clean, the program places a special end-mark token at the end of the expression when it starts. When it reaches the end-mark, it evaluates any remaining operators, which leaves the result of the expression sitting on the stack.

    After-Init:
        (--) Ok                         -> Expecting-Initial
              + Get-Next-Token
        (--) Error                      ->
              + Terminate-The-Program
    

    The two states Expecting-Initial and Expecting-Operand are similar, except that the first allows End-Mark while the latter does not. I.e. we accept an empty expression (End-Mark in Expecting-Initial), but don't accept an expression that ends when we expect an operand:

    Expecting-Initial:
        (--) Term-Op                    ->
              + Allow-Signed-Number
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Number                     -> Expecting-Operator
              + Stack-The-Number
              + Get-Next-Token
        (--) Left-Par                   -> Expecting-Operand
              + Stack-The-Operator
              + Get-Next-Token
        (--) End-Mark                   ->
              + Terminate-The-Program
    

    These two states handle signed numbers (e.g.. -12, +100) by accepting Term-Op tokens (ie. '+' or '-') so long as these are stuck to a following number. The module Allow-Signed-Number gets the next token, and if this is a number, it kicks the dialog into accepting a number in the same state. It does this using an exception event called Number:

    Expecting-Operand:
        (--) Term-Op                    ->
              + Allow-Signed-Number
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Number                     -> Expecting-Operator
              + Stack-The-Number
              + Get-Next-Token
        (--) Left-Par                   -> Expecting-Operand
              + Stack-The-Operator
              + Get-Next-Token
    

    After stacking an operand, the dialog expects an operator:

    Expecting-Operator:
        (--) Term-Op                    -> Expecting-Operand
              + Unstack-Ge-Operators
              + Stack-The-Operator
              + Get-Next-Token
        (--) Factor-Op                  -> Expecting-Operand
              + Unstack-Ge-Operators
              + Stack-The-Operator
              + Get-Next-Token
        (--) End-Mark                   ->
              + Unstack-All-Operators
              + Unstack-If-End-Mark
              + Terminate-The-Program
       (--) Right-Par                  -> Expecting-Operator
              + Unstack-All-Operators
              + Unstack-If-Left-Par
              + Get-Next-Token
    

    The Defaults state lists all tokens. If a state does not explicitly accept some token, the Defaults state handles it: it issues an error message and terminates the program:

    Defaults:
        (--) Number                     ->
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Term-Op                    ->
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Factor-Op                  ->
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) End-Mark                   ->
              + Signal-Token-Missing
              + Terminate-The-Program
        (--) Left-Par                   ->
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Right-Par                  ->
              + Signal-Invalid-Token
              + Terminate-The-Program
        (--) Exception                  ->
              + Terminate-The-Program
    

    The expression parsing technique shown here is easy to elaborate to support variables, functions, other operators, strings, etc. It is true that some languages have easier ways of evaluating expressions, but this technique is easily applied to languages like assembler and COBOL that do not have support from tools like lex and yacc.

    Dialogs For Dummies

    Explain It Like I'm 5 Years Old

    You've gone through all the examples and still you can't quite get your mind around the concepts? Don't worry, this is normal. It's like anything new -- it takes a while for your brain to change shape.

    So far, we have seen fairly realistic examples - depending on how much booze was consumed for lunch, admittedly. Sometimes it helps to look at the entire issue from other angles. So, here is a small piece extracted from our Liberzine Liberetto I/3. Our correspondent, Big Al, writes...

    iMatix asked me to write a small piece on how I learnt to use Libero. After a short discussion about fees and expenses, I quickly learnt to use Libero, applying the principle 'imagine I am a child of 5'. "No, don't try to interest me in plastic bunnies. Explain Libero to me." "Right, kids, get into two groups. You lot sit there, and you lot sit there. Now, we'll play a game..."

    The first group of kids sit around a large playing board, marked with a large 'START' square, and paths that twist around. Some end in the Snake Pit, others die horrible deaths in the Bathtime Room. Just one path leads through all the dangers to the Cookie Room. The aim of the game is to get to the Cookie Room, of course. Let's look at the START square. You are in a small room. Twisty passages lead left, right, north, and south. You are holding a candle. There is a small breeze coming from the west. The floor is blue. Blue? Yeah.

    The second group of kids have three or four funny dice. Each die is a different colour, with size sides. The blue die says 'Left', 'Right', 'Forwards', 'Backwards', 'Roll Again', and 'Bathtime'. The red die says 'Fight', 'Run', 'Get Eaten', 'Eat it', 'Order a taxi', and 'Bathtime'. (You gotta know how to *really* scare a five-year old.)

    The rules are simple. The first group of kids just shout a colour. The second group of kids roll the matching die and - after puzzling over the letters a while - shout out what the die says. Each time the die rolls, the first group move their piece along the board to a new square.

    You start to see the balance of the game. Neither group of children controls the game, it moves step-by-step until the piece wins or loses. The two groups only communicate by exchanging colours and results. Well, this is Libero. Colours are actions, results are events. And that was the easiest lunch I ever earned.

    A Bit Of Animation

    Sometimes it's hard to mentally connect the dialog to the running program. On the one hand you have the dry, pedantic dialog with its events and states; on the other hand you have a real live program that is actually doing something. A good way to join these two concepts is the -animate option. Use this when you generate code, and you get a display of the dialog steps a program runs through.

    The examples archive has a set of programs that work out the value of arithmetic expressions. This is what one of these looks like as it works out the value of '1 + 1':

    After-Init:
        (--) Ok-Event                       We just started
              + Get-Next-Token                so we collect the next token
    
    Expecting-Operand:
        (--) Number-Event                   We got the first '1'
              + Stack-The-Number            Stick it on the operand stack
              + Get-Next-Token                and get the next token
    
    Expecting-Operator:
        (--) Term-Op-Event                  We got the '+'
              + Unstack-Ge-Operators        Do any higher-priority operators
              + Stack-The-Operator            then stick the '+' on the stack
              + Get-Next-Token                and get the next token
    
    Expecting-Operand:
        (--) Number-Event                   We got the second '1'
              + Stack-The-Number            Stick it on the operand stack
              + Get-Next-Token                and get the next token
    
    Expecting-Operator:
        (--) End-Mark-Event                 We hit the end of the expression
              + Unstack-All-Operators       Do all operators on the stack
              + Unstack-If-End-Mark         The operand stack holds the result
    2                                       This is displayed by the program
              + Terminate-The-Program       End and return to the caller
    

    Frequently Asked Questions

    Why is the documentation in HTML format?

    We originally wrote the documentation using Microsoft Word 5.0 for MS-DOS, a fine word-processor that satisfied our minimalist preferences for a long time. Our target was PostScript on paper. Since those old days, we have changed our minds a little about the best way of moving information from our brains to yours. We like the idea of keeping the help 'on-line' in the literal sense. Effectively, this means using HTML. Now, our approach is two-fold. Firstly, we use HTML as the basic language. It's excellent for the job. Secondly, we have structured the documentation to suit. This means that the Libero documentation is less of a traditional paper book, and more of a hyper-linked text database. Our guiding principle is that you can take the entire HTML kit and put it on a local hard disk, then browse, print, and copy that. There are HTML browsers available for all of our target platforms, and the market is competitive and generally free.

    Why do you not supply binary kits for UNIX (any longer)?

    We used to do this... It just does not work very well, and takes a lot of work. It is generally not a good idea to use an executable built under one version of an operating system on an other. Sometimes, but not always, you can go from an earlier version of Brandix to a later version, but never contrarywise. With some exceptions (see next question) it's a snap to install Libero by compiling the source kit. At iMatix we tend to do this for all our products today.

    I get compile errors on Sun Sparc

    For some bizarre reason, possibly related to the struggle of commercialism over utility, this system does not come with an ANSI C compiler as standard. People often install GNU C, which works well, but there can be conflicts between the Sun header files and GNU runtime library. In theory we fixed these in version 2.11 (by changing prelude.h). If you still get compile errors, send us a listing and we will see what is wrong. On some Sparc systems, the ANSI C compiler is called acc.

    I get compile and link errors and then lr dies

    If you get compile errors (not warnings), or link errors, don't try to run the resulting executable. Or if you do, don't expect reasonable behaviour, unless one can describe a dump as 'reasonable'.

    Does Libero support language X?

    If it is not on the list, the answer is 'not yet'. There are several possible reasons why we have not yet built a schema for a particular languages:

    So far no language has fallen into the first category, while many have fallen into the last.

    How do I add support for language X?

    If it is a language that we can easily get hold of, we'll add support more or less on demand. For instance, if anyone actually comes and asks for an Ada implementation, we could install GNU Ada and get an Ada schema up and running in a few days. The bizarre thing is we don't even ask for money for the whole business. Whatever.

    If the language is one that we cannot get freely -- and we're not in the habit of buying expensive compilers just for fun -- then we can try to build a schema 'by remote control'. In this scenario, you provide us with the specifications of the language, sample programs, etc. We'll do the schema and send you programs to test. Eventually we get a solid version. This can work surprisingly well.

    Finally, you can bite the bullet (more like a 10mm shell) and learn how Libero's schema language works. You can then write the schema yourself. Honestly, if you need to write a schema from scratch, this is probably the hard way.

    I want to customise a schema

    Firstly, be sure that you can't get what you need by adding code to the template file. Then, drop us a line to tell us what you want so we can try to figure-out a way of supporting it in the standard schemas. As a last resort, copy the schema and change it. If your changes are cosmetic, we'd try to convince you to accept our cosmetics. Some projects have such unusual needs that a new schema is the only practical answer. Be warned, however: if you make your own version of a schema, you lose the benefits of any future improvements or fixes to the schema.

    I don't understand Libero

    This is quite normal. Anyone who understands Libero the first time is either lying, has a serious mental problem, or is not a real programmer. The concept is simple, and easy to explain to a lay-person. But if you have spent long enough coercing computers to do your bidding, you get a certain world-view of how things work. Libero does things differently. Keep trying, and after a week or so you will suddenly see it.

    How do I print-out the documentation?

    Probably the best approach is to download the big file that contains the entire documentation kit. Load this into your favourite browser and press 'Print'. Alternatively you can try to convert the HTML file into a word processor format, load it up and print it like that. This is a field that is changing rapidly, for the better.

    Why is there no UNIX Makefile?

    There are several reasons for this: Libero is a portable product, and makefiles are specific to UNIX; we don't really like makefiles; we prefer to try a more 'intelligent' approach when possible; we don't have the time to make and maintain makefiles for all the possible UNIX platforms. Ok, none of these reasons is enough by itself, but you get the idea. Libero is simple to build and install, so the lack of a makefile is not a serious handicap.

    Libero for Windows crashes

    There was a bug in previous releases of Libero for Windows. It was caused by a missing Windows font. Install the latest version.

    Why can't I get the files by ftp?

    Our site does not provide anonymous ftp for reasons of speed and of security. If you do not have http access, send us an e-mail telling us what system you work with, or which files you want, and we'll send them by e-mail back to you.

    Is there a mirror site?

    Not to our knowledge, though we have seen a few older versions of Libero floating around.

    How do I build a mirror site?

    Our website does not (currently) support anonymous ftp, nor other protocols for automatic mirror-site replication. If you want to set-up a mirror site for Libero, contact us and we'll work-out the principles.

    I want to repackage Libero for a CD-ROM

    You do not need our permission, but you must respect our license terms. If your CD-ROM is for a specific platform, e.g. Linux, we suggest that you include the binary kit for that platform, the source kit, and the HTML kit. If you have space on the CD-ROM, supply the HTML tree uncompressed: then people can follow the links through their Internet connection to pick-up other files such as examples.

    If your CD-ROM is a source collection, you may want to provide the Windows version of Libero, the source kit, and the HTML kit.

    Why is Liberetto so silly?

    Everything is relative. When the first issue of Libermanga comes out, you'll see what we mean. Seriously, though, we try to maintain a harmonious balance between education, entertainment, humour, and subtle irony. When that fails, we just go for silly.

    Is there a 'pretty printer' for my dialogs?

    Yes, since version 2.20 - use the -pretty option.

    How is a Libero FSM different from a 'classic' FSM?

    I want my FSM to handle an event queue in realtime

    Use our SMT kernel; this is our solution for event-handling in realtime programs, multi-FSM constructions, communications servers, internet servers, etc.

    Are Libero FSMs good for writing parsers?

    Well, in principle yes, but in practice there are better tools. For instance, Perl is great for writing ad-hoc parsers. If you are working with formal grammars, there are tools that will generate parsers for such grammars. It is usually pointless to re-invent the wheel by hand. There are exceptions. For instance, sometimes the grammar cannot be formalised enough to work with a parser generator. Sometimes a generated parser is too slow. In these cases, a Libero FSM may produce excellent results.

    I downloaded a Libero .zip file to my UNIX, then ftp'd it to my PC - now PKUNZIP won't extract it

    When you ftp a binary file like a .zip file, always set ftp to binary mode (use the command 'bin'). Sometimes ftp is configured like this by default, but more often it assumes you want to transfer text files. So, it expands tabs, converts line-ending, and such, generally turning a .zip file into garbage along the way.

    Is there a Libero mailing list?

    No, not yet. Each month we send out Liberetto, our fanzine. We include the most interesting mail we get, news about changes to Libero, forthcoming releases, etc. This is the closest thing to a discussion group so far. We intend one day to create a usenet news group. A public mailing list may be one step on the way. We welcome any suggestions.

    Libero for Windows complains 'ASSOC.VBX Not Found'

    To use Libero for Windows you need to download TWO .zip files; lrmswin.zip and lrmswins.zip. The second contains 'system files' including various .vbx files. To download an update you only take the first .zip file. This saves download time.

    Installing Libero

    Getting Your Hands On Libero

    Availability and Distribution

    Libero is available as a ready-to-run binary kit for various platforms. You can also download the source kit and re-compile Libero for a specific platform.

    Libero is free software, distributed according to the terms of the GNU General Public License (GPL). This means that you are free to change and re-distribute Libero, even charge for it, but you must always make the source code available under the same terms.

    The Windows version of Libero is also free software, but does not fall under the GNU GPL. You can't get the sources, mostly because it would be embarrassing.

    If you have trouble accessing the iMatix site, or getting your hands on Libero for any reason, send an e-mail to us at imatix.com and we will provide you with Libero by e-mail, or on diskette. We have used the portable Zip and GNU gzip+tar formats for all archives.

    Documentation

    The Libero User's Guide consists of a set of HTML files that you can browse on-line or download and browse locally. You can download:

    Deinstalling Libero

    We made Libero as simple as possible to deinstall. It does not change anything in system files, and keeps its own files in the single directory where you installed it. To de-install Libero, delete this directory.

    What You Will Need

    To install Libero, you'll need:

    1. Either the binary kit for your system, or the source kit which is also available as a GNU gzipped file.
    2. Infozip unzip to extract the archives. If you do not have Infozip unzip for your UNIX or VAX system, you can unzip the files under MS-DOS or Windows and upload them individually. Use PKzip version 2.04g or later or a compatible unzip program.
    3. Alternatively, for UNIX source installation, GNU gunzip.
    4. For UNIX or VMS installations from the source kit, an ANSI C compiler to compile Libero.
    5. A compiler or interpreter for the language that you want to work in. You don't necessarily need this on the machine where you want to run Libero; it's possible to work on one system, then transfer the results to another for compiling and running.
    6. The Libero documentation kit.

    Installing Libero for UNIX

    To install Libero for a UNIX system you must compile it. Download one of:

    lrsrc230.tgz    144126 97/07/14 14:20:26  gzip/tar archive
    lrsrc230.zip    192945 97/07/14 14:20:08  zip archive
    lrsrc231.tgz    151603 98/11/27 10:30:10  gzip/tar archive
    lrsrc231.zip    207869 98/11/27 10:29:10  zip archive
    lrsrc232.tgz    148536 99/05/13 14:46:40  gzip/tar archive
    lrsrc232.zip    201295 99/05/13 14:46:16  zip archive
    

    You'll need an ANSI C compiler. The source kit comes with build scripts for UNIX and VMS. To unpack the sources in portable Zip format, load lrsrc232.zip into a working directory and extract the contents using unzip:

        $ mkdir temp
        $ mv lrsrc232.zip temp
        $ cd temp
        $ unzip -a lrsrc
        $ chmod +x install
    

    To unpack the sources in GNU gzip format, load lrsrc232.tgz into a working directory and extract the contents using gunzip and tar:

        $ mkdir temp
        $ mv lrsrc232.tgz temp
        $ cd temp
        $ gunzip lrsrc
        $ tar -xvf lrsrc232.tar
    

    The install script is smart enough to detect the UNIX system and use appropriate compile switches. It proposes various options: start with (b)uild to compile and (maybe) install Libero. You can run Libero as soon as it is built; you don't need to install it into an install directory. Run install like this (you may need root access if you want to install Libero into a system directory):

        $ su root
        $ chmod +x install
        $ ./install
    

    The install script uses the Korn shell, which is not available on all UNIX systems. You can use install.sh instead, which uses the Bourne shell. On some UNIX systems the Bourne shell does not support functions; then this script will not run either. If you have trouble running install or install.sh, try something like this (it's what the scripts basically try to do):

        $ cc <switches> lr.c lr????.c -o lr
        $ cp lr lrmesg.txt lrschema.* installdir
        $ rm *.o
    

    The <switches> are the compiler switches that do an ANSI C compile. Type man cc for details.

    Once you have built the lr file, and installed it and its data files, you should check that the install directory is on the PATH definition. By default the install script proposes /usr/local/bin as an install directory.

    If you do not like placing non-executable files into a /bin directory, you can place lr there, and the remaining files (lrmesg.txt, lrschema.*) into another directory. This second directory may be on the PATH or be defined in another, arbitrary environment variable; for example, LRPATH. You must then tell Libero which variable you are using with a command-line switch like: -path=LRPATH. You can also specify the path literally: -path=/usr/local/tools/bin. Place such global options in the lr.ini file.

    Note that on UNIX, all filenames are lower-case. If you do not manage to install Libero on a non-MS-DOS system, you can always run Libero on a PC, then upload the generated code. You can also run Libero for Windows or MS-DOS, accessing your UNIX disk via a network. This works pretty well for many people.

    System-specific notes:

    Installing the MS Windows Kit

    Libero for Windows is an interactive editor that provides a drool-proof interface to the standard Libero code generation functions. It runs under Windows 3.1, 95, NT, and other common varieties. The Libero for Windows kit comes in two parts:

    1. lrmswin.zip - Libero for Windows editor. Contains the software and associated files.
    2. lrmswins.zip - Libero for Windows system runtime. You only need to download this once. Contains 'system' files.

    Basically, lrmswin.zip contains the latest version and will change whenever a new release is made. Lrmswins.zip is unlikely to change often. The split is made to save you download time.

    Install as follows:

    Libero for Windows is compatible with standard portable Libero; they share the same files and general ideas about the world. However, Libero for Windows is not distributed under the same terms: it is free software, but you may not modify it, and you cannot get hold of the sources.

    Installing the MS-DOS Kit

    Download Libero for MS-DOS. This was compiled using Borland Turbo-C.

    Create a new directory and extract lrmsdos.zip there. This creates the set of files that you need to run Libero under MS-DOS. You must either add this new directory to your path, or copy certain Libero files into a directory (like C:\UTILS) which is already on your path:

    The LR.EXE file is not too large, and runs in a modest amount of memory. If you are writing really enormous dialogs, you may want to rebuild LR.EXE from the source kit using a DOS extender. I haven't tried this, and I have not heard of anyone running out of memory with Libero under MS-DOS. The 32-bit OS/2 version should run under MS-DOS. You may also want to try the 32-bit MS-DOS version if you run Windows NT or 95.

    Installing the 32-bit MS-DOS Kit

    Download Libero for MS-DOS. This was compiled with Microsoft VC 4.0 as a 'console application'.

    Create a new directory and extract lrdos32.zip there. This creates the set of files that you need to run Libero under Windows 95 or Windows NT in a DOS box. You must either add this new directory to your path, or copy certain Libero files into a directory (like C:\UTILS) which is already on your path:

    LR32.EXE supports long filenames and mixed-case filenames.  

    Installing the OS/2 Kit

    Download Libero for OS/2 [117Kb]. This was compiled with EMX 0.9b, using GCC. Ewen says:

    The binary was compiled using EMX 0.9b (I think it should run fine with EMX 0.9c, the latest version), using: gcc -O2 -Zomf -s lr????.c -o lr.exe As usual people will need the EMX runtime libraries to run the executable; they're on most OS/2 ftp sites including hobbes.nmsu.edu, ftp.cdrom.com, and ftp.leo.org. Since quite a few programs need them, most people have them installed already. It should run okay with the EMX 0.9b runtime libraries and the EMX 0.9c runtime libraries (which are the current ones).
    Oh, BTW, it'll probably run okay under DOS as well (as a 32-bit version) with either the EMX.EXE extender (included in the runtime DLL package mentioned above), or with RSX (I think it's called) which is a DMPI equivilent for EMX.EXE. Being a 32-bit version it might solve ram-cramp issues if anyone is having problems.

    Create a new directory and extract lros2.zip there. This creates the set of files that you need to run Libero under OS/2. You must either add this new directory to your path, or copy certain Libero files into a directory (like C:\UTILS) which is already on your path:

    Installing the VAX/VMS Kit

    To install Libero for Digital OpenVMS you can either download the binary kit for VAX/VMS or the source kit, if you have a C compiler (Vax C, Dec C, or GNU gcc). Currently we do not have a binary kit for Alpha OpenVMS - you must build from the source kit. To rebuild from the source kit, download /pub/libero/src/lrsrc232.zip and place it into a working directory. You need the unzip tool to extract it:

        $ set def [.temp]
        $ create/dir []
        $ ... upload LRSRCxxx.ZIP into this directory
        $ unzip -a -o lrsrc232
        $ @buildvax.txt
    

    If you do not have Unzip on your VMS system, consider installing it - it is well worth the bother. You can also unzip the sources on a PC and upload them from there.

    The file BUILDVAX.TXT is a procedure that compiles and links Libero. Run it (@buildvax.txt). Then copy these files into an installation directory which is world-readable:

    To run Libero your LOGIN.COM file must include these lines:

        $ define/nolog path "directory";  ! name of Libero directory
        $ lr :== $$disk[directory]lr.exe
    

    Installing the Examples

    Each example archive is self-complete, and comes with a small readme.txt file.

    This is the contents of the examples directory:

    acms.zip         49027 96/10/20 19:51:02  ZIP archive
    complete.zip    275949 96/12/28 21:53:00  ZIP archive
    config.zip       12030 96/10/20 19:51:20  ZIP archive
    erbot.zip        20748 96/10/20 19:51:28  ZIP archive
    expr.zip        101786 96/12/28 21:51:56  ZIP archive
    htmlpp.zip      128504 98/11/08 18:54:38  ZIP archive
    install.zip      11276 96/10/20 19:51:48  ZIP archive
    picture.zip      37806 96/10/20 19:51:54  ZIP archive
    stripper.zip      6944 96/10/20 19:52:02  ZIP archive
    tcpip.zip        16960 96/10/20 19:52:08  ZIP archive
    

    Configuring and Testing Libero

    When you have installed Libero, run the lr command without arguments. This should show a page of help.

    Try this from the installation directory, then from a different directory. If Libero cannot find its message file, you'll get an error like this:

    C:\CTOOLS\LIBERO>lr
    LIBERO v2.32 (c) 1991-97 iMatix 
    Options file 'lr.ini' not found.
    Cannot find message file lrmesg on 'PATH' path.
    

    Type 'path' if you're working on MS-DOS, 'echo $PATH' if you're on UNIX, or 'show log path' if you're on VMS. Check that the installation directory is on the path.

    Next, type the command 'lr -settings' to see what settings Libero is using:

    C:\CTOOLS\LIBERO>lr -settings
    LIBERO v2.32 (c) 1991-97 iMatix 
      Option settings for command:
        animate  . . .: No
        author . . . .: Your Name
        check  . . . .: Yes
        compress . . .: No
        defaults . . .: defaults
        dpath  . . . .: -
        generate . . .: Yes
        help . . . . .: No
        idle . . . . .: Yes
        initial  . . .: lr.ini
        option . . . .: -
        path . . . . .: PATH
        plusd  . . . .: No
        pretty . . . .: No
        schema . . . .: lrschema.c
        settings . . .: Yes
        sort . . . . .: Yes
        source . . . .: -
        stats  . . . .: No
        stubs  . . . .: Yes
        style  . . . .: plain
        trace  . . . .: No
    

    You can change any of these default option settings by editing the lr.ini file, or by placing the settings you want in your dialog file.

    Using Libero

    Running The lr Program

    To run Libero, type the 'lr' command like this:

        $ lr [options] dialogname...
    

    Libero is a code generator. It works as follows: you write a dialog description, and Libero generates (by default) C code to execute the dialog. This C code contains (a) various tables and definitions, and (b) the FSM engine. If necessary, Libero also generates (c) a skeleton C program to wrap the whole thing together. When you change your dialog (many times during program development), Libero re-generates (a) and (b) and any new bits needed in (c).

    When you run the lr command, Libero goes through these steps:

    The standard C schema is LRSCHEMA.C. This schema creates two include files, with the dialog's name, but extensions '.D' and '.I'. The first of these contains the DATA, the second the INTERPRETER. Other schemas (for other programming languages) have different conventions.

    Dialogs have the extension .l, though you can be bloody-minded and use other extensions. On systems that support long filenames, the name can be up to 128 characters long, including the extension. I seriously recommend that you stick to eight-character filenames for anything that is remotely useful. When I were a lad, filenames were one hex digit. To create a new dialog, take skeleton.l and copy it. I recommend that you give a dialog the same name as the program it drives. For instance, a source file myprog.c would use a dialog myprog.l. Sometimes I append a 'd' to the name to make the filename (without extension) unique. Then, for instance, the .bak files that my editor creates carry different names.

    The Dialog File Syntax

    This is the skeleton.l file - it is a useful starting point for new dialogs:

    !
    !   myprogd.l   Dialog description for myprog.c
    !
    !   Written:    yy/mm/dd  My name
    !   Revised:    yy/mm/dd
    !
    !   Syntax:  '!' comment header
    !            -option[=value]
    !            [ state-name ':' [ '<'Superstate-Name'>' ]...
    !                  [ '(' event-nbr ')' event-name [ '->' next-
    state ]
    !                  [ '+' module-name ]... ]... ]...
    !
    -source=myprog
    
    After-Init:
        (--) Ok                         ->
              +
        (--) Error                      ->
              + Terminate-The-Program
    
    Defaults:
        (--) Exception                  ->
              + Terminate-The-Program
    

    The dialog file defines a series of states: the first state in the dialog is the starting state, and the remaining states can come in any order. You cannot define the same state twice. A state carries a name, which is purely for your (the reader's) information. A state allows one or more events. The ordering of events within a state is not important. Each event looks like this:

        (--) Event-Name                 -> Next-State-Name
              + Module-To-Execute
              + Module-To-Execute
              ...
    
    The module list is executed when that event occurs in the state. The module list can be empty. The next state name can be blank, meaning 'the current state'. Otherwise the next state name refers to another state within the dialog, excluding the initial starting state.

    The module names correspond to functions, blocks, procedures, etc. in the program. Since various languages and individuals like different conventions for naming modules, Libero always uses Names-Like-This, which may refer to pieces of code labelled 'NamesLikeThis', 'names_like_this', 'NAMES-LIKE- THIS', etc.

    The program always refers to events by adding the suffix '-Event'. Thus, if a dialog refers to an event 'Ok', the program will refer to 'Ok-Event', allowing for whatever name-mashing the language imposes.

    A program never refers to state/next-state names. These are there only to make the dialog more readable, and useful. You could, if you wanted to, refer to states as 'S1', 'S2', 'S3', instead of 'Expecting-Token', 'Have- Number', 'Have-String'. Hahaha. I've seen people doing this.

    An event name '$other' is taken to mean 'all other events'. This is shorthand for typing all other events in the dialog: any event you did not already handle in the state gets the '$other' handling.

    Note that:

    Libero Options

    Like most command-line programs, lr accepts various options. These control things such as the language that Libero generates, the filenames it uses, etc.

    You can supply an option in various ways:

    It sometimes gets a little complex to remember which option is active, especially if you put the same option in various places. For instance you could put '-nocheck' in lr.ini, then run the lr command with the ' check' option. Which takes precedence? The answer is: it works like it should. This is a somewhat arrogant statement, so I'll explain:

    If you are unsure of the options in effect for your dialog, run lr with the -settings option. This displays the current options. You can specify a Libero option in various forms:

    -option
    Normal usage.
    -option=value
    Option takes value.
    -option:value
    Also okay.
    -option="value"
    Don't convert value to lowercase.
    -option:'value'
    The same as above.
    -nooption
    switch off true/false option.
    -option=no
    -option=false
    The same as above.

    You can abbreviate an option name to 3 or more characters. Option values are converted to lower-case unless you place them in single or double quotes. Some options take values; others are true/false options. These is a summary of the options that lr accepts:

    Option            Explanation                        Default
    
    -animate          include animation code             -noanimate
    -author=name      include animation code             ="My Name"
    -check            generate full runtime checks       -check
    -compress         compress generated tables          -compress
    -defaults=state   name of defaults state             =defaults
    -dpath=variable   path for dialog and ini files      ="PATH"
    -generate         generate output code               -generate
    -help             show this information              -nohelp
    -idle             indicate idle modules              -noidle
    -initial=file     read options from file             =lr.ini
    -option=list      define generation options          none
    -path=variable    path variable for schema files     ="PATH"
    -plusd            enforce 'source+D' naming rule     -noplusd
    -pretty           pretty-print dialog file           -nopretty
    -schema=file      schema for code generation         =lrschema.c
    -settings         show option settings per file      -nosettings
    -sort             add module stubs alphabetically    -sort
    -source=file      program for dialog                 name of dialog
    -stats            show dialog statistics             -nostubs
    -stubs            generate stubs for new modules     -stubs
    -style=plain      naming style: some_module_name     -style=plain
    -style=caps       naming style: SomeModuleName
    -style=headline   naming style: Some_Module_Name
    -style=title      naming style: Some_module_name
    -style=normal     naming style: Some-Module-Name
    -style=cobol      naming style: SOME-MODULE-NAME
    -trace            trace code generation to list file
    

    And now in detail:

    Using Exceptions

    Exceptions are basically a tool to reduce the size of a dialog. Instead of defining an extra state to handle a decision (typically the Ok/Error outcome of some work), you assume it's Ok, and carry on with the next action module directly. If it's Error, you raise an exception, which is like a jump to that event in the same state. Any module in progress is completed, but any further modules are not executed by the dialog manager. It's often useful to put exception events into the Defaults state, since certain kinds errors can occur in any state. So you can raise an exception in any code, and know that it will be handled the same way, in the Defaults state. Sometimes we override such central handling by defining specific handling for an exception event in a specific state. Exceptions are thus used to make small control steps within a state (error handling is just one specific example). Such control steps can be used for any purpose. Overusing exceptions leads to impenetrable dialogs.

    Exceptions are a powerful technique that make your dialog leaner and meaner. In a typical dialog many actions do some work that may result in an error condition. For instance, an action that reads some data from a file may discover that the file is no longer accessible due to a 'hard' error (protection, disk crash, insufficient memory,...). Without exceptions, the only way to handle such possibilities is to define additional states - for instance, 'after-reading-data', that check the outcome of each action with a questionable outcome.

    This quickly leads to dialogs with dozens of states that serve only to keep track of these minority cases. In the majority of cases, the errors never happen. This means that the main logic of the dialog can get diluted by all the extraneous states. With exceptions, you write the dialog as though errors were incidental. When an error happens, you treat it as a special case, as an 'exception'. Otherwise, the dialog carries on normally.

    Most real dialogs use exceptions, sometimes heavily, for two reasons:

    1. Exceptions are a good way to handle errors.
    2. Exceptions can make a complex dialog simpler.

    A module raises an exception by setting the variable Exception-Raised to a 'true' value. The module then supplies an event in The-Exception-Event. Quite often I use an event called 'Exception' when I use an exception event to handle fatal errors. This is how we raise an exception using an event called Exception-Event in various languages:

    C and C++:

        exception_raised = TRUE;
        the_exception_event = exception_event;
    

    80x86 assembler:

        mov exception_raised,1
        mov the_exception_event,exception_event
    

    Korn Shell:

        exception_raised=TRUE
        the_exception_event=$exception_event
    

    COBOL:

        MOVE "YES" TO EXCEPTION-RAISED
        MOVE EXCEPTION-EVENT TO THE-EXCEPTION-EVENT
    

    Visual Basic:

        exception_raised = True
        the_exception_event = exception_event
    

    In most languages (except COBOL) Libero provides a function or macro 'Raise-Exception' that does the above. See the later sections on using Libero in each language for details.

    The dialog normally executes all the action modules for an event-in-state, one by one, then switches to the next state for the event. An exception interrupts this flow. The dialog handles exceptions in two steps:

    1. It checks Exception-Raised after executing each module. It this variable has become 'true', it stops and checks the value of The-Exception-Event.
    2. It looks for The-Exception-Event in the current state (or Defaults), then executes the action modules for that event. These modules can in turn raise an exception.

    When a module raises an exception, the dialog does not care what the next state was supposed to be. This is no longer important, since the exception event will determine its own next state.

    Libero also allows a short-cut: if the exception event is the same as the current event, you don't need to supply it. If you just set Exception-Raised but not the event, the dialog loops back to the same event in the same state. Honestly, I don't use this very often. I prefer to separate exception events from 'normal' events.

    Take the example of three actions which the dialog executes in series. First, it reads some data from a file. Then it parses and converts the data. Lastly, it writes the data to another file. Each of these actions has a normal outcome, and possible exceptional outcomes (error during reading, invalid data, error during writing, etc.). If we write the dialog without exceptions, we need these three states:

    Before-Reading:
        (--) Ok                         -> After-Reading
              + Read-Data-From-Input
        (--) Error                      ->
              + Terminate-The-Program
    
    After-Reading:
        (--) Ok                         -> After-Parsing
              + Parse-and-Convert-Data
        (--) Error                      ->
              + Terminate-The-Program
    
    After-Parsing:
        (--) Ok                         -> Another-State
              + Write-Data-To-Output
              + ...
        (--) Error                      ->
              + Terminate-The-Program
    

    In this case, Read-Data-From-Input does something like this (I use C as the language):

    MODULE read_data_from_input (void)
    {
        if (read_from_file (file, input) != NULL)
            the_next_event = ok_event;
        else
            the_next_event = error_event;
    }
    

    By using an exception event (here, Error), we can collapse the three states into a single state:

    Before-Reading:
        (--) Ok                         -> Another-State
              + Read-Data-From-Input
              + Parse-and-Convert-Data
              + Write-Data-To-Output
              + ...
        (--) Error                      ->
              + Terminate-The-Program
    

    Read-Data-From-Input now does this:

    MODULE read_data_from_input (void)
    {
        if (read_from_file (file, input) == NULL)
            raise_exception (error_event);
    }
    

    Some hints about using exception events:

    1. Don't over-use exceptions. What you gain from a more compact dialog you can loose in unclear logic. Remember that exceptions should be used for error handling and special cases, not for primary logic.
    2. Choose good names, and use one exception event for the same purpose everywhere. For instance, use an exception event Fatal-Error to indicate a fatal i/o error that causes the dialog to halt.
    3. The Defaults state is an ideal place to handle exceptions which may occur in many places. However, do place specific exception handling in any state where it is useful.

    Using The Defaults State

    You can define a single Defaults state in any dialog. You use this state to simplify dialogs. Often, the same event can happen in many states, with the same effect. Typically, you want to handle a fatal error at any point by displaying a message and halting the program.

    Rather than repeating the event and its (identical) actions in every state where the fatal error may happen, place it in Defaults. If the event ever happens, Libero looks in the current state first. If it cannot find the event there it looks in the Defaults state. The Defaults state is ideal for error exception events that require central handling.

    After processing the actions for the event, the dialog sets the next state as follows: if the specified next state is empty, it returns to the original state. If some next state is specified, it goes to that state.

    When you use a Defaults state, the generated code for your dialog does not get smaller. It will actually get a little larger. But your dialog source code can usually be simplified, and this is always a good idea.

    Using Sub-Dialogs

    Libero supports a simple state call/return mechanism. This lets you build sub-dialogs that are called from other places in the dialog. In the 'calling' state, use the action Dialog-Call to call the sub-dialog state:

    Calling-State:
        (--) Ok                         -> Sub-State
              + Do-Some-Normal-Work
              + Dialog-Call
        (--) Return                     -> Next-State
              + Continue-After-Call
    

    The Return event controls what happens when the sub-state returns. The Return event can be in the same state, or in the Defaults state.

    The sub-dialog can consist of one or many states. You should probably use some kind of naming convention to indicate that these states go together. At some point, the sub-dialog uses the action Dialog-Return. This ends the sub-dialog and continues in the calling state with the Return event.

    Using Super-States

    Some dialogs have a lot of states that are similar except for small differences, like Expecting-Initial and Expecting-Operand above. We can write these in a more compact way using a shorthand technique called super-states. We put the super-state name between '<' and '>' after the state name:

    Expecting-Initial: <Expecting-Operand>
        (--) End-Mark                   ->
              + Terminate-The-Program
    

    The state inherits the events in the super-state, if they are not already defined. In the above example we say that Expecting-Initial inherits events from Expecting-Operand, and also handles the End-Mark event.

    A super-state can itself be based on super-states, to any level. My advice is: use super-states with care to simplify large dialogs. Don't exaggerate. Some more points about super-states:

    Using Templates

    The standard Libero schemas support templates. This just means that you can build a library of code that the generator will pull-in whenever you use it in a dialog.

    For example, let's say that you are working in a COBOL application where a lot of programs need to do the same work in Initialise-The-Program:

        MOVE LINKAGE-BLOCK TO OWN-LINKAGE-BLOCK
        PERFORM CHECK-LINKAGE-VALUES
        IF CURRENT-LANGUAGE = SPACES
            MOVE "ENGLISH" TO CURRENT-LANGUAGE
    

    You can either:

    Let's say you make a file called TEMPLATE.cob. This looks like this:

    <Initialise-The-Program>
        MOVE LINKAGE-BLOCK TO OWN-LINKAGE-BLOCK
        PERFORM CHECK-LINKAGE-VALUES
        IF CURRENT-LANGUAGE = SPACES
            MOVE "ENGLISH" TO CURRENT-LANGUAGE
    <END>
    

    Now, when you build a new program, Libero magically includes this code in the INITIALISE-THE-PROGRAM section. This is how templates work:

    The file template provides an example that you can use for your own templates.

    Using Get-External-Event

    The Get-External-Event module is empty in most types of program, and very important in other ones. This module lets you centralise any kind of interface to the outside world that collects events. An example of this is the Rover subprogram we designed above. The dialog explicitly does a Wait-for-incoming-message before each new state. We can put this code into Get-external-event, which makes the dialog simpler.

    Here I also move all handling for Onhook to the Defaults state - assuming that the Stop-Local-Tones module is safe to use at any time:

    After-Init:
        (--) Ok                         -> Idle
              +
        (--) Error                      ->
              + Terminate-The-Program
    
    Idle:
        (--) Offhook                    -> Dialing-First
              + Start-Dial-Tone
              + Reset-Dialed-Number
        (--) Request                    -> Ringing
              + Start-Ringing-Local
    
    Dialing-First:
        (--) Digit                      -> Dialing-Next
              + Stop-Local-Tones
              + Have-Dialed-Digit
        (--) Whole-Number               -> Seizing
              + Stop-Local-Tones
              + Send-Outgoing-Request
    
    Dialing-Next:
        (--) Digit                      -> Dialing-Next
              + Have-Dialed-Digit
        (--) Whole-Number               -> Seizing
              + Stop-Local-Tones
              + Send-Outgoing-Request
    
    Seizing:
        (--) Accept                     -> Ringing
              + Start-Ringing-Remote
        (--) Busy                       -> Seize-Failed
              + Start-Busy-Tone
        (--) Reject                     -> Seize-Failed
              + Start-Reject-Tone
    
    Seize-Failed:
        (--) Onhook                     -> Idle
              + Stop-Local-Tones
    
    Ringing:
        !  Events from local phone
        (--) Offhook                    -> Talking
              + Stop-Ringing-Local
        (--) Onhook                     -> Idle
              + Stop-Ringing-Remote
        !  Event from remote phone
        (--) Answer                     -> Talking
              + Stop-Ringing-Remote
    
    Talking:
        !  Event from local phone
        (--) Onhook                     -> Idle
              + Send-Outgoing-Release
        !  Event from remote phone
        (--) Release                    -> Idle
              + Start-Released-Tone
    
    Defaults:
        (--) Onhook                     -> Idle
              + Stop-Local-Tones
    

    Efficiency

    Efficiency of The Generated Code

    People sometimes ask of Libero: "But does it produce efficient programs?". Bad grammar apart, this question gets a considered answer. Usually I say: "What do you mean by 'efficient'?", or "Does a hammer build a solid table?". I have a practical definition of 'efficient': if a program is easy to write, easy to maintain, does the job on time, and no-one has trouble with it, it's efficient. It's also unusual, but that is another soap-box.

    When I wrote Libero, my true goal was to make programming easier and safer, whatever the language. This also gives me a convenient stock answer: if you write a program using Libero, and you find that it runs too slowly, you can rewrite it in a faster language quickly and cheaply.

    Efficiency of The Dialog

    First of all, the size of a dialog has only a slight relationship to the speed of the program. A very large program will load and maybe run slower than a small one, but the program has to be a lot larger.

    Secondly, adding a new state, event, or action module will make the generated code larger. If a dialog has 10 states, then a new event will add 20 words to the program size. If a dialog has 50 states, then a new event will add 100 words. A dialog with 100 states and 100 events will use a whopping 20,000 words of storage for its main tables. It is possible to compress large FSM tables, but I never had a problem that was so complex that it was worth it. It is a function that one could add to Libero in the future. However, a program this complex is likely to be several tens of thousands of lines long.

    So, I generally tell people: if you make your dialog smaller, do it because it is better design, not because it is more efficient.

    Care and Feeding of Dialogs

    Style - Good, Bad, and Ugly

    Some of the worst dialogs that I have seen were written by people who were trying to make their dialogs 'efficient'. It is not easy to write elegant and readable dialogs; it is a little like making pancakes: give the first one to the dog. I have some advice: ask a colleague to read the dialog and explain how the program works. If your colleague cannot understand something, rewrite it.

    The following extracts are from real-life dialogs that went wrong. I changed names and did some surgery to protect the guilty, and myself from prosecution. All I can say in defence of the authors was that these were early works, and that they had too little training.

    Example 1: the author tries to reduce the number of states by looping back to the same state several times. Then he (I know the lad, so I can safely use the pronoun) compounds the sin by using event names that are specific to the state:

    Valorisation:
        (--) Valori-Not-Ok              -> Valorisation
              + Update-Valorisation
              + Get-Next-Valorisation
        (--) Valori-Ok                  -> Valorisation
              + Process-Valorisation
              + Get-Next-Valorisation
        (--) Ok                         -> Valorisation
              + Test-Valorisation
        (--) No-Valori                  -> Valorisation
              + Get-Next-Valorisation
        (--) No-More                    -> Transfer
              + Get-First-Transfer
    
    Transfer:
        (--) Transf-Not-Ok              -> Transfer
              + Update-Transfer
              + Get-Next-Transfer
        (--) Transf-Ok                  -> Transfer
              + Process-Transfer
              + Get-Next-Transfer
        (--) Ok                         -> Transfer
              + Test-Transfer
        (--) No-Transf                  -> Transfer
              + Get-Next-Transfer
        (--) No-More                    ->
              + Close-Data-Files
              + Terminate-The-Program
    

    This is the advice I gave:

    Example 2: the author is struggling with a complex batch process. At this stage he has to do a whole series of actions, with one small difference depending on the contents of a key field:

    After-Test-Key:
        (--) Key-Empty                  -> Have-Order
              + Save-Current-Order-Record
              + Get-Contract-For-Order
              + Get-Company-For-Contract
              + Create-Invoice-Header
              + Update-Company
              + Save-Contract-Key
              + Call-Batch-012
              + Call-Batch-013
              + Get-Next-Order-Record
        (--) Key-Not-Empty              -> Have-Order
              + Treat-For-Current
              + Save-Current-Order-Record
              + Get-Contract-For-Order
              + Get-Company-For-Contract
              + Create-Invoice-Header
              + Update-Company
              + Save-Contract-Key
              + Call-Batch-012
              + Call-Batch-013
              + Get-Next-Order-Record
    

    This was my advice (resisting the urge to say 'junk it!'):

    Example 3: the author has used some other design method to start with, and then translated the result into a dialog. The events have names like 'Case-1', 'Case-2', or 'Ok' and 'Not-Ok'. I've seen this happen often, and it never works. If the project leader is not prepared to use a dialog as a primary design method, there is no benefit in trying to translate another design into a dialog. You must in such cases rewrite the dialog cleanly. A dialog is primarily for a readership, and secondarily for the computer. Ignore this at your peril: you will produce large, incomprehensible dialogs that are error-prone and fragile.

    This is an approach that is so common, and so wrong, that the warning bears repeating: Do not try to mechanically convert existing designs into dialogs. It will not work. You will make a fool of yourself.

    Often, this also applies to designs already expressed as state machines. In one of the worst cases I ever saw, the culprit had mechanically converted a set of state-machine based designs into dialogs. The states were called S1, S2, S3,... The events were called E1, E2, E3,... The modules were called M1, M2, M3,... This happened in 1986. In January 1996 I watched someone painfully maintain one of these programs, trying to understand what E4 in S3 actually meant. Okay, I warned you. Now back to the main story.

    These are some more common problems:

    Choosing Names

    These are some guide-lines for names in a dialog:

    A common beginner's mistake is to confuse events and modules, so that a state has events called 'Delete-Record', 'Print-Record', and so on. It can help to remember that event names must be as general as possible without losing meaning.

    One helpful comparison is to think of events as push-buttons in a pop-up window box. Common events are 'Ok' and 'Cancel', 'Print', 'Delete', and 'Help'. A push-button like 'Delete Next Record' is too specific.

    When To Use Libero

    Don't hammer screws, or vice-versa. If a program is like a building, then Libero is like an architectural design that automatically forms the foundation and scaffolding. In any programming language there are directions and complexities where Libero is worth using. Until you get there, you'll be losing time.

    As far as I can see, these are some typical cases where you will profit from Libero:

    Libero Examples

    Libero 'install' Script for UNIX

    The source files for this package are in install.zip.

    This is a nice example of a UNIX script that is complex enough to warrant a dialog. The script shows a small menu, and does various things depending on the choice:

    Libero installation script                       Version 2.20
    -------------------------------------------------------------
    This script installs or de-installs Libero on your Unix system.
    If you are going to install into a system binary directory you
    will need write-access to the system directory.  You can also
    run Libero from the current directory.
    
    UNIX system="aix", compiler="cc", options="-O2"
    -------------------------------------------------------------
    
    Choose an action:
      (b)uild Libero and optionally install
      (d)e-install Libero
      (c)lean-up current directory
      (q)uit
    
    Choice:
    

    This is the dialog:

    -schema=lrschema.ksh
    
    After-Init:
        (--) Ok                         -> Have-Package
              + What-System-Are-We-On
              + What-Package-Do-We-Have
    
    Have-Package:
        (--) Source                     -> Before-Action
              + Get-Source-Install-Action
        (--) Binary                     -> Before-Action
              + Get-Binary-Install-Action
    
    Before-Action:
        (--) Build                      -> Have-Install-Directory
              + Check-All-Files-Exist
              + Compile-All-Subroutines
              + Link-Main-Programs
              + Get-Directory-Name
        (--) Install                    -> Have-Install-Directory
              + Get-Directory-Name
        (--) Delete                     -> Have-Delete-Directory
              + Get-Directory-Name
        (--) Cleanup                    -> Have-Package
              + Clean-Up-Directory
              + What-Package-Do-We-Have
        (--) Error                      -> Have-Package
              + What-Package-Do-We-Have
    
    Have-Install-Directory:
        (--) Ok                         -> Have-Package
              + Check-Directory-Writable
              + Install-Product-Files
              + What-Package-Do-We-Have
        (--) Current                    -> Have-Package
              + What-Package-Do-We-Have
        (--) Error                      -> Have-Install-Directory
              + Get-Directory-Name
    
    Have-Delete-Directory:
        (--) Ok                         -> Have-Package
              + Check-Directory-Writable
              + Delete-Product-Files
              + What-Package-Do-We-Have
        (--) Error                      -> Have-Delete-Directory
              + Get-Directory-Name
    
    Defaults:
        (--) Error                      ->
              + Return-Error-Feedback
              + Terminate-The-Program
        (--) Quit                       ->
              + Terminate-The-Program
    

    The script reads install.cat to tell it which files to handle. For example, this is the module Compile-All-Subroutines:

    ######################   COMPILE ALL SUBROUTINES ######################
    
    function compile_all_subroutines
    {
        test -f *.o && rm *.o
        for FILE in `awk '/^&.*@c/ {print $2}' $CATALOG`; do
            echo "Compiling $FILE..."
            $CCNAME -c $CCOPTS $FILE
        done
    }
    

    COBOL Picture Parser

    The source files for this package are in picture.zip.

    One of my still-unfinished projects is a portable COBOL compiler. I was writing a blindingly fast parser when I got sidetracked with Libero at the start of 1995. Anyhow, I found that lex was not really good at some of the syntactic warble that COBOL allows, so I finally wrote a specific analyser. The COBOL picture is one of the hairier aspects of the language. I wrote a function, with a dialog, that was robust and fast.

    The function takes a picture string and returns its size, type, and various other bits of info. If the picture is not correct it returns an error message.

    It took just over a week, part-time, to write and test this program, so it can't have been too hard. I wrote a test program that shoves a load of test pictures through the function and prints the results. If you are running MS-DOS, you can try the testpict program. It outputs stuff like this:

    9(3)                        :                      size=3 numeric
    9(3)V                       :                      size=3 numeric
    V9(3)                       :                      size=3 numeric 3 decs
    9(3)V9(3)9(3)9(3)9(3)9(3)   :                      size=18 numeric 15 decs
    9(01                        :                      size=0 undefined
       ^
    ')' missing after repeat specifier.
    ZZZZZ.ZZ9                   : ZZZZZ.....ZZ         size=12 undefined 2 decs
            ^
    decimals cannot be partly zero-suppressed
    9999,(2).(2)                : 9999,,..             size=8 undefined
             ^
    (...) must follow one of: B X Z 0 9 * / + - , $
    

    This package might be useful if you need to analyse COBOL code. There is a lot of it around (150 Gigalines at last count) so this is not such a far-fetched idea.

    There are also some nice functions in the package; for instance some pretty fast file i/o code, which is non-portable enough to squeeze the best out of the C libraries on a number of systems.

    Expression Parser

    The source files for this package are in expr.zip.

    The expression parser that I use in the introduction as an example is a real program that has cropped-up in various places, including in Libero. Often, when I make a new language schema for Libero, I rewrite the expression parser as a test.

    This package has the same program, more or less, written in C, C++, Java, COBOL, Rexx, 8086 assembler and Visual Basic. The ETKPAK archive at TOSC contains a more complete version of the COBOL program that supports mathematical functions.

    Configuration Management System

    The source files for this package are in config.zip.

    On one project I worked on, a friend of mine, Patrick Bedert, had to write a configuration management tool. This tool had to keep track of each of several thousand programs in the system we were developing.

    A program has a life-cycle with various stages: in development, in testing, in integration, in production, etc. The developers can work on a program only when it is in a certain stage.

    After some thought, and lunch, we said this: 'Basically the life-cycle or a program is a state-machine. The states are the life-cycle stages. The events are the actions that the developer wants to, or does, do on the program.'

    So we designed the life-cycle as a Libero dialog. The events were the 'sensitive' actions which either depended on the life-cycle stage, or which changed it. For example, you could only compile a program if you had previously reserved it for modification. You could only do this if it was in the development stage.

    We wrote this program as a UNIX shell script. The script takes a program and action as argument, and returns an exit code that is 0 (okay) or 1 (not okay).

    Whenever a developer wants to do a sensitive action, the script fires-up the dialog. It loads the last state for the program into LR_state, and converts the action into an event. The dialog then runs one cycle, and either accepts the event and perhaps moves to a new state, or rejects the event. We save the state for each program in one file. A small utility, gate, makes sure that this file is never accessed by more than one process at a time.

    I can't supply the actual code, since this belongs to the client who paid for the work. However, I provide a small example, control, that should give you the general idea. Note that this dialog uses a hacked version of lrschema.ksh, with some extra code to generate a table of state names.

    This was a nice example of using a dialog to describe a real problem that is normally beyond the scope of software. It is also the slowest-running program I have ever seen; one step through the dialog can take days!

    Multithreaded Agent For VAX/ACMS

    The source files for this package are in acms.zip.

    One of the most complex programs I ever wrote was a multithreaded terminal handler for a reservation system running on a Digital VAX/ACMS system.

    Digital VAX/ACMS is a transaction processor somewhat akin to IBM's CICS, or Tuxedo for UNIX. In the ACMS model, people work on a front-end program called an 'agent'. The application programs run on the main system, the 'server'. This is a good way to split the client-server responsibilities.

    In our project we were unable to use any of the standard Digital agents (e.g. for DecForms), since we wanted to do our own screen handling. The only recourse was to write our own agent. This is easy enough when each user can run in their own interactive session. The agent program is then a normal program which can handle a terminal and talk to ACMS.

    The fun starts when you need to connect several hundred users (in our case up to a thousand). VMS, like any operating system, cannot handle so many interactive sessions. The only valid approach is to handle all the terminals in a single process. This means writing a multithreaded program where each thread manages one terminal.

    Perceived wisdom has it that this type of work is deadly difficult, and we had something of a fight to convince the client that it would actually work. The schedule was tight, and there was no time (nor will, on my part) to develop the multithreaded code using conventional techniques.

    So, I took the standard Libero C schema and changed it around so that it generated multithreaded code. The multithreaded schema actually generates a tiny kernel that handles the multithreading aspects. The code you write has to be careful, as always, but is only a little more complex than normal single-threaded code.

    We beat the schedule, and made the client happy. The multithreaded agent is about 3500 lines of code, and a hairy program. Still, it has run without trouble for several years, efficiently, and survived several meaty changes.

    E-mail Robot - ERBOT

    The source files for this package are in erbot.zip.

    A fairly complex Awk program, erbot is a kind of remote agent that you can ask to do work via email. For instance, you can use erbot to upload or download files, run commands, etc. all by e-mail. It is password protected, and still experimental.

    HTML Preprocessor

    The source files for this package are in htmlpp.zip.

    Htmlpp is a preprocessor for HTML files, and is intended to simplify the task of maintaining large sets of HTML documents. You provide htmlpp with a document that is a mix of HTML-tagged text and htmlpp commands. Htmlpp generates a set of HTML files from that document.

    During this process, htmlpp replaces symbols, reads include files, builds tables of contents, and generally does a lot of otherwise tedious and error-prone manual work. To use htmlpp you should be happy writing HTML with a simple text editor.

    This is an old version of htmlpp, since this tool is continually getting better. Check-out the links on the iMatix home page for the latest version of htmlpp.

    Complete Examples Archive

    You can download the complete examples package in complete.zip. Unzip this and you get all the example archives. For best results, unzip each one into a separate directory.

    Libero's Languages

    Choosing A Language

    Libero gives you the freedom you get to choose the language that suits your purpose. The way I add support for a particular language is to write a new schema file. This is a kind of script that tells Libero how to generate code for that particular language. Inevitably, the solution I choose for a language tends to vary. For example, the C schema generates a couple of include files to hold the generated code. The UNIX shell schemas in contrast take a source 'module' file and generate the final script (which you don't work on directly).

    This section documents the special aspects of each language (ie. schema). Some aspects that apply to all languages:

    Standard Public Names

    All schemas should support these names, which change syntax according to the -style option:

    Contributed Schemas

    These schemas were contributed by Libero users, and are not documented in detail here. You will need to read the schemas and try them out to see how they work.

     

    The Awk Language

    Overview

    What Is Awk?
    Awk is a standard UNIX tool that comes in several flavours - 'old' awk (/bin/awk), new awk (/bin/nawk), GNU gawk (/bin/gawk). It is a small interpreted language that looks a bit like C, with a strong flavour of sed pattern matching. Awk is mostly used to manipulate text files, but is also fun as a general-purpose programming language.
    Portability
    GNU gawk is available on many platforms. Awk scripts are highly portable if you don't rely on (UNIX) system-dependent commands. GNU Gawk and new awk share most functions.
    Pros and Cons
    Pros: Awk is versatile and easy to learn; GNU gawk is freely available; Awk is an interpreted language and looks a bit like C. Cons: Awk is a simple language, not powerful enough for industrial-strength work; it's interpreted - slower than C but faster than a shell; it looks a bit like C.
    How To Get It
    On UNIX boxes, awk or nawk. On other systems, get GNU gawk.
    Libero Supports
    Libero supports new awk and gawk, but not old awk.

    Working With Awk

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    function module_function_name  ( local_var,... ) {            # Place local variables here
        return                      # Empty module needs 'return'
    }
    function private_function_name (argument,...) {
        the_next_event = ok_event
        raise_exception (error_event)
    }
    

    A fuller example.

    Notes

    My awk programs fall between two extremes. At the one end is a 'filter', to process text according to patterns in each line. This is excellent for smallish problems. As the problem grows, I need to read other files 'on the side', and the logic of my awk program grows until a large part of it is sitting in the BEGIN or END blocks. Eventually I find that I am perhaps reading and writing several files, so I take the plunge and rewrite the program to handle all files with explicit 'getline' calls. The awk program is no longer a filter, but a stand-alone program that resembles a classic procedural program. Of course at this point my Libero hindbrain wakes up and starts muttering 'an' whaddabout Libero, eh?'.

    When I wrote the awk schema, I started with the Visual Basic schema. Bizzarely enough this language resembles awk most in terms of structure and possibilities. I've started thinking of awk as a kind of 'Basic for UNIX'.

    Awk presents some of the same difficulties as Visual Basic. There is no 'include' statement to take an external file as part of the program. The UNIX shells have the same problem.

    <GRIPE>Since awk is tokenised, then executed, messieurs A, W, and K had no real excuse for omitting an 'include' facility.</GRIPE>

    I really don't want to look at the chunk of code that Libero generates, so I always try to find a way to separate this from the hand-written code I am working on. If the language provides an 'include' statement, I place the generated code in an include file. This works great in serious compiled languages which all have something like this. You change the dialog, regenerate the code, and recompile. At any time you can change the program and recompile without having to run Libero again.

    For the awk schema I used another technique: you work in a separate 'module' file (myprog.mod). Libero combines the module file with the generated program to create a final program, which you don't change. This effectively hides the generated code, but introduces an extra step: each time you change the .mod file you must re-run Libero to create the final script. In effect, Libero acting as a preprocessor in place of the missing 'include' statement.

    Notice the slightly unusual format of a generated module header:

    function module_function_name  ( local_var,... ) {
        ...
    }

    I break the function header over two lines so that you can add local variables easily if you need to. Libero recognises the first line as a module header, and does not look at the next line with the local variables. The unusual format also distinguishes module functions from private functions, so ensures that the -idle option works correctly.

     

    The C Language

    Overview

    What Is C?
    C is part of UNIX originally, now almost a universal language. It is a very general purpose language, and typically used for systems programming. ANSI C is a widely supported standard.
    Portability
    ANSI C compilers are available on (almost) all platforms. ANSI C programs can be highly portable if you eschew compiler extensions, and don't rely on libraries that are not portable. Some UNIX vendors include non-ANSI compilers but charge extra for ANSI C compilers. Luckily the GNU gcc compiler is excellent and ported to most UNIX systems.
    Pros and Cons
    Pros: C is flexible and widely known; it is a small and clean language. C compilers are generally well-tuned and sometimes exceptionally good. Cons: no easy string handling; sloppy programmers write horrendous C code.
    How To Get It
    How to get it On UNIX boxes, 'cc', or 'gcc'. GNU gcc is widely available and well-regarded. Commercial C compilers are available for almost all platforms from mainframes to embedded systems.
    Libero Supports
    ANSI C, though you can freely use any extensions you need to. Libero generates ANSI C code, so your compiler must be happy with things like prototypes.

    Working With C

    Example of code

    Code fragments:

    /****************   MODULE FUNCTION NAME   *****************/
    
    MODULE module_function_name (void)
    {
                                 /* Empty module needs no code */
    }
    
    static type private_function_name (argument,...) {
        the_next_event = ok_event;
        raise_exception (error_event);
    }
    

    A fuller example.

    Notes

    I've used Libero to help me write C programs for a few years. It would be false to say I used Libero all the time; this is simply not the case. For example, I wrote Libero in C: there are 12 separate source files, and four of these have dialogs. I used a dialog whenever I had a single function that got rather complex. In Libero, this happens to be in:

    1. the module that parses and loads a dialog (lrload);
    2. the code generator module, which scans the schema and interprets the simple language embedded in the schema (lrcode);
    3. the two modules that evaluate expressions (lrcalc and lreval).

    It's probably no coincidence that all these modules do some kind of parsing. Libero is a code generator, so it's normal that the most complex modules are parsers. As a consultant, I've written some profoundly complex C programs using Libero. In at least one case I still don't see any other way that I could have done the project.

    When you write a C program using Libero, you tend to use a lot of source-local variables. This is disturbing to some programmers, who reckon that a program must consist of lots of internested modules that hide data from each other. Sure, this is one way of managing complexity. Still, modules in a Liberated program aren't nested; they all sit at the same level below the dialog. They all share (mostly) a common set of data objects. In real programs this seems to work nicely, so if you have problems with it, just close your eyes the first few times.

     

    The C++ Language

    Overview

    What Is C++?
    C++ is a recent mutation of C, generally known as 'assembler with objects'. Okay, perhaps a bit unkind. C++ is widely used, combines the generality of C with solid object-orientation. It's the language of choice for Windows GUI programming.
    Portability
    C++ is pretty portable. C++ compilers are available for most platforms, but you may find that the main reasons for using C++ - class libraries - are not portable.
    Pros and Cons
    Pros: C++ is powerful and widely known; it is object oriented. Cons: it's a large language to learn.
    How To Get It
    I guess most C++ users know the answers to this.
    Libero Supports
    Standard C++.

    Working With C++

    Example of code

    Code fragments:

    /////////////////   MODULE FUNCTION NAME   //////////////////
    
    MODULE Cprogram::module_function_name (void)
    {
                                 //  Empty module needs no code
    }
    
    Cprogram::private_function_name (argument,...) {
        the_next_event = ok_event;
        raise_exception (error_event);
    }
    

    A fuller example.

    Specific Schema Options

     

    The COBOL Language

    Overview

    What Is COBOL?
    COBOL is a standard language for business software, a job that it does well. It is heavily used in the mainframe world. COBOL has an undeserved reputation for inefficiency, and a deserved reputation for verbosity. Today, many COBOL applications are being moved from mainframes to UNIX boxes.
    Portability
    Deaf, dumb, blind COBOL programs can be very portable. Otherwise you can use external libraries to achieve 100% portability (catch: you may have to write these libraries yourself).
    Pros and Cons
    Pros: easy to use and teach. An excellent language for mediocre developers, which includes me at least two days of the week. Cons: Tends to encourage large, monolithic programs. Like unreadable C code, this is a question of discipline. COBOL provides no way to define data types directly, and has a verbose and over-rich syntax. COBOL compilers are often unimpressive. This is actually putting it mildly: on several systems that I've worked on, a simple loop in COBOL runs from 100 to 1000 times slower than the same loop coded in assembler. I've seen just one COBOL compiler (Versions 2 and 3 of Realia) that could produce truly excellent code.
    How To Get It
    No free implementations exist, despite various attempts -- including one by me: if a COBOL compiler is Mt. Everest, I got as far as London Heathrow airport. You need to find a commercial COBOL vendor for your system.
    Libero Supports
    ANSI 74 COBOL and ANSI 85 COBOL.

    Working With COBOL

    Example of code

    Code fragments:

    *****************   MODULE FUNCTION NAME   *****************
    
     MODULE-FUNCTION-NAME.
         EXIT
         .
    
     PRIVATE-FUNCTION-NAME.
         MOVE OK-EVENT TO THE-NEXT-EVENT
    
         MOVE ERROR-EVENT TO THE-EXCEPTION-EVENT
         MOVE    "YES"    TO EXCEPTION-RAISED
         .
    

    A fuller example.

    Notes

    COBOL has a reputation for encouraging large unwieldy programs. This is justified to the extent that the language is verbose. I think it's more due to the fact that COBOL programs tend to be written and maintained under particular pressures: get it working, now, and who cares if it is a little unwieldy. You don't often see COBOL code competing on the open market for software where program size and speed are important.

    People working in COBOL expect monsters, so often get them. Now, I've spent a lot of my life writing COBOL - perhaps half a million lines or so, and I know that this unnecessary suffering. Of course it is possible to write clean, organised, commented code without Libero. It is just a lot more effort, and I'm lazy.

    This schema is for batch programs and subroutines. Batch programs are often the largest and most complex COBOL programs, and often benefit from a dialog. I've not seen a lot of subroutines that really needed a a dialog; the expression calculator is one example. Also, if you split a large batch program into smaller subroutines, these can be complex enough to warrant a dialog. Very roughly, I would reckon that a COBOL program of more than 1000 lines is worth building around a dialog.

    Specific Schema Options

     

    The GNU Borne-Again Shell Language

    Overview

    What Is The GNU BASH?
    GNU BASH is the GNU project's UNIX shell. It replaces the (not really) standard UNIX Bourne and Korn shells.
    Portability
    BASH (/bin/bash) is available for all UNIX systems that I know of. BASH scripts can be ported to other UNIX shell languages: the closest is the Korn shell.
    Pros and Cons
    Pros: freely available and standardised. Follows the POSIX standard; has the best of all the UNIX shells in one package. Cons: not necessarily installed on your UNIX box.
    How To Get It
    Get it from your local GNU shoppe. BASH and the other GNU tools are part of all Linux distributions.

    Working With The GNU Borne-Again Shell

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    function module_function_name
    {
        return                      # Empty module needs 'return'
    }
    
    function private_function_name {
        the_next_event=$ok_event
        raise_exception $error_event
    }
    
     

    Programming with UNIX Shells

    1. You must remember to assign events and other variables as follows: the_next_event=$some_event. If you make the common mistake of forgetting the '$', the results are interesting but not useful. The schema checks against this error, at a slight cost in runtime. You can skip these checks by using the -nocheck option. The BASH language lets you declare numeric variables explicitly, so this check is not required in the lrschema.bsh schema.
    2. Your .mod file is not copied entirely, but in sections. The script schemas extract three sections marked with the keywords <HELP> to <END>, <DATA> to <END>, and <CODE> to <END>. You will see :include commands in the schemas that do this. The final <END> is optional, and probably best to ignore.
    3. The <HELP> section is copied to the header of the script. Each line in this section should start with '#'. Some of my scripts are self-anotating: if run with '-h' as argument, they do something like this:
          expand $0 | egrep "^# |^#$" | cut -c 4-80 | more
      
    4. The module Initialise_the_program gets the command-line arguments; ie. it can refer to $1, $2, in the normal way.
    5. The script_name variable contains the command-line variable $0. You could put something like this in initialise_the_program:
          if test "$1" = "-h"; then
             expand $script_name | egrep "^# |^#$" |        cut -c 4-80 | more
             exit
         fi
      

    Also note that:

    1. It is sometimes useful to start a script using the . operator. For instance, to trace a script you could use these commands:
      set -x; . scriptname
      If such a script ends with exit (and the generated ones do), you exit the current shell. If it's your main shell, you are logged-out. To avoid this, enclose the entire command in parentheses:
      (set -x; . scriptname) or run a sub-shell first.
    2. The auto-annotating function explained above won't work if you run the script using the . operator ($0 becomes the name of the shell...).
    3. These shell script schemas are somewhat slow. I optimised the code to use as many internal commands as possible in place of external commands. This cut the time spent in the dialog manager code by 60% over the first version. The bash and ksh versions are the most efficient; bsh does not have a let command, so I need to use the clumsy a = `expr 1 + 1` to increment a variable. The bash shell version is the cleanest, since I can use declare to define numeric variables. Any attempt to assign text to a numeric variable gives an explict shell error message.
    4. I use a function LR_idx to access arrays of numbers by shifting and returning a numeric value. This is the fastest method I could find, and it works in all three shell languages. As a consequence, there is a limit of 254 events (255 is the terminate_event).
    5. If you are doing heavy shell-type programming, consider using Perl.

     

    The Java Language

    Overview

    What Is Java?
    If you've been living in a cave for the last few years you might have missed the news that Java (also called 'Basic for Toasters') is going to replace every other language out there. This is perhaps a little exaggerated, but Java is a very interesting language all the same. Java is not too complex, though you need to learn quite a lot about various 'class libraries' before you can do anything useful with it. The best use for Java right now is to write portable 'plug-ins' that work with any recent web browser on many platforms.
    Portability
    Java is in principle 100% portable, but this claim may be qualified as Java gets implemented on more systems.
    Pros and Cons
    Pros: Java is similar to C++, though cleaner. It's portable and works well with post-1996 Internet browsers. Cons: it's interpreted, so slowish. You will spend some time learning to do anything useful with it.
    How To Get It
    You can download Java kits from Sun; you can buy various Java development environments.
    Libero Supports
    Java objects, console applications, and applets.

    Working With Java

    The README.DOC file included in the beta kit explains how to develop Java programs using Libero.

    A fuller example.

     

    The Microsoft Setup Basic Language

    Overview

    What Is Setup Basic?
    Setup Basic is the Scripting language used and pushed by Microsoft for MS Windows 3.x setup procedures. It is identical to the MS-Test Basic language, version 1.0.
    Portability
    Zero. Unless you are heavily into MS-Test.
    Pros and Cons
    Pros: standard for setup procedures, easily available. Cons: you have to be an experienced Windows developer, and have other visual development tools, such as dialog editors, to use it.
    How To Get It
    It is part of the Windows SDK.
    Libero Supports
    MS Setup BASIC / Test Basic 1.0.

    Working With Microsoft Setup Basic

    Example of code

    Code fragments:

    '****************   MODULE FUNCTION NAME   *****************
    
    Static Sub module_function_name ()
    
    End Sub
    
    Sub private_function_name Static (argument,...)
        the_next_event = ok_event
        raise_exception (error_event)
    }
    

    Notes

    This schema is still in a beta state; I've not used it in any real program. However, if I ever have to write a setup script in this language, this is the way to go.

    When I started writing a setup program for 'Libero for Windows', I got hold of the Microsoft Setup Toolkit, and discovered the joys of writing a setup script. The language is the cut-down Basic that was used in the MS Test product: TestBasic version 1.0. I call this almost-language MST. At least, unlike Visual Basic, MST supports an include statement. So, the MST schema puts the generated code into an .inc file.

    The setup toolkit documentation suggests that you draw a flowchart of the various stages of your install procedure, then code this into an MST program. Hmm. :-( I hate flowcharts, for one thing. For another, I have a much simpler and useful technique up my sleeve, so some quick work on a schema, and 'wallop!'. MST, please meet Mr. Turing.

    As it happens, I then decided that I did not want to use an MST setup program, did not want to start designing dialog boxes and resources. Now the Libero for Windows installation package is a .zip file that you expand into the directory of your choice, full stop. Still, the MST schema is there and it works, barring the odd lazy bug.

     

    The MS Visual Basic Language

    Overview

    What Is Visual Basic?
    Visual Basic (VB) is Microsoft's rapid application development language. It is the standard scripting language for MS applications such as Word and Excel.
    Portability
    None.
    Pros and Cons
    Pros: easy to use and widely used; lots of support from third-party add-ons; interpreted language. Cons: slow screen updates; interpreted language.
    How To Get It
    Buy it from your local software shoppe.
    Libero Supports
    MS Visual Basic 3.0. Not tested with 4.0, but probably will work unchanged.

    Working With Visual Basic

    Example of code

    Code fragments:

    '
    '****************   MODULE FUNCTION NAME   *****************
    '
    Private Sub module_function_name ()
    
    End Sub
    
    Private Sub private_function_name (argument,...)
        the_next_event = ok_event
        exception_raised = True
        the_exception_event = error_event
    }
    

    A fuller example.

    Notes

    1. Since there is no way to distinguish module functions from private functions, the schema turns-off idle module checking.
    2. The .int code file is the dialog interpreter. You have to include both the .bas and .int files in your project. I make the split into two files so that you are not faced with the generated code when you edit the .bas source file.
    3. The .int interpreter calls the .bas source file via a dispatcher function. This function (which is generated) calls a particular module and returns event information. This is not particularly fast, but works okay.
    4. All functions are private; this allows several Liberated programs in the same VB application.
    5. VB sorts functions in a source file into alphabetical order; I called the dispatcher 'ZzDis_...' to ensure that it comes at the end. This is just for cosmetic reasons. If for some reason the dispatcher function comes somewhere in the middle of the source, everything will still work okay. (I have at least one source file where VB did not arrange the functions correctly.)

    Specific Schema Options

     

    The Perl Language

    Overview

    What Is Perl?
    Perl is the mutant four-headed child of Awk, C, Sed, and the UNIX shells, plus just about every other language and feature that its creator, Larry Wall, could think of. I like this language.
    Portability
    Perl is fundamentally portable to UNIX boxes, and has also been ported to many other systems. Perl scripts are highly portable so long as you stay away from system-specific functions. This is pretty much as you would expect.
    Pros and Cons
    Pros: powerful and flexible; freely available; interpreted language. Cons: if you can't handle type-free languages where just about anything goes, return to Pascal. Perl is (IMHO) not a good place to start programming.
    How To Get It
    By Internet from ftp.uu.net and many other places.
    Libero Supports
    Libero supports Perl 5.x

    Working With Perl

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    sub module_function_name
    {
                                       #  Empty module needs no code
    }
    
    function private_function_name {
        $the_next_event = $ok_event;
        &\raise_exception ($error_event);
    }
    

    A fuller example.

    Notes

    If you have used Perl, you will appreciate the way it combines the best of all the UNIX text processing and programming tools. For example, you need only a few lines of Perl to read and parse a parameter file ('ini file') like this:

    #============================================================
    #  Parameters for program
    #============================================================
    
    [General]
        OutBox=outbox             # Subdirectory for sent faxes
    
    [Connect]
        HostName=Badoit           # For Telnet
        UserName=autofax
        Password=autofax
    
    [FaxDriver]
        PauseChar="~"             # Substitute for commas
    
    [Station]
        Id=Station1               # Name of this local system
    
        open (INI, "autofax.ini") || die "Can't open autofax.ini: $!\n";
        while (<INI>) {
            chop;                           #   Remove trailing newline
            #   [SectionName] followed by optional comment
            if (/^\s*\[(\w+)\]\W*(#.*)?$/) {
                ($section = $1) =~ tr/A-Z/a-z/;
            }
            #   Keyword=Value followed by optional comment
            if (/^\W*(\w+)=("[^"*]"|\w+)\W*(#.*)?$/) {
                ($keyword = $1) =~ tr/A-Z/a-z/;
                ($value = $2) =~ s/"//g;    #   Remove any quotes
                $initial {$section, $keyword} = $value;
            }
        }
        close (INI);
    

    Similar code in C is several pages. This applies to many aspects of Perl, so that a complex program may be only a few hundred lines long.

    Perl is also fast, and works well with Libero. The 'require' statement lets me hide the generated code in a separate file.

    The Perl development cycle is simple: when you change your dialog, re-run Libero. When you change the Perl program, re-run it. Hint: get your hands on the 'Programming Perl' Nutshell Handbook.

     

    The Rexx Language

    Overview

    What Is Rexx?
    Rexx is scripting language that originated on the IBM VM/CMS operating system. You'll find it on most IBM operating systems, including PC-DOS 7 and OS/2. It also comes in free and commercial versions for many systems including MS-DOS and Unix.
    Portability
    Rexx is available on many platforms. Rexx scripts are fairly portable if you don't rely on system-dependent commands. There is an ANSI standard for Rexx (X3J18) based on the book "The Rexx Language" by Mike Cowlishaw (who designed Rexx).
    Pros and Cons
    Pros: Rexx is pretty portable from PC's up to big mainframes. Cons: it's an IBM-ish language, almost unknown in the UNIX and Microsoft worlds.
    How To Get It
    It comes as part of most IBM operating systems (PC-DOS 7, OS/2.) You can get free versions from the Internet.
    Libero Supports
    Libero supports a portable Rexx syntax.

    Working With Rexx

    Example of code

    Code fragments:

    /******************   MODULE FUNCTION NAME   ******************/
    
    module_function_name:
        Return                     /*  All modules end in Return  */
    
    private_function_name :
        the_next_event = ok_event
        Call raise_exception (error_event)
        Return
    

    A fuller example.

     

    The UNIX C Shell Language

    Overview

    What Is C Shell?
    The C shell (/bin/csh) is a UNIX shell 'for C programmers'.
    Portability
    The C shell is very standard across UNIX boxes, as far as I know.
    Pros and Cons
    Pros: standard. Your scripts will run on most if not all UNIX boxes. Looks a bit like C. Cons: limited and out-dated. No functions. Looks a bit like C.
    How To Get It
    Should be part of your UNIX system.

    Working With The UNIX C Shell

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    module_function_name: #
        return                      # All modules end in 'return'
    
    private_function_name:          # Actually, not possible
        set the_next_event=$ok_event
        raise_exception $error_event
    
    

    The C shell has no functions (unless I missed something), so I use labelled blocks of code. Each such block must end with 'return'. The schema defines this alias to do a goto back to the dialog engine code. Frankly, I can't understand who would write or use this shell. Get Perl, and use that instead. Flames welcome. Nuff sed.

    See also: Note on UNIX shells.

     

    The UNIX Bourne Shell Language

    Overview

    What Is Bourne Shell?
    The Bourne shell (/bin/sh) is the original UNIX shell. It is basic and slow. Most systems provided an extended version that supports functions.
    Portability
    There are at least two flavours of Bourne shell; with and without functions. The simplest is portable to probably all UNIX systems; the extended Bourne shell to most UNIX systems (I know only one that does not provide it: NeXT.
    Pros and Cons
    Pros: Most standard shell. Cons: functionally poor; like all shells, slower than Awk or Perl or C.
    How To Get It
    Should be installed on your UNIX system.

    Working With The UNIX Bourne Shell

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    module_function_name ()
    {
        return                      # Empty module needs 'return'
    }
    
    private_function_name ()
    {
        the_next_event=$ok_event
        raise_exception $error_event
    }
    

    See also: Note on UNIX shells.

     

    The UNIX Korn Shell Language

    Overview

    What Is Korn Shell?
    The Korn shell (/bin/ksh) is the most advanced 'standard' UNIX shell. It extends the Bourne shell with lots of nice features, and is a lot faster.
    Portability
    The Korn shell is standard when available, but is not supplied on all UNIX boxes.
    Pros and Cons
    Pros: supplied on most UNIXes; your scripts will run quickly. Cons: like all shells, slower than Awk or Perl or C.
    How To Get It
    Korn shell is available in a public-domain version (by Eric Baalberger) for Linux.

    Working With The UNIX Korn Shell

    Example of code

    Code fragments:

    #################   MODULE FUNCTION NAME   #################
    
    function module_function_name
    {
        return                      # Empty module needs 'return'
    }
    
    function private_function_name {
        the_next_event=$ok_event
        raise_exception $error_event
    }
    

    A fuller example.

    See also: Note on UNIX shells.

     

    The 80x86 Assembler Language

    Overview

    What Is 80x86 Assembler?
    This is the native language for most of the world's PCs. It is fast and dangerous. It gives you a 5-to-10 performance improvement over the best C compilers.
    Portability
    Very portable to all compatible CPU's. Comes in various varieties, following the evolution in 80x86 CPU's. The lowest-common denominator is the 16-bit 8086.
    Pros and Cons
    Pros - can't get it faster; can't make it smaller. Cons: multiply your development and maintenance costs by ten.
    How To Get It
    There are various free and shareware assemblers around, which are not all compatible in terms of syntax. The 'standards' are the Borland TASM and Microsoft MASM products.
    Libero Supports
    MS MASM 5.0: .com files, .exe files, and subroutines.

    Working With 80x86 Assembler

    Example of code

    Code fragments:

    ;****************   MODULE FUNCTION NAME   *****************
    
    MODULE module_function_name
           ret                        ; All modules end in 'ret'
    ENDMODULE
    
    private_function_name proc near
        mov the_next_event,ok_event
        raise_exception error_event
    endp
    

    A fuller example.

    Specific Schema Options

    The schema provides these data items, macros, and functions (above the standard ones provided by all schemas):

    Programming with the COM model

    Programming with the EXE model

    Programming with the LSUB model

    Programming with all models

    Libero Technical Reference

    A First Look At Code Generation

    The Schema

    This is the basic logic of Libero's code generator:

    This is a 'hello world' schema:

    :echo "Hello, World."
    

    This is more realistic schema fragment:

    !  Example Libero of schema
    !
    :if not exist $source.c
    :echo "lr I: creating skeleton program $source.c..."
    :output $source.c
    /*===============================================================
     *  $source.c   description...
     *  Written:    $date       $author
     *  Revised:    $date
     *  Skeleton generated by LIBERO $version on $fulldate, $time.
     *===============================================================*/
    
    #include "$dialog.d"                    /*  Include dialog data  */
    
    /********************   INITIALISE THE PROGRAM  ******************/
    
    MODULE $"initialise_the_program" (void)
    {
        $"the_next_event" = $"ok_event";
    }
    :close
    :endif
    

    How does this schema work?

    When Libero replaces variables, it keeps the text as aligned as possible by treating white-space (blanks or tabs) as elastic. For instance, here is some 'C' code before and after replacement. Notice how the right-hand margin does not move:

    /*  Source generated on $date - Libero $version                */
    /*  Source generated on 93/07/12 - Libero 2.10                 */
    

    A variable name ends with the first non-name character (A-Z, a-z, 0-9, _). To mark the end of a variable name that is 'pasted' to some text, use a '\'. For instance, this is a line before and after inserting the $source variable:

        :output $source\d.d
    ==> :output myprogd.d
    

    If you capitalise (at least) the first letter of a variable name, Libero inserts the whole value in uppercase. For example:

        :output $Source\d.d
    ==> :output MYPROGd.d
    

    To output a name that matches the current -style setting, do this (e.g. -style=caps):

        $"the_next_event" = $"ok_event";
    ==> TheNextEvent = OkEvent;
    

    To output a '$', use two '$'s together:

        $"the_next_event" = $$$"ok_event";
    ==> TheNextEvent = $OkEvent;
    

    Read-Only Variables

    You can use these read-only variables anywhere in a schema:

    $author
    The value of the -author option.
    $date
    The current date in a shortened form: 'yy/mm/dd'. Libero always uses this date format for short dates.
    $time
    The current time in a shortened form: 'hh:mm'.
    $fulldate
    The current date in a long form: 'dd Mmm, yyyy'. The month is abbreviated to 3 letters, in English.
    $fulltime
    The current time including seconds: 'hh:mm:ss'.
    $out_count
    The number of lines output so far, including the current line. This starts at 1.
    $dialog
    The name of the dialog file being processed, without extension.
    $schema
    The name of the schema being read. This is the value of the -schema option.
    $source
    The name of the source file for dialog, without an extension. This name is derived as follows: normally, it is the same as the dialog file name. If -plusd was specified, it is formed by adding 'D' to the dialog file name. If -source=xxx was used, this explicitly tells the source name to use.
    $style
    The value of the -style option.
    $defaults
    The number of the defaults state, or zero if none was specified.
    $events
    The number of events in the dialog. This is used to dimension various tables.
    $maxaction
    The maximum action number (largest vector). This is used to dimension various tables.
    $modules
    The number of modules in the dialog. This is used to dimension various tables.
    $states
    The number of states in the dialog. This is used to dimension various tables.
    $vectors
    The number of action vectors in the dialog.
    $version
    The current Libero version number as 4 characters: 'n.nn'; e.g. '2.32'.

    Additionally, you can use $comma, $name, $number, $offset, $tally, $row, and $overflow within a :do block. These variables are explained later. You cannot define a variable with the name of a standard Libero variable.

    Schema Commands

    You can use the following commands in a schema:

    :output filename
    Sends output to filename. Any existing data in the file is lost.
    :extend filename
    Appends output to filename. Creates the file if it does not already exist.
    :close
    Closes an output file opened by a previous :output or :extend. This command is never required, since Libero does an implicit :close before any :output or :extend, and at the end of the schema. If you use :close when no file is open you'll get a warning message.
    :copy fromfile tofile
    Copies one file to another. The tofile is silently overwritten if it already exists.
    :rename fromfile tofile
    Renames one file to another. The tofile may not already exist.
    :delete filename
    Deletes filename without any error if the file does not exist.
    :include [optional] filename [from [to]]
    Includes filename in the generated output code. If you specify from, Libero searches the file for a line containing this text by itself, and starts copying from that point on. If you specify to, Libero ends copying when it finds a line containing just this text. You can put quotes around from and to if these contain embedded spaces. If you use the optional keyword, Libero ignores the :include command if it can't find the specified file. The included file can be anywhere on the path value.
    :echo text
    Echoes text to the standard output, followed by a newline. If the text is enclosed in quotes, leading spaces are not discarded.
    :declare [int | string] [$]name [=expression]
    Defines a variable for later use in the schema. Except for those that Libero supplies as standard, you have to declare any variables that you want to use. You can specify the variable name with or without a '$' in the name.
    Variables are either integers, strings, or variants (either type depending on mood). You cannot assign a string expression to an integer. Otherwise Libero is pretty relaxed about how you mix variable types. The expression provides a default initial value for the variable. If you do not provide an initial value, strings are empty and numbers are zero. You can override any initial value using an -option on the command-line or in the dialog. Use: -opt:var="string" or opt:var=expression.
    The name of a variable cannot be int or string. Variable names are case-independent; :declare $drink and :declare $DRINK refer to the same variable. If you declare an existing variable, you'll get an error message.
    :set [$]name = expression
    Assigns a new value to the variable specified. You can use a '$' before the variable name if you want to. You cannot set a read-only variable.
    :push [$]variable [=expression]
    Creates a new instance of the variable. If you give an expression the new variable gets this value. The variable must then be a read-write variable. You can push a read-only variable but not specify an expression. This may sound pointless, but can be useful: you can set some read-only variables using the :option command, e.g. :option -style=xxxx.
    :pop [$]variable
    Removes the last instance of a variable. Use with :push to change a variable within a block, then reset it to its previous value.
    :do condition
    Starts a code block that is output repeatedly depending on condition. Libero defines a standard set of conditions like :do event. You can also define your own conditions using :do while The do block ends with a matching :enddo.
    :do while logical-condition
    Repeats the code block while the logical_condition is true.
    :enddo [condition]
    Ends the closest previous :do block. If you specify condition, the :do and :enddo conditions must match; this makes your schemas more robust against errors, but is not required.
    :if logical-condition
    Starts a code block that is output if logical_condition is true. The :if block ends with a matching :endif.
    :else
    Outputs the following block of code if the previous :if condition was false.
    :endif [all]
    Ends the closest previous :if block. If you specify all, all open :ifblocks are closed.
    :exit [level]
    Aborts code generation. Use levels 0 for okay, 1 for errors.
    :option -name[=value]
    Enforces a Libero option For example: :option -style=cobol.
    :internal module
    Tells the code-generator that module is required by the generated dialog code, but not by the dialog. A typical case is initialise_the_program. This lets Libero correctly indicate unused modules in the source code.
    :substr from [size]
    Specifies a substring for the next $name or $row insertion. From must be from 1 upwards. If size is not specified, the remainder of the variable's value is used.

    Notes

    Syntax Of Expressions

    The :set and :declare commands assign the value of an expression to a variable. A couple of examples:

    :declare $count = 0
    :set $count = $count + 1
    

    Libero handles the expression depending on the type of the variable:

    An arithmetic expression can include '+', '-', '*', '/', parentheses. All number are four-byte signed long values, i.e. fall into the range +2,147,483,647 to -2,147,483,648.

    Things pretty much work as you would expect, I hope. In practice I always declare variables as int or string and put explicit quotes around strings.

    Syntax Of Logical Conditions

    The :if and :do while commands output some code depending on a logical-condition. Some examples:

    :if $debug
    :  echo "version is $version"
    :endif
    :do while $counter > 0
       ...
    :enddo
    

    A logical condition has two possible forms:

    :if [not] [test] expr1 [logical_operator expr2]
    :while [not] [test] expr1 [logical_operator expr2]
    
    :if [not] [standard_test]
    :while [not] [standard_test]
    

    Logical Operators

    You can make a condition by comparing two expressions. The 'test' keyword is optional and I normally don't use it. The expressions can be numeric or string; if either is a string, Libero treats both as strings. The logical operator can be one of:

    These are the logical operators:

    Standard Tests

    Libero provides these standard tests:

    exist filename
    filename exists in the current directory.
    animate
    The dialog uses the -animation option.
    check
    The dialog uses the -check option.
    caps
    The dialog uses the -style=caps option.
    cobol
    The dialog uses the -style=cobol option.
    headline
    The dialog uses the -style=headline option.
    normal
    The dialog uses the -style=normal option.
    plain
    The dialog uses the -style=plain option.
    title
    The dialog uses the -style=title option.
    defaults
    The dialog defines a Defaults state.
    state name
    name is a state in the dialog.
    event name
    name is an event in the dialog.
    module name
    name is a module in the dialog.

    Control Variables

    Libero supplies various control variables. A control variable affects some aspects of code generation; you set the control variable to tell Libero how to handle something specific for the language you want to generate. Control variables are usually 'write-only'. You don't normally refer to these variables in the code, though you can if you want to. To change a control variable you use the :set command:

    $array_base
    Specifies the base value for tables. You use this when building an array of values. By default this is 0; in some languages it is more useful to start at 1. The $array_base affects the value of $number in a :do block.
    $comma_before
    This value is used as $comma when the current item being output is not the last. Default is ",".
    $comma_last
    This value is used as $comma when the current item being output is the last. Default is empty.
    $comment_ind
    Comment indicator in schema command lines. Default is '#'.
    $module_line
    libero looks for single lines that look like this when creating new module stubs. By default: "MODULE %s (void)". The '%s' symbol represents the module name.
    $number_fmt
    This value is used to format the $number value; numbers are always signed long ints. It must be a valid printf format string. Default is "%ld".
    $row_first
    This value is used to format the first numeric item in a $row value. It must be a valid printf format string. Default is "%ld".
    $row_after
    This value is used to format numeric items in a $row value after the first one. It must be a valid printf format string. Default is ",%ld".
    $row_clean
    Tells Libero to clean-up overflow rows. You get overflow rows when you generate a table that is wider than the $row_width. When $row_clean is 1, Libero removes any non-numeric data from the start of overflow rows. When 0, Libero leaves overflow rows as they are. Default is 0.
    $row_null
    This value is used in $rows when there is no event defined in a certain state. This is by default 0, but can be set to any numeric value by the schema.
    $row_width
    Maximum space, in characters, for a $row item in the generated source. Default is the compile-time constant LR_ROW_WIDTH, defined as 60. Rows longer than this are chopped into 'overflow' pieces.
    $run_away
    If a :do loop does more than this number of iterations, Libero halts it with an error message. Default is the compile-time constant LR_RUNAWAY_LIMIT, defined as 1000. This lets you generate at least a table with the maximum number ofstates, events, or modules (up to 1000 depending on the schema).
    $stub_before
    Used to generate new module stubs. If this string value is not empty, it is output before each module stub header. By default it is "" (empty).
    $stub_between
    Used to generate new module stubs: this string value is repeated to build-up a stub header. Default is "*".
    $stub_first
    Used to generate new module stubs: this string value is output at the start of a stub header line. Default is "/".
    $stub_last
    Used to generate new module stubs: this string value is output at the end of a stub header line. Default is "/".
    $stub_width
    Used to generate new module stubs: defines the width of the stub line. Default is the compile-time constant LR_HEADER_WIDTH, defined as 79.

    To see the value that a control variable had for code-generation, use the -trace option when you generate code. The .lst file produced shows the symbol table, including all control variables, as code generation ended.

    Standard :do Conditions

    Libero defines this set of :do conditions:

    :do action
    For each state defined in the dialog; generates the action for each event in each state.
    :do event
    For each event defined in the dialog.
    :do event local
    For each event defined in the current :do state.
    :do module
    For each dialog module defined in the dialog.
    :do module local
    For each dialog module defined in the current :do event.
    :do nextst
    For each state defined in the dialog; generates the next-state for each event in each state.
    :do state
    For each state defined in the dialog.
    :do stubs [filename]...
    For each dialog module defined in the dialog, but not yet present in the source file (or filename(s) if specified).
    :do vector
    For each action vector required by the dialog. An action vector is the list of modules to execute for an event in a state. Libero collects these and builds a list of unique action vectors.

    You cannot nest these standard :do loops cannot be nested (i.e. twice the same condition).

    Extra Variables In A :do Loop

    Within the different types of :do loop, various extra variables are available. Usually you'll need these to construct tables or lists of names.

    :do while condition

    Repeats the block of code while condition is true. The condition is a logical condition as described above. The loop is executed 0..n times.

    :do event

    Builds a list of events. Outputs the block for each event in the dialog.

    :do event local

    Builds a list of events. Outputs the block for each event in the current state, within a :do state loop. Provides the same variables as :do event, and also:

    :do state

    Builds a list of states. Outputs the block for each state in the dialog.

    :do module

    Builds list of modules. Outputs the block for each module in the dialog.

    :do action

    Builds table containing one row per state, with one item per row for each event transition. Each item is the number of an action vector. Invalid state/event transitions are filled by the value of $row_null.

    :do nextst

    Builds table containing one row per state, with one item per row for each event transition. Each item is the number of the next state. Invalid state/event transitions are marked by the value of $row_null.

    :do vector

    Builds table of action vectors. An action vector is a list of modules executed in series for a state/event transition. Duplicate vectors are collapsed.

    :do overflow

    Used to build source code that has to be output over several lines. The block is output until the overflow row is empty.

    :do stubs [filename]...

    Builds a set of module stubs at the end of the filename. If no filename is specified, assumes you mean the $source file. You can specify a list of filenames; then Libero will scan each file in the list for existing modules, and build stubs for any undefined modules at the end of the first filename. This is useful if you define standard modules in generated code (e.g. an include file).

    If you use the -nostubs option, Libero ignores the :stubs command. If you use the -noidle option, Libero does not check the source for idle (ie. unused) modules.

    General Rules for Code Generation

    Generating Module Stubs

    Take a hypothetical language where a module stub looks like this:

    //###################  SOME FUNCTION   ##################//
    
    MODULE some_function () {
    
    }
    

    To generate stubs like this we add these commands to the schema:

    :internal initialise_the_program
    :internal get_external_event
    :set $module_line  = "MODULE %s () {"
    :set $stub_first   = "//"
    :set $stub_between = "#"
    :set $stub_last    = "//"
    :do stubs
    
    MODULE $name () {
    
    }
    
    :enddo stubs
    

    This is how it works:

    The :do stubs command can come at any point in the schema. It does not do any output to the current output file. However, in many cases it is a good idea to list generated files in the :dostubs line, which means that this should come at the end of the schema, when everything has been generated.

    Generating COBOL Code

    Libero has some built-in knowledge about the COBOL source format that makes it a little easier to generate COBOL code. The COBOL format is 80-column text, where the first six columns are a numeric sequence number. The last eight columns are a comment.

    The State Machine Algorithm

    This is the dialog manager logic as pseudo-code. If you need to write a schema for a new language, this section should be your main specifications.

    The dialog manager consists of a basic loop, plus extra pieces that are generated only if necessary. For example, handling of the Defaults state, error checking, and handling of the dialog stack. For clarity, I've put in the Defaults handling, but nothing else other than the basic loop. The following code is loosely based on C:

        _LR_state = 0                       //  First state is always zero
        initialise_the_program ()
        while (the_next_event != terminate_event) {
            _LR_event  = the_next_event
            _LR_savest = _LR_state
            _LR_index  = _LR_action [_LR_state][_LR_event]
    
            if (_LR_index == 0) {           //  Try Defaults state
                _LR_state = _LR_defaults_state
                _LR_index = _LR_action [_LR_state][_LR_event]
            }
            the_exception_event = _LR_NULL_EVENT
            exception_raised    = FALSE
    
            Get first module in list
            for ever {
                if (we finished the module list OR exception_raised)
                    break
                Execute next module in list
            }
            if (exception_raised) {
                if (the_exception_event != _LR_NULL_EVENT)
                    _LR_event = the_exception_event
                the_next_event = _LR_event
            } else
                _LR_state = _LR_nextst [_LR_state][_LR_event]
    
            if (_LR_state == _LR_defaults_state)
                _LR_state = _LR_savest
            if (the_next_event == _LR_NULL_EVENT)
                get_external_event ()
        }
        return (feedback)
    

    Compiled-In Limitations

    A number of more-or-less arbitrary limits are defined in the header file lrpriv.h. If you need to, you can increase these, then recompile all Libero programs (and if you are smart, drop me a line explaining your reasons):

    The header file prelude.h also defines a constant LINE_MAX, with the value 255, which is used to allocate buffers for input and full filenames (ie. including path). You should not write dialogs wider than a normal page (ie. 80 columns).

    Libero allocates memory dynamically to store its dialog definition, so the maximum size of a dialog actually depends on available memory. You should only change the constants in lrpriv.h if you get a message that specifically says that you have reached such a limit.

    The code that the standard C schema generates assumes that events can be stored in a signed 16-bit word; while state, module, and action numbers can be stored in an unsigned 16-bit word. A more compact version could be made by using 8-bit values.

    If you change Libero, or suspect bugs, compile with the symbol DEBUG defined. This switches on the assertions that are sprinkled through the code. The executable will be a little larger, but if a bug corrupts Libero's internal data structures, you will (usually) get a nice message instead of an unpredictable crash.

    Quick Reference - Libero Schema Language

    General

    ! at start of line - comment to end of line.
    : at start of line - command line.
    : by itself on line - ignored as comment.
    # in command line - comment to end of line.
    $xyz - insert value of variable xyz.
    $xyz\text - insert value of variable xyz followed by text.
    $"text" - format text using -style setting.
    $$ - insert single '$'.

    Read-Only Variables

    $author - value of -author setting.
    $date - current date: yy/mm/dd.
    $time - current time: hh:mm.
    $fulldate - current date: dd Mmm, yyyy.
    $fulltime - current time: hh:mm:ss.
    $out_count - lines output including current line (1 up).
    $dialog - name of current dialog file, no extension.
    $schema - name of schema being read; value of -schema.
    $source - name of source file, no extension; value of -source.
    $style - value of -style.
    $defaults - number of the defaults state, or zero.
    $events - number of events in dialog.
    $maxaction - maximum action number (largest vector).
    $modules - number of modules in dialog.
    $states - number of states in dialog.
    $vectors - number of action vectors in dialog.
    $version - current Libero version: e.g. 2.32.

    Schema Commands

    :output filename - outputs following schema text to filename.
    :extend filename - appends following schema text to filename.
    :close - closes current output file.
    :copy fromfile tofile - copies one file to another.
    :rename fromfile tofile - renames one file to another.
    :delete filename - deletes filename.
    :include [optional] filename [from [to]] - includes filename in current output code. From and to are strings of text that sit by themselves on a line in filename.
    :echo text - echoes text. "\n" is a newline.
    :declare [int | string] $variable [= expression] - defines variable for later use in the schema.
    :set $variable = expression - assigns new value to variable.
    :push [$]variable [= expression] - creates a new instance of variable.
    :pop [$]variable - removes last instance of variable.
    :do condition - starts code block output repeatedly depending on condition.
    :do while logical-condition - repeats code block while logical-condition is true.
    :enddo [condition] - ends closest previous :do block.
    :if logical-condition - outputs code block if logical-condition is true.
    :else - outputs code block if previous :if was false.
    :endif [all] - ends closest previous :if block.
    :exit [level] - aborts code generation: level 0 = okay, 1= error.
    :option -name[=value] - enforces Libero option.
    :internal module - module required by generated dialog code, but not by dialog.
    :substr from [size] - specifies substring for next $name or $row insertion. From starts at 1. If size is not specified, remainder of value is used.

    Expressions

    :declare int $number = 1
    :set $number = ($number + 10) / 2
    :set "$string" = "$string\more_text"
    

    Strings: max. 255 characters. Numbers: from -2,147,483,648 to +2,147,483,647.

    Logical Conditions

    :if $flag            # non-zero number or non-empty string
    :if not condition    # reverse test
    :if $var1 =  $var2   # If strings or numbers are equal
    :if $var1 == $var2   # If equal
    :if $var1 != $var2   # If not equal
    :if $var1 <> $var2   # If not equal
    :if $var1 <  $var2   # If less than
    :if $var1 >  $var2   # If greater than
    :if $var1 <= $var2   # If less than or equal
    :if $var1 >= $var2   # If greater than or equal
    
    :if exist file       # file exists in current directory
    :if animate          # -animate option
    :if check            # -check option
    :if caps             # -style=caps option
    :if cobol            # -style=cobol option
    :if headline         # -style=headline option
    :if normal           # -style=normal option
    :if plain            # -style=plain option
    :if title            # -style=title option
    :if defaults         # dialog defines a Defaults state
    :if state name       # name is state in dialog
    :if event name       # name is event in dialog
    :if module name      # name is module in dialog
    

    Control Variables

    $array_base - base value for tables. Default 0.
    $comma_before - used as $comma when current item is not last. Default ",".
    $comma_last - used as $comma when current item is last. Default empty.
    $comment_ind - comment indicator in schema command lines. Default "#".
    $module_line - template for module header in source file. Default "MODULE %s (void)".
    $number_fmt - used to format the $number value. Must be valid printf format string. Default "%ld".
    $row_first - used to format first numeric item in a $row value. Must be a valid printf format string. Default "%ld".
    $row_after - used to format numeric items in $row after first. Must be valid printf format string. Default ",%ld".
    $row_clean - when 1, Libero removes non-numeric data from start of overflow rows. Default 0.
    $row_null - used in $row's when there is no event defined in state. Default 0.
    $row_width - maximum size of $row item in generated source. Default 60. Longer rows are chopped into 'overflow' pieces.
    $run_away - maximum times through any :do loop. Default 1000.
    $stub_before - output before each stub header. Default "" (empty).
    $stub_first - output at start of stub header line. Default "/".
    $stub_between - repeated to build-up stub header line. Default "*".
    $stub_last - output at end of stub header line. Default "/".
    $stub_width - width of stub header line. Default 79.

    Standard :do Conditions

    :do while condition - repeats while condition is true. Loop is executed 0..n times. Loop variables: $number.

    :do event - builds list of events. Outputs block for each event in dialog. Loop variables: $name, $number, $comma.

    :do state - builds list of states. Outputs block for each state in dialog. Loop variables: $name, $number, $comma.

    :do module - builds list of modules. Outputs block for each module in dialog. Loop variables: $name, $number, $comma.

    :do action - builds table containing one row per state, with one item per row for each event transition. Each item is number of an action vector. Invalid state/event transitions are filled by value of $row_null. Loop variables: $row, $number, $comma, $offset, $tally.

    :do nextst - builds table containing one row per state, with one item per row for each event transition. Each item is number of next state. Invalid state/event transitions are marked by value of $row_null. Loop variables: $row, $number, $comma, $offset, $tally.

    :do vector - builds table of action vectors. An action vector is a list of modules executed in series for a state/event transition. Duplicate vectors are collapsed. Loop variables: $row, $number, $comma, $offset, $tally.

    :do overflow - used to build source code that has to be output over several lines. Block is output until overflow row is empty. Loop variables: $row, $number, $comma, $tally.

    :do stubs [filename]... builds a set of module stubs at end of filename.


    iMatix Designed by Pieter Hintjens © 1996 iMatix