Embedding LUA in PureBasic!?

Everything else that doesn't fall into one of the other PB categories.
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Embedding LUA in PureBasic!?

Post by FloHimself »

Hi,
because of only a few replies in german forum, i will post here again:
I've started playing with the LUA Language (http://www.lua.org) interpreter and started wrapping some functions in a purelib. Not much right now but very interesting to use lua scripts in purebasic. Maybe someone is interested in using lua as script language in purebasic game / app development!?

A small example:
PureBasic Code (Console):

Code: Select all

l = lua_open() 
lua_baselibopen(l) 
lua_iolibopen(l) 
res = lua_dofile(l, "hello.lua") 
lua_close(l) 
Debug PeekS(lua_version())  
LUA Code (hello.lua):

Code: Select all

-- the first program in every language 
io.write("Hello world, from ",_VERSION,"!\n") 
The needed lib and example are >here<
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

In order to make it more interesting..
The Lib seems to be nearly complete. Here is another example of using PureBasic and LUA.

I defined a function in PureBasic, registered it to LUA, called it from a LUA script and passed the return values back to PureBasic.

PureBasic Code (console mode):

Code: Select all

Declare average(l)
Declare lua_register(l, n$, f)

l = lua_open()
lua_baselibopen(l)
lua_iolibopen(l)


OpenConsole()
  PrintN("PureBasic: Registering function 'average' to LUA...")
  lua_register(l, "average", @average())
  PrintN("PureBasic: Execute LUA file 'average.lua'...")
CloseConsole()

  lua_dofile(l, "average.lua")

OpenConsole()
  PrintN("PureBasic: Got back the value #1 (average of arguments) from LUA: " + StrF(lua_tonumber(l, lua_gettop(l)))) ; first result average
  PrintN("PureBasic: Got back the value #2 (sum of arguments) from LUA    : " + StrF(lua_tonumber(l, lua_gettop(l)-1))) ; second result sum
  Delay(10000)
CloseConsole()

lua_close(l)

ProcedureCDLL average(l)
  n = lua_gettop(l)
  sum.f = 0.0
  
  For i = 1 To i <= n
    If lua_isnumber(l, i) = 0
      lua_pushstring(l, "PureBasic: Incorrect argument to function 'average'")
      lua_error(l)
    EndIf
    sum = sum + lua_tonumber(l, i)
  Next i
  
  lua_pushnumber(l, sum/n)  ; first result average
  lua_pushnumber(l, sum)    ; second result sum
  ProcedureReturn 2         ; number of results
EndProcedure

Procedure lua_register(l, n$, f)
  lua_pushstring(l, n$)
  lua_pushcclosure(l, f, 0)
  lua_settable(l, #LUA_GLOBALSINDEX)
EndProcedure
Lua Code (average.lua):

Code: Select all

-- average example
io.write("      LUA: Calling registered PureBasic function 'average'\n") 
io.write("      LUA: Passing the return values back to PureBasic\n") 
return average(35,2,12.4,43,5.3) -- call the registered purebasic function
>Here< is the new Test package. If someone testing this... just play with the args of the everage() function in the LUA script ;)

nighty night,
FloHimself

//EDIT: changed "Procedure" to "ProcedureCDLL" because of CDECL
Last edited by FloHimself on Tue Jan 11, 2005 9:08 am, edited 1 time in total.
User avatar
fsw
Addict
Addict
Posts: 1603
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Post by fsw »

Never worked with LUA though, but this is sure interesting stuff.

I am to provide the public with beneficial shocks.
Alfred Hitshock
Karbon
PureBasic Expert
PureBasic Expert
Posts: 2010
Joined: Mon Jun 02, 2003 1:42 am
Location: Ashland, KY
Contact:

Post by Karbon »

Very cool to use as a scripting engine of some sort.. I'm not sure how (if) I'll use it but it's cool!

Good work!
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net
PolyVector
Enthusiast
Enthusiast
Posts: 499
Joined: Wed Sep 17, 2003 9:17 pm
Location: Southern California
Contact:

!!!

Post by PolyVector »

WOW!
I've tried to impliment luascript before (using a dll) and never could get it right... GREAT WORK!
Keep us all updated on your progress :)
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

Thanks for your feedback!

@PolyVector
The Lib seems to be nearly complete. But there are waiting more then 60 functions for a documentation..

[edit:]
here a link to the complete version:
http://www.florian-s.com/PBLua/index.html
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

Hi,

a new version has been released!
http://pblua.florian-s.com

Flo
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

Hi!

Again a small update. I've added a small script (pbtolua.lua) to this package.
It can be used to generate wrapper functions from purebasics 'Declare'
statement, that can be 'registered' in Lua. Also I've added a small example.

regards,
Flo
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

BTW: Here is an excerpt from the document 'Programming in Lua' i've adapted to
purebasic and i've posted already in the german forum. This is a nice
example how useful Lua can be for your application:
Extending your Application:
A main use of Lua is as a configuration language. In this section, we will illustrate how Lua is used as a configuration language, starting with a simple example and evolving it to more complex tasks. As our first task, let us imagine a very simple configuration scenario: Your program (let us call it 'config_simple') has a window, and you want the user to be able to give the initial window size. Clearly, for such a simple task, there are many options simpler than using Lua, such as environment variables or files with name-value pairs. But even for a simple text file, you have to parse it somehow. So, you decide to use a Lua configuration file (that is, a plain text file that happens to be a Lua program). In its simplest form, such file can contain something like the next lines:

Code: Select all

-- configuration file for program 'config_simple'
-- define window size
width = 200
height = 300
Now, you must use the Lua API to direct Lua to parse this file, and then to get the values of the global variables width and height. The following function does the job:

Code: Select all

Global width, height ; global variables

Procedure LoadConfig(FileName$)
  L = lua_open()
  lua_baselibopen(L)
  lua_iolibopen(L)
  lua_strlibopen(L)
  lua_mathlibopen(L)

  If (lua_dofile(L, FileName$) <> 0)
    MessageRequester("Error","Cannot run configuration file!")
    End
  Else
    lua_getglobal(L, "width")
    lua_getglobal(L, "height")

    If (lua_isnumber(L, -2) = 0) Or (lua_isnumber(L, -1) = 0)
      MessageRequester("Error","Invalid configuration file!")
      End
    Else 
      width   = lua_tonumber(L, -2)
      height  = lua_tonumber(L, -1)
    EndIf
    
    lua_close(L)
  EndIf
EndProcedure
First, it opens the Lua package, and loads the standard libraries (they are optional, but usually it is a good idea to have them around). Then, it uses lua_dofile to run the chunk in file filename. If there is any error (e.g., a syntax error), we will give an error message, and lua_dofile will return an error code; otherwise lua_dofile returns 0. After running the chunk, the program needs to get the values of the global variables.

For that, it first calls the lua_getglobal function, whose single argument (besides the omnipresent lua_State L) is the global name. Each call pushes the corresponding global value into the API stack. We could index from the top, using -2 for the first value and -1 for the second.

To check the type of a stack element, the API provides the functions lua_isnil, lua_isnumber, lua_isstring, lua_isuserdata, and lua_isfunction; they receive the index of the element to check. Our example uses lua_isnumber to check whether both values are numeric. Finally, it uses lua_tonumber to convert such values to float, and PureBasic does the coercion to longs.

Is it worth the use of Lua? As we said before, for such a simple task, a simple file with only two numbers in it would be much easier to use than Lua. But even in this scenario, the use of Lua has some advantages. First, your configuration file can have comments. Second, the user already can do more complex configurations with it. For instance, the script may prompt the user for some information, or it can query an environment variable to choose a proper size:

Code: Select all

-- configuration file for program 'config_simple'
-- define window size
if os.getenv("USERNAME") == "FloHimself" then
	width = 400; height = 200
else
	width = 100; height = 200
end
Even in such a trivial configuration scenario, it is hard to anticipate what users will want; but as long as the script defines the two variables, your PureBasic application works without changes.
A final reason to use Lua is that now it is easy to add new configuration facilities to your program, and this easiness creates an attitude that results in more flexible programs.
download the 1. example: http://www.florian-s.com/PBLua/example/ ... simple.zip
Let us adopt this attitude: Now, you want to configure a background color for the window, too. We will assume that the final color specification is composed by three numbers, where each number is a color component in RGB. A naive approach is to ask the user to give each component in a different global variable:

Code: Select all

