El lenguaje BASIC

El dialecto BASIC clásico de aukimi: tipos, estructuras, los dos atajos, y las reglas que atrapan a todo el mundo. 2 237 comandos detrás de él.

En una frase#

Aukimi trae un dialecto BASIC clásico: un lenguaje completo, no un sistema de macros de juguete, con numéricos tipados, records, métodos, referencias, errores estructurados, corrutinas y compute por WebGPU. Vive en el modo de viewport TXT del Engine y en la vista BASIC del editor de scripts.

JavaScript sigue siendo la API canónica de scripting. BASIC es un frontend opcional: ambos compilan al mismo dispatcher privado, así que un programa en BASIC y uno en JavaScript llegan exactamente al mismo motor. Nada es de segunda clase, y nada es un runtime aparte.

La regla que atrapa a todo el mundo#

Una llamada a un comando siempre lleva paréntesis.

mode = ClipSpaceMode()
SpritePosition(player, 320, 180)

Esto no es una cuestión de estilo. Es lo que le permite al compilador distinguir un comando de una variable, y es por lo que una falta de ortografía ahora se detecta en tiempo de compilación:

EscribesQué pasaba antesQué pasa ahora
SpritePositon(1, 10, 20)compilaba sin problemas, el sprite no se movía, y el fallo salía a la luz más tarde como ... is not definederror de compilación, con la ortografía catalogada más cercana sugerida
Blorp(1, 2)se reenviaba tal cual al JavaScript generadoerror de compilación
ceci nest pas du basicse emitía como una sentenciaerror de compilación

Un error de escritura solía presentarse como un bug del motor. Medido contra el corpus de 84 demos, la protección produjo cero falsos positivos y encontró dos fallos silenciosos reales, SetMusicSystemVolume y GetLightSensorExists, que el catálogo solo conoce como MusicSystemVolume y LightSensorExists.

Dos fugas se dejan abiertas a propósito: console.log("x") y window.x = 1 siguen pasando. Se escribió, se midió y se retiró una protección para ellas: BASIC se ejecuta dentro de un contexto que aporta nombres que el compilador no puede enumerar, así que rechazar una raíz desconocida rechazaba programas válidos. Un falso positivo ahí es peor que la fuga.

La sorpresa más común de todas:

Print(...)dibuja en la pantalla, y un programa lo llama en cada fotograma
Console(...)escribe en la consola del editor, y no hace nada más
Debug(...)el mismo comando bajo el nombre de PureBasic, para la memoria muscular
ConsoleWarning, ConsoleError, ClearConsoleel resto del registro

Print en un bucle de fotogramas ahogaría cualquier error real en la consola, por eso son dos comandos separados en lugar de uno con un flag.

Tipos#

Los tipos numéricos son reales, y se comportan igual en cada destino, el editor, la exportación HTML y la build Bevy/nativa:

  • Enteros con y sin signo de 8, 16, 32 y 64 bits, con coerción determinista, DIV entero, y desbordamiento por wrapping.
  • float32 y float64.
  • Los sigilos clásicos siguen funcionando: name$ es una cadena, speed# un float.

OPTION EXPLICIT es opcional, y las variables implícitas clásicas siguen siendo el comportamiento por defecto para que los programas importados sigan funcionando. Activarlo es el requisito previo para las dos funciones que no pueden ser seguras sin ello, el tipado numérico y las referencias, porque una asignación mal escrita que crea una variable en silencio derrota a ambas.

Estructuras#

Records#

TYPE declara un record. Se anidan de forma arbitraria, y tienen semántica de valor: asignar uno hace una copia profunda, incluidos los arrays y colecciones anidados.

Métodos#

METHOD vincula una función a una instancia, con SELF dentro de ella, New para construir y Delete para destruir. Las copias profundas siguen siendo seguras para los métodos.

Referencias#

REF y BYREF son la misma cosa bajo dos nombres, y ambas dan un parámetro de lectura/escritura en vivo, para un escalar, un record, un campo individual, un array, un cursor de lista o una entrada de map. Las referencias se propagan a través de las llamadas, y se ajustan a tipos de ancho fijo en el límite.

Handles#

@target toma una referencia de primera clase. HANDLE y POINTER son las formas tipadas, con lectura, escritura, validez y liberación comprobadas, y un handle a un record eliminado se invalida en lugar de quedar colgando.

Control y seguridad#

Errores estructurados: TRY / CATCH / FINALLY, anidados, con THROW, RETHROW protegido, y capturas tanto estructuradas como de cadena.

Compilación condicional: #IF / #ELSEIF / #ELSE / #ENDIF, anidados, con feature flags y perfiles explícitos para el editor, HTML, Bevy, nativo/wasm y debug/release. Se resuelve antes de los includes, en cada destino.

Funciones ricas: valores por defecto, OPTIONAL, un PARAMARRAY final (o ...), sobrecargas de aridad seguras, coerción tipada del retorno, y retornos de record y array clonados.

Programas multiarchivo#

#INCLUDE, #INSERT e IncludeFile convierten un programa en varios archivos. El editor crea e importa módulos de proyecto, completa las rutas de include, mantiene el grafo de dependencias, y cada destino de ejecución está preparado para ello.

Corrutinas, y lo que se dejó fuera a propósito#

Las tareas cooperativas están basadas en etiquetas, con handles tipados, un estado de resultado y de error, cancelación en los yields, y awaits sobre una tarea o un grupo. Comparten el scheduler del juego en cada destino.

Los hilos con memoria compartida se excluyen a propósito. No es "todavía no", es excluido. Un scheduler cooperativo que puedes razonar le gana a una condición de carrera que no puedes reproducir.

