Only French available, sorry 🙁
Category Archives: Tutorials
Extend string.match to test and capture patterns
Pattern matching is a powerful feature of Lua’s standard string library. I use it often to automate text file conversion and reporting. For instance, I use pattern matching in a Lua script to format and email my weekly report.
I have maintained a weekly diary of projects I’ve worked on, accomplishments, meetings attended, plans, business travel, and other information since 1988. Each week is logged in a simple text file. This means that despite the evolution of text editors and file media over the decades, I can still read my original diary files. The Lua script that prepares my weekly report scans my latest diary file and uses pattern matches to extract and format an email that adheres to the current reporting standards where I work.
The string.match function is useful for both testing if a pattern exists in a string and for extracting substrings that match a pattern enclosed in parentheses. Lua calls these substrings “captures”. Often I want to do both simultaneously–test for a pattern in an if statement and capture substrings. For instance, in my weekly report generator, I have a bit of code:
if line:match("^Weekly Report.+(%d%d)/(%d%d)/(%d%d%d%d)") then local month = _1 local day = _2 local year = _3 email:setSubject(("John Powers - %s-%s-%s Weekly Report"):format(year, month, day)) |
The first line checks to see if variable line starts with the text “Weekly Report” and contains a date. If it matches, as a side effect it also sets global variables _1, _2, and _3 to the captures, i.e. the month, day, and year captured from string line. The built-in definition of string.match does not have this side effect. But we can extend string.match to gain this new capability.
Here is the code I used to modify string.match.
do local smatch = string.match -- keep the original definition of string.match -- String matching function -- Same results as string:match but as a side effect -- places the captures in global variables _1, _2, ... function string:match(pat) local matches = {smatch(self, pat)} -- call the original match to do the work for i = 1, #matches do -- #matches == 0 if no matches _G["_" .. i] = matches[i] -- assign captures to global variables end return unpack(matches) -- return original results end end |
Note the use of a do … end block. This creates a block that limits the scope of local variable smatch. Only the new function string.match can call it.
Placing captures into global variables is nothing new. Anyone familiar with the AWK, Perl, and Ruby scripting languages will recognize this feature right away.
A new, smarter way to create a Screen Manager
It hasn’t been a long time since Jim Bauwens surprised us with an elegant way to customize the gc object with your own functions. But he’s striking again.
Indeed, he came up with a new, smarter way to create a screen manager.
If you’re not familiar with this great concept, you should head over here.
For those who are, though, you should know that it’s all about thoroughly listing and linking all the events you’ll have defined later in each screen implementation.
For example :
function on.arrowKey(key) activeScreen:arrowKey(key) end function on.timer() activeScreen:timer() end -- long list of all handled events... Then, somewhere else : function myScreen:arrowKey(key) -- Your awesome code goes here end function myScreen:timer() print("I'm the myScreen timer, ticking....") end -- etc. |
It sure works, but …. quite boring, eh ?
Well, let’s look at what Jim created.
I define (t)his new screen manager concept as “Smarter” because with this code, you won’t even have to directly (explicitly) rely on the traditional event handling you’re used to, writing things like on.arrowKey, on.enterKey… or the good old on.paint.
That’s right, with this new method : no more “function on.paint(gc) …” etc.
“What’s this sorcery about ?” , you may wonder ?
Well, once more, it’s all about intelligently using the power of Lua metatables.
In short, metatables are sets of properties defining an object (generally a table) ‘s behaviour.
There is a “__index” property that you can define, that will describe how the table will react when the script calls an undefined element of that table. Pretty useful, believe me.
Well, the thing is that when you write “function on.paint(gc)”, you’re actually defining the “paint” method of the “on” table (thus the dot).
What we want to do, is to get rid of the explicit definition and to “redirect” the paint event to whichever screen we want to.
So, we’re going to use an “eventDistributer” method that takes as arguments whatever its passed, with the use of the “…” (the event followed by its parameters, if any), and “passes” them to the screen we want (checking that the event actually exists (defined) for the screen :
local triggeredEvent = "paint" -- first declaration, it will be overwritten anyway local eventDistributer = function (...) local currentScreen = GetScreen() if currentScreen[triggeredEvent] then currentScreen[triggeredEvent](currentScreen, ...) end end |
This code should be rather clear to you now.
Now, what we have to do is to actually bind that function to the “on” ‘s metatable __index (notice the smart use of closures) :
local eventCatcher = {} eventCatcher.__index = function (tbl, event) triggeredEvent = event return eventDistributer end setmetatable(on, eventCatcher) |
That code tells the Lua script that whenever an event without an explicit “on.xxx” handler occurs, it will execute this function (which returns the function – that’s the closure I was talking about – that will take the args of the event and pass it through the eventDistributer thus actually calling the correct screen’s appropriate event handler). You might find all this confusing, but at some point you sould be able to figure it out 🙂
Anyway, here’s the full code for the screen manager and event redistributer.
(You’ll still need to create (and push) your screens as usual.)
local screens = {} local screenLocation = {} local currentScreen = 0 function RemoveScreen(screen) screen:removed() table.remove(screens, screenLocation[screen]) screenLocation[screen] = nil currentScreen = #screens -- sets the current screen as the top one on the stack. if #screens<=0 then print("Uh oh. This shouldn't have happened ! You must have removed too many screens.") end end function PushScreen(screen) -- if already activated, remove it first (so that it will be on front later) if screenLocation[screen] then RemoveScreen(screen) end table.insert(screens, screen) screenLocation[screen] = #screens currentScreen = #screens screen:pushed() end function GetScreen() return screens[currentScreen] or RootScreen end Screen = class() function Screen:init() end function Screen:pushed() end function Screen:removed() end RootScreen = Screen() -- "fake", empty placeholder screen. local eventCatcher = {} local triggeredEvent = "paint" local eventDistributer = function (...) local currentScreen = GetScreen() if currentScreen[triggeredEvent] then currentScreen[triggeredEvent](currentScreen, ...) end end eventCatcher.__index = function (tbl, event) triggeredEvent = event return eventDistributer end -- finally linking everything setmetatable(on, eventCatcher) |
I’m sure a lot of you readers love a working .tns example directly, so, I made one just for you : click here. It’s a simple example showing a few events, without a single on.xxxx event handler explicitely defined, inside 2 screens. I’ve commented the code quite well so you’d understand quickly, I’m sure 🙂
For a more “real-life” example fully demonstrating this technique (as well as the gc-customizing trick), I suggest downloading Jim’s Memory game here.
How to add your own functions to “gc”
Hi all,
As you know, in order to do graphical things in Lua on the Nspire platform, you have to deal with gc and its methods TI created, like drawString, drawRect, fillArc, etc.
Well, what if you wanted to make a drawRoundRect routine ?
You could certainely do something like :
function drawRoundRect(gc, x, y, wd, ht, rd) if rd > ht/2 then rd = ht/2 end gc:drawLine(x + rd, y, x + wd - (rd), y) gc:drawArc(x + wd - (rd*2), y + ht - (rd*2), rd*2, rd*2, 270, 90) gc:drawLine(x + wd, y + rd, x + wd, y + ht - (rd)) gc:drawArc(x + wd - (rd*2), y, rd*2, rd*2,0,90) gc:drawLine(x + wd - (rd), y + ht, x + rd, y + ht) gc:drawArc(x, y, rd*2, rd*2, 90, 90) gc:drawLine(x, y + ht - (rd), x, y + rd) gc:drawArc(x, y + ht - (rd*2), rd*2, rd*2, 180, 90) end function on.paint(gc) drawRoundRect(gc, 100, 50, 20, 15, 5) ... end |
Indeed, that works.
But wouldn’t it be cool to actually have it like, “gc:drawRoundRect(100,50,20,15,5)”, so it can feel way more natural and wouldn’t need you to explicitely pass gc as an argument ? 😉
Well, here is the definitive solution to this, by Jim Bauwens 😉
function AddToGC(key, func) local gcMetatable = platform.withGC(getmetatable) gcMetatable[key] = func end local function drawRoundRect(gc, x, y, wd, ht, rd) -- the code above end AddToGC("drawRoundRect", drawRoundRect) function on.paint(gc) gc:drawRoundRect(100, 50, 20, 15, 5) ... end |
One more thing :
You may have noticed the use platform.withGC, which is an API “2.0”+ (OS 3.2+) function. Here’s how to “recreate” it for earlier versions :
if not platform.withGC then platform.withGC = function(func, ...) local gc = platform.gc() gc:begin() func(..., gc) gc:finish() end end |
[Update] : John Powers from TI commented that this definition of platform.withGC has some limitations, and proposed this better version (thanks !) :
if not platform.withGC then function platform.withGC(f) local gc = platform.gc() gc:begin() local result = {f(gc)} gc:finish() return unpack(result) end end |
A-to-Z Nspire Lua tutorials
This page links to Steve Arnold’s website where you can find excellent detailed, step-by-step tutorials.
However, they are all in English. Inspired-Lua is making its best to translate them into French too.
The french version so far is available here.
- Lesson 1: First Steps with Lua and TI-Nspire
on.paint, gc (graphics context), drawString, setFont, setColorRGB, platform.window:width, platform.window:height - Lesson 2: Creating a Dynamic Table
tables, for loops, getStringWidth, getStringHeight, concatenation (..), displaying multiple lines, var.recall - Lesson 3: Varying the Table Contents
if..then..else..end, equality (==) vs assignment (=), more on var.recall - Lesson 4: Capturing Input in your Lua Page
on.charIn, var.store, on.backspaceKey, string.usub, platform.window:invalidate, setPen, drawPolyLine, drawRect, on.enterKey - Lesson 5: Putting it all together: Some Applications
page setup template, simple quiz document
- Lesson 6: Quick Start: Controlling a Point on a Graph page
on.arrowUp, on.arrowDown, on.arrowLeft, on.arrowRight, on.create, timer.start, on.timer - Lesson 7: Quick Start: Working with Images in Lua
image.new, drawImage, image.width, image.height, image.copy, on.resize - Lesson 8: Quick Start: Getting Started with Graphics in Lua
drawLine, fillRect, fillPolygon, drawArc, fillArc, isColorDisplay, setAlpha - Lesson 9: Graphical Shape Numbers
Drawing an array, and varying the grid pattern - Lesson 10: Taking Shape Numbers Further
Putting it all together, on.escapeKey, on.tabKey
- Lesson 11: Introducing Classes
class, init, contains, paint, selected, color table, unpack - Lesson 12: Mouse Controls with Classes
on.mouseDown, on.mouseUp, on.mouseMove - Lesson 13: Keyboard Controls with Classes
Using arrowKeys, enterKey, tabKey and escapeKey with classes - Lesson 14: Keyboard Controls with Multiple Classes
Using a table to define multiple classes, #Objects (dimension of table Objects) - Lesson 15: Mouse Controls with Multiple Classes
TrackOffset variables, table.remove, table.insert, cloning selected class objects
- Lesson 16: Further Useful Tips and Tricks I
Useful string commands: find, replace (gsub), split: applied to pretty print function for algebra - Lesson 17: Further Useful Tips and Tricks II
math.eval (and all applied to fraction function along with a template for multiple keyPads) - Lesson 18: Further Useful Tips and Tricks III
Adding your own custom menus. - Lesson 19: Guidelines for Lua Nspired Authoring I
WORKING ACROSS PLATFORMS (Create Once Play Everywhere)1. Display Considerations: Looking Good Everywhere! - Lesson 20: Guidelines for Lua Nspired Authoring II
WORKING ACROSS PLATFORMS (Create Once Play Everywhere)2. Control Considerations: Working Well Everywhere!
The lessons in this next section require OS 3.2 functionality.
- Lesson 21: (3.2) Text Boxes and Rich Text Input
The new and better way to enter and work with text as the user inputs it: Rich Text boxes come alive! - Lesson 22: (3.2) Create Your Own Math and Chem Boxes
Displaying and evaluating mathematics has never been easier now that we can let TI-Nspire MathBoxes take care of the heavy lifting! - Lesson 23: (3.2) Welcome to the Physics Engine! (I)
Based upon the open-source Chipmunk Physics Engine, this powerful new feature adds amazing possibilities to your Nspire scripting and is the perfect tool for creating simulations of all sorts. - Lesson 24: (3.2) Welcome to the Physics Engine! (II)
Bouncing a ball around the screen maybe a very simple simulation, but it can be the start of something much bigger. - Lesson 25: (3.2) Welcome to the Physics Engine! (III)
Bouncing multiple coloured balls around the screen is much more fun. - Lesson 26: (3.2) Welcome to the Physics Engine! (IV)
Adding Segments as Shapes and using these as walls. - Lesson 27: (3.2) Welcome to the Physics Engine! (V)
Polygons as Physics Objects.
- Lesson 28: (3.4) Welcome to the iPad!
Scripting for Touch-Enabled Devices. - Supplement: Working with Scripts on the iPad
- Lesson 29: Lua Objects Gallery Tutorial
Learn how to use the Lua Objects Gallery in creating your own scripts
- Lesson 30: Welcome to BLE
- Lesson 31: BLE – Create your own TI-Nspire Remote
- Lesson 32: BLE – Measuring Temperature with the Vernier Go Wireless Temp
- Lesson 33: BLE – Measuring Heart Rate
- Lesson 34: BLE – Measuring Temperature with the TI Sensor Tag
- Lesson 35: BLE – Build Your Own Weather Station with the TI Sensor Tag
- Lesson 36: BLE – Exploring Movement and Position with the TI Sensor Tag
How to make a .tns from a .lua file ?
You have two options:
1) Use the Nspire computer software
Since version 3.2 (mid 2012), the Nspire software (“TINCS”) includes a script editor to directly create, edit, test, and debug your Lua code from within a TI-Nspire document 🙂
It is available from the Insert menu > Script Editor .
2) Use “Luna”
Luna is an open-source community tool, created by Olivier “ExtendeD” Armand, that can create .tns files (TI-Nspire) out of a .lua file, for instance.
You can download a Windows executable on TI-Planet.
For other platforms, you can compile the source code, available at the same link.
To use it, just write in a terminal / command prompt, the path to the Lua file followed by the path of .tns you want to create:
1 | luna.exe myscript.lua mydocument.tns |
Let’s go to Part 3 now!
Create the is() operator to test an object type
On TI-Nspire, Lua lets you work easily with classes with class() function, that is also used to create inherited classes, based on the mother class. Sadly, since this class concept is purely superficial, there is neither function nor syntax to work with specific kind of classes. In this tutorial, we’re going to see how to reproduce the “is” feature that we meet in C# for example.
The is() operator lets you distinguish between an object created with one class and another object created with another class. We are going to create the is() operator specific to Lua because it doesn’t support inherited classes tests.
Here is an example of what we are trying to do :
function is(obj, class) -- we are going to create it end A = class() function A:init(x) self.x = x end B = class(A) function B:init(x) self.x = x * 2 end t = { A(1), B(1) } for k, v in pairs(t) do if is(v, A) then print("A class, value = "..v.x) elseif is(v, B) then print("B class, value = "..(v.x / 2)) end end |
The goal is here obvious : if we store in a table objects from different classes we some common fields, we d’like to be able to distinguish objects from a particular class.
But how write such a function in that case ? Add a specific field ? No need !
Indeed, the class() function does half of the work. It stores something in the __index field of your object (in reality a table). This one contains a reference to the mother class. When you do a = A(), a.__index is equal to A.__index (by reference) !
As simple as you guessed :
function is(obj, class) return obj.__index == class.__index end |
NB : Since the edge between classes and object is not so far big in Lua (contrary to other OOP langages where an object is created by a class), it is possible to test whether or not two objects have the same mother class with one test less :
A = class() function A:init() end a = A() b = A() is(a, b) -- returns true |
Call the master class constructor
You are obviously a lot that enjoyed the easy-go of Lua classes on TI-Nspire, but also noticed that it is too simple to let you work correctly on a specific case. Let’s explain.
This case is the ability to call the master class constructor in the inherited class in order to avoid repetitions. Indeed, if you change one field in the master class, you must change that field in every inherited classes.
Here is an example :
A = class() function A:init(x) self.t = 0 self.x = x end B = class(A) function B:init(x, y) self.t = 0 self.x = x self.y = y end |
Here, if we would like to change t = 0 from the master class constructor, we also have to change it in the B class constructor in order to be coherent. Ones could think like that :
A = class() function A:init(x) self.t = 0 self.x = x end B = class(A(0, 0)) -- construction of the object before inheriting function B:init(x, y) self.x = x self.y = y end |
But here again, the field x is repeated.
One thing we forgot to take care here is that B is an inherited class of A, so, each field of A are obviously in B. But for the constructor, we’re just redefining it. Is there a way to call the previous constructor from A giving B as parameter ?
Actually, yes !
Something important to know in order to understand the solution, is that this :
dummy = class() function dummy:doSomething() self.mark = 0 end dummy:doSomething() |
is exactly the same of that :
dummy = class() function dummy.doSomething(self) self.mark = 0 end dummy.doSomething(dummy) |
or again :
dummy = class() function dummy:doSomething() self.mark = 0 end dummy.doSomething(dummy) |
The colons allow to give as first parameter the object that has this method. So nothing prevents us to not use this tip and then give to the method a manual first parameter as below !
A = class() function A:init(x) self.t = 0 self.x = x end B = class(A) function B:init(x, y) A.init(self, x) -- call the A class constructor in order to avoir repetitions self.y = y end |