> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/vi/bai-viet-chuyen-sau/09-an-introduction-to-luatex-part-2-understanding-directlua.md).

# Giới thiệu về LuaTeX (Phần 2): Hiểu \directlua

## Mục tiêu của bài viết này

Ở phần đầu của bài viết này, [Giới thiệu về LuaTeX (Phần 1): Nó là gì — và điều gì khiến nó khác biệt đến vậy?](/latex/vi/bai-viet-chuyen-sau/07-an-introduction-to-luatex-part-1-what-is-it-and-what-makes-it-so-different.md), chúng ta đã lược qua LuaTeX như một bộ máy TeX cực kỳ linh hoạt: một hệ thống dàn trang tinh vi, có thể lập trình, cung cấp rất nhiều công cụ để xây dựng các giải pháp kỹ thuật và sản xuất tài liệu.

Trong phần kết thúc này, chúng ta sẽ xem xét kỹ thành phần quan trọng nhất trong bộ công cụ LuaTeX:  `\directlua` là lệnh cung cấp “cổng vào” để điều khiển việc dàn trang của LuaTeX bằng lập trình thông qua ngôn ngữ kịch bản Lua.

Tuy nhiên, để khai thác LuaTeX một cách đầy đủ thông qua `\directlua` đòi hỏi một số kiến thức nền về vài chủ đề TeX: các token của TeX, danh sách token và cơ chế mở rộng. Mục tiêu của bài viết này là khám phá và giải thích những khái niệm TeX cơ bản này: ghép nối các quy trình liên quan đến TeX đằng sau `\directlua` để phát triển sự hiểu biết về cách nó hoạt động và cung cấp nền tảng để xây dựng các giải pháp dàn trang của riêng bạn bằng LuaTeX.

Bài viết này bao gồm nhiều ví dụ ngắn để minh họa và giải thích các khía cạnh then chốt của `\directlua`hoạt động của nó, cố ý tránh mã quá phức tạp để ưu tiên các đoạn mã ngắn. Khi cần thiết, các ví dụ sử dụng TeX cơ bản (thuần/plain)—mặc dù hầu hết mọi người dùng và ưa thích LaTeX (macro), các lệnh TeX cơ bản có ưu điểm là đơn giản.

## Giới thiệu về Lua trong LuaTeX

[Lua](https://www.lua.org/about.html) là một ngôn ngữ kịch bản mà [mã nguồn](https://www.lua.org/download.html) có tính di động cao và dễ nhúng vào các ứng dụng phần mềm, cho phép nhà phát triển tích hợp khả năng kịch bản vào chương trình của họ. Lua đã được nhúng vào [nhiều ứng dụng](https://en.wikipedia.org/wiki/List_of_applications_using_Lua) và là lựa chọn phổ biến trong ngành công nghiệp game phần mềm — có lẽ ví dụ nổi tiếng nhất là [World of Warcraft](https://wowwiki.fandom.com/wiki/Lua_functions).

LuaTeX, đúng như tên gọi của nó, là một bộ máy TeX nhúng ngôn ngữ kịch bản Lua, cung cấp cho người dùng khả năng điều khiển hành vi dàn trang của LuaTeX bằng cách đưa các chương trình Lua (script) vào tài liệu của họ. Bên cạnh việc điều khiển trực tiếp LuaTeX, người dùng có thể khai thác Lua thuần như một ngôn ngữ lập trình rất mạnh để thực hiện những tác vụ có thể cực kỳ khó đạt được bằng ngôn ngữ TeX — vốn, theo bất kỳ thước đo công bằng nào, là một thử thách để học và làm chủ. Thông qua việc bổ sung và tích hợp Lua, LuaTeX trở thành một bộ máy TeX rất linh hoạt và mạnh mẽ, hỗ trợ trực tiếp hai ngôn ngữ lập trình.

### Dùng Lua và TeX trong tài liệu của bạn: nhập \directlua

Lua và TeX là hai *rất khác nhau* ngôn ngữ lập trình: Lua gần với điều mà đa số mọi người nghĩ đến như một ngôn ngữ lập trình hơn nhiều, nhưng TeX, với các mã loại, token, macro và cơ chế mở rộng của nó, khác xa với trải nghiệm/kỳ vọng của hầu hết mọi người về một ngôn ngữ để viết chương trình. Tuy nhiên, như lịch sử đã cho thấy, ngôn ngữ TeX vẫn tồn tại vì nó làm tốt điều mà nó được thiết kế để làm: điều khiển dàn trang, ngay cả khi cách thức hoạt động của nó có phần bí hiểm.

Để giải quyết thách thức khi trộn ngôn ngữ Lua và TeX trong cùng một tài liệu TeX, các nhà phát triển LuaTeX đã giới thiệu một lệnh mới có tên là `\directlua` đây là con đường để sử dụng Lua — vừa như một ngôn ngữ lập trình độc lập đúng nghĩa, vừa để điều khiển hành vi dàn trang của LuaTeX.

Tính năng `\directlua` lệnh cho phép người dùng nhúng mã Lua vào tài liệu TeX của họ; mã đó sau đó được chuyển tới bộ thông dịch ngôn ngữ Lua tích hợp sẵn của LuaTeX. Tuy nhiên, `\directlua` cũng cho phép bạn *kết hợp* mã Lua và (La)TeX với nhau, trong cùng một `\directlua` lệnh — mặc dù điều đó làm phát sinh thêm các phức tạp do sự khác biệt cơ bản giữa các ngôn ngữ lập trình dựa trên Lua và TeX. Thách thức chính khi dùng kết hợp mã (La)TeX và Lua là đảm bảo hai ngôn ngữ đó cùng tồn tại hòa thuận và không “cản trở lẫn nhau”.

`\directlua` phù hợp nhất để dùng với các đoạn mã Lua ngắn trong tài liệu, nhưng bạn cũng có thể dùng nó với các chương trình Lua dài hơn nếu muốn. Thông thường, các chương trình Lua lớn hơn và các thư viện mã Lua được lưu trong các tệp ngoài, có thể được nạp bằng cách dùng `dofile()` hàm trong một `\directlua` lệnh. Từ góc độ xử lý TeX, một ưu điểm quan trọng của việc dùng các tệp mã Lua bên ngoài là tránh được các phức tạp phát sinh từ cơ chế mã loại của TeX — một chủ đề được trình bày đầy đủ trong bài viết này.

### Mô tả trang trọng hơn về \directlua

Tính năng [Sổ tay tham khảo LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) mô tả `\directlua` như sau (đã sửa đổi đôi chút):

> Để trộn mã Lua với đầu vào TeX, cần một vài primitive mới. Primitive `\directlua` được dùng để thực thi ngay mã Lua. Cú pháp cơ bản là `\directlua{⟨code⟩}`. Gói `⟨code⟩` được mở rộng hoàn toàn, rồi được đưa vào bộ thông dịch Lua. Sau khi việc đọc và mở rộng đã được áp dụng cho `⟨code⟩`, danh sách token thu được sẽ được chuyển thành chuỗi như thể nó được hiển thị bằng `\the\toks`.

Dĩ nhiên, điều này về mặt kỹ thuật là chính xác nhưng có lẽ không dễ hiểu nếu không có chút kiến thức về các quy trình TeX cấp thấp — chẳng hạn như token và mở rộng.

## Hiểu \directlua: Chúng ta sẽ đề cập những chủ đề nào?

Trong bài viết này, chúng ta sẽ xem xét kỹ hơn một số chủ đề nền tảng then chốt và đưa ra nhiều ví dụ được thiết kế để minh họa cách `\directlua` hoạt động và chỗ nào (hoặc vì sao) bạn cần cẩn thận khi kết hợp TeX và Lua trong `⟨code⟩`.

Chúng ta sẽ khám phá đủ chi tiết các chủ đề sau để tạo nền tảng cho việc hiểu `\directlua` và việc nó “tiền xử lý” đoạn mã bạn dùng bên trong nó:

* mã loại và token TeX: chuyển văn bản thành token và token thành văn bản;
* quá trình mở rộng của TeX (và cách ngăn mở rộng);
* các cơ chế/dãy thoát của Lua cho ký tự và chuỗi;
* dùng chú thích kiểu Lua;
* một giới thiệu ngắn về API Lua của LuaTeX.

Nếu bạn hiểu cách các bộ máy TeX tạo và dùng token, và nhận thức được cơ chế mở rộng của TeX, thì bạn sẽ có nền tảng cần thiết để khai mở tính linh hoạt đáng kinh ngạc của LuaTeX’s `\directlua` lệnh này.

## Nền tảng: từ văn bản đến token và từ token đến văn bản

Overleaf đã xuất bản một số bài viết đi sâu vào token TeX và các khái niệm liên quan nên chúng tôi sẽ không lặp lại toàn bộ ở đây; thay vào đó, chúng tôi sẽ phác thảo những lĩnh vực/chủ đề liên quan đến việc hiểu rõ hơn về `\directlua`.

Dưới đây là danh sách các bài viết đã đăng trước đó có thể hữu ích:

* [Token TeX là gì?](/latex/vi/bai-viet-chuyen-sau/53-what-is-a-tex-token.md)
* [Danh sách token TeX là gì?](/latex/vi/bai-viet-chuyen-sau/54-what-is-a-tex-token-list.md)
* [Lệnh \expandafter hoạt động như thế nào: Giới thiệu về token TeX](/latex/vi/bai-viet-chuyen-sau/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md)
* [Chuỗi sáu phần: Macro TeX thực sự hoạt động như thế nào?](/latex/vi/chu-de-khac/01-a-six-part-series-how-do-tex-macros-actually-work.md)

### Hiểu về token ký tự

Bất kỳ ký tự nào mà một bộ máy TeX có thể đọc từ tệp văn bản đều được biểu diễn bằng hai giá trị số:

* giá trị *mã ký tự* (giá trị ASCII hoặc, ngày nay, là điểm mã Unicode của nó);
* một giá trị thứ hai, theo cách nhìn của TeX, gọi là *mã loại*.

Những độc giả muốn tìm hiểu thêm về mã loại có thể quan tâm đọc bài giới thiệu này do Overleaf xuất bản: [Vậy chúng ta bắt đầu từ đâu? Từ mã loại](/latex/vi/chu-de-khac/19-how-tex-macros-actually-work-part-1.md#so2c-where-do-we-start3f-with-category-codes).

Ví dụ, nếu một bộ máy TeX đọc vào một ký tự `Một` thì nó sẽ có hai mẩu thông tin: `Một`mã ký tự của nó (65) và mã loại của nó (thường là 11). Khi TeX đã nhập ký tự đó `Một`, mã loại của nó sẽ không bị thay đổi nhưng các macro do người dùng tạo có thể làm thay đổi mã loại, và điều đó có thể ảnh hưởng đến bất kỳ *ký tự* ký tự `Một` sau đó *chưa được đọc* bởi TeX. Do đó, TeX cần ghi lại rằng *ký tự này* ký tự `Một`, *vừa được đọc vào*, có mã loại 11. Để làm vậy, TeX dùng cặp số nguyên (65,11) để tính ra một giá trị số nguyên khác mà nó gọi là *token ký tự*. Bằng cách tính giá trị token đó, giá trị được chuyển tiếp vào quá trình xử lý bên trong của TeX, ký tự cụ thể đó `Một` và mã loại của nó được *liên kết với nhau*; trên thực tế, token ký tự đó *bao hàm* dữ liệu mà TeX cần biết về ký tự đó để dùng cho bất kỳ hoạt động dàn trang tiếp theo nào ở sâu bên trong bộ máy TeX.

#### Token ký tự được tính như thế nào?

Trước hết, cần nhớ rằng các bộ máy TeX dùng mã loại 13 để tạo ra cái gọi là *ký tự hoạt động*: bất kỳ ký tự nào có mã loại 13 đều hoạt động như một mini-macro; vì vậy, và như ta sẽ thấy bên dưới, token cho ký tự hoạt động được tính khác với các ký tự thông thường có các mã loại khác như 10, 11 hoặc 12.

Đối với *không hoạt động* ký tự:

* các bộ máy 8-bit cũ (TeX của Knuth, e-TeX, pdfTeX) tính token ký tự cho *không hoạt động* ký tự bằng

$$\text{(non-active) character token} = (256 \times \text{category code}) + (\text{ASCII character code})$$

* đối với LuaTeX, vốn phải xử lý các giá trị ký tự Unicode, phép tính cho *không hoạt động* ký tự thì tương tự nhưng tạo ra các giá trị số nguyên lớn hơn nhiều:

$$\text{(non-active) character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

Quay lại ví dụ trước đó với chữ A có mã loại 11, LuaTeX sẽ tính ra một giá trị token ký tự là $$2^{21} \times 11 + 65 = 23068737$$. Sau khi được tính, giá trị token ký tự đó *liên kết* ký tự A cụ thể đó với giá trị mã loại 11. Các macro do người dùng tạo có thể thay đổi mã loại cho bất kỳ ký tự A nào xuất hiện sau đó, nhưng mã loại của ký tự này đã được cố định bằng cách chuyển nó thành một token để dùng khi nó đi qua các cơ chế bên trong của LuaTeX. LuaTeX đã bảo toàn, hay bao hàm, ý nghĩa dự định của ký tự đó như được xác định tại thời điểm nó được đọc vào.

Các bộ máy TeX sử dụng tổng cộng [16 mã loại khác nhau](/latex/vi/chu-de-khac/43-table-of-tex-category-codes.md) và *bất kỳ* trong số các mã loại đó đều có thể được gán, thông qua `\catcode` lệnh, cho *bất kỳ* bất kỳ ký tự nào mà bộ máy TeX có thể đọc. Việc thay đổi mã loại được dùng để thay đổi cách các bộ máy TeX xử lý những ký tự cụ thể trong đầu vào, cho phép người dùng TeX viết các macro tạo ra kết quả hoặc hành vi dàn trang đặc biệt.

**Ký tự hoạt động**

Như đã lưu ý, các bộ máy TeX dùng mã loại 13 để gán một “ý nghĩa đặc biệt” cho một ký tự, biến nó thành cái gọi là *ký tự hoạt động* hoạt động như một mini-macro: không cần dấu `\` ở đầu, chỉ riêng ký tự đó, nhờ mã loại của nó, là đủ để kích hoạt hành vi giống macro.

Vì một ký tự hoạt động hoạt động như một mini-macro, nó không được chuyển thành một *token ký tự* mà thành một kiểu token thứ hai (số nguyên) gọi là một *token lệnh*. Chúng được tính như sau:

* đối với các bộ máy 8-bit cũ (TeX của Knuth, e-TeX, pdfTeX), token cho ký tự hoạt động được tính qua:

1. tính một giá trị trung gian gọi là $$\text{curcs}$$ (**cur**rent **c**ontrol **s**quence) trong đó $$\text{curcs} = \text{character code} + 1$$3. tính giá trị token trong đó $$\text{active character token} = \text{curcs} + \text{4095}$$

* đối với LuaTeX, phép tính phức tạp hơn một chút vì nó phải xử lý toàn bộ dải ký tự Unicode, và bất kỳ ký tự nào trong đó cũng có thể được làm cho trở nên hoạt động:

1. tính giá trị số nguyên trung gian $$\text{curcs}$$ bằng cách áp dụng một cái gọi là *hàm băm* lên giá trị điểm mã Unicode của ký tự hoạt động được biểu diễn bằng UTF-8: $$\text{curcs}=\texttt{hashfunction}\text{(UTF-8 text for Unicode value of active character)}$$3. tính giá trị token số nguyên: $$\text{active character token} = \text{curcs} + 2^{29} - 1$$

**Ví dụ**

* Các bộ máy 8-bit: phép tính token cho ký tự hoạt động `~` (mã ký tự 126) cho kết quả là $$\text{curcs} = 126 + 1 = 127$$, cho ra giá trị token là $$4095 + 127 = 4222$$.
* LuaTeX: phép tính token cho ký tự hoạt động `~` cho kết quả là $$\text{curcs}=3186$$ cho ra giá trị token là $$3186 + 2^{29} - 1 = 536874097$$. Token của LuaTeX dùng các giá trị số nguyên lớn hơn nhiều!

### Hiểu về token lệnh

Ngoài việc xử lý *từng* ký tự, các bộ máy TeX dĩ nhiên có thể xử lý *chuỗi* ký tự được gọi là *lệnh* (hoặc, chính xác hơn, *chuỗi điều khiển*). Theo truyền thống, `\` ký tự được dùng để báo hiệu bắt đầu một lệnh nhưng đó chỉ là quy ước — thực tế, bất kỳ ký tự nào có mã loại 0 (ký tự thoát) cũng có thể được dùng thay thế.

Các bộ máy TeX nhận ra hai kiểu lệnh được gọi là *từ điều khiển* và *ký hiệu điều khiển*:

* **từ điều khiển**: các lệnh được tạo từ một hoặc nhiều ký tự có mã loại 11;
* **ký hiệu điều khiển**: các lệnh một ký tự, trong đó mã loại của ký tự đó *không* 11: chẳng hạn như `\$`, `\#` hoặc `\\`.

**Lưu ý**: Các primitive TeX `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` và `\toksdef` cũng được dùng để định nghĩa các chuỗi điều khiển nhưng, khác với các định nghĩa macro thông thường, các chuỗi điều khiển thu được (từ điều khiển hoặc ký hiệu điều khiển) *không thể mở rộng*— chúng ta sẽ khám phá kỹ hơn chúng ở bên dưới.

#### Token lệnh được tính như thế nào?

Cũng như ký tự hoạt động, các bộ máy TeX dùng kiểu giá trị token số nguyên thứ hai để biểu diễn lệnh: *token lệnh*— nhớ rằng ký tự hoạt động cũng tạo ra token lệnh vì chúng hoạt động như các mini-macro.

Các phép tính mà các bộ máy 8-bit dùng để tạo số nguyên token lệnh có thể được tìm thấy trong bài [bài viết trên Overleaf](/latex/vi/bai-viet-chuyen-sau/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md#how-tex-calculates-token-values). Ở đây, chúng tôi sẽ tóm tắt các bước chính trong phép tính token lệnh cho LuaTeX — vốn hơi khác vì LuaTeX phải xử lý các giá trị mã ký tự Unicode có thể lớn hơn đáng kể so với giá trị 8-bit; tuy nhiên, các phép tính của LuaTeX tuân theo cùng các nguyên tắc chung mà các bộ máy 8-bit cũ dùng.

Sau khi phát hiện một lệnh đi vào, các bộ máy TeX, bao gồm LuaTeX, bỏ qua ký tự `\` ở đầu: nó không được dùng trong phép tính giá trị token lệnh mà chỉ đóng vai trò như một “công tắc” báo cho bộ máy TeX biết rằng nó cần xử lý một lệnh. Giá trị token lệnh được tính bằng chuỗi gồm (1 hoặc nhiều) ký tự có trong tên của lệnh — LuaTeX tính token lệnh cho ký hiệu điều khiển và từ điều khiển bằng cùng một thuật toán:

1. tính giá trị số nguyên trung gian $$\text{curcs}$$ bằng cách áp dụng một cái gọi là [hàm băm](https://en.wikipedia.org/wiki/Hash_function) lên chuỗi ký tự Unicode UTF-8 chứa trong tên lệnh: $$\text{curcs}=\texttt{hashfunction}\text{(Unicode UTF-8 string of characters in command name)}$$3. tính giá trị token lệnh trong đó $$\text{command token} = \text{curcs} + 2^{29} - 1$$

**Ví dụ**

* cho `\\` lệnh (một ký hiệu điều khiển), LuaTeX tính $$\text{curcs}=94$$, cho ra giá trị token cho `\\` của $$94 + 2^{29} - 1 = 536871005$$.
* cho `\vskip` lệnh nguyên thủy (một từ điều khiển) LuaTeX tính $$\text{curcs}=3560$$, cho ra giá trị token cho `\vskip` của $$3560 + 2^{29} -1 = 536874471$$.
* cho macro do người dùng định nghĩa `\mynewmacro` (một từ điều khiển) LuaTeX tính $$\text{curcs} = 2971$$, cho ra giá trị token cho `\mynewmacro` của $$2971 + 2^{29} -1 = 536873882$$.

Sau khi được tạo, token có thể được lưu để dùng sau thông qua cái gọi là *danh sách token* hoặc chúng có thể được chuyển ngay để xử lý tiếp bên trong bộ máy TeX. Dùng giá trị số nguyên để biểu diễn token không chỉ hoạt động trên mọi loại nền tảng hệ điều hành/máy tính mà còn là một cách rất hiệu quả để TeX lưu trữ/xử lý dữ liệu.

### Cách một bộ máy TeX nhận diện loại token (lệnh hay ký tự)

Với một giá trị token số nguyên cụ thể, $$T$$, một bộ máy TeX có thể dễ dàng xác định liệu $$T$$ đại diện cho một lệnh hay một ký tự bằng cách kiểm tra xem $$T$$ có vượt quá một $$\text{threshold value}$$— điều đó $$\text{threshold value}$$ phụ thuộc vào bộ máy TeX. Nếu $$T \geq \text{threshold value}$$ thì $$T$$ là một token lệnh thì ngược lại $$T$$ là một token ký tự.  $$\text{threshold value}$$ là $$4095$$ ngưỡng $$2^{29}-1$$ (536.870.911) đối với LuaTeX. Knuth đã thiết kế các phương pháp dùng trong công thức tính token sao cho bộ máy TeX của ông, và mọi bộ máy sau này dựa trên mã/kiến trúc của ông, có thể kiểm tra giá trị token một cách nhanh chóng và dễ dàng.

## Token có thể được tách ra (và chuyển ngược lại thành văn bản)

Token (số nguyên) là cơ chế mà qua đó bộ máy TeX “bao hàm” mọi thứ nó cần ghi lại về một mục đầu vào (ký tự hoặc lệnh). Tuy nhiên, có những lúc bộ máy TeX cần đảo ngược quá trình token hóa — để tìm ra đầu vào ban đầu nào đã được đọc vào để tạo ra giá trị token đó — một ký tự riêng lẻ hoặc một chuỗi gồm một hay nhiều ký tự tạo thành tên của một lệnh:

* **đối với token ký tự**: Bất kỳ token ký tự nào cũng có thể được tách thành hai phần cấu thành: mã ký tự và mã loại tương ứng được gán cho ký tự đó *tại thời điểm nó ban đầu được đọc vào*. Giống như mọi bộ máy TeX, LuaTeX sẽ không thay đổi việc gán mã loại ban đầu đó mà sẽ sử dụng nó trong các hoạt động xử lý nội bộ tiếp theo.
* **đối với token lệnh:** Các bước này chi tiết hơn một chút nhưng nếu bạn xem phép tính token lệnh của LuaTeX, bao gồm token cho ký tự hoạt động, bạn sẽ thấy chúng theo một mẫu: $$\text{command token} = \text{curcs} + 2^{29} -1$$

trong đó $$\text{curcs}$$ được tính theo loại token lệnh đang được tạo: ký tự hoạt động, ký hiệu điều khiển hoặc từ điều khiển.  $$\text{curcs}$$ biến là một *cực kỳ* thành phần quan trọng trong các hoạt động nội bộ của một bộ máy TeX: với bất kỳ giá trị token lệnh (số nguyên) nào, LuaTeX có thể rất dễ dàng trích xuất giá trị của $$\text{curcs}$$ từ token lệnh đó bằng cách dùng $$\text{curcs} = \text{token value} - (2^{29} -1)$$.

### Tại sao $$\text{curcs}$$ lại quan trọng đến vậy?

Biến nội bộ của TeX $$\text{curcs}$$ (**cur**rent **c**ontrol **s**quence) là một thành phần cực kỳ quan trọng trong các hoạt động “bên trong máy” của bộ máy TeX. Mặc dù bạn sẽ không, và cũng không thể, dùng hay truy cập trực tiếp nó trong mã của mình, $$\text{curcs}$$ đóng vai trò then chốt vì các bộ máy TeX dùng giá trị hiện tại của $$\text{curcs}$$ như một chỉ mục trong các bảng nội bộ lưu dữ liệu về mọi lệnh hiện được bộ máy biết đến. Những bảng đó lưu thông tin về ý nghĩa hiện tại của một lệnh: nó làm gì, hoặc đại diện cho điều gì, và ngoài ra chúng còn ghi lại chuỗi ký tự ban đầu được dùng để tính giá trị $$\text{curcs}$$ đó. Bằng cách trích xuất giá trị của $$\text{curcs}$$ từ token lệnh, bộ máy TeX có thể xác định tên, tức văn bản dễ đọc bởi con người, tương ứng với bất kỳ token (lệnh) nào, cho phép nó thực hiện các chuyển đổi từ token sang văn bản vốn là một khía cạnh then chốt của `\directlua`hoạt động của nó.

### Chuyển token số nguyên trở lại thành ký tự hoặc chuỗi ký tự (tên lệnh)

Chúng ta đã thấy rằng các bộ máy TeX chuyển các ký tự đầu vào, hoặc chuỗi ký tự, thành các giá trị token số nguyên, nhưng có những lúc bộ máy TeX cần *đảo ngược* quá trình đó — để xuất ra văn bản dễ đọc bởi con người vốn ban đầu được dùng để tạo ra các giá trị token số nguyên ấy; ví dụ:

* ghi thông báo lỗi hoặc cảnh báo ra màn hình hoặc `.log` tệp;
* xuất mã TeX/LaTeX ra một tệp văn bản thông qua `\write` lệnh;
* khi chuyển một chuỗi token thành văn bản bên trong `\directlua` (như chúng ta sẽ sớm thấy!)

#### Chuyển token ký tự thành văn bản

Như đã lưu ý, token cho các ký tự không hoạt động được tính bằng mã loại và mã ký tự (giá trị Unicode) của ký tự đầu vào. LuaTeX dùng công thức:

$$\text{character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

Đây là một nhiệm vụ lập trình khá đơn giản để tách giá trị số nguyên $$\text{character token}$$ để thu được mã ký tự thành phần của nó ($$\text{Unicode value}$$) và $$\text{category code}$$.

#### Chuyển token lệnh thành văn bản

Tất cả các bộ máy TeX đều lưu tên (chuỗi ký tự) của mọi lệnh mà chúng “biết”: dù lệnh đó là macro do người dùng định nghĩa hay primitive tích hợp — việc lưu tên primitive diễn ra khi bộ máy TeX khởi động, rất lâu trước khi nó bắt đầu xử lý mã của bạn. Với các lệnh do người dùng định nghĩa (macro), tên của macro đó (bỏ đi dấu `\`) được lưu lại như một phần của quá trình định nghĩa macro bên trong các bộ máy TeX.

Khi một bộ máy TeX cần truy cập hoặc xuất văn bản dễ đọc bởi con người từ đó một token lệnh số nguyên ban đầu được tính ra, nó sẽ trước tiên xác định $$\text{curcs}$$ giá trị của token đó; trong LuaTeX, $$\text{curcs} = \text{token} - (2^{29} -1$$). Bằng cách dùng giá trị của $$\text{curcs}$$ được trích xuất từ token lệnh, một bộ máy TeX có thể truy cập một cấu trúc dữ liệu nội bộ gọi là *vùng chuỗi* để xác định chuỗi các ký tự dễ đọc bởi con người ban đầu được dùng để tính giá trị cụ thể đó cho $$\text{curcs}$$ và, do đó, token lệnh tương ứng.

Như chúng ta sẽ thấy, các hoạt động xử lý token này — chuyển chuỗi ký tự thành giá trị token số nguyên và chuyển giá trị token số nguyên trở lại thành chuỗi ký tự (“giải token hóa”) — là *những cơ chế nền tảng* được dùng bên trong `\directlua`.

## Danh sách token

Khi một bộ máy TeX đang đọc đầu vào, tạo token ký tự và token lệnh (và xử lý chúng), nó có thể gặp một số lệnh yêu cầu bộ máy (tạm thời) ngừng chuyển token tiếp đi để xử lý thêm mà thay vào đó lưu chúng lại để dùng sau. Ví dụ phổ biến nhất là định nghĩa một macro bằng một trong các lệnh định nghĩa macro `\def`, `\edef`, `\gdef` hoặc `\xdef`— các lệnh LaTeX như `\newcommand` là các macro cung cấp thêm chức năng, được xây dựng dựa trên các primitive cấp thấp, vốn cuối cùng thực hiện quá trình định nghĩa macro thực sự. Một macro có thể được xem là tên đặt cho một danh sách token được lưu cụ thể: một danh sách token.

Các bộ máy TeX sử dụng *rộng rãi* danh sách token, đặc biệt là [các danh sách tạm thời chỉ dùng nội bộ](/latex/vi/bai-viet-chuyen-sau/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) dùng cho mục đích xử lý nội bộ. Mọi bộ máy TeX cũng cung cấp các lệnh cấp người dùng để tạo danh sách token được lưu lại cho khi người dùng, hoặc chính bộ máy TeX, cần đến. Số lượng lệnh tạo danh sách token (các primitive tích hợp) khác nhau tùy theo bộ máy TeX nhưng tất cả đều chia sẻ một tập tối thiểu cốt lõi được mọi bộ máy hỗ trợ, chẳng hạn như `\toks` primitive.

Trên thực tế, một danh sách token chỉ là một chuỗi giá trị số nguyên được lưu trữ:

* đầu vào được đọc để tạo (tính) các token riêng lẻ, đại diện cho một ký tự hoặc lệnh;
* mỗi token sau đó được lưu lại, bảo toàn thứ tự mà các token được sinh ra từ đầu vào.

Các bộ máy TeX lưu danh sách token bằng một cấu trúc dữ liệu gọi là [danh sách liên kết](https://en.wikipedia.org/wiki/Linked_list) (loại liên kết đơn). Những độc giả muốn biết thêm về danh sách token được mời đọc bài viết trên Overleaf [Danh sách token TeX là gì?](/latex/vi/bai-viet-chuyen-sau/54-what-is-a-tex-token-list.md) bài viết này dùng một phép tương tự để xây dựng các khái niệm/ý tưởng phía sau một danh sách token. Một nghiên cứu sâu về danh sách token của TeX, và cách chúng được dùng trong xử lý macro, có thể tìm thấy trong loạt bài Overleaf [Macro TeX thực sự hoạt động như thế nào?](/latex/vi/chu-de-khac/01-a-six-part-series-how-do-tex-macros-actually-work.md)

#### Danh sách token dưới dạng đồ họa

Hình minh họa sau cho thấy một danh sách token do LuaTeX tạo ra với các giá trị token tương ứng được sinh từ đầu vào sau

`Hi, \TeX! \hskip 5bp`

Ví dụ, nếu chúng ta định nghĩa `\mymacro` là `\def\mymacro{Hi, \TeX! \hskip 5bp}` định nghĩa của `\mymacro` sẽ được lưu trong bộ nhớ bằng một danh sách token như thế này:

![](/files/4881e29290fd9c60711a1ecf65ee4518f8e550ec)

Danh sách token là một chuỗi các mục liên kết gọi là *nút*, tên gọi của một gói nhỏ trong bộ nhớ LuaTeX được cấp để chứa từng mục trong danh sách (giống như từng mắt xích trong một chuỗi). Mỗi nút chứa một giá trị token số nguyên và địa chỉ bộ nhớ của *nút* nút tiếp theo trong chuỗi, tạo thành một cấu trúc dữ liệu gọi là một [danh sách liên kết](https://en.wikipedia.org/wiki/Linked_list). Nút cuối cùng cho biết kết thúc danh sách bằng một “giá trị rỗng” đặc biệt cho nút tiếp theo — vì không có nút nào nữa.

**Ghi chú:**

* Để thuận tiện, chúng tôi đã đưa địa chỉ của từng nút riêng lẻ vào nhưng trên thực tế, dữ liệu đó không được lưu bên trong các nút của danh sách token; chỉ có địa chỉ của *nút tiếp theo* là cần thiết để xây dựng danh sách token của bộ máy TeX.
* Cột thứ hai trong hình minh họa có tiêu đề “Mỗi token có nghĩa gì” hiển thị một loạt khung xám chứa thông tin về token có trong từng nút: những thông tin này chỉ nhằm cung cấp và *không* không phải là một phần của dữ liệu thực sự được lưu trong danh sách token.

Dưới đây là bảng các giá trị token chứa trong danh sách token được minh họa ở trên:

|                 |                  |                                                                        |                   |
| --------------- | ---------------- | ---------------------------------------------------------------------- | ----------------- |
| **Mục đầu vào** | **Loại đầu vào** | <p><strong>Mã loại</strong><br><br><strong>(nếu là ký tự)</strong></p> | **Giá trị token** |
| H               | ký tự            | 11                                                                     | 23068744          |
| i               | ký tự            | 11                                                                     | 23068777          |
| ,               | ký tự            | 12                                                                     | 25165868          |
|                 | ký tự            | 10                                                                     | 20971552          |
| \TeX            | lệnh (macro)     |                                                                        | 536871539         |
| !               | ký tự            | 12                                                                     | 25165857          |
|                 | ký tự            | 10                                                                     | 20971552          |
| \hskip          | lệnh (primitive) |                                                                        | 536874247         |
| 5               | ký tự            | 12                                                                     | 25165877          |
| b               | ký tự            | 11                                                                     | 23068770          |
| p               | ký tự            | 11                                                                     | 23068784          |

**Lưu ý:** Văn bản đầu vào ban đầu của chúng ta có một dấu a sau `\hskip` lệnh nhưng không có token nào đại diện cho ký tự đó trong danh sách token. Ký tự đó đã bị quá trình quét đầu vào (đọc) của LuaTeX hấp thụ vì nó được dùng để kết thúc việc LuaTeX tìm kiếm các ký tự cấu thành `\hskip` lệnh này.

## Cách \directlua thực sự hoạt động

Bây giờ khi chúng ta đã tìm hiểu về token, danh sách token và việc chuyển token thành văn bản, thách thức tiếp theo là hiểu khái niệm token của bộ máy TeX *mở rộng*.

Như đã lưu ý, `\directlua{⟨code⟩}` có thể được yêu cầu xử lý `⟨code⟩` chứa cả mã Lua và TeX/LaTeX nhưng trình thông dịch ngôn ngữ Lua tích hợp sẵn của LuaTeX không hiểu TeX hay LaTeX: vậy điều này có thể hoạt động như thế nào? Làm sao có thể để `⟨code⟩` có thể chứa các chỉ lệnh TeX/LaTeX mà không làm trình thông dịch Lua hoàn toàn bối rối bởi các lệnh nó không hiểu? Ví dụ, lệnh sau đây `\directlua` chỉ dùng các macro TeX, nhưng nó hoạt động:

```
\def\aa{tex}
\def\bb{.}
\def\cc{print}
\def\dd{("Hello")}
\directlua{
   \aa\bb\cc\dd
}
```

Tệp này `\directlua` lệnh tạo ra việc dàn trang `Xin chào` nhưng tại sao và bằng cách nào điều này lại hoạt động khi ngôn ngữ Lua không hiểu các macro TeX?

Câu trả lời nằm trong phần mô tả trước đó mà chúng ta đã mượn từ [Sổ tay tham khảo LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) nhưng chúng ta có thể xem rằng `\directlua{⟨code⟩}` hoạt động bằng cách LuaTeX ban đầu “tiền xử lý” `⟨code⟩` trước khi bất cứ thứ gì được chuyển cho trình thông dịch Lua. Bản chất của việc “tiền xử lý” này — tức là nó thực sự có nghĩa gì và hệ quả của nó đối với `⟨code⟩`—là chủ đề tiếp theo chúng ta sẽ đề cập để giúp độc giả quan tâm tận dụng sức mạnh và tính linh hoạt của `\directlua`.

### LuaTeX xử lý \directlua như thế nào: Cái nhìn đầu tiên

Để mở rộng hiểu biết của chúng ta về `\directlua`các hoạt động “tiền xử lý” của nó, chúng ta có thể bắt đầu với sơ đồ đơn giản sau đây, sơ đồ này cung cấp tổng quan về những gì xảy ra.  `⟨code⟩` được cung cấp cho `\directlua{⟨code⟩}` được trước tiên chuyển thành token bằng các quy trình và phép tính đã thảo luận ở trên; dãy token đó được lưu trong một danh sách token. Khi danh sách token đó đã được tạo, mỗi token trong danh sách được chuyển trở lại dạng biểu diễn văn bản của nó: văn bản do từng token tạo ra — token ký tự hay token lệnh — được ghép lại (nối chuỗi) để tạo thành một chuỗi mã duy nhất được chuyển cho trình thông dịch Lua để thực thi.

![](/files/95da5f255e7a9812b2ef2fe80607931c79345870)

Nhưng khoan, mục đích gì khi đi từ văn bản sang token rồi chuyển các token đó thẳng trở lại thành văn bản? Có lẽ bạn sẽ không ngạc nhiên khi biết rằng, đúng vậy, còn có một quy trình bổ sung và cực kỳ quan trọng mà chúng ta chưa đưa vào hình minh họa này: *mở rộng token*. Mỗi token được tạo ra từ văn bản trong `⟨code⟩` đều chịu một kiểu “kiểm tra” trong đó LuaTeX áp dụng một phép thử để xem token đó có biểu thị một lệnh thuộc tập con được gọi là *các lệnh có thể mở rộng*. Nếu đúng như vậy, LuaTeX lọc bỏ lệnh đó bằng cách *loại bỏ* nó khỏi `⟨code⟩` và *thay thế nó* bằng kết quả của một quá trình mà các bộ máy TeX gọi là *mở rộng token*.

### LuaTeX xử lý \directlua như thế nào: Cái nhìn thứ hai (về mở rộng)

Cơ chế mở rộng của TeX là một thành phần cốt lõi của tất cả các bộ máy dàn trang dựa trên TeX vì, xét cho cùng, mỗi bộ đều bắt nguồn từ (hoặc dựa trên) mã nguồn và thiết kế TeX nguyên bản của Knuth. Tuy nhiên, khái niệm mở rộng rất khó giải thích bằng ngôn ngữ ngắn gọn mà vẫn dễ tiếp cận, vì trong thực tế, mở rộng là một thuật ngữ “bao trùm” dùng để mô tả một quá trình duy nhất — nhưng là quá trình tạo ra nhiều kết quả khác nhau. Những kết quả đa dạng đó là hệ quả của tập hợp lệnh khá pha tạp mà mở rộng có thể áp dụng lên, vì vậy bạn có thể xem rằng mỗi lệnh có thể mở rộng đều có một “hành vi mở rộng” nhất định.

Như một *xấp xỉ ban đầu* để hiểu mở rộng, chúng ta có thể nói rằng việc mở rộng một token (lệnh) có nghĩa là *loại bỏ* lấy token (lệnh) đó khỏi đầu vào hiện tại của TeX và *thay thế* thay nó bằng một dãy token thu được từ việc thực thi chính lệnh có thể mở rộng đó — thay thế token gốc bằng các kết quả/hệ quả của quá trình mở rộng của nó *hành vi*. Tuy nhiên, “định nghĩa” ban đầu này về mở rộng — theo nghĩa tạo ra các token mới để TeX đọc — không hoàn toàn chính xác đối với mọi lệnh có thể mở rộng, nhưng nó đủ tốt để làm điểm khởi đầu.

Để đưa ra một ví dụ đơn giản: primitive TeX `\jobname` là một lệnh có thể mở rộng và *mở rộng* là một dãy token ký tự biểu thị tên của tệp đầu vào TeX chính. Nếu TeX quyết định mở rộng một `\jobname` lệnh (token) thì nó sẽ được *loại bỏ* khỏi nguồn đầu vào hiện tại của TeX và *thay thế* bằng dãy token ký tự mà nó tạo ra — sau đó TeX tiếp tục đọc/xử lý.

Trong `\directlua`..., sau khi một token có thể mở rộng được xử lý (loại bỏ) và thay thế bằng các token mới, LuaTeX sẽ tiếp tục đọc các token mới mà nó vừa đặt vào — nhưng một số token mới đó cũng có thể là có thể mở rộng. Bởi vì `\directlua` thực hiện cái gọi là *mở rộng đầy đủ*, LuaTeX sẽ đọc các token mới đó và một lần nữa đi qua quá trình mở rộng để mở rộng (loại bỏ) mọi token mới (có thể mở rộng) — quá trình mở rộng này tiếp diễn cho đến khi không còn token nào có thể mở rộng. Tuy nhiên, có hai ngoại lệ quan trọng đối với quy tắc “tiếp tục mở rộng” này, cả hai sẽ được thảo luận bên dưới:

* sử dụng cấu trúc `\the\toks`;
* việc cố ý ngăn chặn (ức chế) mở rộng đối với một hay nhiều token được chọn.

Như đã lưu ý, định nghĩa làm việc của chúng ta (xấp xỉ ban đầu) để hiểu mở rộng không bao quát toàn bộ các hành vi mở rộng do tập con các lệnh có thể mở rộng thể hiện. Ví dụ, một số lệnh có thể mở rộng không tạo ra token theo cách mà `\jobname` nó làm, nhưng chúng có thể:

* “lọc” token khỏi đầu vào: các lệnh điều kiện của bộ máy TeX (`\if`, `\ifcat`, `\ifnum`, `\ifdim`, `\ifodd`, `\ifvmode`, …) đều có thể mở rộng. Hành vi mở rộng của chúng là một kiểu “lọc token” — các lệnh điều kiện có thể được dùng trong `\directlua`.
* “đảo” token trong đầu vào: [`\expandafter` lệnh](/latex/vi/bai-viet-chuyen-sau/03-a-six-part-article-series-on-expandafter-tex-tokens-and-expansion.md) là có thể mở rộng và thay đổi thứ tự mà hai token được mở rộng.
* ngăn chặn mở rộng: các lệnh có thể mở rộng `\noexpand` và `\unexpanded` ức chế việc mở rộng các token lệnh trong đầu vào.
* chuyển các chuỗi ký tự trong đầu vào thành token lệnh: `\csname … \endcsname.`
* chuyển các đại lượng nội bộ thành một dãy token ký tự: `\number` và `\the` là các lệnh có thể mở rộng tạo ra một dãy token ký tự biểu thị giá trị của một đại lượng nội bộ.
* chuyển token lệnh thành token ký tự: `\string` và `\detokenize` là các lệnh có thể mở rộng chuyển đối số của chúng thành một dãy token ký tự với mã danh mục 12. Lưu ý rằng `\detokenize` khác với `\string`: `\detokenize` có thể xử lý nhiều token và nó chèn một ký tự khoảng trắng, với mã danh mục 10, sau khi xử lý các token lệnh được tạo từ *từ điều khiển*. Về thực chất, `\detokenize` thêm một ký tự khoảng trắng ở cuối sau tên lệnh — chúng ta sẽ xem một vài ví dụ sau trong bài viết.

#### Tinh chỉnh “định nghĩa” của chúng ta về mở rộng

Bây giờ chúng ta có thể khái quát hóa định nghĩa của mình rằng việc mở rộng một lệnh (token) bao gồm *việc loại bỏ* lệnh (token) đó khỏi nguồn đầu vào hiện tại của TeX và *thay thế nó* bằng kết quả của *các thao tác token* do lệnh đó thực hiện. Về bản chất, quá trình mở rộng khiến một lệnh có thể mở rộng thực hiện một loại “thao tác” nào đó trên các token trong đầu vào hiện tại của TeX, thao tác này ảnh hưởng đến số lượng hoặc hành vi của các token mà TeX sẽ đọc tiếp theo — bản chất chính xác của “thao tác” đó phụ thuộc vào lệnh nào đang được mở rộng. Tất cả macro, và các ký tự hoạt động, đều có thể mở rộng, nhưng chỉ một số ít lệnh tích hợp sẵn của bộ máy TeX (các primitive) được phân loại là có thể mở rộng — danh sách các lệnh có thể mở rộng phụ thuộc vào bộ máy TeX bạn đang dùng.

Mỗi bộ máy TeX mới đều kế thừa các lệnh primitive được tích hợp trong (các) bộ máy tiền nhiệm của nó — (các) bộ máy TeX thế hệ trước mà nó được phát triển từ đó — và một số primitive được kế thừa ấy sẽ có thể mở rộng. Tất nhiên, một bộ máy TeX mới có thể chọn không triển khai một số lệnh primitive có trong các bộ máy trước đó hoặc sửa đổi hành vi của chúng cho phù hợp với nhu cầu của bộ máy mới. Ngoài ra, các bộ máy TeX mới thường triển khai thêm các primitive bổ sung để hỗ trợ các chức năng nâng cao của riêng chúng — một số trong đó cũng có thể mở rộng. Do đó, số lượng lệnh có thể mở rộng mà bạn có sẵn sẽ thay đổi tùy theo bộ máy TeX bạn đang dùng — LuaTeX có một bộ sưu tập khá lớn.

Một khó khăn khác khi giải thích/hiểu về mở rộng, và có lẽ là thách thức thực sự, là biết chính xác *khi nào* một bộ máy TeX thực sự sẽ, hoặc sẽ không, thực hiện quá trình mở rộng. Đó là một chủ đề rộng lớn và phức tạp vì mở rộng được nhúng sâu trong toàn bộ cơ chế bên trong của các bộ máy TeX: chúng ta không có đủ chỗ để bàn chi tiết về điều này ngoài việc sử dụng mở rộng trong `\directlua`.

### LuaTeX xử lý \directlua như thế nào: Cái nhìn cuối cùng

Sơ đồ sau tóm tắt các `\directlua` hoạt động tiền xử lý diễn ra ngay bên trong bộ máy LuaTeX. Trong sơ đồ này, chúng tôi cũng trình bày hai hàm LuaTeX cấp thấp (nội bộ) thực sự đảm nhiệm công việc: `scan_toks()` và `tokenlist_to_cstring()`. Những hàm này được viết bằng ngôn ngữ C và nằm sâu bên trong phần mềm thực thi LuaTeX: chúng là một phần của cơ chế bên trong LuaTeX và không *trực tiếp* có thể truy cập từ mã TeX/LaTeX của bạn.

![](/files/41d3948bd8d9f6ff8e590101ae0224883941736f)

Mô tả sau đây về `\directlua ⟨code⟩`về hoạt động tiền xử lý của nó tóm tắt sơ đồ ở trên.

1. Chuỗi ký tự trong ⟨code⟩ của bạn được xử lý bởi `scan_toks()`. Mục đích của nó là đọc ⟨code⟩ của bạn từng ký tự một để tạo ra các token ký tự và token lệnh. Vì nó đang tạo token, mã danh mục được gán cho mỗi ký tự trong ⟨code⟩, tại thời điểm được đọc vào, là cực kỳ quan trọng.
2. Trong quá trình `scan_toks()`xử lý token (tạo token) của nó, mọi lệnh (token) có thể mở rộng đều được mở rộng *trừ khi* bị ngăn chặn thông qua các lệnh như `\protected` (định nghĩa macro), `\noexpand`, `\unexpanded` v.v. Các ký tự hoạt động (mã danh mục 13) cũng được mở rộng (trừ khi bị ngăn chặn).
3. Luồng token được tạo bởi `scan_toks()` được ghép thành một danh sách token dài — các token có trong danh sách đó bao gồm những token được tạo ra từ việc áp dụng mở rộng cho các lệnh có thể mở rộng (như macro) có trong `⟨code⟩`. Cũng lưu ý rằng `scan_toks()` *không* gây ra hoặc kích hoạt việc thực thi bất kỳ token nào biểu thị một lệnh không thể mở rộng: các token không thể mở rộng như vậy просто được chuyển qua để được đưa vào danh sách token đang được xây dựng.
4. Khi danh sách token đã hoàn tất và mọi hoạt động mở rộng đã kết thúc, danh sách token đó được xử lý bởi một hàm khác có tên là `tokenlist_to_cstring()` hàm này chuyển mỗi token trong danh sách token cuối cùng trở lại biểu diễn văn bản của nó. Điều đó tạo ra một chuỗi văn bản chính là mã Lua sẽ được chuyển cho trình thông dịch Lua. Để thực thi thành công, chuỗi đó cần chứa mã Lua đúng cú pháp.
5. Việc Lua xử lý mã đó diễn ra trong hai bước:
6. Trình thông dịch Lua tích hợp sẵn của LuaTeX phân tích cú pháp và “biên dịch” mã Lua được tạo ra ở các bước trước. Nếu việc phân tích cú pháp/biên dịch đó thất bại, trình thông dịch Lua sẽ tạo ra lỗi (như lỗi cú pháp) — những lỗi đó có thể khiến lần chạy LuaTeX thất bại trừ khi bạn chọn dùng `--interaction=nonstopmode` trên dòng lệnh.
7. Nếu việc phân tích cú pháp/biên dịch thành công, trình thông dịch Lua sẽ thực thi mã đã biên dịch ở bước (5a).

Về cơ bản, `scan_toks()` hàm này là cốt lõi của các hoạt động tiền xử lý của LuaTeX: nhiệm vụ chính của nó là mở rộng tất cả các lệnh TeX/LaTeX có thể mở rộng chứa trong văn bản của `⟨code⟩` và xây dựng một danh sách token từ mọi thứ nó đã xử lý. Một lần nữa, chúng tôi nhấn mạnh rằng `scan_toks()` *không thực thi các lệnh không thể mở rộng* (token): nó chỉ đơn giản là *lưu* những token đó trong danh sách token mà nó đang xây dựng. Khi hoàn tất, danh sách token đó sau đó được chuyển đổi *trở lại thành biểu diễn văn bản* bởi `tokenlist_to_cstring()`—một danh sách token là một khái niệm chỉ có trong TeX, hoàn toàn xa lạ với trình thông dịch Lua, vì vậy cần chuyển nó thành văn bản, trở thành mã Lua để chuyển cho trình thông dịch Lua.

## Mở rộng như một “giao diện” của ngôn ngữ lập trình

Bạn có thể xem `\directlua`quá trình mở rộng của nó như một cơ chế, hay giao diện, để truyền dữ liệu/thông tin từ “Thế giới TeX” sang “Thế giới Lua”: cung cấp một phương thức để ngôn ngữ TeX giao tiếp dữ liệu với ngôn ngữ Lua. Ví dụ, mã TeX như `\number\count75` có thể được dùng để chuyển một giá trị trong “Thế giới TeX” được lưu ở thanh ghi đếm 75 sang biến số nguyên x trong “Thế giới Lua”:

```
\count75=1564 % Data existing in the "TeX World"
\directlua{
   local x=\number\count75 \space % Chuyển dữ liệu TeX sang "Thế giới Lua"
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)
}
```

Điều này tạo ra mã Lua

```
 local x=1564 tex.print("x= "..x) local y = (2*x-65)/5 tex.print(" and y = "..y)
```

**Lưu ý**: Chúng ta đã thêm `<space>\space` sau `\number\count75` để đảm bảo một ký tự khoảng trắng được giữ lại giữa `1564` và `tex.print`—điều đó không thực sự cần thiết ở đây vì Lua vẫn sẽ phân tích cú pháp mã đúng nếu chúng ta bỏ nó đi. Ký tự khoảng trắng ngay sau `\count75` được hấp thụ trong quá trình các bộ máy TeX dùng để tìm kiếm giá trị số — ở đây, giá trị đang được cung cấp cho `\count`. Ký tự khoảng trắng sau `75` được dùng để kết thúc việc LuaTeX tìm kiếm dãy chữ số `75` và được hấp thụ từ đầu vào.  `\space` macro mở rộng để cung cấp ký tự khoảng trắng cần thiết để tách văn bản `1564` và `tex.print`.

Sử dụng mã ở trên, LuaTeX sẽ dàn trang

`x= 1564 and y = 612.6`

Ở đây, cơ chế “truyền dữ liệu” được thực hiện bằng `\number`: một lệnh có thể mở rộng mà trong trường hợp này yêu cầu TeX lấy giá trị lưu trong `\count` thanh ghi `75` và từ giá trị đó (`1546`) tạo ra một chuỗi token ký tự, một token ký tự cho mỗi chữ số, dẫn đến một dãy token cho các chữ số `1`, `5`, `6` và `4`. Bốn token ký tự đó được đưa vào danh sách token chính đang được xây dựng bởi `\directlua` và sau đó được chuyển trở lại biểu diễn văn bản của chúng khi danh sách token được chuyển thành văn bản. Rõ ràng đây là một con đường rất vòng vèo đi từ `\count75` giá trị thanh ghi được lưu bên trong LuaTeX, đến các chữ số dành cho mã Lua, nhưng rốt cuộc thì nó hoạt động.

**MẸO:** Nếu bạn muốn xem xét kết quả của các hoạt động mở rộng trong LuaTeX, bạn có thể viết mã như sau:

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)]]
   print(foo)
}
```

Trong ví dụ này, chúng ta dùng phương pháp dấu ngoặc vuông kép dài để tạo một biến chuỗi `foo` mà mục đích là chứa chuỗi mã Lua được tạo ra từ việc mở rộng mọi thứ nằm giữa `[[` và `]]`. Chuỗi đó được in ra console thông qua lời gọi hàm Lua `print(foo)`.

Trên Overleaf, bạn có thể xem kết quả tương tự bằng cách ghi nội dung của `foo` vào `.log` tệp bằng hàm Lua của LuaTeX `texio.write()`:

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)]]
   texio.write(foo)
}
```

## Các token trong danh sách token \directlua: token không thể mở rộng và token chưa mở rộng

Chúng ta đã lưu ý rằng `\directlua{⟨code⟩}` thực hiện *mở rộng đầy đủ* của `⟨code⟩`: nó loại bỏ và mở rộng tất cả các lệnh có thể mở rộng cho đến khi chỉ còn các token không thể mở rộng. Dãy token được tạo ra bởi `\directlua`quá trình xử lý của nó (trong `scan_toks()` hàm) được ghép lại để tạo thành một danh sách token mà từng token riêng lẻ sẽ được chuyển trở lại thành văn bản để chuyển tiếp cho Lua.

Tuy nhiên, chúng ta עדיין chưa đề cập đến phần cuối của câu chuyện này vì cần xem xét hai “lớp” token lệnh có thể đi lọt vào danh sách token đang được xây dựng bên trong `\directlua`: chúng ta sẽ gọi chúng là *token lệnh viết tắt* và *chưa mở rộng* token:

* **token lệnh viết tắt**: Loại token lệnh này phát sinh từ các chuỗi điều khiển được định nghĩa bằng một trong các primitive TeX `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` và `\toksdef`. Những lệnh primitive này được dùng để định nghĩa các chuỗi điều khiển biểu thị một giá trị số — các chuỗi điều khiển thu được là *không* có thể mở rộng.
* **token chưa mở rộng**: Loại token này phát sinh từ các lệnh thông thường sẽ được mở rộng nhưng `\directlua` đã hoặc là:
* được chỉ thị rõ ràng *không* mở rộng chúng; ví dụ, việc ngăn chặn mở rộng bởi các lệnh `\noexpand` hoặc `\unexpanded`—chúng tôi sẽ giải thích ngay sau đây cách thực hiện;
* đã chèn các token bằng cách xử lý chuỗi `\the\toks` (sẽ nói thêm ở dưới).

### Hai “nhóm” token trong danh sách token \directlua

Dựa trên phần thảo luận của chúng ta, có thể nói rằng các token chứa trong danh sách token đang được xây dựng trong giai đoạn đầu của `\directlua`quá trình tiền xử lý của nó (trong `scan_toks()` hàm) thuộc hai nhóm:

1. *vốn dĩ không thể mở rộng* token

* bất kỳ token nào biểu thị một *ký tự*;
* bất kỳ token nào biểu thị một *primitive* *lệnh*;
* bất kỳ token nào biểu thị một *lệnh viết tắt* (chúng không thể mở rộng, xem bên dưới).

3. *chưa mở rộng* token:

* bất kỳ token nào biểu thị một lệnh có thể mở rộng mà quá trình mở rộng của nó đã bị *ngăn chặn* (hoặc tránh) trong `\directlua`quá trình tiền xử lý của nó.

#### Token lệnh viết tắt: tạo các lệnh không thể mở rộng

Như đã lưu ý, các bộ máy TeX cung cấp một tập primitive (lệnh tích hợp sẵn) có thể dùng để tạo ra *không thể mở rộng* các chuỗi điều khiển (được ký hiệu ở đây bằng `⟨command⟩`). Những primitive này có dạng:

* `\chardef ⟨command⟩ = ⟨numeric value⟩`
* `\mathchardef ⟨command⟩ = ⟨numeric value⟩`
* `\countdef ⟨command⟩ = ⟨numeric value⟩`
* `\dimendef ⟨command⟩ = ⟨numeric value⟩`
* `\skipdef ⟨command⟩ = ⟨numeric value⟩`
* `\muskipdef ⟨command⟩ = ⟨numeric value⟩`
* `\toksdef ⟨command⟩ = ⟨numeric value⟩`

trong đó `⟨numeric value⟩` là một giá trị số nguyên phù hợp với từng lệnh.

Ở đây, chúng ta sẽ lược qua việc sử dụng `\chardef` để minh họa đặc điểm then chốt của các primitive này — tạo ra một `⟨command⟩` mà không thể mở rộng. Bạn có thể dùng ``\chardef\mydollar=`\$`` để tạo chuỗi điều khiển `\mydollar` và dùng nó để dàn trang một `$`:

`I paid \mydollar30.`

Điều này sẽ dàn trang `Tôi đã trả 30 đô la.` Chuỗi điều khiển `\mydollar` được tạo bởi `\chardef` không thể mở rộng, như ta có thể thấy từ ví dụ sau:

```
\chardef\mydollar=`\$
\directlua{
   local x =[[I paid \mydollar30.]]
   texio.write(x)
}
```

Kết quả tạo ra văn bản sau trong `.log` tệp có thể chỉnh sửa

`I paid \mydollar 30.`

Điều này cho thấy `\mydollar` đã *không* được mở rộng trong quá trình `\directlua`tiền xử lý của nó. Khoảng trắng xuất hiện sau `\mydollar` được thêm vào khi một token lệnh được chuyển thành biểu diễn văn bản của nó.

Khi bạn dùng `\chardef` để tạo một chuỗi điều khiển, cách TeX phân loại nội bộ chuỗi điều khiển đó (lệnh) khiến nó trở thành *không thể mở rộng* điều này có hành vi rất khác so với các chuỗi điều khiển được định nghĩa bởi một trong các lệnh định nghĩa macro: \def, \edef, \gdef hoặc \xdef. Như đã lưu ý ở trên, trong quá trình xây dựng danh sách token của nó `\directlua` LuaTeX xem xét từng token lệnh đi vào để kiểm tra khả năng mở rộng. Nếu một token lệnh không thể mở rộng, nó sẽ đi thẳng vào danh sách token và biểu diễn văn bản của nó sau này sẽ xuất hiện trở lại trong chuỗi mã Lua được tạo ra từ việc chuyển các token trong danh sách token trở lại dạng văn bản.

**Ghi chú ngắn về plain TeX so với LaTeX**

Về mặt lịch sử, plain TeX nguyên bản của Knuth đã định nghĩa các ký hiệu điều khiển thường dùng `\%`, `\&`, `\#` và `\$` bằng cách dùng `\chardef`—không dùng một trong các lệnh định nghĩa macro tiêu chuẩn `\def`, `\edef`, `\gdef` hoặc `\xdef`. Ví dụ:

