wandres.dev
STRINGS · inmutables e internadas

Sintaxis: comillas, escapes y corchetes dobles

Las tres formas de escribir un literal de cadena en Lua: comillas con secuencias de escape, escapes numéricos y de Unicode, y las cadenas largas con corchetes dobles y niveles. Cuándo usar cada una y qué hace exactamente el analizador léxico con ellas.

⏱ 16 min

Lua ofrece dos sintaxis para escribir un literal de cadena y son deliberadamente distintas, no redundantes. Las comillas —simples o dobles, intercambiables— procesan secuencias de escape: el analizador léxico traduce \n a un byte de salto de línea antes de que exista la cadena. Los corchetes dobles no procesan absolutamente nada: lo que escribes es lo que obtienes, byte a byte, saltos de línea incluidos, y su sistema de niveles permite anidar contenido que a su vez contiene corchetes. Elegir mal entre las dos es la causa habitual de que una expresión regular, una ruta de Windows o un fragmento de código incrustado deje de significar lo que creías.

🎯 Al terminar esta lección sabrás
  • Dominar las secuencias de escape simbólicas, decimales y hexadecimales.
  • Emitir puntos de código Unicode con \u y entender qué produce exactamente.
  • Usar cadenas largas con niveles y conocer las dos reglas que las gobiernan.
  • Decidir con criterio entre comillas y corchetes dobles en cada situación.

Comillas y secuencias de escape

Las comillas simples y las dobles son equivalentes: no existe la distinción entre cadena interpolada y literal que traen otros lenguajes. La única diferencia práctica es cuál de las dos tienes que escapar dentro del literal, así que se elige la que evite escapes.

Dentro de comillas, la barra invertida introduce una secuencia de escape. Las simbólicas son las clásicas de C: \a alarma, \b retroceso, \f avance de página, \n nueva línea, \r retorno de carro, \t tabulador, \v tabulador vertical, más \\, \" y \' para los propios delimitadores.

local a = 'dijo "hola"'          -- comillas simples fuera, dobles dentro
local b = "dijo \"hola\""        -- equivalente, con escapes
local ruta = "C:\\Users\\ada"    -- cada barra invertida se duplica
local fila = "id\tnombre\n"

Hay dos escapes menos conocidos y muy útiles. La barra invertida al final de línea introduce un salto de línea literal en la cadena y permite continuar en la línea siguiente. Y \z descarta toda la secuencia de espacios en blanco que le sigue, saltos de línea incluidos: sirve para partir un literal largo en el fuente sin introducir ni un byte de más.

local mensaje = "primera linea\
segunda linea"                    -- contiene un salto de linea real

local sql = "SELECT id, nombre \z
             FROM usuarios \z
             WHERE activo = 1"    -- una sola linea sin espacios sobrantes

Escapes numéricos y de Unicode

Un escape decimal \ddd emite un byte cuyo valor es el número indicado, con un máximo de tres dígitos. Su hermano hexadecimal \xXX exige exactamente dos dígitos y es el que deberías preferir por legibilidad. Ambos operan a nivel de byte: no saben nada de codificaciones ni de caracteres.

