Comparison with C and C++


If you are a C or C++ programmer, you will notice many similarities between Lua and C or C++. However, Lua is not C or C++ and, in fact, there are many differences. You can learn more about these issues by reading Lua documentation such as the books Programming in Lua, currently in its 4th edition ("PIL"), Lua Quick Start Guide, and others, which are available through Amazon and oter booksellers. Also see Going Further with Lua and the online Lua Reference Manual at https://lua.org/. This distribution of the Mira Pro Script Language also includes a large number of profusely commented sample scripts that can run as provided or be a launching point for writing your own scripts.

Below are described some important topics that are useful to know before beginning to write scripts. The list of topics discussed below is not complete, nor is it intended to substitute for a reading of Programming in Lua or other Lua documentation. These are just some highlights of the language that will be especially familiar to someone who has programmed in C, C++, or Pascal.

Chunks

The basic execution unit of Lua is called a "chunk", or more commonly one might call it a module. Lua considers a chunk to be all of the source code contained in a single file. When Lua inserts another chunk into a script by the Include method, the chunk is compiled and executed. This adds a new dimension to the well known C language #include directive. You can see the benefit of this auto-execution in the example An Improved Filter Kernel Script, in which the Included chunk is pre-executed to compute filter kernels. Because of this architecture, it is useful to place classes and functions, as well as definitions, into separate chunks that you Include into the main script—or into other chunks themselves.

Comments

A comment is a line or block of text that is ignored by the Lua compiler. Using a comment allows you to annotate your Lua source text without affecting the executable code that Lua creates. Lua provides two types of comments:

bullet.gif    An inline comment uses two dashes, --, to signify an inline comment in the same way that C++ uses the // token. Everything from the -- to the end of line is ignored by the compiler. By default, the Mira Script Editor colors in-line comments green, like you see in the Microsoft Visual C++ source editor. -- this is a comment

bullet.gif    A block comment is made using --[[ to begin the comment and --]] to end the comment. The block comment can extend over any number of lines. This works like the C language /* */ pair, except that the Lua --[[ --]] pair may contain anything whereas the /* */ cannot contain other /* or */ tokens.

Operators

bullet.gif    Lua provides an assortment of C language-like operators such as + - * / ^ < > ==, ~=, >=, and <=. Operators follow the typical rules of precedence when used in expressions; for example, a/b + c/d is parsed as (a/b) + (c/d).

bullet.gif    Also included are logical operators and, or, and not, which are equivalent to the C language &&, || and ~. There are two major differences between C operators and Lua operators: ~= and ^. The "not equals" operator is ~= in Lua but != in C. In Lua, ^ is the exponentiation operator but in C, ^ is the XOR operator.

bullet.gif    Lua does not support pre-fix and post-fix operators ++ and--, or operators of the form += and &=.

Types

Lua supports several data types, including numbers, strings, table, functions, and pointers. See Lua documentation for a detailed description.

bullet.gif    Numbers are always double precision real values, like the double type in C and C++. Some Mira functions like Printf can use integer formats like %d, %x, and%p to treat numbers as integer or pointer types.

bullet.gif    Strings have their usual meaning but you cannot access characters directly by treating the string as an array of characters. However, the string package included with Lua provides a rich collection of string manipulation and pattern matching functions.

bullet.gif    Tables provide data structuring. A table is a collection that can be easily used for various kinds of data structuring applications. See the description of Tables, below.

bullet.gif    Arrays are a specfic type of table used in the Mira Pro Script library. An array is a table that contains no keys, and whose members are accessed using subscripts.

bullet.gif    Functions have their usual meaning. A function may return 0 or more values and return 0 or more arguments. The function is terminated by an end statement. Here are some examples:

 

function foo( a )

  return a + 5.5

end

 

function goo( a, b, c )

  local x = a * b

  local y = a + b - c

  return x, y

end

 

function hoo( ... )

  return arg

end

 

function Printme( str )

  Printf( "%s\n", str )

end

 

To invoke these functions, use them in an expression, as in these examples:

f = foo(x)

z, w = goo( 3.5, f, w )

                 t = hoo( 4, 5, 6, 7, 8 )

                 Printme( "Hello world!" )

The 3rd example uses a function that takes a variable number of arguments and returns them bundled into a table. This uses lua's hidden "arg" parameter.

Functions in Lua are first class values, meaning that a function name can be passed as an argument of another function.

Blocks and Scope

Scope refers to the extent to which a value or function may be seen by other "parts" of the program. Values have two levels of scope: local and global. All values have global scope unless they are explicitly declared local using the local statement. A local variable its scope limited to the block where it is defined.

