El lenguaje
Vaca usa llaves { } y palabras clave en español. No depende de la
indentación y el punto y coma es opcional en todo el lenguaje.
# Un programa completo y pequeño
importar tiempo
fn saludar(nombre) {
retornar a"Hola, {nombre}"
}
imprimir(saludar("Mundo"))
imprimir(a"Hoy es {tiempo.fecha_iso()}")
Variables y constantes
Una asignación declara la variable si no existe en ningún ámbito visible.
nombre = "Ada"
edad = 36
activo = verdadero
Las anotaciones de tipo son opcionales y puramente informativas. El motor no comprueba tipos:
contador: Entero = 0
precio: Decimal = 19.99
mensaje: Texto = "hola"
Una variable anotada sin = se inicializa en nulo:
pendiente: Texto
imprimir(pendiente) # nulo
Las constantes se declaran con constante y no se pueden reasignar. Su
nombre debe ir en MAYÚSCULAS; el analizador rechaza cualquier nombre con
minúsculas:
constante LIMITE = 100
constante AÑO = 2026
# Esto falla:
# constante Limite = 100
# LIMITE = 200
Tipos
| Tipo | Ejemplos | Notas |
|---|---|---|
Entero |
42, -7 |
64 bits con signo |
Decimal |
3.14, -0.5 |
coma flotante de 64 bits |
Texto |
"hola", a"Hola {nombre}" |
UTF-8 |
Logico |
verdadero, falso |
booleanos |
Nulo |
nulo |
ausencia de valor |
Lista |
[1, 2, 3] |
secuencia mutable |
Diccionario |
{"clave": "valor"} |
mapa texto → valor |
Funcion |
fn(x) { ... } |
función o cierre |
Clase / Instancia |
nuevo Perro() |
objetos |
Tarea |
resultado de async fn |
se espera con esperar |
tipo(valor) devuelve el nombre del tipo en tiempo de ejecución:
imprimir(tipo(42)) # Entero
imprimir(tipo(3.14)) # Decimal
imprimir(tipo("hola")) # Texto
imprimir(tipo([1, 2])) # Lista
Coerción numérica
Los enteros y decimales se mezclan sin conversión explícita. Si alguno de los dos es decimal, el resultado es decimal:
imprimir(10 + 2.5) # 12.5
imprimir(10 == 10.0) # verdadero
La división entre enteros devuelve un entero truncado; usa un decimal para obtener fracción:
imprimir(7 / 2) # 3
imprimir(7 / 2.0) # 3.5
Texto e interpolación
El prefijo a activa la interpolación de expresiones entre llaves:
nombre = "Ada"
edad = 36
imprimir(a"{nombre} tiene {edad} años")
imprimir(a"El doble de 21 es {21 * 2}")
Dentro de la interpolación puedes usar variables, operadores, llamadas y accesos a miembros:
persona = {"nombre": "Ada", "pais": "Reino Unido"}
imprimir(a"{persona.nombre} vive en {persona.pais}")
Escapes disponibles: \n, \t, \", \\ y \{.
imprimir("Línea 1\nLínea 2")
imprimir("Comillas: \"hola\"")
Métodos de texto
Además de las funciones del módulo cadenas, cada texto tiene métodos:
| Método | Descripción |
|---|---|
longitud() |
Número de caracteres |
mayusculas() / minusculas() |
Cambia el caso |
recortar() |
Quita espacios de los extremos |
contiene(texto) |
Lógico |
empieza_con(prefijo) / termina_con(sufijo) |
Lógico |
dividir(separador) |
Lista de partes |
separar_lineas() |
Lista de líneas |
reemplazar(viejo, nuevo) |
Texto nuevo |
subtexto(inicio, fin) |
Copia un rango; fin exclusivo |
indice_de(texto) |
Posición o -1 |
repetir(veces) |
Repite el texto |
invertir() |
Invierte los caracteres |
caracteres() |
Lista de caracteres |
a_entero() / a_decimal() / a_logico() |
Conversión |
frase = " Vaca habla español "
limpia = frase.recortar()
imprimir(limpia.mayusculas()) # VACA HABLA ESPAÑOL
imprimir(limpia.dividir(" ")) # ["Vaca", "habla", "español"]
imprimir(limpia.reemplazar("Vaca", "Rust"))
Métodos de número
| Método | Descripción |
|---|---|
absoluto() |
Valor absoluto |
redondear(decimales = 0) |
Redondeo |
piso() / techo() |
Suelo y techo |
a_texto() |
Convierte a texto |
imprimir((-3).absoluto()) # 3
imprimir((3.14159).redondear(2)) # 3.14
imprimir((3.9).piso()) # 3
imprimir((3.1).techo()) # 4
Operadores
| Categoría | Operadores |
|---|---|
| Aritmética | +, -, *, /, % |
| Comparación | ==, !=, <, >, <=, >= |
| Lógica | y, o, no |
| Asignación compuesta | +=, -=, *=, /= |
| Indexación | lista[i], texto[i], dict["clave"] |
| Acceso | objeto.campo, modulo.funcion |
Precedencia, de mayor a menor:
- Llamadas, miembros e índices.
- Unarios:
no,-,+,esperar. *,/,%.+,-.- Comparaciones.
y.o.
y y o hacen cortocircuito: el lado derecho solo se evalúa si hace falta.
si lista.longitud() > 0 y lista[0] == "primero" {
imprimir("coincide")
}
+ concatena textos y listas:
imprimir("hola" + " " + "mundo")
imprimir([1, 2] + [3, 4]) # [1, 2, 3, 4]
Verdad y falsedad
Son falsos: falso, nulo, 0, 0.0, el texto vacío, la lista vacía y el
diccionario vacío. Todo lo demás es verdadero.
si no "" {
imprimir("el texto vacío es falso")
}
Conversiones y builtins
| Función | Qué hace |
|---|---|
entero(valor) |
Convierte a Entero (texto, decimal o lógico) |
decimal(valor) |
Convierte a Decimal |
texto(valor) |
Convierte a Texto |
logico(valor) |
Aplica la verdad de Vaca |
longitud(valor) |
Longitud de texto, lista o diccionario |
dividir(texto, separador) |
Divide un texto y devuelve una lista |
tipo(valor) |
Nombre del tipo |
rango(inicio, fin, paso) |
Lista de enteros; fin es exclusivo |
copiar(valor) |
Copia superficial de listas y diccionarios |
afirmar(condicion, mensaje) |
Lanza si la condición es falsa |
error(valor) |
Lanza un error |
leer(prompt) |
Lee una línea de la entrada estándar |
imprimir(...) |
Imprime valores separados por un espacio |
rango es exclusivo en el final, a diferencia de para ... hasta:
imprimir(rango(3)) # [0, 1, 2]
imprimir(rango(1, 4)) # [1, 2, 3]
imprimir(rango(5, 0, -1)) # [5, 4, 3, 2, 1]
Condicionales
si edad >= 18 y edad <= 65 {
imprimir("en edad laboral")
} sino si edad < 18 {
imprimir("menor")
} sino {
imprimir("jubilado")
}
Las condiciones no necesitan paréntesis. Los bloques siempre usan llaves.
Bucles
mientras
contador = 0
mientras contador < 5 {
imprimir(contador)
contador = contador + 1
}
para ... hasta
El límite es inclusivo. El paso es opcional y puede ser negativo:
para i = 1 hasta 5 {
imprimir(i) # 1, 2, 3, 4, 5
}
para i = 10 hasta 0 paso -2 {
imprimir(i) # 10, 8, 6, 4, 2, 0
}
para ... en
Recorre listas, textos (carácter a carácter) y diccionarios (sus claves, ordenadas):
para fruta en ["pera", "uva", "higo"] {
imprimir(fruta)
}
para letra en "vaca" {
imprimir(letra)
}
para clave en {"b": 2, "a": 1} {
imprimir(clave) # a, b
}
romper y continuar
para i = 1 hasta 10 {
si i == 3 { continuar }
si i == 6 { romper }
imprimir(i) # 1, 2, 4, 5
}
Funciones
fn sumar(a, b) {
retornar a + b
}
imprimir(sumar(2, 3)) # 5
- Las funciones se hoisten: puedes llamar a una función declarada más abajo en el mismo ámbito.
- Sin
retornar, una función devuelvenulo. - Una función puede llamarse a sí misma (recursividad).
Parámetros por defecto
fn saludar(nombre, idioma = "ES") {
si idioma == "EN" {
retornar a"Hello, {nombre}"
}
retornar a"Hola, {nombre}"
}
imprimir(saludar("Ada"))
imprimir(saludar("Ada", "EN"))
Funciones anónimas y closures
doble = fn(x) { retornar x * 2 }
imprimir(doble(21)) # 42
fn crear_contador() {
cuenta = 0
retornar fn() {
cuenta = cuenta + 1
retornar cuenta
}
}
siguiente = crear_contador()
imprimir(siguiente()) # 1
imprimir(siguiente()) # 2
Callbacks
fn ejecutar(accion, valor) {
retornar accion(valor)
}
imprimir(ejecutar(doble, 5)) # 10
imprimir(ejecutar(fn(x) { retornar x * x }, 5)) # 25
Ámbito
Cada bloque { } crea un ámbito. Las variables declaradas dentro no existen
fuera; las asignaciones a variables exteriores sí las modifican.
total = 0
si verdadero {
total = 10
_interna = 99
}
imprimir(total) # 10
# imprimir(_interna) # error: no está definida
Funciones async
Una función async arranca en un hilo y devuelve una tarea. esperar
bloquea hasta obtener el resultado:
async fn consultar(nombre, ms) {
esperar tiempo.dormir(ms)
retornar a"{nombre} lista"
}
a = consultar("A", 200)
b = consultar("B", 200)
resultado_a = esperar a
resultado_b = esperar b
Consulta HTTP y concurrencia para el modelo completo, los contadores atómicos y los límites.
Listas
numeros = [3, 1, 2]
mixta = [1, "dos", verdadero, nulo]
vacia = []
Los índices empiezan en 0. Los índices negativos cuentan desde el final:
imprimir(numeros[0]) # 3
imprimir(numeros[-1]) # 2
Métodos de lista
| Método | Descripción |
|---|---|
agregar(valor) |
Añade al final |
insertar(indice, valor) |
Inserta en una posición |
eliminar(indice) |
Elimina y devuelve el elemento |
pop() |
Elimina y devuelve el último |
limpiar() |
Vacía la lista |
longitud() |
Número de elementos |
vacia() |
verdadero si está vacía |
unir(otra) |
Devuelve una lista nueva concatenada |
sublista(inicio, fin) |
Copia un rango; fin exclusivo |
invertir() |
Invierte en el sitio |
ordenar() / ordenar(fn) |
Ordena en el sitio con comparador opcional |
contiene(valor) |
Busca un valor |
indice_de(valor) |
Índice o -1 |
mapear(fn) |
Transforma y devuelve una lista nueva |
filtrar(fn) |
Conserva los que cumplen |
reducir(fn, inicial) |
Acumula de izquierda a derecha |
para_cada(fn) |
Ejecuta por cada elemento |
buscar(fn) |
Primer elemento que cumple, o nulo |
unir_texto(separador) |
Junta los elementos como texto |
suma() / promedio() |
Solo para números |
copiar() |
Copia superficial |
numeros = [3, 1, 2]
numeros.agregar(4)
numeros.ordenar()
imprimir(numeros) # [1, 2, 3, 4]
pares = numeros.filtrar(fn(n) { retornar n % 2 == 0 })
dobles = numeros.mapear(fn(n) { retornar n * 2 })
total = numeros.reducir(fn(acc, n) { retornar acc + n }, 0)
imprimir(pares) # [2, 4]
imprimir(dobles) # [2, 4, 6, 8]
imprimir(total) # 10
Diccionarios
persona = {
"nombre": "Ada",
"edad": 36,
"lenguajes": ["Vaca", "Rust"]
}
Se accede con corchetes o con punto. Una clave inexistente devuelve nulo
en ambos casos; en una instancia, un campo inexistente sí es un error:
imprimir(persona["nombre"]) # Ada
imprimir(persona.nombre) # Ada
imprimir(persona["falta"]) # nulo
imprimir(persona.falta) # nulo
Métodos de diccionario
| Método | Descripción |
|---|---|
obtener(clave, defecto) |
Valor o defecto (nulo si se omite) |
set(clave, valor) |
Inserta o actualiza |
incrementar(clave, cantidad) |
Suma atómicamente a un entero |
existe(clave) |
verdadero si la clave existe |
eliminar(clave) |
Elimina y devuelve el valor |
longitud() |
Número de pares |
llaves() |
Lista de claves ordenadas |
valores() |
Lista de valores en orden de clave |
items() |
Lista de pares [clave, valor] |
unir(otro) |
Devuelve un diccionario combinado |
limpiar() |
Vacía el diccionario |
copiar() |
Copia superficial |
visitas = {}
visitas.incrementar("total")
visitas.incrementar("total", 2)
imprimir(visitas.obtener("total")) # 3
imprimir(visitas.llaves()) # ["total"]
para clave en visitas {
imprimir(a"{clave} = {visitas.obtener(clave)}")
}
Clases y POO
clase Animal {
fn iniciar(nombre) {
este.nombre = nombre
}
fn hablar() {
lanzar "cada animal debe implementar hablar"
}
}
clase Perro(Animal) {
fn iniciar(nombre, raza) {
este.nombre = nombre
este.raza = raza
}
fn hablar() {
retornar a"{este.nombre} dice guau"
}
}
perro = nuevo Perro("Toby", "mestizo")
imprimir(perro.hablar())
imprimir(perro.raza)
iniciares el constructor y se llama automáticamente connuevo.estees el receptor dentro de un método.clase Hijo(Padre)hereda propiedades y métodos.- Los métodos se resuelven dinámicamente en la instancia.
lanzaren la clase base sirve como contrato abstracto.- No existe
super: si necesitas la lógica del padre, factorízala en una función o vuelve a implementarla. - El constructor no puede ser
async.
Excepciones
fn dividir(a, b) {
si b == 0 {
lanzar {"codigo": "DIV_CERO", "mensaje": "no se puede dividir"}
}
retornar a / b
}
intentar {
imprimir(dividir(10, 2))
imprimir(dividir(1, 0))
} atrapar error {
imprimir(a"Error {error.codigo}: {error.mensaje}")
}
lanzarpuede lanzar cualquier valor: texto, diccionario, lista, etc.error("mensaje")es un atajo para lanzar un texto.intentarcapturalanzar,error(...),afirmar(...)y errores de ejecución (índices, variables inexistentes, aridad, etc.).sistema.salir(codigo)no se captura: termina el proceso.- El bloque
atraparpuede omitir la variable:} atrapar { ... }.
intentar {
afirmar(1 == 2, "las matemáticas están rotas")
} atrapar error {
imprimir(a"Capturado: {error}")
}
Importaciones
importar json
importar mate como mat
importar "util.vaca" como util
- Los módulos nativos se importan por nombre.
comopermite usar un alias.- Los archivos
.vacase importan entre comillas y exponen sus funciones, constantes y variables públicas. - Las importaciones se cachean por ruta y se detectan los ciclos.
- Un módulo de archivo puede importar otros módulos.
Comentarios y estilo
# Comentario de una línea
// También de una línea
/*
Comentario
de bloque
*/
- Las llaves son obligatorias para los bloques.
- El punto y coma es opcional.
- Las constantes son inmutables y su nombre debe ir en
MAYÚSCULAS; el analizador lo comprueba antes de ejecutar. - El analizador rechaza variables locales declaradas y nunca usadas. Usa
_para descartar un valor o un nombre que empiece por_para silenciar la comprobación.
Siguiente paso
La biblioteca estándar está documentada en Módulos nativos. Para ver programas completos, consulta Ejemplos de uso.