Compute por WebGPU#

Buffers de storage tipados f32 / i32 / u32, pipelines WGSL en bruto, dispatch ordenado por fotograma y lectura de memblock, compute real de GPU desde BASIC.

⚠️ Solo en Engine. El destino HTML lanza un error de capacidad explícito, y el destino Bevy informa de la incompatibilidad antes de la build en lugar de producir un binario que falla en el primer dispatch.

Dos atajos para el mismo problema#

597 de los comandos se llaman Set/Get<Object><Property>: Sprite 66, Tween 58, Text 32, Edit 29. El nombre del objeto está dentro del nombre de la función y su id es el primer argumento de cada línea. Ambos atajos de abajo eliminan esa repetición, y ninguno es una API nueva: el compilador reconstruye el nombre real del comando y lo busca en el catálogo.

WITH <family>, dentro de un bloque#

WITH SPRITE s
  \position 100, 200      ` SpritePosition(s, 100, 200)
  \size 64, 64            ` SpriteSize(s, 64, 64)
  x# = \x                 ` SpriteX(s)
ENDWITH

Al inicio de una sentencia, \prop escribe; en cualquier otro sitio lee. La posición decide, así que no hay una segunda notación que aprender. WITH hero, un solo token, sigue siendo el bloque de record; WITH SPRITE s selecciona una familia del catálogo.

s.position(...), en todo lo demás#

s = CreateSprite(img)     ` la familia se DEDUCE del comando de creación
s.position(100, 200)      ` SpritePosition(s, 100, 200)
s.visible = 1             ` SpriteVisible(s, 1)
x# = s.x                  ` SpriteX(s)

La familia viene de Create<F> / Load<F> / Clone<F>, o de un s AS Sprite explícito cuando el id llega de otro sitio. Solo se reescriben las variables cuya familia es conocida: los records mantienen sus campos, las listas mantienen sus métodos, SELF.field no se toca.

⚠️ Un parámetro tipado no se reconoce. FUNCTION move(s AS Sprite) no le da a s una familia, la pasada solo lee declaraciones y asignaciones a nivel de sentencia. Se indica aquí porque es un límite real, no un bug que haya que cazar.

⚠️ El catálogo deja caer el verbo en las propiedades, y lo conserva en el ciclo de vida. SpritePosition escribe, SpriteX lee, y ninguno lleva un Set o un Get: 4 comandos en todo el catálogo empiezan por Set, y ninguno empieza por Get. El verbo sobrevive donde algo nace o termina: CreateSprite, LoadImage, DeleteSprite.

Vale la pena saberlo porque una falta de ortografía es un error de compilación, y Set… es la falta de ortografía con la que llega la gente. El catálogo es la autoridad, y la referencia se genera a partir de él.

Interpolación de cadenas, opcional#

Print($"score: {points}")        ` "score: " + Str(points)

{{ y }} escriben llaves literales.

⚠️ El prefijo $ es obligatorio, y no es decoración. Interpolar cada cadena literal rompía el JSON escrito a mano, que la API del mundo usa habitualmente:

CreateTerrain("island", "{\"preset\":\"island\",\"size\":256}")

Ahí las llaves son datos, no huecos. Una cadena sin el prefijo se deja intacta sea lo que sea que contenga.

Nombrar es mostrar#

Un programa clásico crea cientos de objetos. Solo los que NOMBRA entran al Scene Graph, SpriteName(id, "player") y sus equivalentes son lo que pone un objeto en el árbol donde el resto del Engine puede verlo. LightName y ParticlesName hacen lo mismo para una luz y un emisor.

El tutorial Nombra lo que tu programa crea lo recorre paso a paso, la misma escena, una vez con el árbol vacío y otra con tres entradas en él.

El catálogo de comandos#

2 237 comandos, 2 550 sobrecargas públicas, 43 categorías. Las más grandes:

3D286Studio106File61
Core200Input95Memblock43
Tweening149Input-Raw943D Particles41
3D Physics144Multiplayer91World40
Sprite135Text68Particles38
Platform1112D Physics66Sound37

más JSON (36), Image (35), Skeleton (34), XML (34), Music (29), Preference (27), Extras (21), HTTP (21), String (21), Math (19), Regular Expression (19), Video (18), Dictionary (17), Benchmarking (15), Compute (14), Date (13), Maths (12), Time (12), StringBuilder (9), Compression (7), Font (8), Error (4), Array (3), Cipher (2), Sort (2).

Studio y Compute son las incorporaciones propias de Aukimi al dialecto clásico, los comandos de consola, el nombrado de objetos, y el pipeline de WebGPU.

La referencia completa de comandos los enumera todos, con cada parámetro y cada sobrecarga, y su propio buscador, generado a partir del mismo catálogo que cuenta esta página, así que no puede quedarse atrás. El panel de API propio del editor lee ese mismo catálogo.

Esta página documenta el lenguaje; aquella documenta los comandos.

Límites y problemas frecuentes#

  • Los paréntesis no son opcionales en un comando. La forma desnuda de PureBasic (Debug "x") se rechaza, porque reabriría la ambigüedad variable/comando de la que depende la protección en tiempo de compilación.
  • Una función de usuario gana sobre un comando catalogado del mismo nombre, y una variable puede compartir el nombre de un comando en una declaración tipada, a la izquierda de una asignación, o como raíz de una ruta.
  • Las variables y las funciones de usuario no distinguen mayúsculas de minúsculas, y sus espacios de símbolos son realmente independientes de los del catálogo.
  • Print no es un log. Ver más arriba; es el error que llena una consola.
  • Compute no exporta. Escribe la ruta alternativa antes de depender de ella.