Runtime Android (forja-android-rt)
Crate: crates/forja-android-rt/ — VM de Forja en Android vía JNI (Rust ↔ Kotlin)
¿Por qué existe?
Lleva la VM de Forja (ForjaFast) a Android. Expone una biblioteca nativa .so con un punto de entrada JNI (System.loadLibrary("forja_android_rt")) que las apps Kotlin/Java usan para compilar, ejecutar y evaluar código Forja, más un Native Activity opcional que lanza la GUI de Forja (forja-gui-rt) directamente desde assets/main.fa.
Arquitectura JNI
El crate declara funciones #[no_mangle] pub extern "C" cuyo nombre sigue la convención JNI (Java_com_forja_Runtime_native*). Todas pasan por la macro jni_panic_boundary!, que envuelve el cuerpo en catch_unwind y convierte un panic de Rust en una excepción Java (panic_a_excepcion).
| Función JNI | Descripción |
|---|---|
nativeVersion() -> String | Retorna forja-android-rt v{versión} |
nativeCrearSession(maxInst) -> Long | Crea una sesión (una ForjaFast con registry Android y límite de instrucciones; default 10M) y devuelve el índice opaco como handle; -1 si falla |
nativeDestruirSession(ptr) / nativeResetSession(ptr) | Libera o resetea la sesión del slot |
nativeEjecutar(ptr, source, rutaBase) -> ForjaResult | Compila con forja::compilar_pipeline, carga el bytecode, ejecuta y devuelve un objeto Java ForjaResult (output + instrucciones + duración ns) |
nativeCompilarABytecode(ptr, source) -> ByteArray | Compila y serializa a formato .fbc (serializar_bytecode) |
nativeEjecutarBytecode(ptr, bytecode) -> ForjaResult | Deserializa (con verificación de CRC) y ejecuta bytecode pre-compilado; invoca el callback de output si está registrado |
nativeEvaluar(ptr, expresion) -> Any? | Envuelve la expresión en funcion __expr__() { retornar {expr} } __expr__(), la compila y ejecuta, y devuelve el valor del tope del stack convertido a Java (o el output si hubo escribir) |
nativeSetOutputCallback(ptr, Consumer<String>) / nativeSetInputCallback(ptr, Supplier<String>) | Guardan GlobalRefs de los callbacks; se invocan con attach_current_thread sobre el JavaVM guardado en JNI_OnLoad |
JNI_OnLoad / JNI_OnUnload | Guarda el JavaVM en JAVA_VM (para attach de hilos) y limpia las sesiones al descargar la lib |
Sesiones
ForjaSessionInner envuelve una ForjaFast + callbacks de output/input + cache de bytecode. Las sesiones se guardan en un registro global tipo slot: SESSIONS: OnceLock<Mutex<Vec<Option<Mutex<ForjaSessionInner>>>>>. con_sesion(handle, f) hace el doble lockeo y valida el handle; crear_session_inner reutiliza slots vacíos.
JNI Bridge — Conversión de valores
jni_bridge.rs convierte los ValorFast (NaN tagging) a objetos Java y viceversa:
| ValorFast (Forja) | Objeto Java |
|---|---|
| Nulo | null |
| Booleano | java.lang.Boolean |
| Entero | java.lang.Long |
| Decimal | java.lang.Double |
| Texto | java.lang.String |
| Arreglo | java.util.ArrayList<Object> |
| Mapa | java.util.HashMap<String, Object> |
| Exacto (BigDecimal) | java.math.BigDecimal (coeficiente i128 ↔ BigInteger.toByteArray() big-endian con signo, con i128_a_bytes_be/bytes_a_i128_be) |
| Objeto | com.forja.ForjaObject (nombre de clase + HashMap de campos __campo_N) |
La conversión inversa (java_a_valor) inspecciona la clase con getClass().getName(): Long/Integer/Short/Byte → Entero, Double/Float → Decimal, Boolean → Booleano, String → Texto, BigDecimal → Exacto, ArrayList/List → Arreglo, HashMap/Map → Mapa, ForjaObject → objeto, y tipos desconocidos se convierten vía toString().
resultado_a_java arma un com.forja.ForjaResult (List de output + ejecutadas + duracionNs + ForjaError), y error_a_java_error mapea ForjaAndroidError a ForjaError con tipo COMPILE/RUNTIME_x/CONTRACT/TIMEOUT/INTERNAL/JNI.
Manejo de errores
error.rs define ForjaAndroidError: Compile{mensaje, linea, columna, sugerencia}, Runtime{mensaje, codigo}, Contract{mensaje, linea}, Timeout{mensaje, instrucciones}, Internal{mensaje} y Jni(String). RuntimeErrorCode clasifica stack underflow, variable no declarada, tipo incompatible, división por cero, función no definida, índice fuera de rango, límite de instrucciones, error propagado y desconocido.
| Conversión | Detalle |
|---|---|
From<Vec<ErrorForja>> | Toma el primer error del pipeline de compilación |
From<String> | Clasifica mensajes por keywords: div/0, límite/max_inst, tipo, no declarada, stack/pila, índice, propagado, función… |
From<ErrFast> | Mapea los errores de la VM (DivCero → DivisionPorCero, Limite → Timeout, etc.) |
lanzar_excepcion() | Lanza la excepción Java correcta: ForjaCompileError, ForjaRuntimeError, ForjaContractError, ForjaTimeoutError o ForjaInternalError |
Clases Kotlin
ForjaRuntime (object)
Singleton que carga la librería nativa y declara los external fun JNI. Expone version(), crearSession(maxInst = 10M), ejecutar(source, rutaBase) one-shot síncrono, y ejecutarAsync (corre en un CachedThreadPool y entrega el resultado en el main looper).
ForjaSession (AutoCloseable)
Mantiene un nativePtr opaco y estado persistente entre llamadas: ejecutar(), compilarABytecode(), ejecutarBytecode(), evaluar(), setOutputCallback(), setInputCallback(), reset(), destruir() y close() (soporta use {}).
ForjaResult / ForjaError / ForjaObject
ForjaResult es un data class con output: List<String>, ejecutadas, duracionNs, error: ForjaError? y helpers esExito, duracionMs, texto. ForjaError lleva mensaje + línea/columna + tipo. ForjaObject representa objetos Forja con className y fields.
Registry nativo Android
native_android.rs reemplaza funciones de la stdlib incompatibles con Android: las operaciones de archivos (_archivo_leer, _archivo_escribir, _directorio_*, …) y sistema (_sistema_comando, _sistema_ejecutar) se registran como stub_no_soportado (devuelven Error("Función no soportada en Android")). Las funciones de red/sockets quedan activas si el app declara android.permission.INTERNET. crear_registry_android() arma el registry completo para la sesión.
Native Activity — GUI de Forja en Android
Con target_os = "android", android_main(app: AndroidApp) inicializa el logger a Logcat (tag ForjaRuntime), abre assets/main.fa desde el AssetManager, lo tokeniza/parsea con el lexer y parser de Forja, y lanza la UI dinámica con forja_gui_rt::gui_nativa::build_and_run_android(&programa, None, None, true, app) — la misma GUI Xilem/Masonry del runtime nativo.
Template de app Android
El directorio android/template/ es el proyecto Gradle que genera forja construir-apk:
| Archivo | Contenido |
|---|---|
build.gradle.kts (raíz) | Android Gradle Plugin 8.7.0 + Kotlin 2.1.0 |
app/build.gradle.kts | namespace com.forja.app, compileSdk/targetSdk 35, minSdk 26, ABI filters arm64-v8a + x86_64, Java/Kotlin 17; depende de libs/forja-android-rt-0.9.0.aar, appcompat y activity-ktx |
app/src/main/java/com/forja/app/MainActivity.kt | Activity genérica: intenta cargar assets/main.fbc (bytecode) o assets/main.fa (fuente), crea una sesión, ejecuta en un hilo background y muestra el output en un TextView dentro de un ScrollView |
app/src/main/assets/main.fa | Código Forja de la app (editado por el usuario) |
android/AndroidManifest.xml | package com.forja, minSdkVersion 26, sin permisos especiales (el .so se carga dinámicamente) |
Flujo del build: el fuente .fa se compila a bytecode (.fbc) con forja construir-apk → la app carga forja-android-rt.so y ejecuta el bytecode → el output de escribir() se muestra en pantalla.
Integración con la VM de Forja
La sesión crea una ForjaFast del crate forja (path = "../../.."), le setea el límite de instrucciones y reemplaza vm.native_registry con crear_registry_android(). Compilación, serialización de bytecode y ejecución usan las APIs públicas de forja: compilar_pipeline, bytecode::serializar/deserializar_bytecode, cargar_bytecode y ejecutar.
val session = ForjaRuntime.crearSession() → session.ejecutar("escribir("Hola Android!")") → session.destruir().DEFAULT_MAX_INST = 10_000_000 por sesión y JNI_VERSION_1_6 en JNI_OnLoad.