```
   \chardef\#=`\#
   \chardef\$=`\$
   \chardef\%=`\%
   \chardef\&=`\&
```

Cú pháp `` `\ `` là một phương pháp TeX để lấy giá trị mã ký tự số. Trong chế độ plain TeX cũ, các ký hiệu điều khiển này không thể mở rộng (do `\chardef`) nhưng LaTeX (hoặc các gói) có thể định nghĩa lại chúng thành *macro* để cung cấp chức năng nâng cao — điều đó sẽ khiến chúng trở thành có thể mở rộng, vì vậy bạn có thể cần lưu ý điều này.

**Điều này ảnh hưởng đến \directlua như thế nào?**

Hãy so sánh kết quả của đoạn mã sau khi chạy dưới plain TeX và LaTeX. Để đơn giản, chúng ta sẽ ghi kết quả vào `.log` tệp bằng hàm API Lua của LuaTeX `texio.write()`.

```
\directlua{
   local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]]
   texio.write(x)
}
```

Chạy đoạn mã này bằng **plain TeX** sẽ tạo ra đầu ra sau trong `.log` tệp, cho thấy kết quả của mọi lần mở rộng:

```
\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

Rõ ràng, dưới plain TeX thì không có ký hiệu điều khiển nào`\$`, `\#`, `\%` hoặc `\&` được mở rộng — vì tất cả chúng đều được tạo bằng `\chardef`.

Chạy đoạn mã đó bằng **LaTeX** tài liệu:

```
\documentclass{article}
\begin{document}
   \directlua{local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]] texio.write(x)}
\end{document}
```

sẽ tạo ra đầu ra sau trong `.log` tệp có thể chỉnh sửa

```
\protect \TU\textdollar 150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

Rõ ràng, chạy LaTeX tạo ra kết quả khác với plain TeX vì trong LaTeX lệnh `\$` đã được mở rộng, cho thấy nó là một macro.

**Lưu ý:** Trong cả plain TeX và LaTeX `\directlua` đã không xử lý đầy đủ bất kỳ ký hiệu điều khiển nào `\%`, `\&`, `\#` và `\$` để tạo ra ký tự tương ứng. Trong quá trình mở rộng do `\directlua` thực hiện, các token biểu thị những ký hiệu điều khiển này — hoặc, đối với LaTeX, phần mở rộng của chúng — đi thẳng qua danh sách token chính đang được xây dựng.

**Lưu ý:** Ký hiệu điều khiển được tạo từ một ký tự đơn không thuộc mã danh mục 11, chẳng hạn như `\#`. Khi một token biểu thị một ký hiệu điều khiển được chuyển trở lại biểu diễn văn bản của nó, các bộ máy TeX không chèn một ký tự khoảng trắng sau văn bản đó. Cách xử lý đặc biệt này đối với các ký hiệu điều khiển là một quy tắc tích hợp sẵn trong cách các bộ máy TeX vận hành.

### Các token chưa mở rộng: ngăn chặn mở rộng

`\directlua`việc tiền xử lý của nó là một ví dụ mà trong đó một bộ máy TeX đang thực hiện mở rộng nhưng bạn có thể muốn *ngăn chặn* việc mở rộng được áp dụng cho một hoặc nhiều token vốn nếu không sẽ được mở rộng. Lấy một ví dụ khác, LuaTeX (và tất cả các bộ máy TeX) thực hiện một quá trình mở rộng, tương tự như của `\directlua`, khi chúng xử lý `\write` lệnh:

`\write file-number {⟨material⟩}`

\write yêu cầu một bộ máy TeX xuất ra `⟨material⟩`—thường chứa các lệnh TeX/LaTeX—ra một tệp văn bản (`file-number`); bất kỳ lệnh có thể mở rộng nào bên trong `⟨material⟩` sẽ, nếu không bị ngăn chặn, được mở rộng trước khi `⟨material⟩` được thực sự ghi ra tệp đó.

Như bạn có thể đoán, các bộ máy TeX cung cấp các lệnh để ngăn chặn hoặc điều khiển việc mở rộng:

* `\noexpand⟨token⟩`: ngăn chặn việc mở rộng của duy nhất `⟨token⟩`;
* `\unexpanded{⟨material⟩}`: ngăn chặn việc mở rộng của tất cả các lệnh có thể mở rộng (token) trong `⟨material⟩`. Về cơ bản, đây là phiên bản nhiều token của `\noexpand`;
* `\protected`: một tiền tố được thêm vào các định nghĩa macro nhằm ngăn macro đó được mở rộng trong một số trường hợp nhất định (chẳng hạn như trong khi `\directlua`, `\write` hoặc `\edef`).

Mặc dù tên gọi gợi ý điều khác, cả `\noexpand` và `\unexpanded` đang *các lệnh có thể mở rộng* và đều là những ví dụ tốt cho cách nhìn quá trình mở rộng của một bộ máy TeX như đang thực hiện “các thao tác trên token”: thao tác ở đây là ngăn việc mở rộng của một hay nhiều token (lệnh) tiếp theo. Bởi vì `\noexpand` và `\unexpanded` cả hai đều là các lệnh có thể mở rộng nên chúng bị loại bỏ và được xử lý (thực thi) trong khi `\directlua`trong quá trình tiền xử lý của nó khi nó xây dựng danh sách token từ `⟨code⟩`.

#### \noexpand ⟨token⟩

`\noexpand ⟨token⟩` ngăn chặn việc mở rộng của duy nhất `⟨token⟩`. `\noexpand` trong `\directlua` sẽ được mở rộng (bị loại khỏi đầu vào) và được thay thế bằng kết quả từ “hành vi mở rộng” của nó. Kết quả của việc mở rộng `\noexpand` là tạo ra một (ẩn) đặc biệt `⟨marker token⟩` được đặt trước token gốc `⟨token⟩` có việc mở rộng sẽ bị ngăn chặn: token đó `⟨marker token⟩` đóng vai trò như một cờ báo rằng “không mở rộng token tiếp theo”. Bởi vì `\directlua` đang thực hiện mở rộng đầy đủ nên nó sẽ xử lý lại bất kỳ token nào sinh ra từ “hành vi mở rộng” của một lệnh có thể mở rộng. Do đó, khi việc mở rộng của `\noexpand ⟨token⟩` hoàn tất, LuaTeX quay lại đọc các kết quả và thấy chuỗi gồm hai token `⟨marker token⟩⟨token⟩` điều này khiến token gốc `⟨token⟩` đi qua, không được mở rộng, vào danh sách token đang được xây dựng bởi `\directlua`.

**Ví dụ**

Nếu chúng ta viết

```
\directlua{
   local x= "\TeX"
}
```

trang `\TeX` macro được mở rộng thành các token cấu thành của nó, mà trong plain TeX, sẽ dẫn đến đoạn văn bản sau được truyền cho Lua (lưu ý: Lua không thể xử lý đoạn mã này, đây chỉ là một ví dụ để minh họa quá trình đó):

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

Nếu chúng ta *ngăn chặn* việc mở rộng của `\TeX` macro bằng cách dùng `\noexpand`

`\directlua{local x= "\noexpand\TeX"}`

mã Lua sau được tạo ra (một lần nữa, Lua không thể chạy mã này; nó chỉ `\noexpand`):

`local x= "\TeX "`

Nhờ `\noexpand`, `\directlua` sẽ không mở rộng `\TeX` mà chỉ cho phép giá trị token đại diện cho `\TeX` lệnh đi qua nguyên vẹn, vào danh sách token đang được xây dựng trong giai đoạn đầu của `\directlua`quá trình tiền xử lý của nó.

**Lưu ý:** Ký tự khoảng trắng xuất hiện sau `\TeX` được LuaTeX đưa vào bởi quá trình chuyển đổi tiếp theo từ `\TeX` giá trị token số nguyên trở lại dạng văn bản của nó (trong `tokenlist_to_cstring()` hàm).

#### \unexpanded{⟨material⟩}

`\unexpanded` là một lệnh có thể mở rộng ngăn việc mở rộng của tất cả các token được tạo từ `⟨material⟩`. Như chúng ta đã lưu ý, khi một bộ máy TeX thực hiện mở rộng thì bất kỳ lệnh có thể mở rộng nào đều *loại bỏ* khỏi đầu vào và *thay thế* bằng kết quả từ “hành vi mở rộng” của nó; vậy điều đó thực sự có nghĩa gì đối với `\unexpanded`? Thông thường, trong *mở rộng đầy đủ*, một khi quá trình mở rộng của một lệnh cụ thể hoàn tất thì bộ máy TeX sẽ tiếp tục đọc/xử lý bất kỳ token nào phát sinh từ “hành vi mở rộng” của lệnh đó—nó cần tiếp tục mở rộng bất kỳ token nào đã được tạo ra. Tuy nhiên, `\unexpanded` *bỏ qua* mọi bước mở rộng tiếp theo: đây là cách nó làm điều đó.

Bên trong bộ máy TeX, `\unexpanded` lệnh trước tiên chuyển các ký tự và lệnh trong `⟨material⟩` thành một danh sách token tạm thời gồm *chưa mở rộng* token. Sau khi tất cả token đã được tạo và lưu trong danh sách token tạm thời đó, `\unexpanded` lệnh khiến `\directlua` sang *bỏ qua* việc quay lại để đọc và xử lý chúng—mặc dù \directlua đang thực hiện mở rộng đầy đủ. Thay vào đó, các *chưa mở rộng* token đi thẳng qua và được hợp nhất vào danh sách token chính đang được xây dựng bởi `\directlua` (trong `scan_toks()` hàm). Bằng cách này, mọi thứ trong `⟨material⟩` được chuyển thành token và quá trình mở rộng bị bỏ qua đối với tập token đó. Cách thức hoạt động của `\unexpanded{⟨material⟩}` tương tự như việc dùng `\the\toks`, mà chúng ta sẽ bàn dưới đây.

**Ví dụ**

`\unexpanded` tạo ra kết quả theo cách tương tự như `\noexpand` nhưng nó có thể ngăn việc mở rộng của nhiều token; đây là một ví dụ:

```
\directlua{
   local x = "\unexpanded{\foo\bar\foobar}. But Lua can't process this code!"
}
```

điều này tạo ra đoạn văn bản sau làm mã cho Lua:

`local x = "\foo \bar \foobar . But Lua can't process this code!"`

**Lưu ý**: Có các ký tự khoảng trắng sau mỗi tên lệnh. Một lần nữa, đó là hệ quả của việc LuaTeX chuyển đổi tiếp theo các token chưa được mở rộng `\foo`, `\bar` và `\foobar` trở lại thành văn bản trong `tokenlist_to_cstring()` hàm.

