lua_from_fluid.lua:

murgaLua generator from FLUID project file

1.    Introduction

The Lua file lua_from_fluid.lua is a tool used to build a GUI interface for murgaLua, with the help of FLUID (FLTK User Interface Designer). FLUID is a good tool to design GUI programs using FLTK as a graphical toolkit, but it generates C++ code, which is not adapted to murgaLua. The program takes a .fl file written by FLUID and generates Lua code that should run under murgaLua. Two other tools make a similar conversion, although with less features. Here is a summary of the programs:

·              convertFluidToMurgaLua.lua, originally a shell script from Markku Kotiaho, ported in Lua by John Murga. It uses the generated C++ code as input. Supports only one function named make_window and a limited number of special features.

·              fluid_parser from iGame3D, written in C using flex and bison. It takes the original FL file as input. Supports most widget features and functions, but not classes. The generated code is however poorly formatted.

·              lua_from_fluid.lua, written by Patrick Rapin, takes the original FL file as input. Supports virtually all features of FLUID (classes, widget classes, menus, inline callbacks, internationalization …) and tries to keep the generated code readable.

2.    Configuration

Most of the parameters affecting the generation of Lua script come from the FLUID project. But it appears that some configuration variables are desired because the needs and preferences of users are not always the same. A table containing the default values of the configuration parameters is declared at the top of the generator script, and can be easily edited to fit user's need. Alternately, the option values can be overridden by the command line parameters, or through a direct call to the main function (see next chapters). Currently, the following options exist in the generator:

 

Option

Default

Description

shebang

nil

