What are lua scripts. Lua programming language. What data types does the Lua language support?

I am a sentimental programmer. Sometimes I fall in love with programming languages ​​and then I can talk about them for hours. I will share one of these hours with you.

lua? What's this?

Lua is a simple embeddable language (it can be integrated with your programs written in other languages), lightweight and understandable, with a single data type, with a uniform syntax. The perfect language to learn.

What for?

Lua may be useful to you:

* if you are a gamer (plugins for World of Warcraft and many other games)
* if you write games (very often in games, the engine is written in C / C ++, and AI - in Lua)
* if you are a system programmer (you can write plugins for nmap, wireshark, nginx and other utilities in Lua)
* if you are an embedded developer (Lua is very fast, compact and requires very few resources)

1. Learn to program. At least a little. It doesn't matter what language.
2. Install Lua. To do this, either download version 5.2 here (http://www.lua.org/download.html), or look for it in the repositories. Version 5.1 will also work, but be aware that it is very old.

Run all the examples from the article in the terminal with a command like "lua file.lua".

First Impressions

Lua is a dynamically typed language (variables get types on the fly depending on the assigned values). You can write on it in both imperative and object-oriented or functional style (even if you don’t know how it is, it’s okay, keep reading). Here is Hello world in Lua:

My first lua app: hello.lua print "hello world"; print("goodbye world")

What can be said about the language:

* single-line comments start with two dashes "--"
* brackets and semicolons can be omitted

Language operators

The set of conditional statements and loops is quite typical:

Conditional statements (there may not be an else branch) if a == 0 then print("a is zero") else print("a is not zero") end -- short form if/elseif/end (instead of switch/case) if a == 0 then print("zero") elseif a == 1 then print("one") elseif a == 2 then print("two") else print("other") end -- count loop for i = 1, 10 do print(i) end -- loop with precondition b = 5 while b > 0 do b = b - 1 end -- loop with postcondition repeat b = b + 1 until b >= 5

THINK: what could the loop "for i = 1, 10, 2 do ... end" mean?

In expressions, you can use the following operators on variables:

* assignment: x = 0
* arithmetic: +, -, *, /, % (remainder), ^ (exponentiation)
* logical: and, or, not
* comparison: >,<, ==, <=, >=, ~= (not-equal, yes-yes, instead of the usual "!=")
* string concatenation (operator ".."), eg: s1="hello"; s2="world"; s3=s1..s2
* length/size (# operator): s="hello"; a = #s ('a' will be equal to 5).
* get element by index, eg: s

There were no bit operations in the language for a long time, but in version 5.2, the bit32 library appeared, which implements them (as functions, not as operators).

Data types

I lied to you when I said that the language has one data type. It has a lot of them (like every serious language):

* nil (absolutely nothing)
* boolean numbers (true/false)
* numbers (numbers) - without division into integers / real. Just numbers.
* strings - by the way, they are very similar to strings in pascal
* functions - yes, the variable can be of type "function"
* thread
* arbitrary data (userdata)
* table

If everything is clear with the first types, then what is userdata? Recall that Lua is an embeddable language and usually works closely with program components written in other languages. So, these "foreign" components can create data for their own needs and store this data along with lua objects. So, userdata is the underwater part of the iceberg, which, from the point of view of the lua language, is not needed, but we simply cannot ignore it.

And now the most important thing in the language - tables.

tables

I lied to you again when I said that the language has 8 data types. You can consider that it is one: everything is tables (this, by the way, is also not true). A table is a very elegant data structure, it combines the properties of an array, a hash table (“key” - “value”), a structure, an object.

So, here is an example of a table as an array: a = (1, 2, 3) -- an array of 3 elements print(a) -- prints "2" because the indexes are counted from one -- And the table is in the form of a sparse array (which does not have all elements) a = () -- empty table a = 1 a = 5

THINK: what is a in the case of a sparse array?

In the example above, the table behaves like an array, but in reality, we have keys (indexes) and values ​​(array elements). And at the same time, keys can be any type, not just numbers:

A = () a["hello"] = true a["world"] = false a = 1 -- or like this: a = ( hello = 123, world = 456 ) print(a["hello")) print( a.hello) is the same as a["hello"], although it looks like a struct with fields

By the way, since the table has keys and values, you can iterate through all the keys and their corresponding values ​​in a loop:

T = ( a = 3, b = 4 ) for key, value in pairs(t) do print(key, value) -- will print "a 3", then "b 4" end

But what about objects? We will learn about them a little later, first - about functions.

Functions

Here is an example of a regular function.

Function add(a, b) return a + b end print(add(5, 3)) -- print "8"

The language functions allow you to take multiple arguments and return multiple arguments. So arguments whose values ​​are not explicitly specified are considered equal to nil.

THINK: why would you want to return multiple arguments?

Function swap(a, b) return b, a end x, y = swap(x, y) -- by the way, this can be done without a function: x, y = y, x -- and if the function returns multiple arguments, -- you don't need them - ignore them with the -- special underscore variable "_" a, _, _, d = some_function()

Functions can take a variable number of arguments:

In the prototype, a variable number of arguments is written as ellipsis function sum(...) s = 0 for _, n in pairs(arg) do -- in the function they are accessed as a table "arg" s = s + n end return a end sum(1, 2, 3) -- will return 6 sum(1, 2, 3, 4) -- will return 10

Since functions are a full-fledged data type, you can create function variables, or you can pass functions as arguments to other functions

A = function(x) return x * 2 end -- function that multiplies by 2 b = function(x) return x + 1 end -- function that increments by 1 function apply(table, f) result = () for k, v in pairs(table) do result[k] = f(v) -- replace the element with some function of this element end end -- THINK: what calls t = (1, 3, 5) apply(t, a) apply(t, b)

Objects = functions + tables

Since we can store functions in variables, we can also store them in table fields. And it already turns out as-if-methods. For those who are not familiar with OOP, I will say that its main benefit (at least in Lua) is that the functions and the data they work with are nearby - within the same object. For those who are familiar with OOP, I will say that there are no classes here, and prototype inheritance.

Let's move on to examples. We have an object, let's say a light bulb. She knows how to burn and not burn. Well, there are two actions you can do with it - turn it on and off:

Lamp = ( on = false ) function turn_on(l) l.on = true end function turn_off(l) l.on = false end -- these are just functions for working with the turn_on(lamp) turn_off(lamp) structure

And if the light bulb is made an object, and the turn_off and turn_on functions are made fields of the object, then we get:

Lamp = ( on = false turn_on = function(l) l.on = true end turn_off = function(l) l.on = false end ) lamp.turn_on(lamp) lamp.turn_off(lamp)

We are forced to pass the light bulb object itself as the first argument, because otherwise our function will not know which light bulb to work with in order to change the on / off state. But in order not to be verbose, Lua has an abbreviation that is usually used - lamp:turn_on (). In total, we already know several such syntax simplifications:

lamp:turn_on() -- the most common notation lamp.turn_on(lamp) -- this is also correct from a syntax point of view lamp["turn_on"](lamp) -- and this

Continuing to talk about abbreviations, functions can be described not only explicitly, as fields of a structure, but also in a more convenient form:

Lamp = ( on = false ) -- through a dot, then the argument must be specified function lamp.turn_on(l) l.on = true end -- through colons, then the argument is implicitly set itself, as the variable "self" -- "self" - and there is the light bulb for which the function lamp:turn_off() method was called self.on = false end

Interesting?

Special Features

Some table function names (methods) are reserved and have a special meaning:

* __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) - are called when arithmetic operations are performed on table
* __unm(a) - unary operation "minus" (when they write something like "x = -x")
* __lt(a, b), __le(a, b), __eq(a, b) - calculate the comparison result (<, <=, ==)
* __len(a) - called when "#a" is done
* __concat(a, b) - called on "a..b"
* __call(a, …) — called on "a()". Variable arguments are arguments when called
* __index(a, i) - access to a[i], provided that such an element does not exist
* __newindex(a, i, v) - create "a[i] = v"
* __gc(a) - when an object is removed by garbage collection

By overriding these methods, you can overload operators and use the syntax of the language for your own purposes. The main thing is not to overdo it.

Inheritance

For those who do not know OOP, inheritance allows you to extend the functionality of an already existing class. For example, just a light bulb can turn on and off, and a super-light bulb will also change its brightness. Why do we need to rewrite the turn_on/turn_off methods when we can reuse them?

In Lua, there is a concept of a meta-table for this, i.e. ancestor table. Each table has one parent table, and the child table can do everything that the parent can do.

Let's say that we have already created the lamp table object. Then the super-bulb will look like this:

Superlamp = ( brightness = 100 ) -- specify the parent table setmetatable(superlamp, lamp) -- and its methods are now available superlamp:turn_on() superlamp:turn_off()

Functionality extension

Many types have parent tables (well, strings and tables for sure, numbers and booleans, and nil does not have them). Let's say we want to add all strings using the "+" operator, not ".." . To do this, you need to replace the "+" (__add) function for the parent table of all rows:

S = getmetatable("") -- get the parent table of the row s.__add = function(s1, s2) return s1..s2 end -- change the method -- check a = "hello" b = "world" print(a + b) -- write "helloworld"

Actually, we can still replace the print function with "print = myfunction", and many other hacky things can be done.

Scopes

Variables are either global or local. When created, all variables in Lua are global.

THINK: why?

To specify the local scope, write the local keyword:

Local x local var1, var2 = 5, 3

Don't forget this word.

Error processing

Often, if errors occur, it is necessary to stop the execution of a certain function. You can, of course, do a lot of checks and call "return" if something went wrong. But this will increase the amount of code. Lua uses something like exceptions.

Errors are generated using the error(x) function. Anything can be passed as an argument (what is related to the error - a string description, a numeric code, an object with which an error occurred, etc.)

Usually after this function the whole program crashes. And this is not always necessary. If you are calling a function that might throw an error (or its child functions might throw an error), then call it safely with pcall():

Function f(x, y) ... if ... then error("failed to do somthing") end ... end status, err = pcall(f, x, y) -- f: function, xy: its arguments if not status then -- handle err error. In our case, err contains the error text end

Standard Libraries

There are many non-standard libraries, they can be found on LuaForge, LuaRocks and other repositories.

Between Lua and non-Lua

But what if the functionality of the standard libraries is not enough for us? What if we have our C program and we want to call its functions from Lua? There is a very simple mechanism for this.

Let's say we want to create our own function that returns a random number (Lua has math.random(), but we want to learn). We will have to write the following C code:

#include #include #include /* actually what to do when calling `rand(from, to)` */ static int librand_rand(lua_State *L) ( int from, to; int x; from = lua_tonumber(L, 1); /* first function parameter * / to = lua_tonumber(L, 2); /* second function parameter */ x = rand() % (to - from + 1) + from; lua_pushnumber(L, x); /* return value */ return 1; / * return only one argument */ ) /* in Lua "rand" corresponds to our librand_rand() function */ static const luaL_reg R = ( ("rand", librand_rand), (NULL, NULL) /* end of list of exported functions */ ); /* called when the library is loaded */ LUALIB_API int luaopen_librand(lua_State *L) ( luaL_openlib(L, "librand", R, 0); srand(time(NULL)); return 1; /* succeed */ )

Those. Lua provides us with functions to work with data types, to take function arguments and return results. There are very few functions, and they are quite simple. Now we build our library as dynamic and we can use the rand() function:

Random = require("librand") -- load the library print(random.rand(1, 100)) print(random.rand(0, 1))

What if we want to call Lua code from our programs? Then our programs should create a Lua virtual machine, in which Lua scripts will be executed. It's much easier:

#include "lua.h" #include "lauxlib.h" int main() ( lua_State *L = lua_open(); // create the Lua virtual machine luaL_openlibs(L); // load the standard libraries luaL_dofile(L, "rand. lua"); // execute script lua_close(L); // close Lua return 0; )

Everything.

You can now write in Lua. If you find out interesting points about Lua that could be reflected in the article - write!

LUÁ, iau, vb. I.trans. I. 1. A prinde un obiect în mână spre a l ţine (şi a se servi de el) sau spre a l pune în altă parte. ♢ exp. A lua altă vorbă = a schimba (cu dibăcie) subiectul unei discuţii. A(şi) lua picioarele la spinare = a pleca… … Dicționar Român

Lua- Logo Basisdaten Paradigmen: Skriptsprache, imperativ, funktional, objektorientiert … Deutsch Wikipedia

Lua- [] Información general Paradigma Multiparadigma: interpretado, imperativo, funcional, orientado a objetos, basado en prototipos Apareció en … Wikipedia Español

LUA- Apparu en 1993 Auteur Luiz Henrique de Figueiredo, Roberto Ierusalimschy et Waldemar Celes Implémentations Lua, LuaJIT, LLVM Lua, LuaCLR, Nua, Lua A ... Wikipedia en Français

LUA- (portugiesisch für Mond) ist eine Skriptsprache zum Einbinden in Programme, um diese leichter weiterentwickeln und warten zu können. Eine der besonderen Eigenschaften von Lua ist die geringe Größe des kompilierten Skript Interpreters. Lua wurde… … Deutsch Wikipedia

lua- s. f. 1. O unico planeta satélite da Terra. 2. Tempo compreendido entre dois novilúnios. 3. Mês. 4.Cio. 5. O mesmo que peixe lua. 6. Disco de ouro ou prata que os Timores usam ao pescoço, como símbolo de… … Dicionario da Lingua Portuguesa

Lua- may refer to: * Lua (programming language), a lightweight, extensible programming language * Lua (Yu Gi Oh! 5D s) * Lua (goddess), the Roman goddess * Lua (martial art), a traditional Hawaiian martial art * Lua (song), a single by the folk rock… … Wikipedia

LUA abbreviation: LUA last universal ancestor (also translated as "Last universal ancestor" (English LUA, Last Universal Ancestor), otherwise Last universal common ancestor (English LUCA, last universal common ... ... Wikipedia

lua- (Del gót. lôfa, palma de la mano). 1. f. Especie de guante hecho de esparto y sin separaciones para los dedos, que sirve para limpiar las caballerías. 2. Mar. Revés de las velas por la parte donde van cazadas con viento largo o en popa. 3. Mar.… … Diccionario de la lengua española

Lua- Lua, römische Göttin, Tochter des Saturnus, welcher nach der Schlacht zur Sühne des vergossenen Blutes erbeutete Waffen von dem Feldherrn verbrannt wurden … Pierer's Universal-Lexikon

LUA- Dea quaedam apud vett, a luendo, expiandoque nomen sortita, quae praeerat lustrationibus, et lustris. Vide Turneb. Adver s. l. 16.c. 20. etl. 23.c. 23. etl. 19.c. 11. Eius meminit A. Gell. l. 13.c. 22. cum ait in libris Sacerdotum Pop. Rom … Hofmann J. Lexicon universale

Books

  • Lua Programming
  • Programming in the Lua language. Guide, Jerusalem to Robert. The book is dedicated to one of the most popular embedded languages ​​- Lua. This language has been used in many games and many different applications. The language combines a small volume ...

Introduction

This guide is intended for those with limited experience with LUA. We'll cover the basics of how to code, the building blocks for you to create more complex code, and provide some examples. The manual is written in such a way that it is immediately put into practice. Therefore, you should open the Tabletop Simulator and your LUA editor to proceed further.

This is the first guide in this series. The second is Learning Lua More. The third is a set of useful functions called Learning Lua Functions.

Before the first keystroke

First, I would highly recommend installing Atom if you are going to be scripting in Tabletop Simulator. It knows what functions can be used and will import/export code to/from TTS.

Then you must bookmark . You will be referencing this site frequently once you start writing your scripts. Here you will find special features in Tabletop Simulator and how they work. You will most often use the API and Object pages, at least in my experience.

Training

When you save your scripts to Tabletop, it will use your last save and then load the scripts into it. Therefore, for any script you intend to write, you will need to do the following:

  • Prepare the table the way you want it.
  • Save the table.
  • Load the table.
For this exercise, take an empty table and create two objects (I used a square block and a rectangular block) and a red checker.

Press it repeatedly, because of course you will.

EXTRA CREDIT: When you create tables, there are several ways to do it. The method used here was to provide visual clarity. However, creating button parameters like this takes up a lot of space if you have a lot of buttons. I prefer to create my tables in a way that saves space but doesn't go over the right margin. Using our example, I would create a parameter table like this:

button_parameters = ( click_function="buttonClicked", function_owner=nil, label="Press Me", position=(0,0.8,0), rotation=(0,0,0), width=500, height=500, font_size= one hundred )

EXTRA CREDIT: This is the perfect moment to start playing with the different things you can do with objects. Go to the "Object" page in the Knowledge Base and try the material. Move objects, make them switch positions, change their colors, whatever you think.

EXTRA CREDIT: Also, every time the button is clicked, the click_function is run with two parameters. The first is a reference to an object, specifically a reference to the object that the button is bound to. The second is the color (for example, "Blue" - blue) in the string format of the color of the player who pressed the button.

5) Logical assertion

Variable Comparison

Again remove all scripts inside the buttonClicked() function. We are going to create a new variable and then change it. The new variable will be of Boolean type. Boolean values ​​can only be true, false. Boolean values ​​are always written in small letters. First, we'll create our variable under our object and checker GUID.

trueOrFalse = true

Then, in buttonClicked , we'll set up some logic to check if the value is trueOrFalse. If it is true, it will print that it is True and switch it to False. If the button is pressed again, will print that it is False and toggle the value to True.

if trueOrFalse then print("trueOrFalse was true.") --trueOrFalse was true. trueOrFalse = false else print("trueOrFalse was false.") --trueOrFalse was false. trueOrFalse = true end

We could also write it like this "if trueOrFalse == true then", but that's not necessary. Remember that the IF statement needs to be passed a boolean value. And since trueOrFalse is already one of those, we can let go of "== true".

A loop is a section of code that can run multiple times. This is one of the more complex elements that you will use in LUA. They often come with tables, allowing you to run code for each entry in the table.

This is another type - ipairs. Pairs is needed for tables without numeric keys, and ipairs is needed for a table with consecutive numeric keys (arrays). ipairs is in order, while pairs can be in any order.

Our guest today is a real fighter of the hidden front. You might have seen it in games (World of Warcraft, Angry Birds, X-Plane, S.T.A.L.K.E.R.) or Adobe products (Lightroom) but didn't even think about its existence. Meanwhile, this language is almost 25 years old and all this time it has quietly made our virtual life a little better.

Brief information

Lua would have been coined in 1993 at the Catholic University of Rio de Janeiro. The name is translated from Portuguese as the Moon, and the creators kindly ask you not to write LUA, so that, God forbid, someone would take the name for an abbreviation. It is a multi-paradigm scripting language that uses the prototypical OOP model.

The typing here is dynamic, and metatables are used to implement inheritance, that is, this is a great tool for extending the capabilities of your product. And because of its compactness, it is suitable for use on almost any platform. Judge for yourself: the Lua 5.3.4 tarball weighs only 296 kilobytes (uncompressed - 1.1 megabytes), the interpreter (written in C) for Linux - from 182 to 246 kilobytes, and the standard set of libraries - another 421 kilobytes.

The code

In appearance, and in features, Lua looks like another attempt to remake JavaScript, if not for the fact that the latter appeared two years later. See for yourself:

Let's start with the traditional:

print("Hello world")

Agree, familiar and not too informative. A more interesting example from a Lua familiarity point of view is calculating the factorial of an input number:

Function fact(n)
if n == 0 then
return 1
else
return n * fact(n-1)
end
end

Print("enter a number:")
a = io.read("*number") -- read a number
print(fact(a))

Everything is very clear. By the way, Lua supports parallel assignment:

And in conclusion, a rather simple example using libraries:

#include
#include
#include
#include
#include

int main(void)(
char buff;
int error;
lua_State *L = lua_open(); /* opens Lua */
luaopen_base(L); /* opens the basic library */
luaopen_table(L); /* opens the table library */
luaopen_io(L); /* opens the I/O library */
luaopen_string(L); /* opens the string lib. */
luaopen_math(L); /* opens the math lib. */

While (fgets(buff, sizeof(buff), stdin) != NULL) (
error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
lua_pcall(L, 0, 0, 0);
if (error) (
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}

Lua_close(L);
return 0;
}

Advantages and disadvantages

So what is good about Lua?

Firstly, as already noted, its compactness, and coupled with the fact that the source code is written in C, you get full interaction with one of the most popular languages ​​​​on the planet and a wide range of available platforms.

Development environments

LDT (Lua Development Tools) for Eclipse - an extension for one of the most popular IDEs;

ZeroBrane Studio is a specialized environment written in Lua;

Decoda is not the most popular cross-platform IDE, but is an alternative;

SciTE is a good editor that fully supports Lua;

WoWUIDesigner - guess for which game this environment helps to process scripts, including those in Lua?

useful links

http://www.lua.org/home.html - official site with all the necessary information, tutorial, books, documentation and even some specific humor;

http://tylerneylon.com/a/learn-lua/ - great tutorial from Tyler Neylon. Suitable for programmers with experience who know English well (however, there will be no big problems with the dictionary either) and just want to expand their horizons;

https://zserge.wordpress.com/2012/02/23/lua-in-60-minutes/ - the basics of Lua in 60 minutes from a programmer who is obviously not indifferent to this language. In Russian;

http://lua-users.org/wiki/LuaTutorial - wiki tutorial;

https://youtube.com/watch?v=yI41OL0-DWM- YouTube video tutorials that will help you visually understand the IDE setup and the basic principles of the language.

Lua gives you the power; you build the mechanisms.
// Robert Jerusalem


Introduction

Lua is a programming language designed to be embedded in other applications to enable their users to write configuration scripts and high-level scripts. Lua supports procedural, object, and functional programming styles, but is also a simple language. The Lua interpreter is written in ANSI-C and is a library that can be connected to any program. In this case, the control program can call library functions to execute a piece of Lua code and work with the data defined in this code. The control program can also register its own functions so that they can be called from Lua code. The latter feature allows Lua to be used as a language that can be adapted to any application. Another use of Lua is to write simple independent scripts. There is a simple Lua interpreter for this purpose, which uses this library to execute code entered from the console or from a file.

Lexical conventions

Identifiers can contain letters, numbers, and underscores, and cannot start with a number.

Identifiers that begin with an underscore and consist only of capital letters are reserved for internal use by the interpreter.

Identifiers distinguish between upper and lower case letters.

String literals can be enclosed in single or double quotes. They can use the following special character sequences:

\n line feed (LF = 0x0a) \a bell \r carriage return (CR = 0x0d) \b backspace \t tab \f form feed \\ backslash character \v vertical tab \" quote \[ left square bracket \ " apostrophe \] right square bracket \ddd character with code ddd (decimal) \0 character with code 0

If there is a backslash character at the end of a line in the source file, then the definition of a string literal can be continued on the next line, into which a newline character is inserted at that point.

String literals can also be enclosed in double square brackets [[....]] . In this case, the literal can be defined on multiple lines (newline characters are included in the string literal) and no special character sequences are interpreted in it.

If the "[[" characters are immediately followed by a newline, then it is not included in the string literal.

As line delimiters, in addition to double square brackets, the character [===[ .... ]===] can be used, in which an arbitrary number of equal signs are located between repeated square brackets (the same for the opening and closing delimiter).

Numeric constants can have an optional fractional part and an optional decimal exponent specified by the characters "e" or "E" . Integer numeric constants can be specified in hexadecimal using the 0x prefix.

A comment begins with "--" (two consecutive minus characters) and continues to the end of the line. If the characters "--" are immediately followed by the characters "[[" , then the comment is multi-line and continues until the characters "]]" . A multiline comment can contain nested character pairs [[....]] . As delimiters of multiline comments, in addition to double square brackets, the character [===[ .... ]===] can also be used, in which an arbitrary number of equal signs are located between repeated square brackets (the same for the opening and closing delimiters). A string constant escapes the characters at the start of a comment.

If the first line of the file starts with a "#" character, then it is skipped. This allows Lua to be used as a script interpreter on Unix-like systems.

Data types

Lua has the following data types:

Nil empty boolean boolean number numeric string string function function userdata user data thread thread table associative array

The nil type corresponds to the absence of a value for the variable. This type has a single value, nil .

The boolean type has two values: true and false .

The value nil is treated as false . All other values, including the number 0 and the empty string, are treated as boolean true .

All numbers are represented as double precision real numbers.

Strings are arrays of 8-bit characters and may contain a null character within them. All strings in Lua are constant, i.e. You cannot change the contents of an existing row.

Functions can be assigned to variables, passed to functions as an argument, returned as a result from a function, and stored in tables.

The userdata type corresponds to an untyped pointer that can be used to locate arbitrary data. A Lua program cannot directly work with such data (create, modify them). This data type has no predefined operations other than assignment and comparison for equality. At the same time, such operations can be defined using the metamethod mechanism.

The thread type corresponds to an independently executing thread. This data type is used by the coroutine engine.

The table type corresponds to tables - associative arrays that can be indexed by any values ​​and that can simultaneously contain values ​​of arbitrary types.

The type of an object stored in a variable can be found by calling the type() function. This function returns a string containing the type's canonical name: "nil", "number", "string", "boolean", "table", "function", "thread", "userdata" .

Conversions between numbers and strings happen automatically when they are used in the appropriate context.

Arithmetic operations take numeric arguments, and attempting to perform such an operation on strings will convert them to numbers. String operations performed on numbers result in their conversion to a string using some fixed format conversion.

You can also explicitly convert an object to a string using the tostring() function or to a number using the tonumber() function. For more control over the process of converting numbers to strings, you should use the format conversion function.

Variables

Variables do not need to be declared in Lua. A variable appears the moment it is first used. If a variable is used that has not been previously initialized, then it has the value nil . Variables do not have a static type, the type of a variable is determined by its current value.

A variable is considered global unless it is explicitly declared local. The declaration of local variables can be located anywhere in the block and can be combined with their initialization:

local x, y, z local a, b, c = 1, 2, 3 local x = x

When initializing a local variable to the right of the equal sign, the input variable is not yet available and the value of the variable external to the current block is used. That is why the example in the third line is correct (it demonstrates a commonly used language idiom).

For local variables, the interpreter uses lexical scopes i.e. the scope of a variable extends from where it is declared (first used) to the end of the current block. In this case, a local variable is visible in blocks internal to the block in which it is declared. A local variable disappears when it goes out of scope. If a local variable is defined outside the block, then such a variable disappears at the end of the execution of this section of code, since the section of code is executed by the interpreter as an unnamed function. An initialized global variable exists for the entire duration of the interpreter.

To remove a variable, you can simply assign the value nil to it.

Arrays, functions and userdata are objects. All objects are anonymous and cannot be the value of a variable.

Variables store references to objects. When assigning, passing to a function as an argument and returning from a function as a result, objects are not copied, only references to them are copied.

tables

Tables (table type) correspond to associative arrays that can be indexed by any value other than nil and that can simultaneously contain values ​​of arbitrary types other than nil . Table elements can also be indexed by objects - tables, functions, and objects of type userdata . Array elements that do not receive a value as a result of an assignment default to nil .

Tables are the main data structure in Lua. They also represent structures, classes, and objects. In this case, indexing by the string name of the structure field is used. Since an array element can be a function, methods are also allowed in structures.

Arrays are indexed using square brackets: array . The struct.field entry is equivalent to the following entry: struct["field"] . This syntactic feature allows tables to be used as records with named fields.

A table constructor is an expression that creates and returns a new table. Each execution of the constructor creates a new table. The table constructor is a list of field initializers enclosed in curly braces (possibly empty), separated by a comma or the character ";" (semicolon). The following options are valid for field initializers:

exp2 table = exp2 name = exp table["name"] = exp exp table[j] = exp

In the latter case, the variable j runs through successive integer values, starting from 1 . Initializers of the first two kinds do not change the value of this counter. Here is an example of table construction:

X = ( len = 12, 11, 12, = 1123 )

After executing such an operator, the table fields will have the following values:

X["len"] = x.len = 12 x = 11 x = 12 x = 1123

If the last element of the initializer list is a function call, then the return values ​​of the function are sequentially placed in the initializer list. You can change this behavior by enclosing the function call in parentheses. In this case, of all the values ​​returned by the function, only the first one is used.

The last initializer may be followed by an optional field initializer separator character (comma or semicolon).

Operations

The main operations are listed below:

Sign change + - * / arithmetic ^ exponentiation == ~= equality< <= >>= order not and or logic.. string concatenation # getting length of string or array

When applying arithmetic operations, strings that have a numeric value are cast to it. When concatenating numeric values, they are automatically converted to strings.

When comparing for equality, no type conversion is performed. Objects of different types are always considered different.

Accordingly, "0" ~= 0 , and when indexing, a and a["0"] correspond to different cells of the array. When comparing objects for equality/inequality, object references are compared. Variables that refer to the same object are equal.

When determining the order, the types of the arguments must match, i.e. numbers are compared to numbers, and strings are compared to strings.

Equality and order relations always result in true or false i.e. boolean value.

In boolean operations, nil is treated as false , and all other values, including zero and the empty string, are treated as true .

When calculating the value, a short circuit is used - the second argument is evaluated only if necessary.

The following table of priorities and associativity of operations is valid:

^ not # -(unary) * / + -< > <= >= ~= == .. and or

Boolean operations and related idioms

The not operator always returns a boolean value, taking an argument of arbitrary type (in this case, only the value nil corresponds to the boolean value false , the rest are treated as true). In contrast, the and and or operators always return one of their arguments. The or operator returns its first argument if its value is anything other than false or nil, and its second argument otherwise. The and operator returns its first argument if its value is false or nil and its second argument otherwise. This behavior is based on the fact that all values ​​other than nil are treated as true .

There are several common idioms associated with this behavior. In the following table, the idiomatic operation is shown on the left and the equivalent conventional notation on the right:

X = x or v if x == nil then x = v end x = (e and a) or b if e ~= nil then x = a else x = b end

The first idiom is often used to assign a default value to an uninitialized variable. The second idiom is equivalent to the C "shny operator x = e ? a, b (here it is considered that the value of the variable a is different from nil).

Operators

Lua does not have a dedicated function that starts the execution of a program. The interpreter sequentially executes the statements it receives from a file or from a control program. In doing so, it precompiles the program into a binary representation that can also be stored. Any block of code runs as an anonymous function, so it can define local variables and return a value from it.

Operators can (but not necessarily) be separated by ";" .

Multiple assignments are allowed:

var1, var2 = val1, val2

In this case, alignment is performed - extra values ​​are discarded, and the variables corresponding to the missing values ​​are assigned the value nil . All expressions on the right side of a multiple assignment are evaluated before the assignment itself.

If a function returns multiple values, then multiple assignments can be used to get the return values:

X, y, z = f(); a, b, c, d = 5, f();

In general, if a function call is at the end of the list of values ​​located to the right of the assignment sign, then all values ​​returned by the function are appended to the end of the list of values. You can change this behavior by enclosing the function call in parentheses. In this case, of all the values ​​returned by the function, only the first one is used.

The main operators are listed below:

Do ... end if ... then ... end if ... then ... else ... end if ... then ... elseif ... then ... end if ... then . .. elseif ... then ... else ... end while ... do ... end repeat ... until ... for var = start, stop do ... end for var = start, stop, step do ... end return return ... break

The do ... end block turns a sequence of statements into a single statement and opens up a new scope in which you can define local variables.

In the if , while , and repeat statements, all expression values ​​other than false and nil are treated as true.

Here is the general form of the if statement:

If ... then ... (elseif ... then ...) end

The return statement may contain no return values, or it may contain one or more expressions (a list). Since the block of code is executed as an anonymous function, the return value can be not only for the function, but also for an arbitrary block of code.

The return and break statements must be the last statements in the block (i.e. they must either be the last statements in the block of code, or be located immediately before the words end , else , elseif , until). Within a block, you must use the do return end or do break end idiom.

The for statement is executed for all values ​​of the loop variable starting from the start value and ending with the finish value inclusive. The third value, if given, is used as the step to change the loop variable. All of these values ​​must be numeric. They are evaluated only once before the loop is executed. The loop variable is local to that loop and not accessible outside of the loop body. The value of a loop variable cannot be changed inside the loop body. Here is a pseudocode demonstrating the execution of the for statement:

Do local var, _limit, _step = tonumber(start), tonumber(stop), tonumber(step) if not (var and _limit and _step) then error() end while (_step>0 and var<=_limit) or (_step<=0 and var>=_limit) do ... var = var + _step end end

Functions

A function definition is an executable expression (function constructor) that evaluates to an object of type function:

F = function(...) ... end

Parentheses contain a (possibly empty) list of function arguments. The list of function arguments can end with an ellipsis - in this case, the function has a variable number of arguments. The body of the function is placed between the closing brace and the end statement.

The following short forms are available for defining a function:

Function fname(...) ... end fname = function(...) ... end local function fname(...) ... end local fname = function(...) ... end function x .fname(...) ... end x.fname = function(...) ... end function x:fname(...) ... end x.fname = function(self, ...) ... end

At the time of execution of the function constructor, the closure- a table of all local variables available in the function and external to it. If a function is passed as a return value, then it retains access to all variables included in its closure. Each time the function constructor is executed, a new closure is built.

A function call consists of a function reference and a (possibly empty) list of arguments enclosed in parentheses. A function reference can be any expression that evaluates to a function. There cannot be a newline between the function reference and the opening parenthesis.

The following short forms are available for calling functions:

F(...) f((...)) f("...") f"..." f("") f"" f([[...]]) f[[. ..]] x:f(...) xf(x, ...)

In the first case, the only argument is a table constructed on the fly, and in the next three, a string literal. In the latter case, x is used as a table, from which the variable f is retrieved, which is a reference to the called function, and the same table is passed to the function as the first argument. This syntactic feature is used to implement objects.

When a function is called, values ​​of simple types are copied into arguments by value, while for objects, references are copied into arguments. When the function is called, the number of arguments is aligned - extra values ​​are discarded, and the arguments corresponding to the missing values ​​get the value nil . If at the end of the parameter list there is a call to a function that returns multiple values, then all of them are added to the argument list. You can change this behavior by enclosing the function call in parentheses. In this case, of all the values ​​returned by the function, only the first one is used.

If a function has a variable number of arguments, then values ​​not mapped to any of the arguments can be obtained by using the specific name... . This name behaves like a set of values ​​returned by a function i.e. when it appears within an expression or from the middle of a list of values, only the first return value is used. If the name... appears in the last position of the list of values ​​(in table constructors, in case of multiple assignment, in the list of arguments when calling a function, and in the return statement), then all return values ​​are placed at the end of this list (list completion rule). To suppress this behavior, the name... can be placed in parentheses. You can turn a set of values ​​into a list using the (...) idiom.

If a function takes a single argument and treats it as a table whose elements are indexed by the names of the formal parameters of the function, then in this case the named arguments mechanism is actually called:

Function rename(arg) arg.new = arg.new or arg.old .. ".bak" return os.rename(arg.old, arg.new) end rename( old = "asd.qwe" )

A return from a function occurs both when the execution of its body is completed, and when the return statement is executed. The return statement can return one or more values. If at the end of the list of return values ​​there is a call to a function that returns multiple values, then all of them are added to the list of arguments. You can change this behavior by enclosing the function call in parentheses. In this case, of all the values ​​returned by the function, only the first one is used.

If the function is called as a statement, then all return values ​​are destroyed. If the function is called from an expression or from the middle of a list of values, then only the first return value is used. If the function is called from the last position of the list of values ​​(in table constructors, with multiple assignments, in the argument list when calling the function, and in the return statement), then all return values ​​are placed at the end of this list (list completion rule). To suppress this behavior, a function call can be enclosed in parentheses.

There is a predefined unpack() function that takes an array whose elements are indexed from 1 and returns all of its elements. This function can be used to call functions that take a variable number of arguments, dynamically generating a list of actual arguments. The reverse operation is performed as follows:

List1 = (f()) -- create a list of all values ​​returned by f() list2 = (...) -- create a list of all values ​​passed to a function with a variable number of arguments

The variable number of return values, the list completion rule, and the ability to pass not all formal parameters to a function can interact in a non-trivial way. Often a function (eg foo()) will return a response on normal completion and nil and an error message on abnormal completion. The assert(val, msg) function throws an error message (by calling the error(msg) function) if val is false or nil and returns val otherwise. Then the operator

V = assert(foo(), "message")

on success, sets v to the value returned by foo() . In this case, foo() returns a single value, and assert() takes a msg parameter of nil . In case of an error, the assert() function receives nil and an error message.

Iterators

Iterators are used to enumerate the elements of arbitrary sequences:

For v_1, v_2, ..., v_n in explist do ... end

The number of variables in the list v_1, ..., v_n can be arbitrary and does not have to match the number of expressions in the list explist . The role of explist is usually a call to the iterator factory function. Such a function returns an iterator function, the state, and the initial value of the loop's control variable. The iterator is interpreted like this:

Do local f, s, v_1 ​​= explist local v_2, ... , v_n while true do v_1, ..., v_n = f(s, v_1) if v_1 == nil then break end ... end end

At each step, the values ​​of all v_k variables are calculated by calling the iterator function. The value of the control variable v_1 controls the termination of the loop - the loop terminates as soon as the iterator function returns nil as the value for the variable var_1 .

In fact, the iterations are controlled by the v_1 variable, and the rest of the variables can be considered as the “tail” returned by the iterator function:

Do local f, s, v = explist while true do v = f(s, v) if v == nil then break end ... end end

The statement in which the factory function is called is interpreted as a normal assignment statement, i.e. a factory function can return an arbitrary number of values.

Iterators without internal state

A stateless iterator does not store any internal information that allows it to determine its position within the iterable's container. The next value of the control variable is calculated directly from its previous value, and the state is used to store a reference to the iterable container. Here is an example of a simple iterator without internal state:

Function iter(a, i) i = i + 1 local v = a[i] if v then return i, v else return nil end end function ipairs(a) return iter, a, 0 end

Iterators that store state in a closure

If the iterator needs internal state to traverse the container, then the easiest way is to store it in a closure created by the context of the factory function. Here is a simple example:

Function ipairs(a) local i = 0 local t = a local function iter() i = i + 1 local v = t[i] if v then return i, v else return nil end end return iter end

Here, the iterator stores the entire context in a closure and does not need the state and current value of the control variable. Accordingly, an iterator does not take a state and a control variable, and a factory does not return a state value and a start value for the control variable.

Standard Iterators

Most often, iterators are used to traverse the elements of tables. There are several predefined iterator factory functions for this. The pairs(t) factory returns an iterator giving at each step the index in the table and the value placed at that index:

For idx, val in pairs(tbl) do ... end

This iterator is actually easy to define using the standard next(tbl, idx) function:

Function pairs(tbl) return next, tbl, nil end

The next(tbl, idx) function returns the index value following idx during some traversal of the tbl table (the call to next(tbl, nil) returns the initial index value; after exhausting the table elements, nil is returned).

The ipairs(tbl) factory returns an iterator that works exactly like the one above, but is designed to traverse tables indexed by integers starting from 1 .

Meta tables

Each table and object of type userdata can have a meta-table - a regular table whose fields determine the behavior of the original object when some special operations are applied to it. For example, when an object is found to be an operand in an addition, the interpreter looks in the metatable for a field named __add and, if such a field is present, uses its value as the function that performs the addition. Meta tables allow you to define the behavior of an object during arithmetic operations, comparisons, concatenation, and indexing. You can also define a function to be called when an object of type userdata is freed. Indexes (field names) in a meta table are called events, and the corresponding values ​​(event handlers) - metamethods.

By default, a newly created table does not have a meta table. Any table mt can be made a meta table of table t by calling the setmetatable(t, mt) function. The getmetatable(t) function returns the meta table of table t, or nil if the table has no meta table. Any table can act as a meta-table for any other table, including itself.

Lua defines the following events:

Add, __sub, __mul, __div arithmetic operations __pow exponentiation __unm unary minus __concat concatenation __eq, __lt, __le comparison operations __index access by missing index __newindex assignment to new table element __call function call __tostring conversion to string __metatable get meta table

The expression a ~= b evaluates to not (a == b) . The expression a > b evaluates to b< a . Выражение a >= b is calculated as b<= a . При отсутствии метаметода __le операция <= вычисляется как not (b < a) т.е. с помощью метаметода __lt .

For binary operations, the handler is selected as follows: the first operand is queried and, if it does not define a handler, then the second operand is queried. For comparison operations, a metamethod is chosen only if the operands being compared are of the same type and have the same metamethod to perform the operation. The user's guide provides Lua pseudocode demonstrating the context for calling metamethods.

The __index event handler can be a function or a table. In the case of a function, the handler is called and the table and index value are passed to it. Such a function must return the indexing result. In the case of a table, this table is re-indexed with the same index. If a __newindex event handler is present, it is called instead of assigning a value to a new table element. If this handler is a table, then the assignment is made in that table.

The __tostring metamethod allows you to handle the conversion of an object (table or userdata) to a string. The __metatable metamethod allows you to handle the operation of getting a meta table. If this field has a value in the meta table, then the getmetatable() function will return the value of this field, and the setmetatable() function will fail.

Examples of using meta tables

Default value for table fields

The following example uses meta tables to assign a default value to missing table elements:

Function set_def(t, v) local mt = ( __index = function() return v end ) setmetatable(t, mt) end

Here's a more non-trivial solution that doesn't use a separate meta table for each default value:

local key = () local mt = ( __index = function(t) return t end ) function set_def(t, v) t = v setmetatable(t, mt) end

Here, the local (empty) table key is used as a known unique index by which the default value v is stored in the original table t. All tables that are set to the default value of missing fields share a common mt metatable. The __index meta method of this meta table intercepts accesses to missing table fields and returns the value stored in the table itself at index key .

This solution has a drawback: a new key-value pair appears in the table, which will appear when you try to iterate over all the elements of the table.

proxy table

In the following example, an empty table acts as a proxy that redirects access to table fields:

local key = () local mt = ( __index = function(t,k) return t[k] end, __newindex = function(t,k,v) t[k] = v end ) function proxy(t) local proxy = () proxy = t setmetatable(proxy, mt) return proxy end

Here, the local (empty) table key is used as a known unique index, by which the reference to the source table t is stored in the proxy table. A proxy is a table whose only element has an index key , so accessing any element of the proxy will result in a call to the meta method. The common meta-table for all proxies defines the __index and __newindex meta-methods that extract the source table from a single proxy element, indexing it with the key table.

Meta-methods can provide arbitrary discipline for handling references to fields of the source table. Simple examples are logging accesses or generating an error when trying to change the value of a table element.

"Weak" tables

If an object has been used as an index to a table, or a reference to it has been stored in the table, then the garbage collector will not be able to dispose of such an object. At the same time, in some cases it is desirable to have a table in which the relationship of its elements with keys and / or values ​​is “weak”, i.e. does not interfere with garbage collection. Typically, such a need arises when caching the results of calculations in a table and storing the attributes of objects in a table indexed by the objects themselves. In the first case, a weak relationship is desirable for values, in the second - for indices.

The association of table elements with objects (values ​​and indices) is determined by the value of the __mode string field of its meta-table. If this field contains the character "k" , then the link for indexes (keys) is made weak; if it contains the symbol "v" , then the link for the values ​​is made weak. A field can contain both characters, which will make a weak link for both indexes and values.

If weak tables are used, then for the above problem of matching a table with a default value for missing fields, the following solution can be given:

local defaults = () setmetatable(defaults, ( __mode = "k" )) local mt = ( __index = function(t) return defaults[t] end ) function set_def(t, d) defaults[t] = d setmetatable(t , mt) end

Here's another solution where weak tables store meta tables that match the number of distinct default values:

local metas = () setmetatable(metas, ( __mode = "v" )) function set_def(t, d) local mt = metas[d] if mt == nil then mt = ( __index = function () return d end ) metas [d] = mt end setmetatable(t, mt) end

Global context

All global variables are fields in a regular table called global context. This table is available through the _G global variable. Since all global variables are context fields, then _G._G == _G .

The global context makes it possible to access global variables by a dynamically generated name:

Val = _G _G = val

Because the global context is a regular table, a meta table can correspond to it. The following example introduces environment variables into the global scope as read-only global variables:

Local f = function (t,i) return os.getenv(i) end setmetatable(_G, (__index=f))

The same trick allows you to disable access to uninitialized global variables.

Packages

Packages are the primary way to define a set of related functions without polluting the global scope. Typically, a package is a single file that defines a single table in the global scope that contains all the functions of this package:

my_package = () function my_package.foo() ... end

You can also make all functions local and separately form a table of exported functions:

Local function foo() ... end local function bar() ... end my_package = ( foo = foo, bar = bar, )

The package is loaded using the require() function, and at load time, the name passed to this function (it may not contain an extension that is added automatically) is available through the _REQUIREDNAME variable:

If _REQUIREDNAME == nil then run_some_internal_tests() end

Classes and Objects

The tbl:func() construct (when declaring a function and when calling it) provides the basic features that allow you to work with a table as with an object. The main problem is to generate many objects that have similar behavior i.e. generated from the same class:

Function class() cl = () cl.__index = cl -- cl will be used as a meta table return cl end function object(cl, obj) obj = obj or () -- may already have filled fields setmetatable(obj, cl ) return obj end

Here the class function creates an empty table, prepared to become the object's meta table. Class methods are made fields of this table i.e. a class is a table that simultaneously contains the object's methods and its meta-methods. The object() function creates an object of the given class - a table with the given class as the meta-table. The second argument can be passed to a table containing the initialized fields of the object.

Some_Class = class() function Some_Class:foo() ... end function Some_Class:new() return object(self, ( xxx = 12 )) end x = Some_Class:new() x:foo()

Inheritance

In the described implementation, the class meta-table is left unused, making it easy to implement inheritance. The descendant class is created as a class object, after which it sets the __index field in it so that it can be used as a meta table:

Function subclass(pcl) cl = pcl:new() -- instantiate cl.__index = cl -- and make it a class return cl end

Now you can add new fields and methods to the derived class:

Der_Class = subclass(Some_Class) function Der_Class:new() local obj = object(self, Some_Class:new()) obj.yyy = 13 -- add new fields return obj end function Der_Class:bar() ... end -- and new methods y = Der_Class:new() y:foo() y:bar()

The only non-trivial point here is to use the new() function from the ancestor class and then replace the meta table by calling the object() function.

When accessing the methods of an object of a class-heir, first of all, they are searched in the meta-table, i.e. in the derived class itself. If the method was inherited, then this lookup will fail and the meta-table of the derived class will be accessed, i.e. to the ancestor class.

The main drawback of the above general solution is the impossibility of passing parameters to the new() function of the ancestor class.

Command Line Arguments

The command-line arguments passed at startup are available as elements of the arg array.

Liked the article? Share with friends: