Posted on by

Today’s guest tutorial comes to you courtesy of Matt Webster, an aspiring mobile app developer and veteran of Corona SDK. He’s been involved with .NET-based websites for many years and currently manages mobile app-related web services in London. With a passion for physics, Matt has contributed numerous posts and code samples to the Corona Code Share, listed on his sporadic technical blog. You can follow Matt on Twitter here.


book-featWhile Corona SDK is simple, powerful, and has many useful APIs, sometimes there’s that “one extra little thing” you wish was there. Often, that one thing is apparently simple enough to be incorporated into an existing Lua API, but it may appear “forgotten about.” In this tutorial, we’ll learn how to:

  1. Add new functionality to the existing libraries.
  2. Extend the existing functionality.

Corona API Libraries

The core functionality of Corona SDK is provided by API libraries, the documentation for which is found here:

http://docs.coronalabs.com/api/index.html

If we take a look at the string library, we see a collection of functions:

  • string.byte()
  • string.char()
  • string.find()
  • string.format()
  • etc…

It so happens that these functions are not written in Lua; instead, they are “hooks” into lower-level functionality written in either Objective-C (iOS) or Java (Android). The same is true for the math, graphics , and other libraries.

Some libraries, however, are written completely in Lua, for example Corona’s widget library. Corona Labs has even made the original source code available. Whichever implementation the engineers chose for their code, the fact remains that in the “Lua world” each function is tied to the rules of Lua. In fact, every library — including the string library — is actually a Lua table. That’s right: string is a table and all of its functions are members of that table.

As a result of this, we can do some clever things quite easily!


A Useful Custom Function

The first thing we’ll learn is how to add our own functions to Corona’s own API libraries. Why would we want to do that? Well, let’s say you’ve written a really useful function which removes the leading and trailing spaces from a string. In most languages, this is called trim().

local function trim( str )
   return ( str:gsub("^%s*(.-)%s*$", "%1") )
end

Don’t worry about what’s actually happening inside the function. Just know that you’ve written it, it’s awesome, and it works really well on strings that have annoying and unnecessary spaces at the start and/or end.

print( trim( " Hello World! " ) )
--Outputs: Hello World!

A common practice for Corona developers is to put this trim() function into a custom Lua module such as utils.lua. This is fine, but we can easily make it more memorable and categorically accurate — after all, this function is a “string” function, so why not access it like the built-in string functions?


Adding to Corona’s APIs

To be clear, if our custom trim() function is in a file called utils.lua, we want all the work done in that file. To facilitate this, utils.lua must be loaded into memory using a standard require() call:

require("utils")

And the function in the utils.lua may look like this:

local function trim( str )
   return ( str:gsub("^%s*(.-)%s*$", "%1") )
end

Now let’s add this function to the Corona string library. In utils.lua, after we define our function, we follow it with a standard table value assignment (this is the magic bit):

string.trim = trim

That’s it! You can now call the function from anywhere in your code.

string.trim(" Hello World! ")
--Outputs: Hello World!

The Beauty of Libraries

Let’s say we have a string defined:

local str = " Hello World! "

What’s great about having the trim() function in the string library is that we can now call the function as a member of any string variable:

print( str:trim() )
--Outputs: Hello World!

This is because the string library represents string variables in general.


Lua 101 (Sort of)

Note: Skip to the end of this tutorial for links to some more advanced topics in the Lua language.

Section 6 of the Programming in Lua website describes functions as “first-class values with proper lexical scoping.” You may be wondering, “what on earth does that mean?”

Well, in short it means that when you define a function in Lua, you can pass its name around just like any variable. You can even pass it as an argument to a function or store it in a variable to be used later. You may already have done this many times without even thinking about it.

One common instance of passing a function as a value is with the timer.performWithDelay() function:

local function HelloWorld()
   print( "Hello!" )
end

timer.performWithDelay( 1000, HelloWorld, 10 )

This code will print “Hello!” once per second for ten seconds. In general terms, what’s happening is this:

  1. The low-level timer function waits for 1000 milliseconds (1 second).
  2. The timer function calls the HelloWorld() function.
  3. The timer function then decrements the counter (started at 10) by 1 and begins again.

Storing Functions in Variables

Just as we can pass a function as a parameter to another function, we can also store the function in a variable. This allows us to treat the variable as that function:

local helloFunc = HelloWorld
helloFunc()

The code above:

  1. Declares a variable called helloFunc.
  2. Assigns the HelloWorld() function to the value of the helloFunc variable.
  3. Calls the variable as a function.

Which will print:

Hello!

Using Functions as First Class Values

So, now we know that we can:

  • Add functions to Corona’s APIs.
  • Store functions in variables.
  • Pass functions as parameters.

To enhance Corona’s own functionality, we will make use of all three of these Lua features.


Building on What’s There

Now your clever little function is there in the API and working happily along with the others. You’re calling it from your code, content in the knowledge that it’s a fun new feature of the API.

However, now we notice a problem with one of the existing string library functions. The gsub() function lets us do some pretty powerful things. Take a look at the documentation page and you’ll see that it is basically a search-and-replace:

local str = "Hello banana"
print( string.gsub( str, "banana", "Corona user" ) )
--Outputs: Hello Corona user

Unfortunately, what it does not do is allow us to specify a list of key/value string pairs to do a bulk search and replace. For example, we cannot provide a table to be used, like this:

local searchreplace = {
   Hello = "Success",
   banana = "Corona user"
}

To do this, we need a for loop to iterate over the entries in the table and call the gsub() function for each entry:

local str = "Hello banana"
for k,v in pairs( searchreplace ) do
   str = string.gsub( str, k, v )
end

print( str )
--Outputs: Success Corona user

The pairs() function is a clever little Lua function which identifies each index in the searchreplace table and returns its name and value. This allows Lua tables to be used as dictionaries quite effectively. Don’t worry about the clever bit inside the loop — just know that you’ve written another awesome function which you’ll be using everywhere you’ve got lots of boring string replacement to do.

Now let’s put that loop in a function:

local function gsubEx( str, searchreplace )
   for k,v in pairs( searchreplace ) do
      str = string.gsub( str, k, v )
   end
   return str
end

This function lets us reuse the neat little string-replacing loop anywhere we like:

gsubEx( str, searchreplace )
--Outputs: Success Corona user

Easy, yes?


Improving What’s There

Let’s put this all together and actually override the gsub() function. First, we need to get the gsub() function into a variable. As we saw earlier, this is possible because, in Lua, functions are first class values:

local gsub = string.gsub

With this, we can call the gsub() function just by using the gsub variable. Now that we’ve stored the function, we need to overwrite the Lua gsub function by replacing it with our own. We saw this earlier, too. We’ll use the same function parameters which the real gsub() function has:

string.gsub = function( s, pattern, repl, n )
end

What we’ve done in just two lines is to take a copy of the gsub() function (to avoid losing it) and replace it with our own function. We can now put any logic in there that we like. In this example we’ll check if the pattern parameter is a string or a table:

if ( type(pattern) == "string" ) then
   --do something
else
   --do something else
end

If you take a quick look at the string.gsub() documentation, you’ll see that the pattern parameter is always a string. It’s the search pattern used to identify what should be replaced in the s string parameter.

If the pattern parameter is a string, we can just call the original function. How do we do that? Well, remember that we kept a copy of the original gsub() function around, so use that:

if ( type( pattern ) == "string" ) then
   return gsub( s, pattern, repl, n )  --call the original function and return whatever it returns
else
   --do something else
end

If the pattern parameter is not a string, however, we know that it will be a table of key/value pairs to use in our clever little loop. The loop can go in the second half of the if statement:

else
   --do something else (loop over the keys and replace them in the input string 's')
   for k,v in pairs( pattern ) do
      s = gsub( s, k, v )
   end
   return s
end

Note that we’ve modified the loop a little to make use of our new parameter names s and pattern.


Putting it Together

Putting this all together, we can see that the our override and new gsub() function look like this:

local gsub = string.gsub
string.gsub = function( s, pattern, repl, n )

   if ( type(pattern) == "string" ) then
      return gsub( s, pattern, repl, n )
   else
      for k,v in pairs( pattern ) do
         s = string.gsub( s, k, v )
      end
      return s
   end
end

The result of all this work is that the string.gsub() function has been replaced with our own logic which will call the original function if the parameters are normal, but do its own work (while reusing the original function) if the parameters have changed. Presto! We’ve just extended the functionality of Corona’s own library:

print( string.gsub( "Hello banana", { Hello="Success", banana="Corona user" } )
--Outputs: Success Corona user

What’s great about this is that you can do it with any API library provided by Corona.

Warning!

I’m sure you can think of many possibilities using your new-found “god mode,” but you must be careful.

  • Don’t lose your original function by forgetting to store it in a variable.
  • Don’t call your own function from within itself or you’ll end up with an infinite loop.
  • Always make sure that your overriding function calls the original at some point, otherwise you’re not overriding, you’re replacing.

This is an advanced topic so experiment with it and become very familiar with its usage. Do not write production-ready code unless you fully understand what’s happening. There’s a lot of smoke and mirrors here — it’s easy to get lost.


Examples

Here are a few additions to your libraries that you might find interesting and useful:

1. “math” library — adding a function to calculate length:

--returns the distance between points a and b
math.lengthOf = function( a, b )
   local width, height = b.x-a.x, b.y-a.y
   return ( width*width + height*height ) ^ 0.5  --math.sqrt( width*width + height*height )
end

2. “math” library — adding a function to clamp values:

--returns a value clamped between a range
math.clamp = function( val, low, high )
   if ( val < low ) then return low end
   if ( val > high ) then return high end
   return val
end

3. print() function — overriding print() to not print when running on a device:

--override print() function to improve performance when running on device
if ( system.getInfo("environment") == "device" ) then
   print = function() end
end

4. “math” library — adding a function to calculate nearest multiples:

--rounds up to the nearest multiple
math.nearest = function( number, multiple )
   return math.round( (number / multiple) ) * multiple
end

References


Posted by . Thanks for reading...

6 Responses to “Tutorial: Extending Libraries Without Native Code”

  1. Marcos Tito

    Hi people !

    By the way, could you list the top Lua libraries for Corona SDK ?

    Does anybody suggest a must have “tool set ” of Libraries ?

    How about the best libraries for business Apps ?

    I know there are plenty of libraries, thus I would like to hear from the community the most widely used ones!

    Thank you !

    Reply

Leave a Reply

  • (Will Not Be Published)