Related articles in this series:

Flutter thermal update and dynamic UI generation

Lua 15 Minutes quick Start (PART 1)

Lua 15 minutes quick Start (II)

Lua and C language call each other

Dart and Lua invocation in LuaDardo

Advanced grammar

The iterator

An iterator is a code structure that allows us to iterate over all the elements of a collection. In Lua, iterators are usually represented by functions: each time a function is called, the function returns the “next” element in the collection.

The generic for iterator

The generic for holds iterating functions internally. It actually holds three values: iterating functions, state constants, and control variables. The syntax is as follows:

for var-list in exp-list do
    body
end
Copy the code

Var -list is a comma-separated list of one or more variable names. Exp -list is a list of one or more expressions, again separated by commas. Typically, an expression list has only one element, a single call to an iterator factory. Lua calls the first (or only) variable in the list of variables a control variable, whose value is never nil during the loop because the loop ends when it is nil.

Example:

array = {'Ali'.'Sina'.'Meituan'}

for key,value in ipairs(array) do
   print(key, value)
end
Copy the code

Implementation process

  1. Initialize, and evaluate the expression after in. The expression should return the three values required by general for: an iteration function, a state constant, and a control variable

    As with many-valued assignment, if an expression returns less than three results it is automatically supplemented with nil, and the excess is ignored

  2. Call the iteration function with state constants and control variables as arguments (note: for structures, state constants are not useful, only get their value at initialization and pass it to the iteration function)

  3. Assigns the value returned by the iterating function to the list of variables

  4. The loop ends if the first value returned is nil, otherwise the body of the loop is executed

  5. Go back to step 2 and call the iteration function again

In general, iterators contain two types:

  1. A stateless iterator
  2. A multistate iterator

A stateless iterator

A stateless iterator is one that does not preserve any state. Use stateless iterators to avoid the overhead of creating new closures. In general, each iteration, the iterating function is called with the values of two variables (state constants and control variables) as arguments. A stateless iterator can fetch the next element using only these two values.

Ipairs is a stateless iterator that iterates through every element of a set.

Implement a stateless iterator:

function iter (a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    end
end

function mypairs (a)
    return iter, a, 0 -- Returns three values
end

array = {'Ali'.'Sina'.'Meituan'}

for key,value in mypairs(array) do
   print(key, value)
end
Copy the code

A multistate iterator

Iterators need to hold multiple state information rather than simply state constants and control variables. There are two ways to do this:

  1. Using closures
  2. Encapsulate all the state information in the table and use the table as the state constant of the iterator. Since all the information can be stored in the table in this case, iterating functions usually don’t need a second argument

Examples of using closures:

function walk_it(collection)
   local index = 0
   local count = #collection
   Closure functions
   return function (a)
      index = index + 1
      if index <= count
      then
         Returns the current element of the iterator
         return collection[index]
      end
   end
end

array = {'Ali'.'Sina'.'Meituan'}

for value in walk_it(array) do
   print(value)
end
Copy the code

The name “iterator” is somewhat misleading because the iterator does not actually iterate: the true iteration is done by the for loop, and the iterator simply provides successive values for each iteration. Perhaps it’s better to call it a “generator,” which means generate elements iteratively; However, the name “iterator” has become widely used in other languages such as Java.

Yuan table

Metatable is a restricted class in the object-oriented domain. Like classes, meta-tables define the behavior of instances. However, meta-tables are more limited than classes because they can only give the behavior of a predefined set of operations; Also, meta-tables do not support inheritance.

Each type of value in Lua has a predictable set of operations. For example, we can add numbers, we can concatenate strings, and we can insert key-value equivalents in tables. However, when you can’t add two tables, compare functions, or call a string, you need meta-tables. For example, if a and B are both tables, then you can define how the Lua language evaluates the expression A +b through a meta-table. When Lua tries to add two tables, it first checks if one of them has a metatable and if it has an __add field. If Lua finds the field, it calls the value of the field, called metamethod (a function), a function that calculates the sum of the two tables.

Every value in Lua can have a meta table. Each table and user data type has a separate meta-table, while values of other types share the same meta-table for the corresponding type. But we can only set meta tables for tables; If you want to set up meta-tables for other types of values, you must do so through C code or debug libraries.

Lua provides two functions to manipulate meta-tables:

  1. setmetatable(table,metatable)Setmetatable to the specified table. Setmetatable will fail if an __metatable key exists in the metatable
  2. getmetatable(table)Returns the metatable of the object

A table can be a meta-table of any value; A set of related tables can also share a common meta-table that describes their common behavior; A table can also become its own meta-table, describing its own unique behavior. In short, any configuration is legal.

mytable = {name="zhangsan"}           Define a normal table
mymetatable = {}                      -- Define a normal table as a meta-table
setmetatable(mytable,mymetatable)     Set myMetatable to the metatable of myTable
getmetatable(mytable)                 - return mymetatable

The above is equivalent to the following shorthand
mytable = setmetatable({name="zhangsan"}, {})Copy the code

Element method

__index

The __index field in Lua is a common table-dependent meta-method. If the __index field corresponds to a different table, Lua looks for the __index key in the meta table that corresponds to that table when the table is accessed by a key that does not exist. I’m going to look up the corresponding key in another table.

Myable = { age = 17 }
Define an empty table
t = {}

-- Set the metatype for t
setmetatable(t,{__index = Myable})
print(t.age)  17 -
Copy the code

If the __index field value is a function, Lua calls that function, and the table and keys are passed to the function as arguments. Nil when accessing a field that doesn’t exist in a table. In effect, these accesses cause the interpreter to look for a meta-method called __index. If you don’t have this meta-method, it’s nil; Otherwise, the meta-method provides the final result.

mytable = setmetatable({name="Zhang"}, 
    {
    __index = function(mytable, key)
        if key == "address" then
            return "my value"
        else
            return "not exist"
        end
    end
    })

print(mytable.name)  - zhang SAN
print(mytable.age)   -- not exist
Copy the code

Here, we can use the meta table’s __index field to simulate the implementation of object-oriented inheritance features.

__newindex

The meta method __newindex is similar to __index, except that the former is used for updating tables and the latter for querying tables. When an index is assigned to a table that does not exist, the interpreter looks for the __newindex metamethod, and if the metamethod exists, the interpreter calls it without performing the assignment. Like the metamethod __index, if the metamethod is a table, the interpreter performs assignment in that table rather than in the original table.

With this meta-method, we can easily create a read-only table by simply doing the update and throwing an exception.

__add

Here, we can add two tables by setting the __add field of the meta table. It’s like the operator is overloaded.

  mytable = setmetatable({ 1.3.5 }, {
    __add = function(mytable, newtable)
                for k,v in ipairs(newtable) do
                    table.insert(mytable, v)
                end
                return mytable
           end})

  secondtable = {7.11.13}

  mytable = mytable + secondtable
  for k,v in ipairs(mytable) do
    print(v)
  end
Copy the code

The operator fields available to the meta table

model describe
__add Corresponding operator ‘+’
__sub Corresponding operator ‘-‘
__mul Corresponding operator ‘*’
__div The corresponding operator ‘/’
__mod Corresponding operator ‘%’
__unm Corresponding operator ‘-‘
__concat The corresponding operator ‘.. ‘
__eq Corresponding operator ‘==’
__lt Corresponding operator ‘<‘
__le Corresponding operator ‘<=’

__call

The meta table field __call is used to handle table variable names when methods are called.

sum = setmetatable({10.28}, {
  __call = function(mytable, newtable)
    local s = 0
    for k,v in ipairs(mytable) do
        s = s + v
    end

    for k,v in ipairs(newtable) do
      s = s + v
    end
    return s
  end
})

table2 = {1.3.5}
print(sum(table2)) Returns the sum of the addition of two table elements
Copy the code

__tostring

Modify the printout behavior of a table

tb1 = {"lua"."dart"."go"."rust"}

tb1 = setmetatable(tb1,{
  __tostring = function(mytable)
    s = "{"
    for k, v in pairs(mytable) dos = s.. v..","
      if k == #mytable thens = s.. v.."}"
      end
    end
    return s
  end
})

print(tb1)
Copy the code

Module and package

Module is similar to a package library, which can put some common code in a file and call it in other places in the form of API interface, which is conducive to code reuse and reduce code coupling.

From the user’s point of view, a module is code (either written in Lua or C) that can be loaded through the function require, which creates and returns a table. This table is like some kind of namespace that defines things exported from the module, such as functions and constants.

Create a custom module named myModule myModule. lua

mymodule = {}

Define a constant
mymodule.constant = "This is a module constant."

Define a function
function mymodule.hello(a)
    print("Hello World! \n")
end

Define a private function
local function pfunc(a)
    print("This is a private function defined in the myModule module.")
end

Define a public function to call a private function pfunc
function mymodule.call_func(a)
    pfunc()
end

-- Return module
return mymodule

The module definition is complete
Copy the code

The essence of a module is to define a table. Constants or functions in the call module can therefore be manipulated as if they were elements in the call table

Load the module defined above
require("mymodule")

print(mymodule.constant)
mymodule.call_func()
Copy the code

You can also give the module an alias and call it again

-- Define an alias m
local m = require("mymodule")

Call constants through aliases
print(m.constant)
Call a function through an alias
m.call_func()
Copy the code

Usually when a function has only one string argument, the parentheses are omitted, so the code to load the module will say local m = require “mymodule”

Module loading mechanism

The require function has its own file-path loading strategy, which attempts to search for and load modules from Lua files or C libraries. Its path for searching Lua files is stored in the global variable package.path.

When Lua is started, the global variable is initialized with the value of the environment variable LUA_PATH. If the environment variable is not found, it is initialized using a default path defined at compile time. Of course, if the LUA_PATH environment variable is not available, you can also customize the configuration.

LUA_PATH File path with a semicolon (;). Separator, the last 2 semicolons; The new path is followed by the original default path. Such as:

export LUA_PATH="~/lua/? .lua;;"

If the target file is found, the package.loadfile function is called to load the module. Otherwise, you go to the C library. The file path to search for C libraries is taken from the global variable package.cpath, which is initialized by the environment variable LUA_CPATH. The strategy for searching C libraries is the same as above, except that the file name is changed from.lua to.so or.dll. If found, the require function loads it via package.loadlib.

Object orientation of Lua

Objects are made up of properties and methods. The most basic structure in Lua is a table. Lua can use tables to describe the properties and methods of objects. Classes in Lua can be simulated by table + function, and inheritance can be simulated by meta-table.

-- Use table to define classes
Rectangle = {area = 0, length = 0, breadth = 0}

-- Class method new
function Rectangle:new (o,length,breadth)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  self.length = length or 0
  self.breadth = breadth or 0
  self.area = length*breadth;
  return o
end

Class method printArea
function Rectangle:printArea (a)
  print("Rectangle area:".self.area)
end


r = Rectangle:new(nil.6.8)
print(r.length)  -- The dot accesses the attributes of the class
r:printArea()    The colon accesses a member function of the class
Copy the code

Example of simulated inheritance:

Define the parent class
Shape = {area = 0}

Define class method new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end

-- Defines the class method printArea
function Shape:printArea (a)
  print("The area is:".self.area)
end

Define the Square class to inherit Shape
Square = Shape:new()
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

Create object
mysquare = Square:new(nil.16)
mysquare:printArea()
Copy the code

coroutines

Coroutines are similar to threads in that they have separate stacks, separate local variables, separate instruction Pointers, and share global variables and most of the rest with other coroutines. The main differences between threads and coroutines are:

A program with multiple threads can run several threads at the same time, whereas coroutines need to run cooperatively. Only one coroutine is running at any given time, and the running coroutine is suspended only when explicitly asked to be. Coroutines are similar to simultaneous multithreading, and several threads waiting for the same thread lock are similar to coroutines.

All coroutine related functions in Lua are placed in the table Coroutine. The function create is used to create a new coroutine that takes only one argument, the code that the coroutine is to execute (the coroutine body, a function). Create returns a value of type Thread, the new coroutine. Normally, the create function takes an anonymous function as an argument, for example:

co = coroutine.create(
    function(a)
        print("hello")
    end
)

print(type(co))
Copy the code

A coroutine has four states:

  • Suspended

  • Running

  • Normal

  • In the dead

The status of coroutines can be checked with the function coroutine. Status:

print(coroutine.status(co))
Copy the code

Note that when a coroutine is created, it is in a pending state, meaning that the coroutine does not run automatically when it is created. The coroutine. Resume function is used to start or restart the execution of a coroutine and change its state from suspended to run:

coroutine.resume(co) 
Copy the code

If you run the above code in interactive command line mode, it is a good idea to put a semicolon on the last line to prevent the return value of the output function resume.

The real power of coroutines is in the yield function, which allows a running coroutine to suspend itself and then resume later. Cases of cases:

co2 = coroutine.create(
    function(a)
        for i=1.10 do
            print(i)
            coroutine.yield(a)end
    end
)

coroutine.resume(co2) 1 -
coroutine.resume(co2) 2 -
coroutine.resume(co2) - 3
Copy the code

A coroutine is created, and resume is called to start running the coroutine. The coroutine enters a loop in which the code executes normally until the first yield is reached, and the coroutine is suspended. Resume is called again, and execution continues until yield is reached. Resume is called repeatedly, and the process is repeated until the last time resume is called, the coroutine completes (beyond the condition of the for loop, the for loop exits) and returns without output. If we try to resume it again, the resume function returns false and an error message.

It is important to note that only when we wake up (calling resume) the coroutine does the function yield finally return. Let’s look at an example with an argument and a return value:

co3 = coroutine.create(
    function (a , b)
        local sum = a + b
        coroutine.yield(sum) 
    end
)

Pass in the coroutine body parameters A and b via the resume function
If the coroutine body returns a value, the yield function is passed in to return it. Externally, the return value is received through the resume function
Note that the first return value of the resume function is a Boolean that indicates whether the coroutine was successfully called. The second return value is the exact value returned by the coroutine body
print(coroutine.resume(co3,2.3))

Copy the code

Implement the producer-consumer model

Here, we use Lua’s coroutines to implement a classic generator consumer problem

local new_productor

Producers -
function productor(a)
     local i = 0
     while true do
          i = i + 1
          send(i)     -- distribute the products to consumers
     end
end

- customers
function consumer(a)
     while true do
          local i = receive()     -- Receive from the producer
          print(i)
     end
end

function receive(a)
     local status, value = coroutine.resume(new_productor)
     return value
end

function send(x)
     coroutine.yield(x)     -- x represents the value that needs to be sent, and when the value is returned, the coroutine is suspended
end

-- Start program
new_productor = coroutine.create(productor)
consumer()
Copy the code

File with the I/O

Lua’s I/O library is mainly used to read and process files. Read and write files in simple mode (like C) and full mode

  • The Simple Model has a current input file and a current output file, and provides actions on those files.
  • The complete model is implemented using external file handles. It defines methods for all file operations as file handles in an object-facing manner

Simple mode uses standard I/O or uses one current input file and one current output file

Open the file in read-only mode
file = io.open("test.lua"."r")
Set the default input file
io.input(file)

Print the first line of the file
print(io.read())
-- Close the open file
io.close(file)

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

Open write only files in appending mode
file = io.open("test.lua"."a")
Set the default output file
io.output(file)

-- Write the string "end..." on the last line of the file.
io.write("end...")
-- Close the open file
io.close(file)
Copy the code

Simple mode can do some simple file operations, but in some advanced file operations, it is inadequate. For operations such as reading multiple files simultaneously, full mode is appropriate.

Open the file in read-only mode
file = io.open("test.lua"."r")
Print the first line of the file
print(file:read())
-- Close the open file
file:close(a)----------------------------------------------

Open write - only files in append mode
file = io.open("test.lua"."a")
Write a string on the last line of the file
file:write("--test")

-- Close the open file
file:close(a)Copy the code

Note that full mode calls functions in file: mode rather than IO.

The Lua open file operation is modeled as follows:

file = io.open (filename [, mode])
Copy the code

The mode parameter has a number of optional values:

model describe
r To open a file in read-only mode, the file must exist
w Open a file in write-only mode and overwrite it with a new empty file if it exists; If the file does not exist, create one
a Open write – only files in append mode. If the file does not exist, create a new file. If the file exists, the written data is appended to the end (EOF reserved)
r+ To open a file in read-write mode, the file must exist
w+ Open the read/write file and overwrite it with a new empty file if it exists. If the file does not exist, create one
a+ withaThe pattern is similar, but the file is readable and writable
b Open in binary mode
+ Indicates that a file can be read or written

Some other IO methods:

function describe
io.tmpfile() Returns a handle to a temporary file that is opened in update mode and automatically deleted at the end of the program
io.type(file) Checks whether file is an available file handle
io.flush() Writes all data in the buffer to a file
io.lines(optional file name) Returns an iterating function that gets one line of the file each time it is called, and returns nil when it reaches the end of the file, but does not close the file

Lua 15 Minutes quick Start (PART 1)


Follow the public account: the path of programming from 0 to 1

Or follow a blogger’s video school