local nulo = "\0"                 -- Lua admite el byte cero dentro de cadenas
print(#"a\0b")                    --> 3
local esc = "\27[1m"              -- secuencia ANSI en decimal
local esc2 = "\x1b[1m"            -- la misma, en hexadecimal
print(esc == esc2)                --> true

Que el byte cero sea un byte más y no un terminador es una diferencia esencial frente a C: una cadena Lua es una secuencia de bytes con longitud explícita, no una secuencia terminada en nulo. Puedes guardar contenido binario íntegro sin trucos.

El escape \u es de otra naturaleza. Recibe un punto de código Unicode entre llaves, en hexadecimal, y emite su codificación UTF-8, que puede ocupar de uno a cuatro bytes. No introduce un tipo carácter en el lenguaje: sigue produciendo bytes, solo que los correctos.

local euro = "\u{20AC}"           -- tres bytes en UTF-8
print(#euro)                      --> 3
print(euro)                       --> €

local acentuada = "caf\u{e9}"     -- dos bytes para la e acentuada
print(#acentuada)                 --> 5
Los corchetes dobles existen porque escapar es una fuente de errores, no una comodidad

Cuando incrustas en un literal algo que ya tiene su propia gramática —una expresión regular, una plantilla, una ruta de Windows, un fragmento de código de otro lenguaje, un bloque de JSON— entras en el terreno del escapado en capas, y ahí es donde el software se rompe de formas difíciles de diagnosticar. Cada nivel de anidamiento duplica las barras invertidas, y una vez que llevas cuatro seguidas nadie en tu equipo puede decir con certeza qué llega al motor de destino sin ejecutarlo. El literal con corchetes dobles elimina el problema de raíz al declarar una zona sin interpretación: el analizador léxico copia bytes hasta encontrar el cierre correspondiente y no toca ni una barra invertida. Y como el propio contenido podría incluir corchetes de cierre, Lua no se conforma con un delimitador fijo: le añade niveles, esos signos igual entre corchetes, de modo que siempre puedes elegir un delimitador que no aparezca en tus datos. Es la misma idea que los delimitadores personalizables de otros lenguajes, resuelta con dos caracteres y sin sintaxis nueva. La consecuencia de diseño va más allá de la comodidad: cuando el sistema de tu programa que consume el texto es otro lenguaje, tu responsabilidad es entregarle exactamente los bytes que el autor escribió, y solo un literal sin interpretación garantiza esa fidelidad. Escapar a mano es aceptable para un tabulador; para un cuerpo de texto es deuda.

Cadenas largas y niveles

Un literal largo se abre con [[ y se cierra con ]]. No procesa escapes, admite saltos de línea reales y tiene una única regla peculiar: si el primer carácter tras la apertura es un salto de línea, se descarta. Esa regla existe para que el texto pueda empezar en la línea siguiente sin introducir una línea vacía inicial.

local plantilla = [[
Hola, %s.
Tu ruta es C:\Users\%s
]]                                -- las barras invertidas son literales

Cuando el contenido incluye ]] —código Lua anidado, arrays de otro lenguaje, documentación sobre el propio Lua— se sube de nivel intercalando signos igual entre los corchetes. La apertura y el cierre deben usar el mismo número de signos, así que siempre existe un nivel libre.

flowchart TD
N0[Nivel 0 abre con dos corchetes] --> P0[No sirve si el texto contiene el cierre]
P0 --> N1[Nivel 1 intercala un signo igual]
N1 --> P1[Sirve salvo que el texto tenga ese mismo cierre]
P1 --> N2[Nivel 2 intercala dos signos igual]
N2 --> OK[Elige el nivel mas bajo que no aparezca en los datos]
local doc = [==[
Ejemplo de cadena larga de nivel 0:
local s = [[texto sin escapes]]
Aqui el cierre interno no termina el literal externo.
]==]

La misma sintaxis de niveles gobierna los comentarios largos, que son un guion doble seguido de un literal largo. Subir de nivel en un comentario permite comentar bloques de código que ya contienen comentarios largos, y desactivar un bloque añadiendo o quitando un guion en la línea de apertura es un truco clásico de depuración.

--[[ bloque desactivado
print("no se ejecuta")
--]]

---[[ basta un guion mas arriba para reactivarlo
print("si se ejecuta")
--]]

Hay dos limitaciones que conviene tener presentes antes de abusar del literal largo. La primera es que no admite escapes en absoluto, ni siquiera cuando los necesitas: si tu texto requiere un tabulador real o un byte de control, tendrás que concatenar un fragmento entre comillas o generarlo con string.char. La segunda es que el contenido queda exactamente como se escribió en el fuente, sangrado incluido; un literal largo indentado dentro de una función arrastra esos espacios a cada línea, y eso rompe formatos sensibles a la columna como YAML o Markdown.

local function config()
  return [[
    clave: valor
    otra: valor
  ]]                              -- cada linea lleva cuatro espacios de mas
end

La solución habitual es escribir el literal pegado al margen izquierdo aunque rompa la estética del bloque, o postprocesarlo eliminando el sangrado común. La primera opción es la que verás en el código real: la fidelidad del dato manda sobre la simetría visual del fuente.

✍️

Comillas para texto corto

Mensajes, claves, formatos y cualquier literal de una línea que necesite escapes simbólicos. Elige la comilla que no aparezca dentro.

🧱

Corchetes para texto ajeno

Plantillas, consultas, rutas de Windows, expresiones regulares y fragmentos de otro lenguaje. Ni una barra invertida que duplicar.

🔢

Escapes numéricos para bytes

Secuencias de control, datos binarios y protocolos. Prefiere el hexadecimal: dos dígitos exactos y correspondencia directa con la documentación.

🌐

El escape de Unicode emite UTF-8

Produce la codificación del punto de código indicado, no un carácter. Sigues manejando bytes, solo que los correctos y sin depender del editor.

⚔️ Cinco literales que deben salir exactos
  1. Escribe una ruta de Windows con cuatro segmentos de dos maneras: con comillas y escapes, y con corchetes dobles. Compara ambas con el operador de igualdad.
  2. Construye la secuencia ANSI que pone el texto en negrita usando \27, \x1b y \u{1b}. Verifica que las tres producen la misma cadena.
  3. Crea una cadena que contenga un byte cero en el centro y comprueba con # y con string.byte que Lua no la trunca.
  4. Escribe un literal largo que contenga otro literal largo de nivel 0 y compruébalo imprimiendo el resultado.
  5. Parte una consulta SQL de cinco líneas usando \z de forma que la cadena final no contenga ningún espacio duplicado, y verifica su longitud.