-- configuration file for program 'config_complex'
width = 200
height = 300
background_red = 30
background_green = 100
background_blue = 0
This approach has several drawbacks; it is too verbose (most real programs need dozens of different colors, for window background, window foreground, menu background, etc.); and there is no way to pre-define some colors, so that, later, you can simply write something like background = WHITE. A better option is to use a table to represent a color:

Code: Select all

background = {r=30, g=100, b=0}
The use of tables gives more structure to the script; now it is easy for the user or the application to pre-define colors for later use in her configuration file:

Code: Select all

BLUE = {r=0, g=0, b=255}
...
background = BLUE
To get these values from your PureBasic program, you can do as follows:

Code: Select all

lua_getglobal(L, "background")
If (lua_istable(L, -1) = 0)
  MessageRequester("Error","'background' is not a valid color table")
  End
Else 
  red   = lua_getfield(L, "r")
  green = lua_getfield(L, "g")
  blue  = lua_getfield(L, "b")
EndIf
As usual, you first get the value of the global variable background, and then you make sure that it is a table. Next, we use getfield to get each color component. This function is not part of the API; we must define it, as follows:

Code: Select all

Procedure lua_getfield(L, key$)
  lua_pushstring(L, key$)
  lua_gettable(L, -2); get background[key]
  If (lua_isnumber(L, -1) = 0)
    MessageRequester("Error", "Invalid component in background color!");
    End
  EndIf
  result = lua_tonumber(L, -1)
  lua_settop(L, -2) ; = lua_pop(L,n) = lua_settop(L, -(n)-1)
  ProcedureReturn result
EndProcedure
Again, the problem is polymorphism: There are potentially many versions of getfield functions, varying the key type, value type, error handling, etc. lua_gettable receives the index of the table, pops the key from the stack, and pushes the corresponding value. Our private lua_getfield assumes that the table is at top of the stack, so after pushing the key (lua_pushstring) the table will be at index -2. Before returning, lua_getfield pops the retrieved value from the stack, to leave the stack in the same state it was before the call. (The argument 1 in the call to lua_pop is the number of elements to pop.) Now the user still can use color tables, but he can also use names for the more common colors, such as background = WHITE.

Code: Select all

-- configuration file for program 'config_complex'
-- define window size

BLUE 	= {r=0, g=0, b=255}
GREEN 	= {r=0, g=255, b=0}
RED 	= {r=255, g=0, b=0}

if os.getenv("USERNAME") == "FloHimself" then
	width = 400; height = 400
	background = RED
else
	width = 100; height = 100
	background = {r=30, g=100, b=0}
end
download the 2. example: http://www.florian-s.com/PBLua/example/ ... omplex.zip
FloHimself
Enthusiast
Enthusiast
Posts: 229
Joined: Wed May 14, 2003 3:38 pm
Location: Lüneburg - Germany

Post by FloHimself »

Updated for use with Purebasic v3.90+
PolyVector
Enthusiast
Enthusiast
Posts: 499
Joined: Wed Sep 17, 2003 9:17 pm
Location: Southern California
Contact:

Post by PolyVector »

Great work Flo!
I can't wait until 3D support is better, I'm going to use this lib like crazy :)
User avatar
Rings
Moderator
Moderator
Posts: 1435
Joined: Sat Apr 26, 2003 1:11 am

Post by Rings »

For everyone (even remotely) interested in the Lua programming language, now the book "Programming in Lua" (by the chief Lua architet itself, Roberto Ierusalimschy), is available for free online: http://www.lua.org/pil/
SPAMINATOR NR.1
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

..

Post by NoahPhense »

Nice work!

What is going to be your main use for LUA?

- np
Karbon
PureBasic Expert
PureBasic Expert
Posts: 2010
Joined: Mon Jun 02, 2003 1:42 am
Location: Ashland, KY
Contact:

Post by Karbon »

Ierusalimschy
That has to be the strangest name I've ever seen. How exactly does one pronounce that? I'm trying but it just sounds like I'm sneezing.
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net
Moonshine
Enthusiast
Enthusiast
Posts: 263
Joined: Tue May 25, 2004 12:13 am
Location: UK

Post by Moonshine »

I think it's err - ru - sa - lim - shi
Mark my words, when you least expect it, your uppance will come...
Post Reply