A block is defined as the body of a control structure (loop or if-elseif), a function, or a chunk. But declaring a variable inside a block does not make it local; a variable is global unless you explicitly declare it local, and then its scope is limited to the block where it was declared. This architecture is known as "Lexical Scope". By explicitly declaring the variable local, you are stating that you do not want it seen outside the block. For example:

  function foo() ; local a = 5 ; return a ; end

hides the variable a from outside the function foo, whereas

  function foo() a = 5 ; return a ; end

allows a to be used outside of foo. In this second example, the global value of a will be updated by the function foo even if the function does not return a to the caller. (Notice that some optional; separators were used in the above function definitions even though Lua does not require them).

Functions

Lua Functions work like functions in other languages. However, there are some unique features which are described here. A basic function requires a name and an end statement. An empty function that does nothing would look like this:

  function foo() end

or, we could also write the same thing like this:

  foo = function() end

These are equivalent declarations of a function in Lua. In both cases the script tells Lua that foo is a function and that it takes zero arguments. To make this function do something, we add some internal statements, like this:

  function foo(s)

    I = new_image()

    if not I:Open(s) then

      Exit("bad file name")

    end

    return I

  end

You may wish to place functions in external files that are made available to the script at run time using the Include function. When the Include statement is processed, Lua executes the contents of the included file.

Lua allows function names to be passed to another function as an argument. Since Lua does no prototyping as in C and C++, the details of the passed function do not matter. Lua knows only that the argument list is passing a reference to a function.

One other comment about the preceding example: Notice that the object I is created inside the function foo. By default, Lua creates values that are global rather than local, which is the opposite of C and C++. Therefore, the value I can be returned to, and used by the calling function. To make I (or any other value) local to the function, use the local declaration; see Blocks and Scope, above.

Tables

A table is a collection of key-value pairs used for working with objects of any type. Values may be stored in a table using a name key, which is a string. The values may be of any type. For example, you can assign a value to a table using a syntax like this: T.key = value. You would then fetch the value like this: MyValue = T.key, which returns whatever was assigned as value, so that MyValue == value evaluates as true. You could also access that member as MyValue = T["key"].

A table can be treated as an array of numbers, strings, other tables, or other things of any supported type. It may be used to create a data structure, a class, or a package of other tables. The Table construct is exceptionally versatile.

A table is created using a syntax like t = {}. What goes between the{} defines the members of the table. A table can include properties like a C languagestruct. The properties may be initialized when the table is constructed, as in T = { value1=12, value2="a string" }. Table properties are accessed using the dot (.) operator. For example, in this table, T.value1 begins with the value 12. To add members later, just use the syntax T.key = value or T[key] = value outside the {}.

A table be treated as an array by indexing its members at a numerical index using the [] operator with indices beginning at 1. The first member of table T can be indexed element can be indexed as an array using integral numbers beginning at index 1, as in [1], [2], etc. This is different from the C language protocol of indexing objects starting at 0. As an example, suppose we have 3 tablesa, b, and c. Then we could assemble them into a single super-table that is indexed like an array as follows:

     a = {}

     a[1] = "car"

     b = { "truck", "jeep" }

     c = { "bus", "crane", "dog", 4 }

     t = {}

     t[1] = a ; t[2] = b ; t[3] = c

 

Then, t[3][2] == "crane" evaluates true ( for true, see Special Values, below).

A table may also contain functions. A table function is accessed using the dot operator, as in T.MyFunction(). To assign functions to a table, do this:

     function F1()

         ...

     end

     function F2()

         ...

     end

     T = {}

     T.MyFunc1 = F1

     T.MyFunc2 = F2

 

In the example above, the ... refers to whatever is contained in the function body. To access these functions, use T.MyFunc() and so on.

As described in Classes, below, you can setup a table to be a class containing both data and functions. To access the functions you can then use either the dot operator or the colon operator (:).

Classes

The Lua table is so versatile that it can be used to create a class for use in object oriented programming. This capability is detailed in the book Programming in Lua. The dot operator may be used to access a table function when there is only a single instance of a particular table. However if you use a new operator as described in PIL, then you can create multiple instances of the same kind of table. Then, to tell Lua which instance the function refers to, you should use the colon (:) operator to access the class functions (but use the dot operator for class properties). For a class o in which a function foo has been declared, the following statements are equivalent:

     y = o:foo(x)      and      y = o.foo( o, x )

As you can see, the colon operator avoids passing the object o for use by the function. The colon hides the use of a pointer to the class object, called the this or self pointer. Using the colon syntax allows the function foo to know who it belongs to, which means that it can work with other members of the same class. The C++ language does this too, but hides the mechanism while making a this pointer available if you need it. Lua allows you to access the Table and Array Functions (you can also call them class methods) using either the dot operator and explicit self/this pointer, or simply use the colon operator.