#### các định nghĩa macro \protected

Tính năng `\protected` lệnh là một tiền tố được áp dụng cho một định nghĩa macro để ngăn macro đó được mở rộng khi TeX đang xây dựng một danh sách token đã mở rộng, chẳng hạn như danh sách token được tạo bởi `\directlua`quá trình tiền xử lý của nó.

**Ví dụ**

Giả sử bạn định nghĩa các macro sau có và không dùng tiền tố `\protected` tiền tố:

```
\def\macroA{"This unprotected macro contains a string"}
\protected\def\macroB{"This protected macro also contains a string"}
```

Nếu bạn dùng toán tử nối chuỗi của Lua (`..`) để viết

```
\directlua{
   local x=\macroA..\macroB
}
```

`\directlua`quá trình tiền xử lý của nó sẽ tạo ra đoạn mã sau để truyền cho Lua:

`local x="This unprotected macro contains a string"..\macroB`

`\macroA` không được định nghĩa bằng `\protected` nên nó được mở rộng, tạo ra phần đầu tiên của chuỗi cần nối, nhưng `\macroB` được định nghĩa bằng `\protected` nên nó chưa được mở rộng.

Trong quá trình tiền xử lý, `scan_toks()` hàm của LuaTeX đã tạo một token cho `\macroA`, nhận ra đây là một lệnh có thể mở rộng thông thường và đã mở rộng nó: việc mở rộng đó tạo ra một chuỗi các token ký tự, một token ký tự cho mỗi ký tự trong `"This unprotected macro contains a string"`. Mỗi token ký tự được chuyển tiếp và thêm vào danh sách token đang được xây dựng.

Khi `scan_toks()` tạo token cho `\macroB` nó nhận ra lệnh đó được định nghĩa là `\protected` và không mở rộng nó: token đại diện cho `\macroB` đi qua nguyên vẹn (không được mở rộng), vào danh sách token đang được xây dựng. Sau khi danh sách token đó được xây dựng xong, giai đoạn tiếp theo của tiền xử lý, trong `tokenlist_to_cstring()` hàm, là chuyển tất cả token trong danh sách token trở lại dạng văn bản của chúng. Token chưa được mở rộng đại diện cho `\macroB` được phát hiện và chuyển thành biểu diễn văn bản của nó, tạo ra văn bản `\macroB` xuất hiện trong mã dành cho Lua. Lưu ý rằng Lua thực sự không thể nối `"This unprotected macro contains a string"..\macroB` để tạo ra chuỗi cuối cùng vì `\macroB` không có ý nghĩa trong cú pháp Lua, dẫn đến lỗi `unexpected symbol near '\'`.

**Thông tin thú vị**: Gói `\protected` lệnh được giới thiệu bởi $$\varepsilon\text{-}\mathrm{\TeX}$$, phần mở rộng lớn đầu tiên của phần mềm TeX gốc của Knuth, và được hỗ trợ bởi tất cả các bộ máy TeX mà lịch sử mã nguồn của chúng bao gồm $$\varepsilon\text{-}\mathrm{\TeX}$$.

### Các token chưa được mở rộng: Sử dụng \the\toks trong \directlua

Đời sống lập trình sẽ không còn như cũ nếu thiếu những “trường hợp đặc biệt” để xử lý và việc dùng `\the` kết hợp với `\toks` trong một `\directlua` lệnh là một trong những trường hợp đặc biệt như vậy.

#### Tóm lược về \toks

Lệnh nguyên thủy TeX `\toks` yêu cầu một bộ máy TeX lưu một số token để dùng sau này: thay vì được chuyển tiếp để xử lý tiếp, các token đó được để sang một bên và lưu trong một vị trí bộ nhớ được chỉ định bằng một *thanh ghi token*. Ví dụ, chúng ta có thể yêu cầu một bộ máy TeX tạo một số token và lưu chúng trong vị trí thanh ghi token `100` bằng cách dùng

`\toks100={Hi, \TeX! \hskip 5bp}`

Ở đây, TeX sử dụng thanh ghi token `100` để truy cập một vị trí đã biết trong bộ nhớ của nó: một vùng lưu trữ dành cho việc chứa các danh sách token.

Các token đại diện cho mọi thứ nằm giữa `{` và `}` được tạo ra, *nhưng không được mở rộng*, và được nối lại trong một danh sách token—tương tự danh sách token mà chúng ta đã xem xét trước đó trong bài viết này. Để dùng lại các token đó, chúng ta sẽ viết `\the\toks100` trong đó `\the` (một lệnh có thể mở rộng) yêu cầu TeX lấy các token đã lưu và chèn chúng vào vị trí nơi bạn đã viết `\the\toks100`. Một cách khác để hiểu điều này là `\the\toks` khiến TeX chèn một số token vào vị trí đó.

Tính năng `\toks` lệnh *không mở rộng* bất kỳ token nào mà nó được yêu cầu tạo và lưu: nó chỉ đơn giản chuyển các ký tự và lệnh giữa `{` và `}` thành token rồi lưu chúng lại.

#### Quay lại \directlua

Trong phần thảo luận về mở rộng, chúng ta đã lưu ý `\directlua{⟨code⟩}` thực hiện *mở rộng đầy đủ* của `⟨code⟩`: loại bỏ tất cả các lệnh có thể mở rộng và thay thế chúng bằng kết quả của hành vi mở rộng—tiếp tục *mở rộng tiếp* bất kỳ token nào phát sinh từ lần mở rộng ban đầu của một lệnh có thể mở rộng.

`\the` là một lệnh có thể mở rộng nên `\directlua` sẽ mở rộng nó; tuy nhiên, khi `\the` được dùng kết hợp với `\toks` trong `\directlua`, như trong `\the\toks⟨token register⟩`, các token được chèn vào *không được mở rộng thêm nữa*. Việc mở rộng của `\the\toks⟨token register⟩` chèn chuỗi *chưa mở rộng* token, được lưu trong `⟨token register⟩`, trực tiếp vào danh sách token đang được xây dựng bởi `\directlua`: hành vi này bỏ qua quá trình mở rộng đầy đủ thông thường. Về thực chất, những token đó đi qua, *chưa mở rộng*để được hợp nhất vào danh sách token chính đang được xây dựng bởi `\directlua`—quá trình đi qua nguyên vẹn này đối với các token chưa được mở rộng về cơ chế tương tự với `\unexpanded`, như đã bàn trước đó.

**Ví dụ**

Giả sử chúng ta định nghĩa macro `\mymacro` là `\def\mymacro{\TeX}`. Nó chỉ chứa một token cho `\TeX` lệnh (vốn là một macro): vì vậy ta có một lệnh có thể mở rộng `\mymacro` chứa một macro khác `\TeX`, vốn cũng có thể mở rộng.

Đoạn mã sau sẽ khiến Lua cố gắng tạo một biến chuỗi `x`:

```
\def\mymacro{\TeX}
\directlua{
   local x="\mymacro"
}
```

Bên trong \\`directlua`, token cho `\mymacro` được mở rộng nhưng kết quả lại là một token có thể mở rộng khác, `\TeX`và token đó lại tiếp tục được mở rộng. Trong plain TeX, các lần mở rộng đó tạo ra đoạn văn bản sau được chuyển cho Lua:

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

Mã này cố gắng định nghĩa một chuỗi chứa văn bản biểu diễn phiên bản đã được mở rộng của `\TeX` macro. Nếu bạn thử chạy ví dụ này, Lua sẽ cố gắng tạo chuỗi đó nhưng sẽ thất bại, sinh ra lỗi:

`invalid escape sequence near ' "T\k'.`

Sau này trong bài viết này, chúng ta sẽ tìm hiểu ý nghĩa của “chuỗi thoát không hợp lệ”.

Bây giờ hãy so sánh việc dùng `\mymacro` với việc đặt `\TeX` token bên trong một danh sách token được tạo bởi một `\toks` lệnh:

```
\toks100={\TeX}
\directlua{
   local x="\the\toks100"
}
```

của LuaTeX `\directlua` sẽ tạo ra chuỗi văn bản này cho Lua:

`local x = "\TeX "`

Ký tự khoảng trắng sau `\TeX` được tạo ra bởi quá trình chuyển đổi lệnh-token-sang-chuỗi của LuaTeX.

**Nhưng lưu ý**: Gói `\TeX` macro đã *không* được mở rộng thành các token cấu thành của nó. `\the\toks100` đã khiến các token được lưu trong thanh ghi 100 được chèn vào nhưng chỉ có vậy: chúng *không* không được mở rộng thêm và được hợp nhất vào danh sách token chính đang được xây dựng bởi `\directlua` (trong hàm `scan_toks()`). Việc đưa token vào một danh sách token được tạo bởi `\toks` là thêm một cách nữa để ngăn token được mở rộng.

Nếu chúng ta chạy ví dụ này thì nó cũng tạo ra lỗi:

`invalid escape sequence near ' "\T'.`

Chúng ta sẽ tìm hiểu các chuỗi thoát của Lua sau trong bài viết.

## Các lệnh/kỹ thuật khác được dùng trong mở rộng

Trong phần này, chúng ta xem xét một số lệnh/phương pháp TeX bổ sung có thể hữu ích trong các tình huống đang áp dụng mở rộng (chẳng hạn như trong `\directlua`).

### \string ⟨token⟩

`\string` là một lệnh có thể mở rộng chuyển ⟨token⟩ thành một chuỗi các token ký tự, mỗi token có mã loại 12.

Ví dụ, `\string\TeX` sẽ tạo ra một chuỗi gồm 4 token ký tự `\`, `T`, `e` và `X` trong đó mỗi ký tự được gán mã loại 12 (bao gồm cả ký tự `\` đầu tiên).

Nếu chúng ta viết

```
\directlua{
   local x="I will use \string\newcommand"
   print(x)
}
```

trang `\string` lệnh sẽ được mở rộng, tạo ra một chuỗi các token ký tự với mã loại 12. Sau khi `\string` được mở rộng, các token ký tự kết quả (đại diện cho từng ký tự trong `\newcommand`) sẽ được hợp nhất vào danh sách token chính đang được xây dựng bởi `\directlua`. Một khi `\directlua` đã xây dựng xong danh sách token chính, các token cấu thành của nó được chuyển trở lại dạng văn bản, tạo ra đoạn mã sau để chuyển cho bộ thông dịch Lua:

`local x="I will use \newcommand" print(x)`

Khi đoạn mã này được chuyển cho Lua, `print(x)` sẽ xuất ra chuỗi `x` lên màn hình (console). Tuy nhiên, chúng ta đã tinh tế “chơi khăm” một chút và cố tình dùng một lệnh ví dụ bắt đầu bằng `\n`. Nếu bạn có thể chạy ví dụ này trên một cài đặt TeX cục bộ, bạn sẽ thấy Lua in ra đoạn văn bản sau trên màn hình:

```
   Tôi sẽ dùng
   ewcommand
```

Để chạy đoạn mã này trên Overleaf, bạn có thể yêu cầu LuaTeX ghi trực tiếp vào `.log` tệp bằng hàm API Lua của LuaTeX `texio.write(*string*)`:

```
\directlua{
   local x="I will use \string\newcommand"
   texio.write(x)
}
```

Nếu bạn kiểm tra `.log` tệp kết quả, bạn sẽ thấy nó cũng chứa

```
   Tôi sẽ dùng
   ewcommand
```

Đầu ra bất ngờ này là do Lua diễn giải `\n` ở đầu của `**\n**ewcommand` như chuỗi thoát cho ký tự xuống dòng (mã ký tự 10): nó giả định rằng bạn muốn bắt đầu một dòng văn bản mới bắt đầu bằng `ewcommand`. Chúng ta sẽ bàn về các chuỗi thoát của Lua sau trong bài viết này.

### \detokenize{⟨material⟩}

`\detokenize` về hiệu ứng, là phiên bản nhiều token của `\string` và nó cũng là một lệnh có thể mở rộng chuyển mọi thứ trong `⟨material⟩` thành một chuỗi các token ký tự với mã loại 12—*trừ* các ký tự khoảng trắng (giá trị ASCII/Unicode 32) vốn có mã loại 10. `\detokenize` cũng chèn một ký tự khoảng trắng ở cuối sau các tên lệnh là *từ điều khiển* (ví dụ, `\foo`) nhưng không chèn ký tự khoảng trắng sau *ký hiệu điều khiển* (ví dụ, `\#`, `\%` v.v.).

### Ví dụ

Ngay cả khi các macro `\foohoo`, `\foo`, `\bar` và `\foobar` không được định nghĩa, nếu bạn viết như sau:

```
\directlua{
   local x = "\string\foohoo\detokenize{\foo\bar\foobar}"
}
```

nó sẽ tạo ra đoạn văn bản sau làm mã để chuyển cho bộ thông dịch Lua

`local x = "\foohoo\foo \bar \foobar "`

Nếu bạn không dùng `\string` và `\detokenize` và viết:

`\directlua{local x = "\foohoo\foo\bar\foobar"}`

`\directlua` sẽ xử lý `\foohoo`, nhận ra đó là một lệnh và cố gắng mở rộng nó; nhưng vì `\foohoo` không được định nghĩa nên sẽ dẫn đến lỗi:

```
   ! Undefined control sequence.
   l.1 \directlua{local x = "\foohoo
                      \foo\bar\foobar"}
         ?
```

Bởi vì `\string` và `\detokenize` chuyển đối số của chúng thành một chuỗi các token ký tự, `\directlua`quá trình mở rộng của nó vẫn có cơ hội phát hiện các token lệnh có thể mở rộng `\foohoo`, `\foo`, `\bar`, hoặc `\foobar`: chúng được biến thành các chuỗi token ký tự rất lâu trước khi có thể kích hoạt việc mở rộng.

Như đã lưu ý trước đó, việc mở rộng một lệnh bao gồm việc loại bỏ nó khỏi đầu vào và thay thế nó bằng kết quả từ “hành vi mở rộng” của nó. Kết quả của việc mở rộng (thường là các token) sau đó được bộ máy TeX đọc. Ở đây, “hành vi mở rộng” của `\string` và `\detokenize` là hấp thụ các token ký tự và token lệnh từ đầu vào và chuyển chúng thành các chuỗi token ký tự, ban đầu được lưu trong một danh sách token tạm thời, mà `\directlua` sau đó đọc. Những token ký tự đó được hợp nhất vào danh sách token chính đang được xây dựng bởi `\directlua`.

Hình minh họa sau cho thấy cách `\string` chuyển `\foohoo` lệnh thành một chuỗi token ký tự, tạo ra một danh sách token tạm thời sau đó được đọc bởi `\directlua` để hợp nhất các token ký tự đó vào danh sách token chính đang được xây dựng.

![](/files/ea5cf7d2c7072a40148de66eec01b5783778a04d)

Nếu `\string` hoặc `\detokenize` gặp các ký tự trong đối số của chúng, ví dụ, `\string a` hoặc `\detokenize{abc}` các ký tự đó (ở đây, với mã loại 11) tạo ra token ký tự nhưng với mã loại 12.

Ghi chú:

Nếu chúng ta quay lại ví dụ trên:

`\directlua{local x = "\string\foohoo\detokenize{\foo\bar\foobar}"}`

điều này tạo ra đoạn văn bản sau làm mã để chuyển cho bộ thông dịch Lua

`local x = "\foohoo\foo \bar \foobar "`

chúng ta có thể nhận thấy như sau:

* `\detokenize` đã chèn một ký tự khoảng trắng sau mỗi tên macro nhưng `\string` không làm như vậy.
* `\string` tác động lên một token duy nhất.
* Trong chuỗi `"\foohoo\foo \bar \foobar "` được dùng để định nghĩa `x` chúng ta sẽ một lần nữa gặp cơ chế ký tự thoát của Lua (được thảo luận bên dưới):

  * `\bar` bắt đầu bằng `\b` đây là chuỗi thoát của Lua dùng để biểu diễn [ký tự xóa lùi](https://en.wikipedia.org/wiki/Backspace) (mã ký tự 8);
  * lệnh `\foohoo`, `\foo` và `\foobar` đều bắt đầu bằng `\f`, chuỗi thoát của Lua dùng để biểu diễn [ký tự xuống trang](https://en.wikipedia.org/wiki/Page_break#Form_feed) (mã ký tự 12).

  Bởi vì các chuỗi ký tự `\b` và `\f` được dùng trong một chuỗi được tạo bằng dấu ngoặc kép `"..."` chúng sẽ tạo ra kết quả không mong muốn trừ khi thực hiện các bước để ngăn điều đó bằng cái gọi là của Lua *ngoặc vuông dài* phương pháp tạo chuỗi: một chủ đề mà giờ chúng ta có thể bàn cùng với các chuỗi thoát của Lua.

## “Chuỗi thoát của Lua” là gì?

Các ngôn ngữ lập trình dành riêng một số ký tự cho “mục đích đặc biệt” như một phần của cú pháp ngôn ngữ: thực chất, các ký tự đó được định nghĩa là có một dạng ý nghĩa đặc biệt nào đó. Tuy nhiên, đôi khi bạn cần tạm thời “tắt” ý nghĩa đặc biệt của một ký tự như vậy nếu, chẳng hạn, bạn muốn ký tự đó được nhúng như một phần của một chuỗi dài hơn nơi hành vi chuẩn của nó sẽ gây ra lỗi cú pháp. Về bản chất, ký tự đó cần được xử lý *không có* theo cách kích hoạt cách hiểu thông thường của nó—để lướt qua mà không bị chú ý. Để làm điều này, các lập trình viên dùng một kỹ thuật gọi là *thoát* trong đó một “ký tự đặc biệt” được biểu diễn bằng cái gọi là *chuỗi thoát*.

Một ví dụ tiêu chuẩn (Lua cũng hỗ trợ) là dùng dấu ngoặc kép bên trong một chuỗi, trong đó bạn thoát các dấu ngoặc kép bên trong bằng chuỗi thoát `\"`:

`"Khi được hỏi về LuaTeX, họ trả lời: \"Đó là một bộ máy TeX tuyệt vời!\" Tôi đồng ý."`

Ngôn ngữ Lua cung cấp một số cơ chế để làm việc với các chuỗi thoát:

* các chuỗi chuẩn bao gồm `\n` (xuống dòng), `\r` (ký tự xuống đầu dòng), `\\` (dấu gạch chéo ngược), `\"` (dấu ngoặc kép), `\t` (tab ngang), `\v` (tab dọc) và `\'` (dấu nháy đơn);
* `\xXX`, trong đó `XX` là một chuỗi gồm đúng hai chữ số thập lục phân;
* `\ddd`, trong đó `ddd` là một chuỗi gồm tối đa ba chữ số thập phân;
* vào thời điểm bài viết này được viết (tháng 8 năm 2019), phiên bản mới nhất của LuaTeX, mặc dù chưa có trên Overleaf, sử dụng Lua phiên bản 5.3, phiên bản này đã giới thiệu hỗ trợ cho các chuỗi thoát UTF-8: `\u{XXX}`. Cơ chế thoát này dành cho các ký tự Unicode mã hóa UTF-8, trong đó `XXX` là một chuỗi gồm một hoặc nhiều chữ số thập lục phân biểu diễn mã điểm của ký tự. Lưu ý rằng các dấu ngoặc bao quanh `{ }` là bắt buộc.

### Kiểm soát các chuỗi thoát

Theo truyền thống, các chuỗi được định nghĩa bằng dấu ngoặc kép như trong `"đây là một chuỗi"`; trong một chuỗi như vậy bạn có thể dùng các chuỗi thoát: `"đây là một chuỗi.\nBây giờ tôi sẽ sang một dòng mới."`. Tuy nhiên, Lua có một cơ chế thứ hai và *rất* tiện lợi để định nghĩa chuỗi: cái gọi là *ngoặc vuông dài* cơ chế, trong đó bạn định nghĩa một chuỗi bằng cách đặt văn bản trong `[[` và `]]`:

`[[Tôi là một chuỗi ngoặc vuông dài]]`

Trong một chuỗi được tạo bằng phương pháp ngoặc vuông dài, cơ chế thoát ký tự của Lua sẽ bị *tắt*: các chuỗi thoát được xử lý như các ký tự thông thường. Ví dụ, trong chuỗi

`[[Tôi là một chuỗi ngoặc vuông dài\n chuỗi]]`

trang `\n` chuỗi thoát không được coi là ký tự xuống dòng đơn lẻ (mã ASCII 13) mà là hai ký tự thông thường: `\` được theo sau bởi `n`.

### Tại sao các chuỗi ngoặc vuông dài lại hữu ích như vậy?

Như chúng ta sẽ khám phá sau, LuaTeX cung cấp một bộ các hàm Lua chuyên biệt, tích hợp sẵn mà bạn có thể dùng với `\directlua` để điều khiển hành vi dàn trang của LuaTeX. Trong số rất nhiều hàm đó có một hàm gọi là `tex.print(*string*)` cho phép bạn truyền `*string*` nội dung từ mã Lua trở lại LuaTeX để dàn trang. Một ví dụ rất đơn giản là:

`\directlua{tex.print("Hello, World!")}`

sẽ khiến LuaTeX dàn trang `Hello, World!`

Tính năng `*string*` được dùng trong `tex.print(*string*)` cũng có thể bao gồm văn bản biểu diễn các lệnh TeX và LaTeX để LuaTeX xử lý. Tuy nhiên, các lệnh TeX/LaTeX bắt đầu bằng một `\` ký tự gây vấn đề với các chuỗi được tạo bằng dấu ngoặc kép vì Lua sẽ cố phân tích chuỗi, phát hiện ký tự `\` đầu tiên và diễn giải nó như sự bắt đầu của một chuỗi thoát. Khi Lua cố xử lý chuỗi thoát đó, nó thường sẽ thất bại vì ký tự `\` đầu tiên kết hợp với ký tự đầu tiên trong nhiều tên lệnh TeX/LaTeX không tạo thành một chuỗi thoát hợp lệ mà Lua biết. Ví dụ, khi xử lý một chuỗi như `"Tôi thích \LaTeX"` Lua sẽ thấy `\L` và báo lỗi “chuỗi thoát không hợp lệ”, và đây là nguyên nhân của các lỗi đã nêu ở trên.

#### Các chuỗi ngoặc vuông dài sẽ cứu nguy!

Phương pháp tạo (định nghĩa) chuỗi bằng ngoặc vuông dài cực kỳ hữu ích vì mặc dù các lệnh TeX/LaTeX bắt đầu bằng một `\` ký tự, phương pháp chuỗi ngoặc vuông dài vô hiệu hóa (tắt) cơ chế chuỗi thoát của Lua. Đây là một ví dụ ngắn, nhớ rằng chúng ta cần ngăn các macro bị mở rộng bằng cách, chẳng hạn, `\protected` hoặc `\noexpand`.

Giả sử chúng ta định nghĩa một `\newtest` macro như thế này

`**\protected**\def\newtest#1{Đối số: #1}`

và dùng nó trong `\directlua` với hàm LuaTeX Lua API `tex.print()`:

```
\directlua{
   tex.print("\newtest{Xin chào}")
}
```

Do việc sử dụng `\protected`, macro `\newtest` không được mở rộng, dẫn đến văn bản sau được truyền cho Lua:

`tex.print("\newtest {Hello}")`

Ký tự khoảng trắng được thêm sau `\newtest` và trước dấu ngoặc nhọn mở (`{`) là một tác dụng phụ của `\directlua`việc chuyển các token lệnh của LuaTeX trở lại dạng biểu diễn văn bản của chúng.

Mã này được truyền cho Lua, sau đó thực thi hàm LuaTeX `tex.print()` nhưng có một vấn đề biểu hiện theo những cách phụ thuộc vào các phông chữ bạn đang dùng. Trong LaTeX trên Overleaf, bạn sẽ thấy kết quả như thế này:

![](/files/7321ff45fcdedc21b6041f6f62838d1fb8f1e3ae)

kèm theo một cảnh báo trong tệp log:

```
   Ký tự bị thiếu: Không có
   (U+000A) trong phông [lmroman10-regular]:+tlig;!
```

Trong plain TeX, bạn có thể thấy kết quả trông giống như thế này:

![](/files/7246e5ad7ab8c14cfbb2f4be8213b4cbf5c86b1c)

Trong cả hai trường hợp, `\newtest` macro không được gọi và kết quả không như chúng ta mong muốn. Lỗi này do cơ chế ký tự thoát của Lua: trong văn bản `\newtest {Hello}` tên macro bắt đầu bằng `\n` mà Lua nhận ra là chuỗi thoát cho ký tự xuống dòng nên nó thay `\n` bằng ký tự ASCII 10, hay dạng hex là 0A. Trong thông báo lỗi của LaTeX, `U+000A` là một cách biểu diễn giá trị Unicode bằng 4 chữ số hex.

Vì `\n` được chuyển thành ký tự xuống dòng, LuaTeX không thấy một lệnh macro mà thay vào đó tin rằng nó được yêu cầu dàn trang một đoạn văn bản bắt đầu bằng mã ký tự ASCII 10:

`⟨ASCII 10⟩ewtest {Hello}`

Tùy theo phông chữ được dùng, LuaTeX có thể hoặc không thể dàn trang được `⟨ASCII 10⟩` ký tự nhưng phần văn bản còn lại được xuất nguyên như vậy với `{` và `}` được xử lý như một nhóm và không được in.

Plain TeX cho kết quả khác vì phông mặc định là Computer Modern Roman có một bảng mã lạ dẫn đến việc ký tự mã 10 được dàn trang thành một chữ Omega hoa.

Để ngăn các vấn đề này, chúng ta cần dùng chuỗi ngoặc vuông dài để ngăn cơ chế thoát của Lua được áp dụng. Kết quả đúng được tạo ra với

`\directlua{tex.print([[\newtest{Hello}]])}`

tạo ra kết quả được hiển thị trong ảnh chụp màn hình sau:

![](/files/c8a442a6d1039b85cf229a4ac1a78798e96ef296)

### Mở rộng và không thực thi các lệnh không thể mở rộng

Khi bàn về mở rộng, chúng ta đã lưu ý rằng đó là một quá trình trong đó một bộ máy TeX *loại bỏ* một lệnh (token) có thể mở rộng khỏi đầu vào hiện tại và *thay thế* nó bằng kết quả do lệnh có thể mở rộng đó tạo ra. Vì \directlua đang thực hiện các hoạt động *chỉ mở rộng* (để tạo ra một danh sách token), nó *không* không đưa việc xử lý của LuaTeX đi xa hơn thế. Khi một lệnh có thể mở rộng đã được đọc và mở rộng hoàn toàn, các kết quả của phép mở rộng đó—thường bao gồm các lệnh không thể mở rộng (token)—sẽ được đưa vào danh sách token đang được xây dựng, sẵn sàng được chuyển trở lại văn bản để gửi cho Lua.

Có một nguyên tắc quan trọng ở đây: trong các hoạt động *chỉ mở rộng* được thiết kế để tạo ra một danh sách token, các bộ máy TeX, kể cả LuaTeX, *không thực thi* bất kỳ lệnh TeX nguyên thủy, tích hợp sẵn nào không thể mở rộng.

Trong trường `\directlua{⟨code⟩}`, nếu phiên bản đã mở rộng hoàn toàn của `⟨code⟩` tạo ra hoặc chứa các lệnh TeX/LaTeX không thể mở rộng thì chúng *sẽ được chuyển cho Lua* (dưới dạng văn bản).

#### Ví dụ

Sau đây là một ví dụ để chứng minh rằng các nguyên thủy không thể mở rộng không được thực thi trong quá trình xử lý chỉ mở rộng (như trong `\directlua`). Giả sử chúng ta định nghĩa một macro `\setcountreg` như sau:

`\def\setcountreg#1#2{\count#1=#2\relax}`

**Lưu ý**: Chúng ta dùng `\relax` sau tham số `#2` để ngăn LuaTeX vượt quá khi quét đầu vào để tìm giá trị số (đối số) khớp với tham số `#2`.

Nếu, ở ngoài `\directlua`, sau đó chúng ta chạy macro như thế này

```
   \setcountreg{100}{50}
   Giá trị trong thanh ghi đếm 100 là \the\count100.
```

thì nó sẽ xuất ra

`Giá trị trong thanh ghi đếm 100 là 50.`

Trong ngữ cảnh này, bất kỳ bộ máy TeX nào cũng sẽ xử lý macro `\setcountreg`—mở rộng macro, xác định các đối số và tiếp tục đọc *và thực thi* (chạy) các lệnh chứa trong văn bản thay thế của macro (định nghĩa). Kết quả ở đây là gán `50` làm giá trị được lưu trong thanh ghi `\count100`.

Tuy nhiên, khi một bộ máy TeX đang thực hiện các hoạt động *chỉ mở rộng* , như trong `\directlua`, nó *sẽ không thực thi* các lệnh không thể mở rộng có trong định nghĩa của macro.

Nếu chúng ta viết

```
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{50}]]
}
```

nó tạo ra văn bản sau đây làm mã cho Lua:

`local x = [[\count 100=50\relax ]]`

Mã Lua được tạo ở trên cho thấy rằng trong `\directlua` trang `\setcountreg` đã được mở rộng, các đối số của nó được nhận diện và thay thế vào tham số thích hợp (`#1` và `#2`) nhưng không đi xa hơn thế: lệnh TeX nguyên thủy không thể mở rộng `\count` đã *không được thực thi* trong quá trình xử lý mở rộng của `\directlua`.

Tuy nhiên, LuaTeX sẽ thực thi mã TeX nếu chúng ta chuyển chuỗi kết quả `x` *trở lại LuaTeX* qua `tex.print(x)` như thế này

```
\count100=50 % đặt \count100 về giá trị khởi đầu là 50
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{250}]]
   tex.print(x)
}
Giá trị được lưu trong thanh ghi đếm 100 là \the\count100.
```

Sau khi `\directlua` đã hoàn tất, kết quả sẽ là

`Giá trị được lưu trong thanh ghi đếm 100 là 250.`

cho thấy rằng thanh ghi đếm `100` bây giờ thực sự chứa giá trị `250`.

Mã Lua được tạo từ ví dụ trên là

`local x = [[\count 100=250\relax ]] tex.print(x)`

Mã này định nghĩa `x` là một chuỗi được tạo bằng phương pháp ngoặc vuông dài, phương pháp này được dùng để tránh lỗi với các chuỗi thoát sai. Nếu chúng ta dùng dấu ngoặc kép `"..."` để định nghĩa x, tổ hợp ký tự `\c` ở đầu của `\count` sẽ gây ra lỗi: `invalid escape sequence near ' "\c"'`.

Lệnh Lua API của LuaTeX `tex.print(x)` dẫn đến việc LuaTeX thực thi chuỗi mã TeX `\count 100=250\relax` và `\count100` được gán giá trị `250` như thấy từ kết quả dàn trang:

`Giá trị được lưu trong thanh ghi đếm 100 là 250.`

#### Thận trọng: các macro và LuaTeX Lua API

Trong ví dụ trên, chúng ta đã thấy rằng trong quá trình tiền xử lý (mở rộng) của `\directlua`LuaTeX không thực thi mã `\count 100=250`, mã này chứa `không thể mở rộng` lệnh nguyên thủy `\count`: để chạy (thực thi) mã đó, chúng ta phải *trả nó lại cho LuaTeX* qua `tex.print()`.

`\directlua` chỉ là một trường hợp trong đó LuaTeX đang thực hiện xử lý chỉ mở rộng để xây dựng một danh sách token. Còn có những lệnh khác thực hiện các hoạt động xử lý mở rộng và tạo danh sách token tương tự, chẳng hạn như `\write` và `\edef`: các lệnh đó cũng không thực thi các nguyên thủy không thể mở rộng trong quá trình xử lý mở rộng. Đây là một nguyên tắc chung rằng các bộ máy TeX không thực thi các nguyên thủy không thể mở rộng khi xây dựng một danh sách token trong các hoạt động xử lý chỉ mở rộng.

**Viết lại macro của chúng ta để dùng LuaTeX Lua API**

Chúng ta có thể viết lại `\setcountreg` macro bằng một hàm LuaTeX Lua API có tên là `tex.setcount()`, do đó tránh dùng các lệnh TeX để thay đổi giá trị được lưu trong thanh ghi đếm `100`:

```
   \def\setcount#1#2{\directlua{tex.setcount(#1,#2)}}
   \count100=50
   thanh ghi đếm 100 chứa \the\count100\par
   \setcount{100}{250}
   thanh ghi đếm 100 bây giờ chứa \the\count100\par
```

Mã này sẽ dàn trang:

```
thanh ghi đếm 100 chứa 50
thanh ghi đếm 100 bây giờ chứa 250
```

Ở đây chúng ta đang dùng `tex.setcount()`, một trong nhiều hàm Lua API của LuaTeX, để *truy cập trực tiếp* vùng lưu trữ dữ liệu nội bộ của LuaTeX để đặt giá trị `250` vào vị trí bộ nhớ biểu diễn thanh ghi đếm `100`. Chúng ta, trên thực tế, *đã bỏ qua* các phương pháp xử lý đầu vào tiêu chuẩn của bộ máy TeX trong LuaTeX: đọc đầu vào, tạo token và thực thi các lệnh TeX nguyên thủy. Tuy nhiên, có một lời cảnh báo: bằng cách sử dụng các hàm Lua API của LuaTeX, hoạt động xử lý chỉ mở rộng *có thể dẫn đến các tác dụng phụ*: những thay đổi đối với các giá trị được lưu bên trong bộ máy TeX mà nếu chỉ dùng các lệnh TeX/LaTeX thuần túy thì sẽ không thể xảy ra.

**Ví dụ: tác dụng phụ bất ngờ**

Sau đây là một ví dụ để minh họa *bất ngờ* các tác dụng phụ có thể phát sinh với các macro sử dụng `\directlua`. Giả sử chúng ta viết mã sau:

```
\def\dochange{\directlua{tex.setcount(999,12345)}}
\edef\careful{\dochange}
\the\count999
```

Chạy mã này sẽ dàn trang `12345`!

Sao lại như vậy? Chúng ta đã không *cố ý* gọi bất kỳ mã hay macro nào để đặt giá trị đó vào thanh ghi đếm `999`. Hay là có?

Chúng ta đã định nghĩa `\dochange` bằng một `\directlua` lệnh dùng `tex.setcount()` để lưu giá trị `12345` trong thanh ghi đếm `999`: trong mã TeX nó tương đương với `\count999=12345`. Sau đó chúng ta đã dùng nguyên thủy TeX chuẩn `\edef` để định nghĩa macro `\careful`—chính việc dùng `\edef` là thứ kích hoạt tác dụng phụ bất ngờ.

`\edef` mở rộng hoàn toàn đối số của nó: ở đây, nó phát hiện một macro có thể mở rộng `\dochange` và mở rộng nó. Macro `\dochange` sử dụng lệnh có thể mở rộng `\directlua` chứa một lệnh gọi Lua API; vì vậy việc mở rộng `\dochange` dẫn đến việc mở rộng `\directlua` và điều đó khiến `tex.setcount()` được gọi, làm thay đổi giá trị trong thanh ghi đếm `999`.

Nếu chúng ta định nghĩa lại `\dochange` để dùng các lệnh TeX:

```
   Trước: thanh ghi đếm 999 chứa \the\count999.\par
   \def\dochange{\count999=12345\relax}
   \edef\careful{\dochange}
   Sau: thanh ghi đếm 999 chứa \the\count999.\par
```

chạy mã này sẽ dàn trang

```
Trước: thanh ghi đếm 999 chứa 0.
Sau: thanh ghi đếm 999 chứa 0.
```

Rõ ràng là không có tác động nào lên `\count999`. Khi `\edef` định nghĩa `\careful` nó mở rộng `\dochange` nhưng phép mở rộng đó chỉ tạo ra các nguyên thủy TeX không thể mở rộng: chúng *không được thực thi* nhưng chỉ đơn thuần *được lưu trữ* trong danh sách token cấu thành định nghĩa của `\careful`.

Chỉ để chắc ăn, cùng nguyên tắc đó giải thích vì sao điều này tạo ra kết quả dàn trang:

```
\def\dochange{\directlua{tex.print("Hello")}}
\edef\careful{\dochange}
```

## Giới thiệu ngắn gọn về Lua API của LuaTeX

Như chúng ta đã thấy, `\directlua` không chỉ cho phép bạn viết mã Lua thông thường, hoặc kết hợp giữa mã Lua và TeX/LaTeX, mà còn cung cấp quyền truy cập vào một bộ các hàm Lua bổ sung (đặc thù cho LuaTeX) mà bạn có thể dùng (gọi) để giao tiếp với, hoặc trực tiếp điều khiển, cách thức hoạt động bên trong của phần mềm dàn trang LuaTeX. Chúng ta đã dùng một số hàm Lua trong bài viết này, `tex.print()`, `texio.write()`, `tex.setcount()` và những hàm này, cùng với *nhiều* hàm khác, được ghi tài liệu trong [Sổ tay tham chiếu LuaTeX](http://www.pragma-ade.com/general/manuals/luatex.pdf) trong đó các nhóm hàm liên quan được gọi là *thư viện*.

Bạn có thể xem các hàm Lua này như Lua API của LuaTeX (**Một**pplication **P**rogramming **I**nterface) cung cấp các công cụ để xây dựng những giải pháp dàn trang và kỹ thuật tài liệu tinh vi bằng cách điều khiển hành vi dàn trang của LuaTeX bằng Lua làm trình điều khiển.

Như đã lưu ý, LuaTeX tổ chức API của nó thành một tập hợp các hàm mà nó gọi là thư viện: các nhóm hàm có liên quan với nhau qua mục đích hoặc hành động. Mỗi tập hàm được thiết kế để cung cấp quyền truy cập vào một khía cạnh cụ thể của các quy trình nội bộ, cấu trúc dữ liệu, vùng lưu trữ dữ liệu và thuật toán dàn trang của LuaTeX. Về mặt nội bộ, LuaTeX được cấu thành từ nhiều thành phần: các thư viện/công cụ phần mềm (chủ yếu viết bằng C) không chỉ bao gồm chính bộ máy TeX mà còn các hệ thống phụ khác gồm Lua, MetaPost, Kpathsea, FontForge, libpng và zlib. Các thư viện này được tích hợp để tạo nên các tính năng và chức năng của phần mềm thực thi LuaTeX, và thông qua Lua API mà người dùng được truy cập vào chức năng của LuaTeX bắt nguồn từ sự tích hợp và phối hợp của các thành phần phần mềm đó.

## Một số ví dụ và cạm bẫy

Trong phần này chúng tôi trình bày thêm một số ví dụ sử dụng các chủ đề, khái niệm và giải thích đã nêu trong bài viết này.

### Dùng ký tự dấu ngã (\~)

Ngôn ngữ Lua dùng `~` ký tự (gọi là dấu ngã) như một phần của cú pháp của nó, bao gồm cả cú pháp để thực hiện phép kiểm tra “không bằng”; ví dụ, để kiểm tra xem một biến `x` có không bằng `4` chúng ta có thể viết:

```
   local x=3
   if x ~= 4 then
   print("x không bằng 4")
   end
```

Nếu chúng ta thử chạy đoạn mã Lua đơn giản này qua `\directlua`:

```
\directlua{
   local x=3
   if x ~= 4 then
   print("x không bằng 4")
   end
}
```

chúng ta sẽ gặp lỗi:

`[\directlua]:1: dự kiến 'then' gần '\'.`

Thật lạ vì mã của chúng ta là đúng: chúng ta đã dùng `'then'` và không có `\` ký tự trong mã của chúng ta, vậy đã xảy ra điều gì? Để hiểu điều này, chúng ta phải nhớ rằng, đối với TeX/LaTeX, `~` thường được định nghĩa là một “ký tự đặc biệt” với mã danh mục 13: các ký tự hoạt hóa được gọi như vậy là những mini-macro và vì thế có thể được mở rộng. Khi `\directlua` phát hiện `~` ký tự thì nó được mở rộng bằng cách *loại bỏ nó* khỏi đầu vào và *thay thế nó* cùng với kết quả của phép mở rộng. Khi dùng plain TeX, văn bản (mã) kết quả mà LuaTeX tạo ra và chuyển cho bộ thông dịch Lua thực ra không chứa `~` ký tự, và là:

`local x=3 if x \penalty \@M \ = 4 then print("x không bằng 4") end`

Tính năng `~` ký tự đã được *loại bỏ* và *mở rộng* thành các lệnh thành phần của nó—mã Lua ở trên là kết quả từ định nghĩa của plain TeX về ký tự hoạt hóa `~`. Giờ ta có thể thấy vì sao Lua phản hồi lỗi `dự kiến 'then' gần '\'`—nó bắt đầu phân tích mã này nhưng gặp từ `\penalty` vốn không có nghĩa gì đối với Lua và tạo ra lỗi cú pháp.

Để sửa điều này, ký tự `~` cần có một mã danh mục an toàn vào thời điểm `\directlua` đang xử lý mã của bạn; chẳng hạn, ta có thể tạm thời thay đổi mã danh mục của `~` thành 11 (chữ cái) bằng cách đặt mã vào trong một nhóm:

```
\begingroup
\catcode`\~=11
\directlua{
   local x=3
   if x ~= 4 then
   print("x không bằng 4")
   end
}
\endgroup
```

Đoạn mã này hoạt động như mong đợi và `x không bằng 4` được in ra console. Còn có các lựa chọn khác: ta có thể dùng các lệnh có thể mở rộng `\noexpand` hoặc `\string`.

#### Dùng \string⟨token⟩

Ta có thể áp dụng `\string` cho ký tự đơn `⟨token⟩` `~` vốn có mã danh mục 13 (ký tự hoạt hóa); `\string` chuyển `~` ký tự để tạo một token ký tự có mã danh mục 12. Nếu ta làm

```
\directlua{
   local x=3
   if x \string~= 4 then
   print("x không bằng 4")
   end
}
```

nó tạo ra mã Lua mà ta cần:

`local x=3 if x ~= 4 then tex.print("x không bằng 4") end`

#### Dùng \noexpand⟨token⟩

Ta có thể dùng `\noexpand~` để ngăn việc mở rộng ký tự hoạt hóa `~`

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x không bằng 4")
   end
}
```

Token chưa mở rộng `~` đi qua vào danh sách token đang được xây dựng trong `\directlua` và sẽ được chuyển trở lại thành văn bản, tạo ra mã Lua hoạt động.

### Dùng ký tự \#

Trong ngôn ngữ Lua, ký tự `#` có thể dùng để tìm độ dài của một bảng. Tuy nhiên, nếu ta thử đoạn mã sau

```
\directlua{
   local tbl = {}
   tbl[1] = "Xin chào"
   tbl[2] = "Thế giới"
   tex.print("Độ dài bảng là "..#tbl)
}
```

ta có thể kỳ vọng LuaTeX dàn trang

`Độ dài bảng là 2`

nhưng nó tạo ra lỗi:

`\directlua]:1: cố lấy độ dài của một giá trị số`

Lỗi này xảy ra vì `#` ký tự thường có mã danh mục 6 (tham số macro)—ký tự `#` ký tự có hai cách dùng trong TeX/LaTeX: để chỉ các tham số macro (`#1`, `#2`… `#9`) và văn bản thay thế trong các mẫu căn chỉnh (cho `\halign` và `\valign`).

Khi `\directlua` đang tạo token để xây dựng danh sách token của nó, nó thấy `#` ký tự với mã danh mục 6 và tạo một token ký tự thích hợp để biểu diễn nó. Khi đến lúc chuyển danh sách token cuối cùng trở lại dạng văn bản, token ký tự cho # (với mã danh mục 6) nhận cách xử lý đặc biệt: nó được xuất ra dưới dạng *hai ký tự liên tiếp*: `##`, dẫn tới đoạn mã sau được chuyển sang Lua:

`local tbl = {} tbl[1] = "Xin chào" tbl[2] = "Thế giới" print(##tbl)`

Khi chuyển sang mã Lua, ký tự `#` gốc đã bị nhân đôi và điều đó tạo ra lỗi:

`\directlua]:1: cố lấy độ dài của một giá trị số`

Vấn đề này phát sinh do cú pháp của TeX dùng ký hiệu thăng kép `##` để biểu diễn hoặc tạo ra một `#` token đơn; cú pháp này được dùng trong các macro định nghĩa các macro khác nhận tham số, hoặc trong các macro dùng để tạo mẫu cho các `\halign` hoặc `\valign` lệnh tạo bảng. Điều này khá khó hiểu nên hãy xem một ví dụ.

#### Ví dụ

Giả sử ta định nghĩa một macro `\mymacro` nhận một tham số duy nhất, `#1`nhưng nó cũng định nghĩa một macro thứ hai `\foo` vốn cũng nhận một tham số duy nhất. Để phân biệt giữa tham số `#1` dùng với `\mymacro` và nhu cầu định nghĩa `\foo` để dùng tham số riêng của nó `#1` cú pháp TeX yêu cầu bạn dùng `##1` bên trong `\mymacro` để biểu diễn tham số sẽ được dùng với `\foo`:

`\def\mymacro#1{\def\foo##1{#1 Xin chào##1}}`

Nếu bạn viết `\mymacro{Này!}` nó sẽ định nghĩa macro `\foo` thành

`\def\foo#1{Này! Xin chào#1}`

Lưu ý rằng `\mymacro`tham số của `#1` (`Này!`) đã được đưa vào định nghĩa của `\foo` và chuỗi `##1` đã được chuyển thành `#1` trong định nghĩa của `\foo`. Vì vậy ta có thể dùng `\foo` như sau:

`\foo{, Thế giới!}`

sẽ dàn trang `Này! Xin chào, Thế giới!`

Ta có thể giải quyết `\directlua`cách xử lý của `#` ký tự bằng cách tạm thời thay đổi mã danh mục của nó trước khi LuaTeX xử lý mã. Ví dụ:

```
\begingroup
   \catcode`\#=11
   \directlua{
   local tbl = {}
   tbl[1] = "Xin chào"
   tbl[2] = "Thế giới"
   tex.print("Độ dài bảng là "..#tbl)
}
\endgroup
```

Điều này tạo ra mã Lua

```
local tbl = {} tbl[1] = "Xin chào" tbl[2] = "Thế giới" tex.print("Độ dài bảng là "..#tbl)
```

và nó dàn trang ra kết quả mà ta mong đợi:

`Độ dài bảng là 2`

### Dùng ký tự %

Trong TeX/LaTeX, ký tự `%` thường được dùng để thêm chú thích một dòng vào mã của bạn: để báo cho bộ máy TeX bỏ qua mọi thứ từ điểm đó đến hết dòng mà `%` được viết. Tuy nhiên, trong ngôn ngữ Lua, ký tự `%` được dùng trong một số hàm xử lý chuỗi rất hữu ích, chẳng hạn như `string.format(...)`, `string.gmatch(...)`, và `string.gsub(...)` trong đó ký tự `%` đóng vai trò quan trọng như một phần cú pháp của các hàm đó.

Khi dùng với TeX/LaTeX, `%` hoạt động như ký tự chú thích vì nó được gán mã danh mục 14. Để nó hành xử như một ký tự bình thường, và tắt hành vi TeX/LaTeX thông thường của nó, ta cần đổi mã danh mục của nó sang một giá trị an toàn, chẳng hạn 12. Ví dụ `\directlua` dưới đây sử dụng một số kỹ thuật đã bàn trước đó trong bài, cùng với một kỹ thuật mà ta chưa đề cập: ``\catcode`\^^M=12``, cho phép ta dùng chú thích Lua trong mã của mình; điều này được thảo luận bên dưới.

#### Ví dụ

Các ví dụ sau được lấy từ [lua-users.org](http://lua-users.org/wiki/StringLibraryTutorial), đã được chỉnh sửa thích hợp để dùng trong `\directlua`.

```
\documentclass{article}
\begin{document}
\begingroup
\ttfamily
\let\\\relax
\catcode`\^^M=12 %<---ta sẽ tìm hiểu thêm ở dưới!
\catcode`\%=12
\directlua{
   local str -- khai báo một biến cục bộ để giữ kết quả

   tex.print("Dùng string.format():".."\\par")

   str=string.format("%s %q", "Xin chào", "người dùng Lua!") -- chuỗi và chuỗi có dấu ngoặc kép
   tex.print(str.."\\par")
   str = string.format("%c%c%c", 76, 117, 97) -- ký tự
   tex.print(str.."\\par")
   str=string.format("%e, %E", math.pi, math.pi) -- số mũ
   tex.print(str.."\\par")
   str=string.format("%f", math.pi) -- số thực
   tex.print(str.."\\par")
   str=string.format("%g, %g", math.pi, 10^9) -- số thực hoặc số mũ
   tex.print(str.."\\par")
   str = string.format("%o, %x, %X", 99, 125, 125)  -- bát phân, thập lục phân, thập lục phân
   tex.print(str.."\\par")

   tex.print("\\vskip3mm".."Dùng string.gmatch():".."\\par")

   for word in string.gmatch("Hello TeX user", "%a+") do
      tex.print(word.."\\par")
   end

   tex.print("\\vskip3mm".."Dùng string.gsub():".."\\par")
   str=string.gsub("banana", "(an)", "%1-") -- bắt mọi lần xuất hiện của "an" và thay thế
   tex.print(str.."\\par")
}
\endgroup
\end{document}
```

Ảnh chụp màn hình sau cho thấy kết quả dàn trang của đoạn mã trên:

![Dùng các hàm chuỗi Lua trong \directlua](/files/387b9893e4f15ca5db6718832882bc664d67b598)

## Vì sao mã Lua được hiển thị trên một dòng?

Như bạn có thể nhận thấy, tất cả các đoạn mã Lua (được tạo ra) trong các ví dụ của bài này đều được trình bày dưới dạng một dòng văn bản duy nhất: các ngắt dòng vốn có trong `\directlua` các đoạn mã không được giữ nguyên. Vì sao vậy? Đó là vì các ngắt dòng trong mã Lua đã bị *loại bỏ* trong quá trình tiền xử lý của LuaTeX trong `\directlua`, khiến mã Lua trở thành một dòng văn bản dài duy nhất. Hành vi đó có thể truy nguyên đến cách các bộ máy TeX xử lý các ký tự kết thúc dòng—được ký hiệu bằng `\r` (carriage return) và `\n` (line feed) trong tài liệu lập trình. Vì sao ta cần bận tâm đến những chi tiết này sẽ trở nên rõ ràng khi ta bàn về việc dùng cơ chế của Lua để chú thích bỏ qua các đoạn mã.

Khi phần mềm ghi (lưu) một tệp văn bản, mỗi dòng văn bản riêng lẻ được kết thúc bằng các ký tự gọi là “newline”—ký tự newline thực tế phụ thuộc vào ứng dụng và hệ điều hành đang được dùng để ghi tệp đó. Wikipedia có một [bài viết thú vị](https://en.wikipedia.org/wiki/Newline) khám phá lịch sử/sự tiến hóa của các ký tự newline đang được dùng ngày nay.

Với bất kỳ tệp văn bản nào, các dòng văn bản riêng lẻ của nó có thể được kết thúc bằng nhiều tổ hợp ký tự khác nhau, được gọi là carriage return (ký tự ASCII/Unicode 13) và/hoặc line feed (ký tự ASCII/Unicode 10), được ký hiệu bằng `\r` và `\n` tương ứng. Vì các bộ máy TeX được thiết kế độc lập nền tảng nên chúng cần một phương pháp để vượt qua tính chất vốn phụ thuộc nền tảng của các ký tự kết thúc dòng dùng trong tệp văn bản. Dĩ nhiên, các bộ máy TeX có một phương pháp tích hợp sẵn (nhưng có thể cấu hình) để xử lý các ký tự kết thúc dòng.

### Các bộ máy TeX xử lý kết thúc dòng như thế nào

Khi LuaTeX đang xử lý `\directlua{⟨code⟩}` nó đọc văn bản chứa trong `⟨code⟩` và áp dụng các phương pháp chuẩn của bộ máy TeX để xử lý mọi ký tự kết thúc dòng có trong `⟨code⟩`. Theo mặc định, các phương pháp chuẩn của TeX khiến mọi ký tự kết thúc dòng (carriage return và line feed) bị loại bỏ và thay bằng ký tự khoảng trắng. Ta nói “theo mặc định” vì cách bộ máy TeX xử lý các ký tự kết thúc dòng có thể được thay đổi thông qua một tham số do người dùng cấu hình có tên là `\endlinechar`. Ở đây, ta sẽ trình bày tổng quan ngắn gọn gồm hai bước nhưng chi tiết hơn có thể tìm thấy trong bài viết của Overleaf [Giới thiệu về \endlinechar: Cách TeX đọc các dòng từ tệp văn bản](/latex/vi/bai-viet-chuyen-sau/05-an-introduction-to-endlinechar-how-tex-reads-lines-from-text-files.md).

#### Bước 1: TeX chèn ký tự kết thúc dòng riêng của nó

Sau khi đọc một dòng văn bản từ tệp đầu vào của bạn, các bộ máy TeX ngay lập tức loại bỏ mọi `\r` hoặc `\n` ký tự khỏi cuối dòng đó. Tiếp theo, các bộ máy TeX *chèn* (thêm lại) ký tự kết thúc dòng riêng của chúng vào cuối dòng đó. Ký tự này được xác định bởi giá trị của một tham số TeX do người dùng cấu hình có tên là `\endlinechar` và chính nhờ cơ chế này mà các bộ máy TeX có thể xử lý các ký tự kết thúc dòng theo cách độc lập nền tảng: chúng tự chọn và đặt ký tự kết thúc dòng, bất kể ban đầu trong tệp văn bản đầu vào chứa gì.

Thông thường, các bộ máy TeX dùng thiết lập

`\endlinechar=13`

tức là ký tự carriage return (`\r`). Tuy nhiên, người dùng luôn có thể gán một mã ký tự khác làm giá trị của `\endlinechar`—điều mà ta sẽ thấy sau trong bài này.

Do đó, mọi ký tự kết thúc dòng có trong `⟨code⟩` được xử lý bởi `\directlua{⟨code⟩}` đều bị loại bỏ và thay bằng một ký tự đơn do chính bộ máy TeX xác định. Lưu ý rằng các bộ máy TeX thực hiện việc xử lý cuối dòng này ngay sau khi đọc một dòng văn bản mới từ tệp và *trước khi* xử lý mọi ký tự trong dòng đó (để tạo token). Tuy nhiên, đây chưa phải là hết: điều mà bộ máy TeX *thì* làm với những ký tự kết thúc dòng đó (nó đã chèn vào) giải thích vì sao mã Lua trở thành một dòng duy nhất.

#### Bước 2: TeX chuyển ký tự kết thúc dòng của nó thành khoảng trắng

Ngoài việc chèn ký tự kết thúc dòng riêng của mình, được xác định bởi giá trị của `\endlinechar`, các bộ máy TeX cũng dùng mã danh mục 5 cho các ký tự nên được *coi là* là ký tự kết thúc dòng. Điều này dẫn đến việc các bộ máy TeX thường làm việc với:

1. một ký tự kết thúc dòng được xác định bởi `\endlinechar`;
2. chính ký tự đó *thường* được gán mã danh mục 5.

Chính việc TeX làm với ký tự kết thúc dòng đó giải thích vấn đề của ta về các dòng Lua đơn lẻ. Khi một bộ máy TeX xử lý một dòng đầu vào, cuối cùng nó sẽ phát hiện ký tự cuối cùng trong dòng đó: ký tự được xác định bởi `\endlinechar`. Thông thường, ký tự đó có mã danh mục 5, khiến TeX *thay nó* bằng ký tự khoảng trắng: tức là ở cuối dòng, TeX thực chất loại bỏ ký tự kết thúc dòng của nó và thay bằng một khoảng trắng. Nhân tiện, các bộ máy TeX cũng dùng các ký tự có mã danh mục 5 để phát hiện các dòng trống và bắt đầu một đoạn mới, nhưng ở đây ta sẽ không bàn đến điều đó.

Dĩ nhiên, vì là TeX, bạn có thể thực hiện đủ loại thủ thuật lập trình macro đặc biệt bằng cách đặt lại `\endlinechar` thành một ký tự khác, và/hoặc gán cho ký tự được chỉ định cho `\endlinechar` một giá trị mã danh mục theo ý bạn.

Nếu bạn muốn ngăn mã Lua trở thành một dòng văn bản duy nhất, bạn có thể (tạm thời) thay đổi giá trị được gán cho `\endlinechar` hoặc thay đổi mã danh mục của ký tự kết thúc dòng chuẩn `\r`.

### Ký pháp ^^ kỳ lạ của TeX

Trong các phần sau, ta sẽ gặp ký pháp `^^` bất thường của TeX, được biết đến như “cơ chế ký tự mở rộng”. Nó được Knuth thiết kế như một cách để hỗ trợ gõ các “ký tự điều khiển” như ký tự kết thúc dòng, tab, v.v. Ví dụ:

* `^^J` biểu thị mã ký tự 10 (`\n`, line feed);
* `^^M` biểu thị mã ký tự 13 (`\r`, carriage return).

Các chuỗi ký tự như `^^M` được chuyển thành các mã ký tự tương ứng ngay từ đầu trong quá trình quét đầu vào của TeX, khi TeX đang đọc các ký tự đầu vào để tạo ra các token ký tự tương ứng.

### Thay đổi ký tự được gán cho \endlinechar

Ghi nhớ rằng ta vẫn cần ngăn việc mở rộng của `~` ký tự, ta có thể viết

```
\begingroup
\endlinechar=10 % Đổi ký tự kết thúc dòng thành \n
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x không bằng 4")
   end
}% không muốn \n xuất hiện ở đây
\endgroup% hoặc một \n ở đây
```

Thiết lập trên cho `\endlinechar` khiến LuaTeX thêm mã ký tự 10 (`\n`, line feed) vào cuối mỗi dòng nó đọc vào. Ta làm vậy vì `\n` (line feed) thường có mã danh mục 12, điều này bạn có thể kiểm tra bằng cách viết ``\the\catcode`\^^J``. Vì `\n` không có mã danh mục 5, LuaTeX sẽ không chuyển nó thành ký tự khoảng trắng nên nó vẫn ở cuối mỗi dòng được LuaTeX đọc vào. Điều này khiến một ký tự có mã 10 vẫn còn ở cuối mỗi dòng, nhờ đó đi vào danh sách token đang được xây dựng bởi `\directlua` và sau đó xuất hiện trở lại trong mã Lua khi danh sách token được chuyển thành văn bản. Với thay đổi trên, mã Lua được gửi tới bộ thông dịch Lua dưới chuỗi ký tự sau:

**\n**local x=3\*\*\n**if x \~= 4 then**\n**print("x không bằng 4")**\n**end**\n\*\*

trong đó **\n** ký pháp này nhằm biểu diễn mã ký tự 10 *không* một macro nào đó chưa biết `\n`. Giờ thì bộ thông dịch Lua sẽ thấy các ngắt dòng trong mã, đúng như nó đã được viết ban đầu trong `\directlua` lệnh:

```
   local x=3
   if x ~= 4 then
   print("x không bằng 4")
   end
```

Nhân tiện, hãy lưu ý rằng ký tự đầu tiên trong chuỗi mã Lua là `\n` (trước từ khóa `cục bộ` ). Ký tự đó `\n` phát sinh từ dòng

`\directlua{`

vì có một ngắt dòng ngay sau dấu mở `{` và điều này cũng được giữ nguyên. Để ngăn điều đó, bạn có thể viết

`\directlua{%`

### Thay đổi mã danh mục của \r

Để giữ các ngắt dòng trong mã Lua, ta cũng có thể thay đổi mã danh mục của `\r` thành một giá trị khác 5, để `\r` không còn được nhận dạng (coi là) ký tự kết thúc dòng nữa. Với kỹ thuật này, LuaTeX vẫn dùng `\endlinechar=13` và sẽ tiếp tục thêm một `\r` vào cuối mỗi dòng; tuy nhiên, vì `\r` không còn có mã danh mục 5, LuaTeX sẽ không nhận ra `\r` ký tự đó là ký tự kết thúc dòng: nó sẽ không chuyển nó thành khoảng trắng và sẽ truyền qua nguyên vẹn để xuất hiện trong mã Lua.

Ghi nhớ rằng ta vẫn cần ngăn việc mở rộng của `~` ký tự, ta có thể viết

```
\begingroup
\catcode`\^^M=12 % đổi mã danh mục của \r thành 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x không bằng 4")
   end
}
\endgroup
```

Trong trường hợp này, mã Lua được gửi tới bộ thông dịch Lua dưới dạng:

**\r**local x=3\*\*\r**if x \~= 4 then**\r**print("x không bằng 4")**\r**end**\r\*\*

trong đó `\r` ký pháp này nhằm biểu diễn mã ký tự 13 chứ không phải một macro nào đó chưa biết `\r`. Cũng như ở `\endlinechar` ví dụ, giờ bộ thông dịch Lua sẽ thấy các ngắt dòng trong mã, đúng như nó đã được viết ban đầu trong `\directlua` lệnh:

```
   local x=3
   if x ~= 4 then
   print("x không bằng 4")
   end
```

Nhân tiện, cũng lưu ý rằng ký tự đầu tiên trong chuỗi mã Lua là `\r` (trước từ khóa local): điều này cũng phát sinh từ dòng

`\directlua{`

#### Vì sao \r dùng mã danh mục 12 mà không phải mã danh mục 11?

Câu trả lời là do nguy cơ vô tình gây ra lỗi do `\r` (có mã danh mục 11) được thêm vào cuối các lệnh TeX/LaTeX được đọc từ tệp đầu vào của chúng ta. Hãy xem ví dụ này:

```
\begingroup
\catcode`\^^M=11 % đổi mã danh mục của \r thành 11
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x không bằng 4")
   end
}
\endgroup
```

và nó tạo ra lỗi:

```
   ! Undefined control sequence.
   l.9 \endgroup
```

Làm sao điều đó có thể đúng vì `\endgroup` là một lệnh nguyên thủy chuẩn của TeX? Nguyên nhân của lỗi khá tinh vi: Khi LuaTeX đọc dòng văn bản cuối cùng—dòng chứa `\endgroup`—nó cũng thêm `\endlinechar` ký tự `\r` vào cuối dòng đó. Giờ đây, trong bộ nhớ của nó, LuaTeX thấy chuỗi ký tự

`\endgroup\r`

trong đó ta dùng `\r` để chỉ ký tự có mã 13—chứ không phải tên của một macro TeX chưa biết nào đó `\r`.

Vào thời điểm LuaTeX đọc dòng này từ tệp văn bản của chúng ta, trạng thái ban đầu `\begingroup` vẫn còn hiệu lực: ta đang ở bên trong một nhóm chưa được đóng lại bằng cách thực thi lệnh tương ứng `\endgroup` lệnh—điều này sẽ khiến `\r` trở lại giá trị mã danh mục trước đó là 5.

Khi LuaTeX bắt đầu xử lý (tạo token) từ dòng văn bản `\endgroup\r` nó nhận ra ký tự đầu tiên `\` là ký tự thoát, kích hoạt LuaTeX bắt đầu tìm tên của một lệnh. Để xác định tên lệnh, LuaTeX tìm một chuỗi ký tự có mã danh mục 11 nhưng vì `\r` cũng có mã danh mục 11 nên LuaTeX nghĩ rằng ký tự `\r` (vẫn có mã danh mục 11) tạo thành *một phần của lệnh* có tên `\endgroup\r` mà dĩ nhiên không tồn tại, nên LuaTeX báo một `Chuỗi điều khiển không được định nghĩa` lỗi. Đó là lý do ta dùng mã danh mục 12 chứ không phải 11.

Vì thông báo lỗi của LuaTeX được ghi ra console nên ta không thể dễ dàng thấy/nhận ra `\r` ký tự đó, nên không rõ điều gì đã gây ra lỗi.

### Vì sao ta phải bận tâm về các ký tự kết thúc dòng?

Lý do là để cho phép dùng phương pháp chú thích của Lua trong mã của bạn! Bạn có thể dùng cơ chế chuẩn của LuaTeX là thêm `%` ký tự để chú thích bỏ qua từng dòng trong mã của bạn; tuy nhiên, ngôn ngữ Lua có cơ chế riêng, rất hữu ích, *nhiều dòng* để chú thích mà bạn có thể muốn tận dụng.

Hãy bắt đầu bằng việc xem điều gì xảy ra nếu ta cố dùng chú thích một dòng của Lua mà không xử lý vấn đề ngắt dòng. Trong khi TeX dùng ký tự `%` để chú thích bỏ qua các dòng mã đơn lẻ, Lua dùng hai dấu gạch ngang: `--`.

Điều gì xảy ra nếu ta thử chạy đoạn này:

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- Tôi sẽ xuất kết quả của phép kiểm tra phức tạp này
   print("x không bằng 4")
   end
}
```

Ta nhận được lỗi:

`[\directlua]:1: dự kiến 'end' gần <eof>`

Lỗi này do không có ngắt dòng trong mã Lua được chuyển cho bộ thông dịch, nên nó chỉ thấy một chuỗi liên tục duy nhất trong đó chú thích bắt đầu ở giữa chuỗi đó:

```

local x=3 if x ~= 4 then -- Tôi sẽ xuất kết quả của phép kiểm tra phức tạp này print("x không bằng 4") end
```

Mọi thứ sau `**local x=3 if x ~= 4 then**` được coi là bị chú thích bỏ qua, khiến bộ thông dịch thấy một khối mã Lua không hoàn chỉnh, dẫn tới lỗi

`dự kiến 'end' gần <eof>`.

trong đó `<eof>` có nghĩa là kết thúc tệp.

Như bạn có lẽ đã đoán, ta phải khắc phục điều này bằng cách bảo đảm các ngắt dòng được truyền sang mã Lua kết quả, và ta có thể làm được điều đó bằng cách, chẳng hạn, thay đổi mã danh mục của `\r` thành 12:

```
\begingroup
\catcode`\^^M=12 % đổi mã danh mục của \r thành 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- Tôi sẽ xuất kết quả của bài kiểm tra phức tạp này
   print("x không bằng 4")
   end
}
\endgroup
```

Bây giờ, trình thông dịch Lua thấy một chuỗi nhưng nó chứa `\r` các ngắt dòng như được viết trong `\directlua` đoạn:

**\r**local x=3\*\*\r**if x \~= 4 then**\r\*\*-- Tôi sẽ xuất kết quả của bài kiểm tra phức tạp này\*\*\r**tex.print("x không bằng 4")**\r**end**\r\*\*

Thực chất, điều này tương đương với việc viết

```
   local x=3
   if x \noexpand~= 4 then
   -- Tôi sẽ xuất kết quả của bài kiểm tra phức tạp này
   print("x không bằng 4")
   end
```

điều đó có nghĩa là Lua có thể xử lý chính xác đoạn mã này và bỏ qua dòng mà chúng ta đã chú thích.

**Chú thích khối**

Ngôn ngữ Lua cũng hỗ trợ một cú pháp mà nó gọi là [“chú thích khối”](https://www.lua.org/pil/1.3.html) (hoặc *chú thích dài*): những chú thích này bắt đầu bằng `--[[` và có hiệu lực cho đến `]]`tương ứng. Chúng ta có thể dùng cú pháp tiện lợi này để viết chú thích nhiều dòng, hoặc chú thích các phần mã mà chúng ta muốn tạm thời loại bỏ:

```
\begingroup
\catcode`\^^M=12 % đổi mã danh mục của \r thành 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   --[[ Tôi sẽ xuất kết quả của bài kiểm tra phức tạp này
   đơn giản vì nó thực sự là
   một kết luận thật đáng kinh ngạc]]
   print("x không bằng 4")
   end
}
\endgroup
```

## Kết luận

Trước hết, xin chúc mừng nếu bạn đã đọc hết bài viết đồ sộ này! Chúng tôi đã cố gắng tạo ra một hướng dẫn tương đối toàn diện về các khái niệm và chủ đề liên quan đến TeX, cung cấp nền tảng cần thiết để khai thác tối đa LuaTeX thông qua `\directlua` lệnh. [liên hệ với chúng tôi](https://www.overleaf.com/contact) với các bình luận về bài viết này hoặc đề xuất về những chủ đề khác mà bạn muốn chúng tôi viết.

Chúc bạn $$\text{Lua}\mathrm{\TeX}\text{-ing!}$$ từ Graham Douglas và nhóm Overleaf.

### Và cuối cùng... chỉ cần dùng gói luacode

Mặc dù TeX và Lua hoạt động theo những cách hoàn toàn khác nhau, hai ngôn ngữ này chia sẻ một số ký tự có “ý nghĩa đặc biệt” trong ngữ cảnh của mỗi ngôn ngữ—chẳng hạn như \\\\, %, \~, #, ^, &—dĩ nhiên, Lua và TeX gán những ý nghĩa đặc biệt đó cho *rất* mục đích khác nhau. Việc tìm hiểu các ký tự gây rắc rối cho thấy vì sao có thể phát sinh khó khăn và cách bạn có thể giải quyết chúng; tuy nhiên, việc tự sửa thủ công nhiều đoạn mã Lua nhỏ có thể khá tốn công nên đa số người dùng thích dùng các gói LaTeX loại bỏ những thách thức đó. Một gói như vậy là [`luacode`](https://ctan.org/pkg/luacode?lang=en) cung cấp một bộ tính năng được thiết kế để đơn giản hóa việc làm việc với `\directlua`, nhưng ít nhất giờ đây bạn có thể hiểu rõ hơn các vấn đề `luacode` mà nó giải quyết cho bạn.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/vi/bai-viet-chuyen-sau/09-an-introduction-to-luatex-part-2-understanding-directlua.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
