Ngôn ngữ lập trình Lua: những hỏi – đáp bên lề (phần 2)

Phần tiếp theo của Lua: những hỏi đáp bên lề trình bày về các vấn đề: dữ liệu, hệ điều hành, các thư viện, phần cứng, tính tương thích, API của C và Lua 5.2. Nội dung như sau:

2. Dữ liệu
2.1 Làm thế nào để truy cập các đối số dòng lệnh
2.2 Làm thế nào để bóc tách các đối số dòng lệnh
2.3 Làm thế nào để đọc được tất cả các dòng trong một file?
2.4 Đọc các số bằng cách nào?
2.5 Định dạng đẹp các chữ và số bằng cách nào?
2.6 Đọc dữ liệu CSV bằng cách nào?
2.7 Làm việc với dữ liệu nhị phân bằng cách nào?
2.8 Có các thư viejn biểu thức thường quy không?
2.9 Bóc tách các dữ liệu văn bản có cấu trúc phức tạp?
2.10 Có thư viện toán học nào cho số phức, các số có độ chính xác coa, với ma trận, v.v.?
2.11 Bóc tách văn bản XML bằng cách nào?
2.12 Giao tiếp với các cơ sở dữ liệu như thế nào?
2.13 Lưu giữ trạng thái chương trình?
3. Hệ điều hành
3.1 Lấy ngày giờ hiện thời như thế nào?
3.2 Thực hiện một câu lệnh ngoài như thế nào?
3.3 Tạm dừng chương trình vài giây bằng cách nào?
3.4 Làm thế nào để liệt kê nội dung thư mục? Hỏi thuộc tính của file?
3.5 Chạy chương trình như một dịch vụ/Daemon?
3.6 Lua có các luồng thực thi không?
3.7 Truy cập Windows registry bằng cách nào?
3.8 Gửi một email?3.9 Lua có dùng làm ngôn ngữ lập trình được không?
3.10 Đo thời gian trôi qua trong chương trình?
4. Các thư viện
4.1 Một ngôn ngữ nhỏ gọn, nhưng tôi cần thêm thư viện. Tôi sẽ tìm chúng ở đâu?
4.2 Hiện có những bộ GUI (giao diện đồ họa) nào?
4.3 In đồ họa và biểu đồ vào file?
4.4 Làm thế nào để lập giao diện với các thư viện C/C++ của tôi?
4.5 Liệu có một foreign function interface (FFI, giao diện hàm ngoại)?
5. Phần cứng
5.1 Điện thoại thông minh
5.2 Những yêu cầu tối thiểu cho một thiết bị nhúng là gì?
6. Tính tương vận (vận hành cùng các ngôn ngữ khác)
6.1 LuaJIT nhanh hơn hẳn Lua được chỉnh (vanilla). Được vậy có mất gì không?
6.2 Dùng Lua với .NET, Java hay Objective-C?
6.3 Tôi cần các thư viện đó của Python/Perl qúa; liệu có thể truy cập được chúng không?
6.4 Lua có thể tương vận với CORBA hay các dịch vụ Web không?
7. C API
7.1 Làm thế nào để duyệt một bảng Lua?
7.2 Làm cách nào lưu lại một bảng hay hàm Lua để sau này còn sử dụng?
7.3 Làm cách nào để tạo ra một bảng nhiều chiều trong C?
7.4 Liệu có thể dùng các biệt lệ C++?
7.5 Làm cách nào để gắn kết một lớp C++ vào Lua?
7.6 Có khác biệt nào giữa các hàm lua và luaL?
7.7 Tôi được làm gì với ngăn xếp Lua và nội dung của nó trong hàm C của tôi?
7.8 Có sự khác biệt nào giữa userdata và userdata nhẹ?
8. Lua 5.2
8.1 Những mã lệnh nào trong Lua 5.1 sẽ hỏng trong Lua 5.2?
8.2 Những đặc điểm mới của Lua 5.2 là gì?

2 Dữ liệu

2.1 Làm thế nào để truy cập các đối số dòng lệnh?

Các đối số dòng lệnh được truyền vào file mã lệnh dưới dạng bảng toàn cục tên là arg. Các đối số thực tế là arg[i] trong đó i chạy từ 1 đến #arg – hoặc bạn có thể dùng ipairs. arg[0] cho tên của file mã lệnh khi nó được gọi; arg[-1] là tên của file chạy Lua được dùng.

> lua arg.lua one "two three"
1       one
2       two three
-1      lua
0       arg.lua