Suppose we have a function foo that sets the value of a global variable v and returns its prior value (see Blocks and Scope about global variables):

     foo = function( value )

           old_v = v

           v = value

           return old_v

     end

This function could be added to a class using either the dot or colon operator but we will show only the case using the colon operator (:). The function is declared as a class member like this:

     -- first, declare the class CClass

     CClass = { v=0 }

 

     function CClass:foo ( value )

           old_v = self.v

           self.v = value

           return old_v

     end

 

     -- create an instance of the class CClass      

     C = CClass:new()

 

     -- use the instance C to do something

     val = 15

     C.MyNewValue = 15      -- create a new class member on-the-fly

     MyOldValue = C:foo( val )      -- assigns a value to v in the object C

 

     -- when finished with C, delete the instance

     C:delete()

See Programming in Lua for more information about classes and object oriented programming in Lua.

Special Values

Lua implements the concept of the C language NULL, which in Lua is called nil. The purpose of nil is to be special—that is, to be different from all other values. This value is useful for testing for the existence of objects or whether an object or value has actually been assigned. For example, if you test a value name before is is declared with a value, then the result is nil. Similarly, a function that creates an instance of a class might return nil if the class construction failed. For example,

  a = Create()

  if a == nil then

    Exit("a could not be created.")

  end

This code fragment could also be written more compactly as

  a = Create()

  if a == nil then Exit("bad a!") end

Lua includes a boolean type as the special values true and false. Anything non-zero evaluates true but a function might return the explicit values true or false which can be tested as such.

The keyword not provides negation, so that the statement false == not true evaluates true.

Dynamic Typing

You do not need to declare variables, even for strings. Lua performs dynamic typing, which means that it determines the type of value you want at the time that something is assigned to it. If you assign a numeric value or expression to a token, then the token becomes a number. Strings work the same way. In addition, if you create a new instance of a class, the new method returns a reference to the class object in memory, which you assign to a token so that the script can access the class members.

Since Lua also implements the Table concept as a data structure like a C struct or array, dynamic typing also works with complex objects to make your scripts more capable and easier to debug. For example, you can create a function in the script and the function can create a class object, such as a CImage. The function returns the instance of the CImage as its value, which is assigned to a token in the calling script. The calling script can work with that object thereafter, including deleting it at some point. The calling script does not need to know in advance that the statement calling the function returns a reference to a class object; the calling script learns about the type of the reference when the script actually uses it. For example, here is a class version of a function foo:

  function foo(s)

-- function takes one parameter

    I = new_image()

-- create an image object

    if not I:Open(s) then

-- try to open a file from path s

      Exit("bad file name")

-- exit the script

    end

 

    return I

-- return a reference to the object

  end

-- end of the function

 

 

  I = foo("MyPath.fts")

-- return a CImage and assign to I

  if I then

-- test that I is valid (non 0)

  Printf("Rows:%d", I:Rows())

-- print the number of rows in the image

  I:delete()

-- done using the object Image

  end

 

In the above sample script, a CImage object is created within a separate function that loads an image from a file. Since I is not declared as local inside foo, it is global and can be accessed by the calling function (see Blocks and Scope, above). The value I is returned as a matter of programming style, to make clear that it was created by foo. If foo did not return the value of I, the calling function still could use I but the code would be more obscure because it would not be clear exactly where I came from.

Math Library

The Lua language contains only a minimum number of math functions such as min and max. This was done to keep the language maximally portable across computing platforms. Instead, Lua provides a rich collection of math functions in the math package. These packages are actually Lua tables containing functions. Therefore, you access these math functions using the . syntax, as in math.sqrt() or math.log(). This package is described in the Lua documentation. When you run a script, Mira automatically includes the math package functions.

Other Libraries

Lua provides a string package with a rich assortment of string processing functions, an io package containing input/output functions, and other packages described in the Lua documentation. When you run a script, Mira automatically includes the string and io package functions.

String Formatting

Mira includes string functions for string formatting, including Printf and Sprintf. These provide C-like string formatting for numbers or different types, strings, pointers, and hexadecimal values.

bullet.gif    The Pro Script Printf function works like the C printf function by sending formatted text to an output device. Pro Script sends this formatted string to a Mira Text Editor window. There is also a Printf method in the provided CTextView class.

bullet.gif    The Pro Script Sprintf function works like the C sprintf function by formatting to a string. However, Sprintf takes the format string as its first argument and returns the formatted string that must be assigned to a value. For example, str = Sprintf( "%s, %d, %lg", a, b, c ) formats the values a, b, and c and assigns the resulting string to the value of str . You do not need to declare str as a string before it is assigned a value.

Block Structures

The C and C++ languages provide a conditional block structure of the form

     if {...} else if {...} else {...}

where the {} are used when more than one statement is used. Lua provides the same functionality using the syntax

     if ... then ... elseif ... then else ... end

where there may be any number of elseif blocks. Lua parses the statements in the ... without any {} style delimiters, regardless of whether there are 1 or 1000000 statements involved. If it makes the script easier to understand, you can use () around the ... in the initial condition, as in if ( ... ) then. Lua also allows you to place the entire block on any number of lines. For example, you can do it this way:

  if ( a + b < 5 ) then

    c = b

  else if ( a > 10 ) then

    c = a

  end

It is often easier to put a short if block inline, as shown below. Here is the same block written inline and without parentheses:

    if a + b < 5 then c = b else if a > 10 then c = a end

Loop Structures

Lua provides 3 types of loops: for, while and repeat. The for and while loops terminate with an end statement, but the repeat loop terminates when an until condition statement.

bullet.gif    The for loop uses rigid limits that cannot be changed. For example,

 

                       for i = 2,14,4

                              -- code goes here

                       end

 

This loop runs with values of i = 2, 6, 10, and 14. It cannot test a stopping condition in the same way as a C-style for loop. However, code inside the loop can perform some kind of test and may exit the loop early using a break or return statement.

The for loop offers another syntax that is useful in iterating through a table, as in for k, v in a do. For details, see Programming in Lua.

bullet.gif    The while loop uses the keyword do to test a condition at the beginning, as in

           

                       while ( a < 10 ) do

                             -- code goes here

                       end

 

This loop runs until the condition a < 10 fails. The while loop can run 0 times if the condition fails immediately when the loop is entered.

bullet.gif    The repeat loop is similar to a while loop but tests its condition at the end of the loop. A repeat loop runs at least 1 time. For example,

                 

                       repeat

                 -- code goes here

                       until ( a < 10 )

 

The parentheses around the until condition are optional.

Multiple Return Values

A Lua function can return a value just like a C function. However, a Lua function can return 0 or more values. For example, the following syntax is valid in Lua:

   a, b, c, d = R:Get()

-- return the limits of a CRect object R

This bit of code returns 4 numbers that are the limits of a rectangle in the CRect class.

One caveat for using multiple returns is that, if a function with multiple returned values is used as an argument to another function, then only the first returned value is used by the function, unless that function is the only argument. Therefore, we have 3 cases to consider:

    a, b, c, d = R:Get()

-- returns 4 value for use by the script

    Printf("%lg,%lg,%lg,%lg,%lg", R:Get(), x )

-- prints the values of a and x

    Printf("%lg,%lg,%lg,%lg", R:Get() )

-- prints all 4 values for a, b, c, and d

Using multiple returns is handy when you may want a general function to return, say, a value and a status flag, but sometimes you want to use only the returned value and other times you want to use both the returned value and the status.

String Concatenation

Lua uses the .. operator to concatenate strings. For example, suppose the value of x is 10.5. Then

     s = "a" .. "b" .. Sprintf("c:%.3lf", x)

produces s = "abc:10.500". Notice that the value returned by Sprintf is a string.

Literal Strings

Mira provides a capability for adding WYSIWYG strings to your programs using Lua's literal string capability. A literal string is processed to look exactly the way you wrote it in the Script Editor, including line breaks, spaces, and tabs. To create a literal string, simple enclose the literal region between the tokens [[ and ]]. Lua interprets the literal string as everything between the [[ and ]] when the chunk is compiled at run time. Lua does not enter these tokens into the resulting string; it just uses[[ and ]] to switch-on and switch-off the literal string interpreter.

In the following example, we use the Printf function to format some text which we want to appear a certain way in the Mira Text Editor window. Part of the string formatting uses static text as laid out, the other part involves formats like %s and %d that are filled in at run time. Writing the source code to get this right without using a literal string would certainly be a lot of work! Everything between the [[ and ]] prints exactly as written:

 

Printf(

     [[

     File = '%s'

     PixelType = '%s', or Type = %d

     Dimensions = [%d x %d]

     Time Obs = '%s' = %lg sec

     Date Obs = '%s' = %lg days

     Exptime = %.2lf sec

     Object = '%s'

     Filter = '%s'

     -------------------------------------------

     ]],

           -- after the comma, enter the parameter list as normal

     I:Path(60), sType, nType, nCols, nRows,

     sTime, nTime, sDate, nDate, nExptime, sObject, sFilter )

Note that the literal string includes new lines and offsets from the left margin. Therefore, if you used the script exactly as shown above, each printed line would show a tab offset from the left margin. The format string above does not include new line characters. If you did not use a literal string, the format would need to include \r\n at each point where you want a line break to occur.

Related Topics

Protocol for Using Classes, Creating Classes


Mira Pro x64 Script User's Guide, Copyright Ⓒ 2023 Mirametrics, Inc. All Rights Reserved.