If it is defined as a Unix file path to the Lua interpreter, a Unix shebang (#!) will be added at top of the file. If the path is in a Windows format (either containing backslashes or ending in .exe), a batch header is added to turn the script into a valid Windows .BAT file.

indentation

2

If it is a number, that number of spaces will be added at the start of each line for each current level. If it is a string, it is replicated by the current level and placed at start of each line.

currentvar

"o",

Current widget variable name. FLUID normally uses o when generating C++ code. Each widget is assigned to a local variable of that name, in addition to a named variable if required.

textfilter

nil

Internationalization text function. If defined to a function name, every text label will be passed to that function. If not defined, the parameter set in "Edit > Project Settings > Internationalization > Use: GNU gettext > Function" will be used.

check

"none"

Action to perform at the end with the generated code chunk:

·        "syntax": the Lua syntax is checked using loadstring

·        "run": the script is compiled and run

·        "none" or other value: do nothing

 

3.    Run from command line

The general form of the command line format is the following:

 

murgaLua lua_from_fluid.lua [–option value ...] input.fl [output.lua]

 

The input file is the FLUID project file name. The output file is the file name for the generated Lua code. If the output file is – (a single dash), the resulting Lua code is written to standard output instead. If it is not specified at all, the result is thrown away; this is of course useless unless combined with the check option. One or more options can be specified before the input file. The option name must match one of the strings of the first column of previous table, preceded by a dash sign; otherwise it will be silently ignored.

4.    Run as a module

It is also possible to use the generator as a Lua module. First, you load the code in Lua using either using require or dofile. This will export a global function named LuaFromFluid, which has three arguments: the input file name, an optional output file name, and an optional configuration table. This latter, if provided, must contain the same index keys as described in the configuration table above, and will override the corresponding options. Any additional field name will be silently ignored. The function returns the generated code chunk as a string.

Examples:

 

require 'lua_from_fluid'

assert(LuaFromFluid('demo.fl', 'demo.lua'), "syntax error")

LuaFromFluid('demo.fl', nil, {check='run'})

5.    Code chunks

It is important to note that on each place where FLUID permits to enter custom, you have to enter Lua code and not C++ code. No attempt is made to convert the code from C++, except for "Comment" items.

Sometimes, this yields to problems because FLUID performs a limited syntax checking on the custom code (parenthesis matching), and misinterprets Lua comments and the Lua 5.1 length (#) operator. It is recommended to patch FLUID, by returning NULL in function c_check of Fl_Function_Type.cxx. The hack is described at this address:

http://www.murga-projects.com/forum/showthread.php?tid=291

 

The generator expects the following type of code chunk depending on where it is placed:

FLUID item

Field

Expected code

Class

Name

An identifier, mapped to the constructor function name

Subclass

Ignored

Function

Name

An identifier followed by parenthesizes. Arguments are just a list of identifiers (without type names).

Return type

Ignored

Access list

Select local or global variables, as described later in the Variables chapter.

C Declaration

Ignored

Code

Code

Any valid Lua code chunk.

Code Block

Conditional

Normally a test condition like "if condition then"

Second line

Ending block line. If empty, the keyword end is assumed.

Declaration

Value

A single identifier. Only supports variable declaration.

Access list

Selects local or global variables.

Declaration block

First line

A constant test like "if true then"

Second line

Ending block line. If empty, the keyword end is assumed.

Header/source

Ignored

Comment

Code

Any text. If C++ comment style is used (//), it is converted to Lua style (--). Also, -- is added at start of each line not beginning neither by -- nor //.

Header/source

Ignored

Any widget,
in C++ tab

Class

An optional identifier. If specified, the widget is created by calling that function, which is supposed to be a custom widget constructor, instead of the default fltk:Fl_* base function.

Type list

Changes the default widget class name.

Name

An optional identifier. Uses that variable name for the widget in addition to the currentvar option.

Access list

Selects local or global variables.

Extra code

Usually Lua code in the form "o:fct(param)". It is placed verbatim after widget construction and standard methods calls.

Callback

If it is a single identifier, it is supposed to be a global or local callback function name.
If it is more complex, it is supposed to be the body of a callback function beginning with "
function(self)" and ending by "end". The callback function is placed inline, just after the widget construction, so it has full lexical access to local variables. Inside it, you can refer to the original object either with self, with o or with the optional widget name.

User data

Ignored

Type

Ignored

When

Calls the when method with the proper value

No change

Changes the value passed to when method

 

6.    Classes

The FLUID notion of "class" can be relatively well translated in Lua using a table with a metatable. The class will be rendered as a constructor function, of the same name as the class name. The constructor function creates a table in which every member function or variable is put, and returns that table. It takes an optional table argument as input. Every key-value pair of that table is copied into the new object, enabling initialization or copy features. The main trick in the implementation is that a metatable is mapped to the object, and the current function environment is modified inside the constructor function, so that new global variables are written inside the object table, and global variables are read either from the object or the really global environment _G.

Outside the constructor function, you can access to the public methods or variables using the dot (.) operator. Do not use the colon (:) operator for member calls, because member functions already have full access to their class instantiation as an upvalue.

If a member function has exactly the same name as the class function, it will be renamed __constructor in the generated code and automatically called at the end of the class definition function. This mimics the behavior of a C++ constructor function.

Unlike in the FLUID generated C++ code J, classes defined inside classes are supported by the generator. This will be simply rendered as a constructor member function inside the constructor function.

7.    Widget classes

With FLUID, you can create new complex widgets by placing a number of base widgets into a group, and adding member functions. This is done with a widget class item. The Lua emulation of a widget class is quite complex. It is rendered as a constructor function taking as input arguments the same ones as any widget: X, Y, W, H and label. A base widget (normally an Fl_Group or Fl_Window) is instantiated using these parameters, resulting in a userdatum. The metatable of this userdatum is modified so that indexing the value for unknown fields put the data into a helper table, placed into a subfield of the metatable. Also, the current function environment is modified in the same way as for regular classes.

You can access any public member using the dot (.) operator, including for member function calls. However, for compatibility with other widgets, each member function is patched to accept both the dot and the colon notations (by testing if the first argument matches the base widget variable).

To use the widget class, you create a regular group or window item in another window and then change the Class field in the C++ property tab to match your custom widget class name.

Normally, in FLTK, all positions are relative to the current window, and not relative to the current group. Because you normally want that the custom widget to behave the same wherever you place it in the user window, the widgets used in a custom Fl_Group based widget are automatically shifted by the position of the group. This shift feature is not supported in FLUID generated C++ code.

8.    Variables

Each widget created is placed into a local variable named o, or whatever you set the option currentvar to. The default name is used for setting object parameters, and can be used in "Extra Code" or "Callback" properties. In addition, if you give a value in the "Name" property, the value is copied into a larger scope variable. How the variable is declared depends whether the current function is a regular function or a class member, and the value of the access property:

1.      Regular function:

·        global: the variable is global (a field in the _G table)

·        local: the variable is declared as local at the top of generating file. It can be accessed by every code chunk of the FLUID project as an upvalue, but is inaccessible for other modules

2.       Class member function:

·        public: the variable is not declared local. The environment of the construction function is modified to the new object itself with an inheritance to the global environment. Inside the class definition, it is enough to access the value as if it was a global variable. From outside, you can also access the value by indexing the object with the variable name.

·        protected: the variable is declared as local at top of the class definition. It can then be accessed as an upvalue throughout the class implementation, including inline callbacks. It is inaccessible to the outside world and even to external callback functions.

·        private: currently the same as protected.

9.    Internationalization and text filtering

In "Edit > Project Settings > Internationalization" property page, if you use "GNU gettext", you can choose a function name. This name is used as a text filtering function called on every label item. The function can for example make a table lookup to implement translation, like in the following example:

 

function gettext(text)

  return text_strings[current_language][text] or text

end

 

You can also use the same mechanism to implement text filtering or macro expansion, like for example replacing "$USER" in labels with the login name of the current user.

The function name can also be forced by setting the textfilter option.

10.          Main chunk

At the end of the generated script, a small piece of Lua code might be added as the main chunk, in order to run the program, depending on whether or not specific function names are defined:

·        If a regular function (not a member function) named main is defined, it will be called by passing to it all script arguments, using the "…" Lua 5.1 notation.

·        If a regular function named make_window (the default function name under FLUID), the function will be called without arguments. The show() method will then be called on all return values, which are supposed to be Fl_Window objects. Finally, Fl:run is called to open the application.

·        If no such a function exists, no main chunk is generated. In this case, we suppose that the generated Lua script is part of a larger project, which will be loaded and run through external code.

11.          License

The code has been placed under the MIT license, the same used by Lua itself.