(trong đó ‘arg.lua’ chỉ là ‘for k,v in pairs(arg) do print(k,v) end`)

Bạn có thể dùng arg[0] để tìm xem file mã lệnh của bạn được đặt ở đâu. Nếu đó là một đường dẫn tuyệt đối thì hãy dùng đường dẫn này. Nếu không thì dùng thư mục hiện hành và điền thêm phần đường dẫn đang có.

Có thể làm một file mã lệnh chạy được trực tiếp trong môi trường Unix bằng cách viết dòng lệnh đầu tiên là ‘#!/usr/local/bin/lua’ hay ‘#!/usr/bin/env lua’ và điều chỉnh quyền thực hiện cho file lệnh. Trong Windows bạn có thể làm bằng cách thêm ‘;.lua’ vào biến môi trường PATHEXT rồi chọn hành động mặc định là thực thi mỗi khi mở file Lua.

Lưu ý rằng arg được đặt bởi trình thông dịch chuẩn; nếu chương trình C/C++ của bạn chạy một file lệnh bằng luaL_dofile thì arg sẽ là nil, trừ phi bạn tự đặt nó.

2.2 Làm thế nào để bóc tách các đối số dòng lệnh?

Không có cách làm chuẩn mực nào (điều này chẳng lạ gì), bởi vậy các gói chương trình thường phải tự thực hiện. Tuy nhiên, việc này không khó nếu sử dụng phép khớp chuỗi trong Lua. Sau đây là cách mà LuaRocks bóc tách các đối số của nó:

--- Tách các biến cờ (flag) khỏi danh sách đối số.
-- Cho trước các đối số dưới dạng chuỗi, thực hiện tách các đối số thành một tập hợp các cờ.
-- Chẳng hạn, cho trước "foo", "--tux=beep", "--bla", "bar", "--baz",
-- hàm sẽ trả về:
-- {["bla"] = true, ["tux"] = "beep", ["baz"] = true}, "foo", "bar".
function parse_flags(...)
   local args = {...}
   local flags = {}
   for i = #args, 1, -1 do
      local flag = args[i]:match("^%-%-(.*)")
      if flag then
         local var,val = flag:match("([a-z_%-]*)=(.*)")
         if val then
            flags[var] = val
         else
            flags[flag] = true
         end
         table.remove(args, i)
      end
   end
   return flags, unpack(args)
end

Lapp cung cấp một giải pháp cấp cao hơn. Nó bắt đầu từ chi tiết bạn phải in ra một chuỗi hướng dẫn sử dụng nếu bạn viết mã lệnh để người khác dùng. Đã vậy, tại sao ta không mã hóa từng cờ cho phép (cùng với kiểu của nó) trong bản thân chuỗi hướng dẫn sử dụng đó?

-- scale.lua
  require 'lapp'
  local args = lapp [[
  Does some calculations
    -o,--offset (default 0.0)  Offset to add to scaled number
    -s,--scale  (number)  Scaling factor
     <number> (number )  Number to be scaled
  ]]

  print(args.offset + args.scale * args.number)

Vì các cờ này được khai báo là gía trị số, nên mã lệnh nhỏ này có khả năng soát lỗi khá tốt:

D:\dev\lua\lapp>lua scale.lua
  scale.lua:missing required parameter: scale

  Does some calculations
   -o,--offset (default 0.0)  Offset to add to scaled number
   -s,--scale  (number)  Scaling factor
    <number> (number )  Number to be scaled

D:\dev\lua\lapp>lua scale.lua -s 2.2 10
  22

D:\dev\lua\lapp>lua scale.lua -s 2.2 x10
  scale.lua:unable to convert to number: x10
  ...

2.3 Làm thế nào để đọc được tất cả các dòng trong một file?

Cách đơn giản nhất là dùng io.lines:

for line in io.lines 'myfile.txt' do
  ...
end

Dạng này sẽ mở file cho bạn và đảm bảo rằng xong xuôi sẽ đóng file lại. Nếu không kèm đối số gì, io.lines() sẽ cho bạn một đối tượng lặp (iterator) trên tất cả dòng dữ liệu từ nguồn vào chuẩn. Lưu ý rằng nó là đối tượng lặp, do vậy ban đầu sẽ không nhồi toàn bộ nội dung file vào bộ nhớ.

Tốt hơn là mở một file theo cách minh bạch bằng io.open, bởi vì như vậy bạn có thể kiểm tra lỗi hẳn hoi:

local f,err = io.open(file)
if not f then return print(err) end
for line in f:lines() do
  ...
end
f:close()

Cách này tương đương với

local line = f:read()  -- mặc định là đọc 1 dòng
while line do
   ...
   line = f:read()
end

Để đọc cả nội dung file vào một chuỗi, hãy dùng định dạng *a với read:

s = f:read '*a'

2.4 Đọc các số bằng cách nào?

Phương thức read nhận một danh sách các chuỗi định dạng. Như vậy để đọc ba con số trên mỗi dòng, ta viết:

local f = io.open 'myfile.txt'  -- luôn gỉa thiết file này tồn tại!
while true do
  local x,y,z = f:read('*n','*n','*n')
  if not x then break end
  ...
end
f:close()

Với những trường hợp phức tạp hơn, người ta thường dùng string.match để trích xuất các con số và dùng hẳn tonumber để chuyển thành dạng số.

2.5 Định dạng đẹp các chữ và số bằng cách nào?

Phép nối chuỗi có tác dụng với cả chuỗi và số, nhưng không cho bạn bất kì quyền kiểm soát nào về định dạng các số này, vốn đặc biệt quan trọng khi xử lý các số phẩy động.

Hàm string.format hoạt động như sprintf: trong C bạn cho nó một chuỗi có chứa chuỗi định dạng, và một vài gía trị. Các định dạng kiểu số được hiểu như  ‘%5.2f’ (một trường có bề rộng 5 kí tự, có dành hai kí tự phần thập phân) hay ‘%02d’ (trường có bề rộng là 2, nhồi thêm số 0 phía trái nếu cần, ví dụ ’01’). ‘%s’ nghĩa là một chuỗi, ‘%q’ là một chuỗi trích nguyên, tức là định dạng mà Lua có thể đọc ngược lại được.

Một hàm printf nhỏ mà hữu ích như sau:

function printf(s,...)
      print(string.format(s,...))
end

Như thường lệ, người bạn của bạn chính là dấu nhắc lệnh; hãy thử viết lệnh đến khi đạt yêu cầu bạn muốn.

2.6 Đọc dữ liệu CSV bằng cách nào?

CSV là định dạng dữ liệu mà các số liệu trên cùng dòng cách nhau bằng dấu phẩy (comma-separated values). Mặc dù về lý, đây là một định dạng đơn giarn nhưng các quy tắc trích nguyên chuỗi lại có thể lằng nhằng

Module tên là Penlight pl.data có thể hữu dụng trong trường hợp này, nhưng không đảm bảo thay thế hoàn toàn được một module CSV chuyên dụng.

LuaCSV là một module C để đọc dữ liệu CSV. Tuy nhiên, giải pháp Lpeg này cũng nhanh chẳng kém, thật là ngạc nhiên và thú vị.

2.7 Làm việc với dữ liệu nhị phân bằng cách nào?

Các dữ liệu nhị phân có thể được xử lý như những chuỗi Lua, vì chuỗi không kết thúc bởi kí hiệu NUL.

> s = 'one\0two\0three'
> = s
one
> = #s
13
> for k in s:gfind '%Z+' do print(k) end
one
two
three

Lưu ý rằng mặc dù toàn bộ chuỗi có thể không được in ra nhưng toán tử chiều dài cho thấy tất cả các byte đều đủ trong chuỗi này! string.gfind lặp qua một mẫu chuỗi; ‘%z’ nghiã là byte NUL (rỗng), còn ‘%Z’ nghĩa là ‘bất kì thứ gì ngoài byte NUL’.

Cũng cần nhớ rằng các kí hiệu thoát ‘\ddd’ trong chuỗi Lua chứa số thập phân, chứ không phải hệ đếm tám (bát phân) như trong C. Cũng không có kí hiệu thoát thập lục phân như ‘\xDD’ dù điều này đang được xem xét trong Lua 5.2.

Giả sử tôi có một chuỗi chứa hai byte của một số nguyên 16-bit, thì s:byte(2)+256*s:byte(1) sẽ hợp lệ, nếu thứ tự byte là kiểu little-endian.

Để thực sự gỡ các gía trị từ chuỗi nhị phân thì cần thêm một chút trợ giúp. Thư viện struct của Roberto Ierusalimschy sẽ hữu ích vì nó biết thông tin về cách bố trí endian.

Chẳng hạn, để ghép một số phẩy động 32-bit và số nguyên 16-bit vào một chuỗi:

> s = struct.pack('hf',42,42)
> = #s
6
> = struct.unpack ('hf',s)
42      42      7

Gía trị sau cùng được trả lại là vị trí mà ta đã dừng đọc, mà trong trường hợp này là qúa vị trí cuối chuỗi. Bạn có thể dùng chỉ số này để duyệt qua một mảng các cấu trúc.

2.8 Có các thư viện biểu thức thường quy không?

Lẽ tự nhiên rồi! Nhưng hãy xem nếu bạn thực sự không thể giải quyết được bằng các mẫu chuỗi có sẵn trong Lua. Dù chúng đơn giản hơn các biểu thức thường quy truyền thống, song chúng đủ mạnh để giải quyết hầu hết các công việc. Trong nhiều trường hợp, bạn có thể hiểu một mẫu chuỗi Lua bằng cách hình dung việc thay thế dấu ‘%’ bởi ‘\’; ưu điểm của % dĩ nhiên là ở chỗ bản thân nó không cần phải thoát trong các chuỗi của C mà Lua dùng đến.

Chẳng hạn, ta cần kết xuất hai số nguyên được tách nhau bởi dấu cách từ một chuỗi:

local n,m = s:match('(%d+)%s+(%d+)')
n = tonumber(n)
m = tonumber(m)

Có một trang liệt kê các mẹo lập trình chuỗi trên Wiki.

Nếu bạn muốn các biểu thức chính quy truyền thống như PCRE, hãy xem lrexlib.

Một thư viện khác có thể xét đến thay cho PCRE là Lpeg.

Đây là những so sánh giữa PCRE và Lpeg đối với bài toán cụ thể.

2.9 Bóc tách các dữ liệu văn bản có cấu trúc phức tạp?

Nếu đã sẵn có một bộ tách đặc chủng cho dữ liệu của bạn thì hãy dùng luôn nó – nghĩa là đừng tạo một bộ bóc tách XML mới nữa!

Với trường hợp riêng của file .INI trong Windows .INI và các file cấu hình Unix, hãy xem xét module Penlight pl.config.

Lua rất mạnh trong việc xử lý văn bản – bạn có thể dùng các mẫu chuỗi có sẵn, biểu thức thường quy PCRE, hay Lpeg.

Đôi khi một bộ quét ngữ nghĩa cho ta một giải pháp sáng rõ hơn là dùng cách khớp mẫu. Cách này đặc biệt hiệu qủa nếu ‘văn bản có cấu trúc phức tạp’ của bạn chính là mã lệnh Lua hay C.

2.10 Có thư viện toán học nào cho số phức, các số có độ chính xác cao, với ma trận, v.v.?

Có vài cách khác nhau để đưa phép tính số phức vào trong Lua. lcomplex cho phép ta viết mã lệnh như sau:

> require 'complex'
> z = complex.new(3,4)
> = z*z
-7+24i
> = z + 10
13+4i
> = z:real()
3
> = z:imag()
4
> = z:conj()
3-4i
> = z:abs()
5
> = z^(1/3)  -- căn bậc ba của z
1.6289371459222+0.52017450230455i
> i = complex.I
> = 10+4*i
10+4i

Một cách khác là, nếu phép tính số phức phải thực hiện càng nhanh càng tốt và bạn không ngại sử dụng một bản chắp vá của Lua thì hãy dùng bản vá LNUM.

lbc cung cấp tính toán với độ chính xác tùy ý dựa trên thư viện BC:

> require 'bc'
> bc.digits()
> bc.digits(20)
> PI = bc.number "3.14159265358979323846"
> = PI
3.14159265358979323846
> = 2*PI
6.28318530717958647692
> = bc.sqrt(PI)
1.77245385090551602729

Các phép toán ma trận có trong LuaMatrix

NumLua là một bộ kết nối (binding) giữa Lua và các thư viện chủ yếu từ Netlib.

GSL Shell là một bộ kết nối tương tác giữa Lua và thư viện khoa học GNU (GSL, GNU Scientific Library). Nó có hai phiên bản; một là bản dựng của Lua có điều chỉnh bằng bộ vá LNUM cho số phức và hai là thư viện mở rộng có thể được gọi từ Lua. Nó sử dụng thư viện  Anti-Grain Geometry (AGG) với tính năng mạnh để vẽ nên các biểu đồ đẹp.

2.11 Bóc tách văn bản XML bằng cách nào?

LuaExpat là bộ kết nối Lua với XML nổi tiếng nhất. Thư viện này lập một API SAX trong đó ta đăng kí các callback để xử lý từng phần tử đã bóc tách được.

Module lxp.lom có nhiệm vụ đưa một văn bản XML vào bộ nhớ dưới dạng bảng LOM (Lua Object Model):

Với đoạn mã XML sau:

<abc a1="A1" a2="A2">inside tag `abc'</abc>

lxp.lom.parse sẽ cho bạn cấu trúc bảng dưới đây:

{
    [1] = "inside tag `abc'",
    ["attr"] = {
        [1] = "a1",
        [2] = "a2",
        ["a1"] = "A1",
        ["a2"] = "A2",
    },
    ["tag"] = "abc",
}

Các quy tắc của LOM rất đơn giản:

  • từng phần tử là một bảng
  • trường tag là thẻ phần tử
  • trường attr là một bảng chứa các thuộc tính; phần mảng thứ tự có các thuộc tính được sắp xếp, còn phần ánh xạ có những cặp tương ứng thuộc tính và gía trị
  • bất kì nhánh con nào đều nằm trong phần mảng của bảng phần tử.  child nodes are in the array part of the element table.

2.12 Giao tiếp với các cơ sở dữ liệu như thế nào?

Trình quản lý cơ sở dữ liệu GNU database manager (GNU database manager, gdbm) lưu trữ một tập hợp các cặp khóa-trị, giống như một mảng băm (hash table) được giữ trên đĩa. lgdbm cung cấp một API đơn giản để quản lý các cơ sở dữ liệu gdbm:

d=gdbm.open("test.gdbm","n")
d:insert("JAN","January")
d:insert("FEB","February")
d:insert("MAR","March")
d:insert("APR","April")
d:insert("MAY","May")
d:insert("JUN","June")

for k,v in d:entries() do
  print(k,v)
end

d:close()

Có một phương thức fetch để truy tìm các gía trị, một phương thức replace để cập nhật các mục sẵn có, và phương thức delete để xóa bỏ một cặp khóa-trị.

Từ các phương thức cơ bản này, rất dễ lập nên một hình thức có dáng vẻ giống Lua hơn:

local M = {
    __index=function (t,k) return t.gdbm:fetch(k) end,
    __newindex=function (t,k,v)
        if v then t.gdbm:replace(k,v) else t.gdbm:delete(k) end
    end
}

function gdbm.proxy(t)
 return setmetatable({gdbm=t},M)
end

Khi đó các lệnh trên có thể được viết lại theo cách tự nhiên hơn:

t=d:proxy()
t.JUL="July"
t.AUG="August"
t.SEP="September"
t.OCT="October"
t.NOV="November"
t.DEC="December"

t.DEC="Dezembro"  -- replace the entry
t.SEP = nil  -- delete the entry

Thường người ta hiểu ‘cơ sở dữ liệu’ là ‘cơ sở dữ liệu quan hệ’ (relational database).

Sau đây là một ví dụ dùng Lua-Sqlite3:

require "sqlite3"

db = sqlite3.open("example-db.sqlite3")

db:exec[[ CREATE TABLE test (id, content) ]]

stmt = db:prepare[[ INSERT INTO test VALUES (:key, :value) ]]

stmt:bind{  key = 1,  value = "Hello World"    }:exec()
stmt:bind{  key = 2,  value = "Hello Lua"      }:exec()
stmt:bind{  key = 3,  value = "Hello Sqlite3"  }:exec()

for row in db:rows("SELECT * FROM test") do
  print(row.id, row.content)
end

Đối tượng LuaSQL cung cấp phần hậu trường cho các hệ thống CSDLQH thông dụng. So với Lua-Sqlite3 thì hơi phức tạp hơn, nhưng cũng hỗ trợ nhiều hơn hẳn cho các hệ thống CSDL:

require "luasql.postgres"
env = assert (luasql.postgres())
con = assert (env:connect("luasql-test")) -- kết nối với nguồn dữ liệu 

res = con:execute"DROP TABLE people"
res = assert (con:execute[[
  CREATE TABLE people(
    name  varchar(50),
    email varchar(50)
  )
]])
-- thêm một số phần tử
list = {
  { name="Jose das Couves", email="jose@couves.com", },
  { name="Manoel Joaquim", email="manoel.joaquim@cafundo.com", },
  { name="Maria das Dores", email="maria@dores.com", },
}

for i, p in pairs (list) do
  res = assert (con:execute(string.format([[
    INSERT INTO people
    VALUES ('%s', '%s')]], p.name, p.email)
  ))
end

cur = assert (con:execute"SELECT name, email from people") -- nhận lại con trỏ

row = cur:fetch ({}, "a") -- muốn đặt chỉ số là tên trường 
while row do
  print(string.format("Name: %s, E-mail: %s", row.name, row.email))
  row = cur:fetch (row, "a") -- sử dụng lại bảng kết qủa 
end

cur:close()
con:close()
env:close()

2.13 Lưu giữ trạng thái chương trình?

Trước hết, hãy xem rằng việc ghi nội dung ra dưới dạng chuẩn như CSV hay XML có phù hợp với nhu cầu không; một cơ sở dữ liệu cũng có thể phù hợp hơn.

Thứ hai là bản thân Lua là một định dạng trong sáng và nhanh gọn để mã hóa dữ liệu có cấu trúc. Rất dễ đọc Lua và ‘chạy’ nó để phát sinh ra các bảng: hãy dùng loadfile() để biên dịch nó, rồi chạy các hàm kết qủa.

Việc ghi ra các bảng Lua dưới dạng mã lệnh Lua thì cần thêm chút cố gắng.

Sau cùng, một chương trình có thể đưa toàn bộ trạng thái của nó vào lưu giữ lâu dài bằng Pluto.

3 Hệ điều hành

3.1 Lấy ngày giờ hiện thời như thế nào?

os.date không kèm theo đối số sẽ cho bạn ngày giờ hiện thời được biểu diễn theo dạng chuẩn của hệ thống máy tính. Bạn có thể truyền cho một chuỗi định dạng như thể hàm strftime của C:

> = os.date()
07/23/09 15:18:49
> = os.date '%A'
Thursday
> = os.date '%b'
Jul
> = os.date '%B'
July
> = os.date '%C'
> = os.date '%a %b %d %X %Y'
Thu Jul 23 15:21:38 2009

Lưu ý: phiên bản strftime của Microsoft không xử lý đủ bộ những chỉ dẫn định dạng. Có lẽ tốt nhất là để mã lệnh hoạt động trên nhiều hệ thống được bằng cách theo sát những nguyên tắc trình bày ở đây (một số chỉ dẫn vốn hợp lệ trong Unix sẽ khiến Lua ngừng chạy trong Windows – Riêng Lua 5.2 thực hiện kiểm tra ở khâu này.)

Đối số thứ hai (tùy chọn) là thời gian cần được định dạng. Đây chính là gía trị được os.time trả lại và (thường) là số giây kể từ mốc thời gian 1/1/1970.

os.time có thể được truyền một bảng để chuyển đổi thành một gía trị thời gian. Hãy xem tài liệu hướng dẫn để biết thêm chi tiết.

3.2 Thực hiện một câu lệnh ngoài như thế nào?

Cách đơn giản nhất là os.execute vống  hoạt động giống như gọi system trong C; nó gọi ra ngoài phần shell để thực hiện các lệnh shell và những chương trình ngoài. Mã trả lại của việc thực thi thường là 0 kí hiệu cho thực thi thành công.

io.popen gọi một lệnh nhưng trả lại một đối tượng file, do vậy bạn có thể đọc được kết qủa đầu ra của lệnh, nếu như đối số thứ hai là ‘r’, song bạn cũng có thể truyền đầu vào cho một câu lệnh với đối số thứ hai là ‘w’. Thật không may là bạn chẳng có một lệnh io.popen2, và bạn không nhận được mã lệnh trả lại.

Có hai nhược điểm nghiệm trọng trong hai hàm này khi được thực hiện trong chế độ giao diện đồ họa Windows: (a) bạn sẽ thấy cái cửa sổ đen sì nháy lên từ os.execute và (b) io.popen chẳng hoạt động.

Một cách khắc phục điều này là dùng COM thông qua LuaCOM. Đó là một phần của Lua for Windows.

LuaCOM cho phép bạn truy cập các đối tượng của Windows Scripting Host (WSH). Phương thức Run được liệt kê tài liệu ở đây.

> require 'luacom'
> sh = luacom.CreateObject "WScript.Shell"
> = sh:Run ('cmd /C dir /B >  %TMP%\\tmp.txt',0)

Đối số cuối cùng ẩn đi cửa sổ mà nếu không có nó sẽ hiện ra.

Một phương án khác nhẹ hơn là sử dụng Winapi.

Hãy cẩn thận khi sử dụng os.tmpname trên Windows; nó không trả lại một đường dẫn đầy đủ đến file tạm thời; để có đường dẫn đủ bạn phải đặt vào trước gía trị của biến môi trường %TMP%. environment variable. Điều này thường chỉ gây khó chịu chút, vì nhiều file tạm thời rút cuộc cũng đặt ở thư mục gốc ổ đĩa; nhưng trong hệ Windows Vista thường điều này không truy cập được (với lý do hợp lý).

3.3 Tạm dừng chương trình vài giây bằng cách nào?

Thật không may là không có lệnh os.sleep(). Nhưng hầu hét các hệ điều hành đều có một lời gọi hàm khiến một mã lệnh dừng thực thi trong một khoảng thời gian. Việc này không tiêu tốn thời gian xử lý, khác với một ‘vòng lặp bận’ cần tránh.

LuaSocket cung cấp một hàm socket.sleep được truyền vào số giây cần dừng; đây là một số phẩy động và do vậy có thể nhận gía trị nhỏ hơn một giây.

Trong Windows, hàm Winapi cung cấp một hàm sleep.

Rất dễ buộc các hàm dừng cho từng hệ điều hành riêng bằng cách dùng LuaJIT FFI.

3.4 Làm thế nào để liệt kê nội dung thư mục? Hỏi thuộc tính của file?

Có một tin xấu là Lua thực ra không biết về hệ thống file. Lua được thiết kế trong phạm vi những việc mà ANSI C làm được, và trên ‘hệ thống’ đó thì không biết về thư mục. Điều này có vẻ kì quặc, song bạn hãy nhớ rằng Lua còn có thể và đã chạy trên những hệ thống nhúng thậm chí không cần đến một hệ thống file.

Bạn có thể dùng io.popen và trực tiếp dùng shell để cho biết nội dung của thư mục là gì. Nhưng khi đó bạn phải ghi nhớ những khác biệt giữa các hệ thống (phải dùng ‘ls’ hay ‘dir /b’?) và do vậy mọi thứ sẽ rối tung lên. Nhưng điều này có thể thực hiện được – hãy xem LuaRock như một ví dụ về một chương trình tinh vi mà hoạt động chỉ bằng cách nâng cao các thư viện chuẩn và shell nằm bên dưới.

Một giải pháp trong sáng hơn là việc dùng một thư viện mở rộng như LuaFileSystem:

> require 'lfs'
> for f in lfs.dir '.' do print(f) end
.
..
lua
lrun
lual
lua51
scite
lscite
lrocks
luagdb
luajit
> a = lfs.attributes 'lua'
> for k,v in pairs(a) do print(k,v) end
dev     64772
change  1248283403
access  1248354582
rdev    0
nlink   1
blksize 4096
uid     1003
blocks  320
gid     100
ino     7148
mode    file
modification    1245230803
size    163092

Vậy kết qủa đã cho thấy ‘lua’ là một file, có kích cỡ 163092 byte. Trường thời gian như ‘modification’ và ‘access’ có thể được chuyển thành dạng ngày giờ có nghĩa bằng os.date.

Nếu bạn thích một thư viện cấp cao dựa trên LuaFileSystem, hãy xem pl.dir , một cách thức cấp cao để làm việc với thư mục và cung cấp các hàm tiện dụng để sao chép hoặc di chuyển file.

3.5 Chạy chương trình như một dịch vụ/Daemon?

Trên Windows, đã có LuaService.

luadaemon sẽ cho phép chương trình bạn trở thành một daemon chỉ với một lệnh gọi daemon.daemonize(); nếu thất bại nó sẽ trở về một chuỗi thông báo lỗi.

3.6 Lua có các luồng thực thi không?

Có và không, tùy theo ý nghĩa của luồng (‘thread’) là gì. Nếu ý của bạn là đa nhiệm với luông hệ điều hành (OS thread), thì câu trả lời là không, nhưng Lua có các coroutine (đồng chu trình) cho phép thực hiện đa nhiệm hợp tác. Một coroutine là một hàm có thể sinh (yield) bất kì lúc nào và có thể hồi phục sau đó. Khi sinh, nó giữ nguyên trạng thái bất kể ngăn xếp gọi hàm có sâu bao nhiêu.

Vì coroutine có thể xa lạ với nhiều bạn đọc, dưới đây là một ví dụ nhỏ:

local yield = coroutine.yield

function cofun()
    yield(10)
    yield(20)
    yield(30)
end

fun = coroutine.wrap(cofun)
print(fun())
print(fun())
print(fun())

=>
10
20
30

Một công dụng mạnh mẽ của coroutine là để viết các bộ lặp. Đễ biểu diễn các thứ như duyệt cây một cách đệ quy thường là việc dễ, và dùng coroutine sẽ giúp ta bọc hàm vào trong một bộ lặp: yield có thể được gọi từ bất kì hàm nào vốn được gọi từ một coroutine.

Điều này thực hiện duyệt ‘theo thứ tự’ cho cấu trúc cây dữ liệu:

function tree(t)
  if t.left then tree(t.left) end
  yield (t.value)
  if t.right then tree(t.right) end
end

function tree_iter(t)
    return coroutine.wrap(function() return tree(t) end)
end

t = {
        value = 10,
        left = {
           value = 20
        },
        right = {
           value = 30
        }
}


for v in tree_iter(t) do
    print(v)
end

=>
20
10
30

Câu lệnh for đang trông đợi một hàm không tham số, và nó sẽ liên tiếp gọi đến khi nil được trả lại. iter thì gói một hàm không đối số gọi đến tree với bảng cây cho trước và trả lại nó – một ví dụ sử dụng closure.

Copas dùng các coroutine theo cách đặc biệt khéo léo để cho phép xây dựng các server mạng. Lý thuyết này được giải thích rất hay ở đây trong cuốn Programming In Lua.

-- Chạy file kiểm tra và kết nối với máy chủ bằng telnet trên cổng được dùng.
-- Máy chủ phải phản hồi được bất kì dữ liệu nào được nhập, để dừng kiểm tra hãy gửi lệnh "quit"

require "copas"

local function echoHandler(skt)
  skt = copas.wrap(skt)
  while true do
    local data = skt:receive()
    if data == "quit" then
      break
    end
    skt:send(data)
  end
end

local server = socket.bind("localhost", 20000)

copas.addserver(server, echoHandler)

copas.loop()

Lưu ý rằng ví dụ này có thể xử lý được nhiều kết nối đồng thời, vì Copas xếp lịch cho các phiên khác nhau dùng coroutine. Việc này thường được thực hiện bằng cách dùng luồng OS hay những hàm gọi thô như select().

Có những thư viện đưa Lua vượt ngoài phạm vi coroutine. Các luồng OS thực thụ đã được bổ sung vào Lua qua LuaThread, dù rằng điều này yêu cầu phải vá mã nguồn. Lưu ý điều này có thể kém hiệu qủa vì yêu cầu một lượng khóa nhất định.

Một cách hiệu qủa để chạy nhiều trạng thái Lua song song nhau được cho bởi Lanes. Các trạng thái riêng này được gọi là ‘lane’ (làn) và chúng không chia sẻ dữ liệu mà chỉ giao tiếp với từng làn qua những đối tượng linda đóng vai trò như kênh thông tin (channel).

Một giới thiệu hay về đa nhiệm và đồng xử lý (concurrency) trong Lua có ở đây, cùng với các so sánh.

Về Lua và tạo luồng bằng các phần mở rộng của C, ở đây có giới thiệuthảo luận thêm. Nói riêng, các coroutine Lua có thể được điều khiển an toàn từ một luồng OS khác trong một phần mở rộng C.

3.7 Truy cập Windows registry bằng cách nào?

Hãy xem ở đây.

Nếu không, hãy dùng LuaCOM:

> require 'luacom'
> sh = luacom.CreateObject "WScript.Shell"
> = sh:RegRead "HKCU\\Console\\ColorTable01"
8388608

Xem bài báo Microsoft này để biết thêm chi tiết. Đây cũng là cách dễ nhất để lấy được các ổ mạng và máy in được kết nối chẳng hạn.

Winapi có các hàm để đọc và ghi registry.

3.8 Gửi một email?

Điều này rất dễ khi dùng module SMTP của LuaSocket:

local smtp = require 'socket.smtp'

r, e = smtp.send {
    from = 'sjdonova@example.com',
    rcpt = 'sjdonova@example.com',
    server = 'smtp.example.com',
    source = smtp.message{
        headers = {
            subject = "my first message"
        },
        body = "my message text"
    }
}
if not r then print(e) end

Song không may là nhờ đó mà spam đã dễ hơn bao giờ hết.

Bạn cũng có thể dùng CDO với LuaCOM.

Còn với phía bên kia, luaPOP3 cung cấp một thư viện để viết các server POP3 đơn giản.

3.9 Lua có dùng được làm ngôn ngữ lập trình được không?

Web applications built in Lua customarily perform better than their more common PHP and ROR brethren. Trước đây Lua thiếu hỗ trợ tốt để phát triển web với mỗi lệnh CGI đơn gỉan, nhưng bây giờ Lua đã hưởng lợi từ những tính năng rộng phát triển từ dự án Kepler Project, bao gồm một khung MVC, các driver cho cơ sở dữ liệu SQL, đặt mẫu (templating), bóc tách XML, v.v.

Orbit là một khung tốt để xây dựng các chương trình web và Sputnik là một khung Wiki mở rộng được cho các web site thao tác với văn bản cần hợp tác biên soạn.

Việc phục vụ các ứng dụng web Lua có thể thực hiện qua FastCGI hay CGI được chỉnh trang lại, nhưng đa số các web server gồm Lighttpd, Apache, và chính Lua server của Kepler là Xavante. Các ứng dụng WASAPI như Orbit hay Sputnik có thể dùng bất kì bộ nào trong số trên.

Nếu muốn tìm cái đơn giản mà bạn có thể khởi đầu cùng, hãy thử Haserl, một bộ bóc tách trang web động dựa trên CGI chạy độc lập được. Bạn chỉ cần đặt vào thư mục cgi-bin của bất kì webhost nào (sau khi bạn đã yêu cầu cài đặt Lua).

3.10 Đo thời gian trôi qua trong chương trình?

Nếu chỉ cần đo chính xác đến từng giaay, thì os.time() là đủ. Để chính xác đến mili giây, đã có os.clock(). Tuy nhiên, có hai cái bẫy trong hàm nhỏ này. Độ chính xác thực sự thường sẽ lớn hơn nhiều so với mili giây, và định nghĩa về ‘thời gian xử lý’ trong Unix và trong Windows lại khác hẳn nhau. Trong Unix, os.clock() trả lại thời gian xử lý (processor time) đã dùng, bởi vậy một đoạn mã lệnh dành phần lớn thời gian để … ngủ thì có vẻ như đã tiêu tốn rất ít thời gian.

socket.gettime() trả lại số giây cùng phần lẻ, hàm này lại dùng được trên nhiều hệ thống.

4 Các thư viện

4.1 Một ngôn ngữ nhỏ gọn, nhưng tôi cần thêm thư viện. Tôi sẽ tìm chúng ở đâu?

Chính những nhà thiết kế Lua đã cố ý giữ phần lõi ngôn ngữ nhỏ gọn và khả chuyển (hoạt động trên nhiều hệ thống) được. Họ hiểu rằng nếu ai cần thư viện thì sẽ tự viết ra. Họ cảm thấy rằng phần việc của mình là cung cấp phần nhân (‘kernel’) và người khác sẽ lo phần phân phối (‘distribution’), chứ không như Linus Torvalds đã làm với Linux. Bởi vậy đã có nhận định chung là ‘Lua không kèm theo phần thêm’.

Vì những thư viện này không phải là chính thống sẵn có nên luôn có xu hướng bị chế tạo lại, nhưng hầu như việc gì cũng có thể làm được với các thư viện mở rộng Lua sẵn có.

Một điểm khởi đầu phù hợp là trang Libraries and Bindings trên Lua Wiki.

LuaForge là kho phần mềm cộng đồng chứa các thư viện Lua và gồm hầu hết những thư viện tốt. Một danh mục hữu ích gồm các module thông dụng có ở đây.

Bạn có thể muốn tải về một bản phân phối gồm Lua cùng một nhóm các thư viện. Trên Windows, nên lựa chọn Lua for Windows trong đó gồm SciTE để biên tập và gỡ lỗi Lua, cộng với một loạt thư viện cùng tài liệu.

Nếu bạn chạy hệ thống Linux dựa trên Debian, như Ubuntu, thì Lua và các thư viện thông dụng đều sẵn có qua apt-get hay Synaptic. Đến rpmseek và gõ vào ‘lua’ sẽ cho bạn hơn 200 kết qủa, bởi vậy nói chung bạn có thể tìm thấy thứ cần bằng cách dùng hệ thống gói phần mềm cho hệ điều hành của mình.

Một cách khác là LuaRocks vốn cung cấp một kho chứa gói phần mềm Lua cho các nền tảng hệ thống khác nhau. Giống như apt-get, nó hiểu được những mối phụ thuộc bởi vậy nếu bạn muốn dùng LuaLogging nó cũng sẽ lấy LuaSocket kèm theo; song khác với apt-get, nó chạy được trên cả Unix, OS X lẫn Windows.

Một lựa chọn khác là LuaDist vốn rất giống một phiên bản để ‘phản pháo’ LuaRocks. Bộ này có thể hấp dẫn nếu như bạn là fan của CMake.

4.2 Hiện có những bộ GUI (giao diện đồ họa) nào?

Trang wiki này là nơi bắt đầu thích hợp. Nói chung, tất cả các thư viện GUI chủ chốt đến giờ đã có phần gắn kết (binding) với Lua.

Có lẽ bộ gắn kết đầy đủ tính năng và được duy trì tốt nhất là wxLua

Cũng có bộ gắn kết với Tcl/Tk. và với Qt.

Một bộ rất gọn nhẹ và dễ dùng, để thoải mái viết mã lệnh là IUP; cả IUP lẫn wxLua đều là thành phần của bộ Lua for Windows nhưng chúng có thể tách riêng và có cả phiên bản cho Unix.

Sau đây là một chương trình vẽ đồ thị đơn giản.

require( "iuplua" )
require( "iupluacontrols" )
require( "iuplua_pplot51"  )

plot = iup.pplot{TITLE = "A simple XY Plot",
                    MARGINBOTTOM="35",
                    MARGINLEFT="35",
                    AXS_XLABEL="X",
                    AXS_YLABEL="Y"
                    }

iup.PPlotBegin(plot,0)
iup.PPlotAdd(plot,0,0)
iup.PPlotAdd(plot,5,5)
iup.PPlotAdd(plot,10,7)
iup.PPlotEnd(plot)

dlg = iup.dialog{plot; title="Plot Example",size="QUARTERxQUARTER"}

dlg:show()

iup.MainLoop()

Với các hàm ứng dụng iupx thì mã lệnh còn rút lại đơn giản thế này:

require "iupx"

plot = iupx.pplot {TITLE = "Simple Data", AXS_BOUNDS={0,0,100,100}}
plot:AddSeries ({{0,0},{10,10},{20,30},{30,45}})
plot:AddSeries ({{40,40},{50,55},{60,60},{70,65}})
iupx.show_dialog{plot; title="Easy Plotting",size="QUARTERxQUARTER"}

Trong số nêu trên, IUP là thư viện GUI tiết kiệm bộ nhớ nhất và cũng nhanh. Chẳng hạn, đoạn mã lệnh vừa rồi chỉ chiếm hơn 5MB bộ nhớ trong Windows.

lua-gtk là một bộ gắn kết trọn vẹn Lua đến GTK+ và những thư viện cốt lõi khác trên màn hình GNOME.

require "gtk"

win = gtk.window_new(gtk.GTK_WINDOW_TOPLEVEL)
win:connect('destroy', function() gtk.main_quit() end)
win:set_title("Demo Program")
win:show()
gtk.main()

Một bộ gắn với GTK khác là lgob.

luaplot là một thư viện vẽ đồ thị bằng Lua, dùng Cairo và có thể tích hợp được với GTK+.

Tùy theo sở thích, việc dùng Lua với Java hay .NET có thể là cách hiệu qủa để tạo ra GUI. Chẳng hạn, dùng LuaInterface bạn truy cập được trực tiếp tới thư viện đồ họa NPlot tuyệt vời. Tương tự, dùng Lua với Java có thể tạo ra các ứng dụng Swing đầy đủ tính năng.

4.3 In đồ họa và biểu đồ vào file?

Lua-GD là bộ gắn kết với thư viện gd. GD đặc biệt hữu dụng cho những ứng dụng Web, vì nó tạo nên kết quả dưới các dạng thông dụng như PNG, JPEG hay GIF.

luaplot nêu trên cũng có thể trực tiếp in vào file định dạng đồ họa.

luagraph là một bộ gắn kết với thư viện Graphviz, vốn cho phép bạn hiển thị những biểu đồ phức tạp – nghĩa là dưới dạng tập hợp các nút (đỉnh) được nối với nhau.

Giao diện đơn giản này đến gnuplot rất hữu ích nếu bạn đã quen với gói gnuplot và muốn tích hợp nó với mã lệnh Lua của bạn.

4.4 Làm thế nào để lập giao diện với các thư viện C/C++ của tôi?

Rất dễ gắn kết mã lệnh Lua với C. Một lý do là Lua chỉ có ít kiểu dữ liệu phức hợp, bởi vậy một khi bạn đã học cách xử lý bảng và userdata từ phía C side, bạn đã gần như thuần thục nghệ thuật gắn kết với Lua.

Các đối số được lấy khỏi ngăn xếp, các gía trị trả lại được đẩy lên ngăn xếp, và hàm C trả lại số phần tử như dự kiến, về đoạn chương trình gọi nó.

// build@ gcc -shared -I/home/sdonovan/lua/include -o mylib.so mylib.c
// đối với mã lệnh C của bạn 
#include <string.h>
#include <math.h>

// đối với mã lệnh Lua 
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// định nghĩa các hàm gọi được từ Lua
static int l_createtable (lua_State *L) {
  int narr = luaL_optint(L,1,0);         // các ngăn của mảng ban đầu, mặc định là 0
  int nrec = luaL_optint(L,2,0);         // các ngăn của từ điển ban đầu, mặc định là 0 
  lua_createtable(L,narr,nrec);
  return 1;
}

static int l_solve (lua_State *L) {
    double a = lua_tonumber(L,1);  // hệ số của x*x
    double b = lua_tonumber(L,2);  // hệ số của x
    double c = lua_tonumber(L,3);  // hằng số
    double abc = b*b - 4*a*c;
    if (abc < 0.0) {
        lua_pushnil(L);
        lua_pushstring(L,"nghiệm ảo!");
        return 2;
    } else {
        abc = sqrt(abc);
        a = 2*a;
        lua_pushnumber(L,(-b + abc)/a);
        lua_pushnumber(L,(+b - abc)/a);
        return 2;
    }
}

static const luaL_reg mylib[] = {
    {"createtable",l_createtable},
    {"solve",l_solve},
    {NULL,NULL}
};

int luaopen_mylib(lua_State *L)
{
    luaL_register (L, "mylib", mylib);
    return 1;
}

Lưu ý quy tắc ở đây; một thư viện chỉ cần xuất một hàm, với tên đặc biệt luaopen_LIBNAME. Nó sẽ luôn trả lại bảng được tạo ra.

Để cho mã lệnh chạy được trên Windows, bạn cần phải đặt luaopen_mylib vào trong môt file .def hoặc dùng chỉ định __declspec(dllexport) trước định nghĩa hàm được xuất. Do các vấn đề thời gian chạy (runtime) trong Windows, bạn thường sẽ phải biên dịch phần mở rộng với cùng bộ dịch dùng để dựng nên phiên bản Lua của bạn.

Cái module nhỏ này xuất hai hàm, vốn đều hữu ích (nhưng ít kết hợp với nhau!). mylib.solve giải phương trình bậc hai và trả lại cả hai nghiệm; nó phát hiện được trường hợp nghiệm ảo và trả lại nil cùng một thông báo lỗi, vốn là quy tắc chung.

mylib.createtable cho phép bạn tạo ra một bảng với khả năng ấn định trước những phần tử mới, mà điều này bản thân Lua không làm được. Điều này có thể hữu ích nếu bạn muốn điền vào một bảng thực sự lớn, mà không bị dính phải độ phức tạp O(log(N)) với thuật toán điền phần tử mới vào bảng.

> m = require 'mylib'
> = m.solve(0.5,10,1)
-0.10050506338833       -18.899494936612
> = m.solve(2,1,1)
nil     nghiệm ảo!
> t = m.createtable(20)
> for i=1,10 do t[i] = 2*i end
> = #t
10

Lưu ý rằng ban đầu bảng có kích thước bằng không, và #t được cập nhật mỗi khi có phần tử mới được điền vào.

Theo quy tắc chung, chỉ nên nhảy vào viết mã C nếu bạn gặp vấn đề cần tối ưu hóa đặc biệt, hay đã có sẵn một thư viện giải quyết tốt bài toán.

Dùng API nguyên gốc là các dễ nhất, song có thể là việc buồn tẻ. Có vài công cụ giúp bạn thực hiện công việc một cách bán tự động, và chúng cũng hiểu được C++, xem 7.5.

4.5 Liệu có một foreign function Interface (FFI, giao diện hàm ngoại)?

Alien là bộ gắn kết Lua tới libffi.

Sau đây là một ví dụ gắn kết với thời gian chạy C; lưu ý rằng Windows là một trường hợp đặc biệt yêu cầu phải nạp một cách tường minh. Alien giúp ta xử lý phần việc tẻ nhạt để gắn kết với C:

require "alien"

if alien.platform == "windows" then
   libc = alien.load("msvcrt.dll")
else
   libc = alien.default
end

libc.malloc:types("pointer", "int")
libc.free:types("void", "pointer")
libc.strcpy:types("void", "pointer", "string")
libc.strcat:types("void", "pointer", "string")
libc.puts:types("void", "string")

local foo = libc.malloc(string.len("foo") + string.len("bar") + 1)
libc.strcpy(foo, "foo")
libc.strcat(foo, "bar")
libc.puts(foo)
libc.strcpy(foo, "bar")
libc.puts(foo)
libc.puts("Yeah!")
libc.free(foo)

Sau đây là ví dụ kinh điển “Hello, World” với GTK+:

local gtk,p,i=alien.load("/usr/lib/libgtk-x11-2.0.so.0"),"pointer","int"

gtk.gtk_init:types(nil,p,p)
gtk.gtk_message_dialog_new:types(p,p,i,i,i,p)
gtk.gtk_dialog_run:types(i,p)

gtk.gtk_init(nil,nil)
gtk.gtk_dialog_run(gtk.gtk_message_dialog_new(nil,0,0,1,"Alien Rocks!"))

Đến đây mọi người có thể mừng rỡ qúa sớm và gắng sức viết những ứng dụng GTK+ đồ sộ bằng Alien, nhưng khi đó sẽ có rất nhiều hàm và hằng số; bạn nên dùng một bộ gắn kết GTK hẳn hoi như lua-gtk. Song nếu bạn chỉ muốn đoạn mã lệnh làm nhiệm vụ hiện một hộp thoại thông báo lên thì ổn thôi.

Một vấn đề khác có thể gây khó dễ khi bạn dùng Alien là bạn phải có đường dẫn đầy đủ đến thư viện chia sẻ trong Unix (Windows có một đường dẫn thư viện chia sẻ thoải mái hơn) và những đường dẫn này sẽ khác nhau tùy theo hệ thống của những lập trình viên phát triển, bảo trì ứng dụng. Chẳng hạn, các thư viện chia sẻ nằm trong /usr/lib64 ở các bản RedHat/SuSE x86_64 (nhưng không phải vậy ở Debian/Ubuntu).

Alien nhanh đến mức nào? Việc gọi sin thông qua Alien sẽ lâu gấp khoảng 3 lần so với math.sin. Vậy rõ ràng phải tốn kém khi ta truy cập các hàm theo cách này, nhưng trong nhiều trường hợp thì công việc thực mà hàm giải quyết được sẽ lợi hơn nhiều so với chi phí thời gian để gọi hàm đó.

4.5.1 LuaJIT FFI

Bộ FFI được lập sẵn trong LuaJIT đã cho ta một cách rất nhanh để từ chương trình Lua truy cập được các hàm và cấu trúc của C. Miễn là LuaJIT hỗ trợ hệ thống của bạn, nó sẽ nhanh bằng (chưa nói là nhanh hơn) so với viết các phần mở rộng  C.  Các khai báo cấu trúc và hàm C đơn giản được hiểu trực tiếp:

ffi.cdef[[
void Sleep(int ms);
]]

từ đó hàm này có thể được gọi với tên ffi.C.sleep.

Các mảng và cấu trúc C có thể được trực tiếp tạo ra và truy cập – lưu ý rằng các mảng được đánh số từ không:

> ffi = require 'ffi'
> arr = ffi.new("double[?]",20)
> for i = 1,20 do arr[i-1] = i end
> = arr[10];
11
 > ffi.cdef [[struct _point {double x,y;};]]
 > point = ffi.new("struct _point")
> point.x, point.y = 10,20
> = point.x, point.y
10      20

5 Phần cứng

5.1 Điện thoại thông minh (Smart Phones)

5.1.1 Google Android?

Có hỗ trợ trực tiếp cho viết mã lệnh Lua dùng trong Android; xem thông cáo này trên Google Open Source blog.

AndroLua cho phép dùng LuaJava để tích hợp Lua chuẩn theo C vào trong các dự án Android.

Corona cho phép phát triển trên các nền tảng Android và iPhone bằng Lua.

5.1.2 Hỗ trợ IPhone?

Apple không còn cấm các trình thông dịch trên iPhone và không lý do gì mà một ứng dụng không thể chứa Lua; các hạn chế có vẻ như nhằm vào việc tải về và chạy các đoạn mã để truy cập các iPhone API. Bạn có thể thấy một iPhone chạy ‘Hello world!’ ở đây.

Cũng có một câu hỏi liên quan được trả lời trên StackOverflow.

Mathew Burke đã thảo luận các lựa chọn cho việc lập trình iOS bằng Lua trong bài báo luanova này, bao gồm CoronaWax.

5.1.3 Điện thoại dựa trên Java?

Kahlua là một dự án hoàn toàn lập nên một động cơ chạy Lua trong Java. Mục tiêu là hoạt động trên hệ thống rút gọn J2ME có trong các điện thoại di động; ví dụ cơ bản là một trình thông dịch Lua nhỏ có thể chạy trên điện thoại của bạn. Một lựa chọn khác là luaj.

Jill (Java Implementation of Lua Language) gần đây mới được phát hành.

5.2 Những yêu cầu tối thiểu cho một thiết bị nhúng là gì?

Lua không phải là một trình biên dịch kinh điển; mã nguồn được biên dịch thành dạng nhị phân và sau đó thực hiện, mà công cụ luac sẽ thực hiện minh bạch cho bạn. Không có trình biên dịch, Lua VM (máy ảo) khi đó chỉ khoảng 40K. Mã lệnh nhị phân có thể được chuyển thành chuỗi C rồi biên dịch trực tiếp trong ứng dụng của bạn; điều này cần thiết nếu thực sự bạn không có một hệ thống file.

eLua là một dự án dành riêng cho Lua chạy độc lập (‘unhosted’) trên các bộ vi xử lý nhúng. Tác gỉa khẳng định “Tôi đề nghị ít nhất là 256k bộ nhớ Flash .. và ít nhất 64k RAM’, dù rằng điều này để hỗ trợ dấu phẩy động. Thực tế rằng Lua có thể được biên dịch để dùng kiểu số khác trở nên có ích trong trường hợp này, vì nhiều bộ vi xử lý không có hỗ trợ dấu phẩy động lập sẵn.

Bản vá LNUM do Asko Kauppi viết cho phép bạn kết hợp điểm mạnh của hai phía: bằng cách cho phép cả hai kiểu số nguyên và phẩy động cùng tồn tại trong Lua. Cách này hoạt động rất tốt trên các bộ vi xử lý tựa-ARM của bảng mạch Gumstix.

6 Tính tương vận (vận hành cùng các ngôn ngữ khác)

6.1 LuaJIT nhanh hơn hẳn Lua được chỉnh (“vanilla”). Được vậy có mất gì không?

Phiên bản gần đây của LuaJIT 2 thiết lập vị thế của LuaJIT như bản hiện thực ngôn ngữ động nhanh nhất. Các bộ vi xử lý Intel và ARM 32/64 bit được hỗ trợ, và hiệu năng có thể tương đương với mã lệnh được biên dịch.

Có một số điểm khác biệt mà bạn cần biết. LuaJIT chặt chẽ hơn Lua được chỉnh và không hỗ trợ cách mà Lua 5.0 xử lý số lượng đối số biến đổi.

LuaJIT giờ đây hỗ trợ việc sổ (dump) và đọc bytecode, nhưng hiện nay các thư viện như Lanes không thể làm việc với nó. Điều này cũng đúng với các phần mở rộng khác như Pluto vốn cần tích hợp sâu sắc với bản hiện thực (implementation) Lua.

6.2 Dùng Lua với .NET, Java hay Objective-C?

Luôn thường có nhu cầu về một ngôn ngữ lập trình gọn nhỏ có thể nhúng vào trong một ứng dụng lớn hơn.

Với các nền tảng VM (máy ảo), có hai cách: một là gắn thư viện Lua C với JNI hay P/Invoke, và cách khác là chạy Lua hoàn toàn trong ‘mã lệnh được quản lý’ (managed code). Nếu bạn thông thuộc với những nền tảng này rồi, Lua cung cấp một cách dễ dàng khám phá những thư viện và mẫu giao diện sẵn có. Mục tiêu tìm những thư viện phụ thêm khi đó sẽ trở thành câu hỏi: ‘Liệu tôi có thể thực hiện nhiệm vụ bằng C# hay Java?’.

6.2.1 Lua với Java

LuaJava cho phép mã lệnh Lua chạy được trong Java. Trình thông dịch Lua (kèm theo chút mã lệnh gắn kết) được tải bằng JNI.

Sau đây là mã lệnh ví dụ:

sys = luajava.bindClass("java.lang.System")
print ( sys:currentTimeMillis() )

(Lưu ý rằng không cần có require 'luajava' vì ta đang chạy bên trong LuaJava.)

newInstance tạo ra một thực thể mới của một đối tượng bên trong một lớp đã đặt tên.

strTk = luajava.newInstance("java.util.StringTokenizer",
    "a,b,c,d", ",")
while strTk:hasMoreTokens() do
    print(strTk:nextToken())
end

Vốn cũng có thể được viết bằng cách

StringTokenizer = luajava.bindClass("java.util.StringTokenizer")
strTk = luajava.new(StringTokenizer,"a,b,c,d", ",")
...

LuaJavaUtils là một tập hợp nhỏ những ứng dụng để giúp sử dụng LuaJava dễ hơn. Module java.import cung cấp một hàm import làm việc như câu lệnh Java cùng tên.

require 'java.import'
import 'java.util.*'
strTk = StringTokenizer("a,b,c,d", ",")
...

Module này chỉnh sửa môi trường Lua toàn cục sao cho các biến toàn cục chưa khai báo thì ban đầu được soát trong danh sách import, rồi soát trong các thực thể lớp, nếu được; phương thức meta call cũng được trùng tải (overload) cho những thực thể này; vì vậy ta có thể viết Class() thay vì luajava.new(Class).

Cũng có một ít các lớp Java để khắc phục những hạn chế của LuaJava, ở chỗ ta không thể thừa kế từ các lớp đầy đủ.

-- một ứng dụng Swing đơn gỉan.
require 'java.import'
import 'java.awt.*'
import 'java.awt.event.*'
import 'javax.swing.*'
import 'java.awt.image.*'
import 'javax.imageio.*'
import 'java.io.*'
-- cái này dành cho PaintPanel và Painter...
import 'org.penlight.luajava.utils.*'

bi = ImageIO:read(File(arg[1] or "bld.jpg"))
w = bi:getWidth(nil);
h = bi:getHeight(nil);

painter = proxy("Painter", {
    painter = function(g)
        g:drawImage(bi, 0, 0, nil)
    end
})

f = JFrame("Image")
f:setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

canv = PaintPanel(painter)
canv:setPreferredSize(Dimension(w,h))

f:add(JScrollPane(canv))
f:pack()
f:setVisible(true)

LuaJava chỉ hỗ trợ Java 1.4, bởi vậy các tính năng khéo léo như generics và danh sách đối số biến đổi thì đều không được hỗ trợ. Ngoài ra, việc xử lý lỗi hiện nay vẫn còn hơi thô sơ.

Kahlua là một dự án nhằm hiện thực hoàn toàn động cơ của Lua trong Java. Mục tiêu nền tảng là bộ runtime rút gọn J2ME tìm thấy ở điện thoại di động; ví dụ cơ bản là một trình thông dịch Lua mà bạn chạy được từ điện thoại di động.

Một lựa chọn khác là luaj hướng đến cả J2ME cùng J2SE và “biên dịch lua-sang-java-bytecode trực tiếp duy nhất”.

6.2.2 Lua với .NET

LuaInterface là một gói của Lua với .NET; đến tận phiên bản 1.5.3 sử dụng P/Invoke, và từ phiên bản 2.0 là mã lệnh quản lý hoàn toàn. Phiên bản 1.5.3 thân thiện hơn với người dùng .NET thường (chẳng hạn một nhà phát triển muốn dùng giao diện WinForms cho Lua) và do đó đây là phiên bản được kèm theo Lua for Windows.

Một cửa sổ lệnh “Hello, World!” bằng LuaInterface:

require 'luanet'
luanet.load_assembly "System"
Console = luanet.import_type "System.Console"
Math = luanet.import_type "System.Math"

Console.WriteLine("sqrt(2) is {0}",Math.Sqrt(2))

Một ứng dụng Windows.Forms cơ bản:

require 'luanet'
luanet.load_assembly "System.Windows.Forms"
Form = luanet.import_type "System.Windows.Forms.Form"
form = Form()
form.Text = "Hello, World!"
form:ShowDialog()

Vì tất cả kiểu dữ liệu cần được nạp một cách rõ ràng, nên điều này có thể hơi nhàm chán. Một thư viện phụ trợ CLRPackage (mã lệnh) được viết chỉ bằng Lua có thể giảm số lượng lệnh cần gõ vào bằng cách cung cấp chức năng import:

require 'CLRPackage'
import "System"
Console.WriteLine("sqrt(2) is {0}",Math.Sqrt(2))

Các ứng dụng Windows.Forms trở nên dễ dàng hơn:

require 'CLRPackage'
import "System.Windows.Forms"
import "System.Drawing"

form = Form()
form.Text = "Hello, World!"
button = Button()
button.Text = "Click Me!"
button.Location = Point(20,20)
button.Click:Add(function()
    MessageBox.Show("We wuz clicked!",arg[0],MessageBoxButtons.OK)
end)

form.Controls:Add(button)
form:ShowDialog()

Các bộ GUI có xu hướng chia thành hai phe nhóm; một dùng vị trí tuyệt đối (như Point(20,20) , bên trên) và một thì tin rằng làm như thế là xấu, và dùng cơ chế gói khiển tố (widget) như Java và GTK. Đây là một phương án khác để xây dựng form:

-- autoform.wlua
require "CLRForm"

tbl = {
    x = 2.3,
    y = 10.2,
    z = "two",
    t = -1.0,
    file = "c:\\lang\\lua\\ilua.lua",
    outfile = "",
    res = true,
}

form = AutoVarDialog { Text = "Test AutoVar", Object = tbl;
    "First variable:","x", Range(0,4),
    "Second Variable:","y",
    "Domain name:","z", {"one","two","three"; Editable=true},
    "Blonheim's Little Adjustment:","t",
    "Input File:","file",FileIn "Lua (*.lua)|C# (*.cs)",
    "Output File:","outfile",FileOut "Text (*.txt)",
    "Make a Note?","res",
}

if form:ShowDialogOK() then
    print(tbl.x,tbl.z,tbl.res,tbl.file)
end

Mã lệnh này tạo ra một hộp thoại có tính năng chỉnh sửa các trường trong bảng tbl. Phong cách lập trình này thường được thấy ở những bộ liên kết với các bảng cơ sở dữ liệu quan hệ, song cũng hợp với dữ liệu kiểu động và kiểm tra được như một bảng Lua.

6.2.3 Lua với Objective-C

LuaCocoa cho phép Lua hoạt động cùng Objective-C. Có một số ví dụ hay trong tài liệu kĩ thuật.

6.3 Tôi cần các thư viện đó của Python/Perl qúa; liệu có thể truy cập được chúng không?

LunaticPython cho phép hai cách chính hiệu để giao tiếp hai chiều giữa Lua và Python; nói riêng, bạn có thể nạp các module Python.

Đây là một hướng dẫn hay về cách dùng Lua cho những lập trình viên Perl.

LuaPerl cũng cho phép gọi mã lệnh Perl từ Lua. Tác gỉa có nói “Tôi có qúa nhiều mã Perl la liệt xung quanh (còn nhiều mã nữa trong các kho CPAN) và sẽ là tội ác nếu như bỏ mặc không đụng đến nguồn tiềm năng này.”

6.4 Lua có thể tương vận với CORBA hay các dịch vụ Web không?

Một bản hiện thực hóa đầy đủ của Lua cho CORBA được cung cấp bởi Oil

Có một sản phẩm thương mại cung cấp các dịch vụ web JSON với Lua.

Rất dễ truy cập những dịch vụ web kiểu JSON:

-- Ứng dụng khách cho API Yahoo Giao thông (http://developer.yahoo.com/traffic/rest/V1/index.html)
-- dùng JSON và Lua
-- Matt Croydon (matt@ooiio.com) http://postneo.com

http = require("socket.http")
json = require("json")

-- Lấy thông tin giao thông của Kansas City, MO
r, c, h = http.request("http://local.yahooapis.com/MapsService/V1/trafficData?appid=LuaDemo&city=Kansas+City&state=MO&output=json")

if c == 200 then
    -- Xử lý phản hồi
    results = json.decode(r).ResultSet.Result
    -- Lặp qua danh sách kết qủa
    for i=1,#results do
        print("Result "..i..":")
        table.foreach(results[i], print)
        print()
    end
end

Xem LuaTwitter để thấy một ví dụ chi tiết hơn về những gì có thể làm được với giao diện giữa Lua và JSON.

Dự án Kepler có hỗ trợ XML-RPC với LuaXMLRPC. Sau đây là một ví dụ ứng dụng nhỏ trên máy khách:

require "xmlrpc.http"

local ok, res = xmlrpc.http.call ("http://www.oreillynet.com/meerkat/xml-rpc/server.php", "system.listMethods")
print (ok)
for i, v in pairs(res) do print ('\t', i, v) end

7 C API

7.1 Làm thế nào để duyệt một bảng Lua?

Việc duyệt một bảng thông thường được thực hiện với lua_next. Mã lệnh sau tương đương với dùng ‘pairs’ trong Lua:

 /* table is in the stack at index 't' */
 lua_pushnil(L);  /* first key */
 while (lua_next(L, t) != 0) {
   /* uses 'key' (at index -2) and 'value' (at index -1) */
   printf("%s - %s\n",
          lua_typename(L, lua_type(L, -2)),
          lua_typename(L, lua_type(L, -1)));
   /* removes 'value'; keeps 'key' for next iteration */
   lua_pop(L, 1);
 }

Để lặp qua các phần tử của mảng, hãy dùng lua_rawgeti. Đây là cách nhanh nhất để truy cập mảng, vì không có phương thức meta nào sẽ được kiểm tra.

Chẳng hạn, hàm dưới đây sao chép một bảng được truyền như đối số của nó:

static int l_copy(lua_State *L) {
    // the table is at 1
    int i,n = lua_objlen(L,1);  // cũng hoạt động được với chuỗi, tương đương với #
    lua_createtable(L,n,0); // đẩy bảng mới, với kích thước mảng chỉ rõ
    for (i = 1; i<=n; i++) {
        lua_rawgeti(L,1,i);
        lua_rawseti(L,-2,i);  // giá trị ở -1, bảng mới ở -2
    }
    return 1; // bảng mới này vẫn còn trên ngăn xếp
}

7.2 Làm cách nào lưu lại một bảng hay hàm Lua để sau này còn sử dụng?

Những người mới thường bối rối vì không có một hàm nào kiểu như lua_totable hay lua_tofunction tương ứng với lua_tostring, v.v. Điều này là vì Lua không bộc lộ một kiểu ‘đối tượng Lua’ chung; tất cả các gía trị đều được truyền bằng ngăn xếp. Tuy vậy, có Registry cung cấp một cơ chế lưu trữ tham chiếu đến bảng và hàm Lua.

int ref;
lua_newtable(L);  // một bảng mới trên ngăn xếp
ref = luaL_ref(L,LUA_REGISTRYINDEX);  // dỡ phần tử và trả lại một tham chiếu đến bảng.

Sau đó, khi bạn cần nhồi lại bảng này vào ngăn xếp, chỉ cần truy cập đến registry bằng số nguyên tham chiếu này:

lua_rawgeti(L,LUA_REGISTRYINDEX,ref);

Cách này cũng có tác dụng với các hàm; bạn dùng luaL_ref để lấy một tham chiếu đến hàm mà sau đó có thể lấy ra và gọi. Điều này cần thiết khi ta lập các callback; hãy nhớ sử dụng lua_pcall tránh cho một thao tác gọi xấu làm đổ vỡ chương trình mà bạn chạy.

7.3 Làm cách nào để tạo ra một bảng nhiều chiều trong C?

Xem thread này trong danh sách Lua mailing. Ta có một ma trận C có tên V, vốn là một mảng gồm nhiều mảng theo hàng, và muốn lập nên một cái tương đương trong Lua.

int i,j;
lua_createtable(L , nRows, 0);  // nhồi bảng chính T vào ngăn xếp
for (j = 1; j <= nRows; j++ ) {
      lua_createtable(L , nCols, 0); // nhồi một bảng hàng R vào ngăn xếp
      for ( i = 1; i <= nCols; i++ ) {
            lua_pushnumber(L, V[j][i]);
            // gía trị thì ở -1, R ở -2
            lua_rawseti(L, -2, i);   // R[i] = V[j][i]
      }
      // R ở -1, T ở -2
      lua_rawseti(L, -2, j); // T[j] = R
}

7.4 Liệu có thể dùng các biệt lệ C++?

Cơ chế lỗi cơ bản của Lua sử dụng setjmp, nhưng cũng có thể biên dịch Lua để cho các biệt lệ C++ dùng được.

Điều quan trọng là không để các biệt lệ xâm nhập qua ranh giới vào Lua, nghĩa là dùng try..catch với mã lệnh của bạn, và nếu bắt được lỗi nào, thì hãy dùng lua_error để báo cho bản thân Lua biết.

7.5 Làm cách nào để gắn kết một lớp C++ vào Lua?

Lunar gói bọc mẫu thông dụng sau đây:

#include "lunar.h"

class Account {
  lua_Number m_balance;
public:
  static const char className[];
  static Lunar<Account>::RegType methods[];

  Account(lua_State *L)      { m_balance = luaL_checknumber(L, 1); }
  int deposit (lua_State *L) { m_balance += luaL_checknumber(L, 1); return 0; }
  int withdraw(lua_State *L) { m_balance -= luaL_checknumber(L, 1); return 0; }
  int balance (lua_State *L) { lua_pushnumber(L, m_balance); return 1; }
  ~Account() { printf("deleted Account (%p)\n", this); }
};

const char Account::className[] = "Account";

Lunar<Account>::RegType Account::methods[] = {
  LUNAR_DECLARE_METHOD(Account, deposit),
  LUNAR_DECLARE_METHOD(Account, withdraw),
  LUNAR_DECLARE_METHOD(Account, balance),
  {0,0}
};
....
Lunar<Account>::Register(L);

Đoạn mã này thực hiện tất cả việc tạo userdata và bảng meta cho bạn; mỗi phương thức trong lớp sẽ là một hàm C bằng Lua bình thường và lấy các đối số từ ngăn xếp như thường lệ.

tolua++ là một cách hấp dẫn vì nó có thể bóc tách các file header C/C++ được chỉnh trang ‘sạch sẽ’ và tạo ra mã lệnh gắn kết.

Một ví dụ rất đơn giản: chẳng hạn ta rất cần hàm C có tên strstr; co trước file này lib.pkg:

const char *strstr(const char*, const char*);

khi đó tolua -o lib.c lib.pkg sẽ phát sinh ra một file gắn kết lib.c vốn có thể biên dịch được và kết nối với thư viện tolua.

require 'lib' có thể đem lại một điều ngạc nhiên, vì không có bảng lib nào tạo ra, chỉ là một hàm tổng thể strstr. Để đặt hàm này vào một bảng lib, thì file lib.pkg phải trông như sau:

module lib
{
  const char *strstr(const char*, const char*);
}

Như vậy file .pkg không thực sự là file header C/C++; tuy nhiên, không khó để chuyển đổi file header thành file .pkg bằng cách chỉnh trang lại chúng.

Một cách khác là dùng luabind.

SWIG là một bộ phát sinh gắn kết thông dụng mà cũng có khả năng hướng đến Lua. Nếu có tồn tại một bộ gắn kết SWIG cho một thư viện, thì sẽ tương đối dễ phát sinh nên những bộ gắn kết Lua từ nó.

7.6 Có khác biệt nào giữa các hàm lua và luaL?

Các hàm lua_* hình thành nên phần API cơ sở của Lua. Nó là kênh thông tin giữa thế giới C và thế giới Lua. Nó cung cấp các thành phần được dùng bởi những hàm luaL_* từ API phụ trợ trong đó có những hàm trợ giúp và hàm tính năng cấp cao hơn.

7.7 Tôi được làm gì với ngăn xếp Lua và nội dung của nó trong hàm C của tôi?

Ngăn xếp mà bạn có được trong hàm C của bạn thì được tạo riêng cho hàm đó và bạn muốn làm gì với nó cũng được. Bởi vậy cứ thoải mái gỡ bỏ các đối số hàm mà bạn không cần đến nữa. Song có một trường hợp phải cảnh giác: không bao giờ gỡ bỏ một chuỗi Lua từ ngăn xếp khi bạn vẫn còn con trỏ đến chuỗi C bên trong nó (nhận được thông qua lua_tostring):

/* gỉa sử là ngăn xếp chứa một đối số chuỗi */
const char* name = lua_tostring(L, 1);
lua_pop(L, 1);  /* cẩn thận... */
puts(name);     /* hỏng! */

Nếu bạn gỡ bỏ chuỗi Lua từ ngăn xếp thì sẽ có khả năng là bạn gỡ bỏ luôn tham chiếu cuối cùng đến nó và bộ gom rác bộ nhớ sẽ giải phóng nó vào dịp sau, để lại bạn một con trỏ lửng lơ.

7.8 Có sự khác biệt nào giữa userdata và userdata nhẹ?

lua_newuserdata huy động một khối bộ nhớ với kích cỡ định trước. Kết qủa được gọi là một userdatum, và khác so với một khối huy động bởi malloc ở hai chỗ quan trọng: thứ nhất, nó sẽ được thu gom bằng bộ gom rác bộ nhớ, và thứ hai là thuộc tính của nó có thể được chỉ định bằng một bảng meta, cũng như với các bảng Lua. Có hai bảng metachỉ dùng được với userdata; __len thực hiện toán tử kích thước (#) và __gc cung cấp một hàm mà sẽ được gọi khi userdatum đó được thu gom. Một ví dụ rõ nét là trong thư viện Lua chuẩn có các kiểu file, mà __gc sẽ đóng chuôi file (file handle) lại. Bảng meta cũng đóng vai trò là kiểu duy nhất của một userdatum.

Mặt khác, userdata nhẹ là những gói bọc đơn giản quanh một con trỏ C. Chúng không có bảng meta, và chúng không được gom cùng rác bộ nhớ.  Công dụng của chúng là phát sinh những ‘chuôi’ mà ta có thể so sánh ngang bằng giữa hai đối tượng một cách đơn giản.

Việc thiết lập một đối tượng mảng đơn giản được thảo luận trong PiL, bắt đầu từ một tập hợp các hàm và kết thúc bằng một đối tượng mà ta có thể gắn chỉ số như một bảng Lua thông thường.

8 Lua 5.2

Phiên bản beta của Lua 5.2 có ở đây. Biến tổng thể _VERSION sẽ là chuỗi “Lua 5.2”, nếu bạn muốn kiểm tra tương thích. Xem trang wiki này để có một danh mục khác, và phần phân tích sâu ở đây.

8.1 Những mã lệnh nào trong Lua 5.1 sẽ hỏng trong Lua 5.2?

Xem danh sách chính thống về đặc điểm không tương thích.

8.1.1 Arg cho các hàm có đối số biến đổi

Gỉa biến arg trong các hàm với đối số bất định (...) không còn được định nghĩa nữa. Xem ở đây.

8.1.2 Phải viết rõ require thư viện debug

Nếu bạn cần các hàm gỡ lỗi, hãy viết debug = require "debug".

8.1.3 unpack được chuyển đến table.unpack

unpack nhận vào một bảng rồi trả lại tất cả nhũng gía trị của nó, ví dụ x,y = unpack {10,20}. Rất dễ viết lệnh local unpack = table.unpack ở trên đầu các file của bạn.

8.1.4 setfenv/getfenv lạc hậu

Nói tóm lại, không còn môi trường hàm trong Lua nữa. Có những cách mới để thực hiện nhiệm vụ thông thường.

8.1.5 module bị lạc hậu

Điều này nhiều khả năng do thay đổi không tương thích, vì module() giờ đây thường dược dùng khi viết ra các module trong Lua. module ngầm sử dụng setfenv vốn đã lạc hậu và đã đón nhận chỉ trích do làm nhiễu không gian tên (namespace) toàn cục.

Có lẽ tốt nhất là dùng phong cách giản dị, không lạ mắt để định nghĩa các module, trong đó từng hàm xuất khẩu đều được chỉ định rõ, và bảng chứa các hàm được trả lại rõ ràng.

  local _M = {}

  function _M.one()
     return _M.two()
  end

  function _M.two()
     return 42
  end

  return _M

Lưu ý rằng module này không cần được đặt tên tường minh; nó có thể đặt bất kì đâu trên đường dẫn module và cần phải require tương ứng. Nhưng bạn không thể gỉa định rằng một bảng module toàn cục đã được tạo ra, do vậy hãy luôn dùng một bí danh.

local mymod = require 'libs.mymod'

Dĩ nhiên bạn cũng có thể dùng tên module tự đặt thay vì _M, nhưng làm vậy để cho thấy rằng cái tên đó là tùy ý.

8.1.6 newproxy đã bị bỏ đi.

Đây luôn là một hàm ‘không được ghi chép tài liệu’ trong Lua 5.1, và người ta coi nó là không cần thiết, bởi nó được dùng chủ yếu để viết các đoạn kết (finalizer hay destructor).Vì phương thức meta the __gc giờ đây đã hoạt động với các bảng Lua nên cách xoay sở này đã không cần đến nữa.

8.2 Những đặc điểm mới của Lua 5.2 là gì?

8.2.1 _ENV và phạm vi ngữ vựng

Biến đặc biệt _ENV có chứa môi trường để tra cứu các biến. Không còn điều gì đặc biệt về bảng toàn cục, nó chỉ là gía trị mặc định cho _ENV.

Đoạn mã lệnh ngắn sau minh họa cho ứng xử đặc biệt này:

  -- env1.lua
  print(_ENV,_G)
  _ENV = { A = 2 }
  print 'dolly'

Và kết qủa là:

  table: 003D3F08 table: 003D3F08
  lua52: env1.lua:3: attempt to call global 'print' (a nil value)

Về hiệu lực, điều này chính là những gì sẽ xảy ra nếu bạn thay đổi môi trường của hàm hiện thời trong Lua 5.1.

Mã lệnh sau đây định nghĩa một khối lệnh trong đó tất cả những truy cập toàn cục đều được xảy ra trong một bảng cho trước:

local t = {}
do
  local _ENV = t
  x = 10
  y = 20
end
=> t == {x=10,y=10}

Ở đây chữ local là quan trọng vì ta không muốn thay đổi môi trường ‘global’ hiệu qủa của chương trình. _ENV được coi như một biến địa phương bất kì.

Trong một phiên bản xem trước (preview) của Lua 5.2 có một cú pháp theo dạng  in t do  vốn tương đương với  do local _ENV=t. Tuy nhiên, nhiều người thấy rằng dùng cách này dễ nhầm lẫn, vì họ dự kiến một cách viết tương đương với câu lệnh with trong Pascal và Visual Basic. Môi trường toàn cục hiện có thì không thể truy cập được bên trong khối lệnh, bởi vậy bạn phải định nghĩa các biến toàn cục nếu mã lệnh yêu cầu đến:

  local t = {}
  local G = _G
  do local _ENV = t
    x = 1.2
    y = G.math.sin(x)
  end

Cũng như trước đây, các biến địa phương và gía trị upvalue đều luôn được phân giải trước.

Một hàm có thể nhận một _ENV làm upvalue:

  local t = {x=10,y=20}
  local f1
  do local _ENV=t
    function f1()
      return x+y
    end
  end

  print(f1())
  ==> 30

Lưu ý sựu khác biệt về ý nghĩa nếu f1 không được khai báo là local; khi đó f1 sẽ trở thành một trường của t. Điều này thực ra là một cách khác để khai báo các module, như thể bạn đang dùng module mà không có package.seeall.

local _M = {}
do local _ENV = _M

function f1() return f2() end

function f2() return 42 end

end
return _M

Hàm load khái quát hóa có thể biên dịch một bó lệnh trong một môi trường cho trước.

local fn = load({x=1,y=10},'return x+y')

Đối số thứ hai của load cũng có thể là một hàm, vốn sẽ được gọi liên tiếp để thu được phần chữ của bó lệnh (xem load).

Để gọi lặp lại cùng hàm này với những môi trường khác nhau, tốt nhất là nên tường minh; hãy đặt môi trường ngữ vựng rồi truyền nó như một tham biến:

function(env) local _ENV=env; return x+y end

hoặc thậm chí gọn hơn là:

function (_ENV) return x+y end

(hãy xem điều này đang được làm thế nào với  setfenv.)

8.2.2 Thư viện phép toán Bit

Các hàm trong bit32 hoạt động với những số nguyên 32-bit có dấu.

> function phex(n) print (('0x%X'):format(n)) end
> phex(bit32.band(0xF0,0x01))
0x0
> phex(bit32.band(0xF0,0xA0))
0xA0

band, ‘bshift,brotate,bor,bnot,btestandbxor`.

Đây là một thay đổi quan trọng, vì Lua thiếu những toán tử bit.

8.2.3 Các dấu thoát thập lục phân như \xFF được phép trong chuỗi

Đặc biệt hữu dụng khi xây dựng các chuỗi nhị phân (nhớ rằng byte NUL được phép dùng trong các chuỗi Lua).

> s = "\xFF\x01"
> = s:byte(1)
255
> = s:byte(2)
1
8.2.4 Dạng mẫu Frontier giờ đây đã là chính thức

Để hiểu được vì sao điều này lại hữu ích, trước hết bạn phải hiểu vấn đề. Xét nhiệm vụ thường gặp là tìm ra những từ hoàn chỉnh trong chuỗi. Dạng mẫu “%Wdog%W” (tức là từ này bao quanh bởi các kí tự không phải chữ cái) nói chung là dùng được nhưng lại hỏng ở đầu và cuối chuỗi, nơi các ‘chốt chặn’ tương ứng lại không khớp. Dạng mẫu frontier %f[c] giải quyết vấn đề này: “%f[%w]dog%f[%W]”. Chốt chặn thứ nhất nói rằng ‘khớp được khi %w trở nên đúng lần đầu tiên’ còn chốt chặn thứ hai nói rằng ‘khớp được khi %W trở nên đúng’, và cả hai sẽ khớp một cách thích hợp ở hai đầu chuỗi.

Trang wiki này cung cấp thông tin đọc thêm bổ ích.

8.2.5 Các bảng tôn trọng phương thức meta __len

Có một số phương thức meta, như __gc__len chỉ áp dụng được cho userdata. Bởi vậy không có cách nào một đối tượng Lua thuần khiết cung cấp ý nghĩa của bản thân cho toán tử #, khiến cho các bảng ‘proxy’ kém hữu ích đi.

8.2.6 Các phương thức meta pairs/ipairs

Một vấn đề có liên quan với các đối tượng Lua muốn điều khiển cách ứng xử giống kiểu bảng, đó là pairs/ipairs hoạt động trực tiếp với nội dung gốc của bảng. Trong Lua 5.2, một đối tượng có thể hỗ trợ cả hai cách lặp trong mảng:

for i =1,#obj do ... end  -- có thể đè lên __len

for i,o in ipairs(obj) do .. end  -- có thể đè lên __ipairs

Một ví dụ cụ thể hơn cho thấy một đối tượng thay thế mảng:

local array = {}
local ipairs = ipairs
do
  local _ENV = array
  function __len(t)
       return #t.store
  end
  function __newindex(t,i,val)
       t.store[i] = val
  end
  function __index(t,i)
       return t.store[i]
  end
  function __ipairs(t)
       return ipairs(t.store)
  end
end

Đối tượng này giờ đây sẽ biểu hiện như một bảng Lua thông thường giống kiểu mảng:

t = new_array()
t[1] = 10
t[2] = 20
print(#t)
for i,v in ipairs(t) do print(i, v) end
=>
2
1       10
2       20

Chính xác hơn, nó đóng vai trò thay thế cho đối tượng phía dưới trong store (vốn có thể là một userdata).

Các thay thế mảng không còn hữu dụng như lẽ thường, bởi các hàm trong table nói chung đều làm việc với các bảng Lua gốc. Chẳng hạn, table.concat không tôn trọng  __len hoặc __ipairs.

8.2.7 package.searchpath

require nhìn vào cả module và đường dẫn mở rộng của Lua, vốn lần lượt là package.pathpackage.cpath. Đôi khi rất có ích nếu biết một module được Lua tìm thấy ở đâu mà không cần thực hiện lại việc tìm kiếm module. package.searchpath nhận tên module name (được hiểu bởi require) và một chuỗi thể hiện đường dẫn cần tìm kiếm:

> =  package.searchpath ('lfs',package.cpath)
/usr/local/lib/lua/5.2/lfs.so
> = package.searchpath ('test.mod', package.path)
./test/mod.lua
8.2.8 Có thể lấy trạng thái thoát từ io.popen

io.popen là một công cụ mạnh, nhưng thường hữu ích là lấy một mã được trả lại cùng với kết qủa xuất ra từ chương trình. Phương thức close giờ đây sẽ trả lại trạng thái thoát chương trình.

> f = io.popen 'ls'
> list = f:read '*a'  -- đọc tất cả kết qủa đầu ra
> print (#list)
143
> = f:close()
0
8.2.9 Các pcall/phương thức meta sinh (yield) được

Xét việc viết một bộ lặp (iterator) với dự kiến dùng cho một đồng trình (coroutine). Hàm lặp phải sinh trong từng lần một, nếu không thì các thread khác sẽ không thể hoạt động được.

function rand (n)
    return function()
        n = n - 1
        coroutine.yield()
        if n == 0 then return end
        return math.random()
    end
end

function co ()
    for x in rand(10)  do
        print(x)
    end
    return 'finis'
end

f = coroutine.wrap(co)
res = f()
while res ~= 'finis' do
    res = f()
end

Chương trình nhỏ này đưa ra lỗi trong Lua 5.1: “attempt to yield across metamethod/C-call boundary” (cố gắng sinh vượt qúa phạm vi ranh giới phương thức meta/lời gọi C) vì hàm lặp được gọi từ C, từ đoạn thực hiện nội tại của câu lệnh for. Như vậy mọi người bị buộc dùng vòng lặp while để thay thế, vốn là điều bất cập.

8.2.10 table.pack

table.pack thì ngược với table.unpack: nó nhận vào một số lượng không xác định các gía trị và lập nên một bảng. Nó xử lý trường hợp bất cập trong đó những gía trị là nil bằng việc đặt n bằng chiều dài bảng thực tế. Bởi vậy thay vì việc viết:

local arg = {n=select('#',...),...}

để xử lý các đối số biến, bây giờ bạn đã có thể viết theo cách tương đương:

local arg = table.pack(...)
8.2.11 Bảng Ephemeron

Trong bài wikipedia có nêu “Ephemeron là một đối tượng tham chiếu mạnh đến nội dung của nó miễn là khóa của ephemeron chưa bị thu gom rác, và sẽ tham chiếu yếu từ khi bị thu gom.”

Trong các bảng với khóa yếu điều này nghĩa là khóa sẽ luôn được thu gom khi không có tham chiếu nào khác đến nó.

8.2.12 Các hàm C nhẹ

Vì không còn các môi trường hàm nữa, nên có thể có một tham chiếu đơn giản đến một hàm C, giống việc mà userdata nhẹ là một vỏ bọc đơn giản quanh một con trỏ.

8.2.13 Gom rác bộ nhớ khẩn cấp

Khi việc huy động (bộ nhớ) thất bại thì bắt buộc phải gom rác toàn bộ. Điều này đặc biệt hữu ích với các thiết bị nhúng trong đó bộ nhớ là nguồn tài nguyên hạn chế.

8.2.14 os.execute thay đổi

Lưu ý rằng os.execute giờ đây đã trả lại một kết qủa khác; trước đây nó trả lại mã trả về thực thụ (thường là 0 với thực hiện thành công); bây giờ thì trả về true, một biểu hiện cho thấy qúa trình kết thúc thế nào, và chỉ khi đó thì shell mới trả về mã.

8.2.15 Lệnh goto

Các nhãn có dạng thế này ::name:: và sau đó bạn có thể viết goto name. Các nhãn đều nhìn thấy được từ khối lệnh mà chúng được định nghĩa. Như tài liệu hướng dẫn có viết “Một lệnh goto có thể nhảy đến bất kì nhãn nào thấy được miễn là nó không nằm trong phạm vi của một biến địa phương.”

7 bình luận

Filed under Tin học

7 responses to “Ngôn ngữ lập trình Lua: những hỏi – đáp bên lề (phần 2)

  1. Pingback: Ngôn ngữ lập trình Lua: những hỏi-đáp bên lề | Blog của Chiến

  2. But…what…? Ugly? Seriously? Besides being undeniably untrue, what a lame retort, courtesy of elementary school. I’ve had the same response as well whe Click http://d2.ae/hool090715

  3. quan

    làm thế nào đề download lua về máy vậy

  4. Ẩn danh

    ad có thể cho e fb em nhờ chút được ko

Bình luận về bài viết này