Hice un lenguaje de programación esotérico (esolang)
3/26/2026
PHD-INTERPRETER es un intento de esolang parodia de PHP no es Turing-complete por que solo:
- Imprime texto por pantalla
- Solo se pueden declarar variables de texto o números
- Se puede poner comentarios
Y poco más, si quieres realizar mejoras, hazlo sin problema developer!.
PHD: Proyecto Hola Developers!
Importante: Necesitas como mínimo el CLI de node
Hola Mundo
hdp!
holaDevelopers 'Hola Developers'!
!hdp
Comando para intepretar el .phd
node inteprete.js intepretado.phd 'Tu nombre'
Fichero de ejemplo de uso .phd
hdp!
@ Edad por defecto
hola edad = 18!
holaDevelopers 'Hola Developers'!
holaDevelopers 'Tu nombre es: //edad y tienes //nombre años'!
!hdp
Esto muestra por consola:
Hola Developers
Tu nombre es: 18 y tienes Tu nombre años
Tutorial del interprete
Lectura de argumentos y preparación
const fs = require('fs');
const path = require('path');
// Leer ruta del fichero .phd y argumentos extra
const [, , phdPath, ...cliArgs] = process.argv;
require('fs')yrequire('path')importan módulos de Node:fssirve para leer archivos.pathayuda a construir rutas absolutas.
process.argves un array con lo que escribes en la línea de comandos:- posición 0: ruta a
node - posición 1: ruta a
phd.js - posición 2: ruta al
.phd(primer argumento “real”) - resto: argumentos extras (por ejemplo el nombre).
- posición 0: ruta a
- La asignación por desestructuración [, ,
phdPath, ...cliArgs]:- ignora los dos primeros elementos,
- guarda el tercero en
phdPath, - y el resto en el array
cliArgs.
if (!phdPath) {
console.error('Uso: node phd.js archivo.phd [arg1 arg2 ...]');
process.exit(1);
}
- Si no pasas un archivo
.phd, muestra un mensaje de uso y termina el programa con código de error 1.
// Variables del lenguaje PHD
const env = Object.create(null);
// Para ejemplo: primer argumento lo guardamos como variable 'nombre'
if (cliArgs[0]) {
env['nombre'] = cliArgs[0];
}
envserá “la memoria” del lenguaje: ahí se guardan las variables que declares conhola.Object.create(null)crea un objeto sin prototipo, para que solo tenga las claves que tú guardes.- Si el usuario pasa algún argumento extra (por ejemplo
Johnny), se mete enenv.nombre. - Así
//nombreen tu lenguaje puede usar ese valor sin declararlo en el.phd. - env es “la memoria” de tu lenguaje, donde guardas todas las variables que declares con
hola nombre = .... Object.create(null)crea un objeto vacío de verdad, sin heredar nada deObject.prototype.- Eso evita cosas raras como que
env.toStringexista por defecto; solo tendrá lo que tú metas. cliArgs[0]es el primer argumento que escribes al llamar a Node, después del nombre del archivo.phd.- El
ifdice: si hay al menos un argumento, guarda ese valor en env['nombre']. Así, dentro de tu lenguaje, //nombre usará ese valor aunque no lo declares en el.phd.
Función principal que recorre el archivo
function runPhd(code) {
const lines = code.split(/\r?\n/);
let insideProgram = false;
let buffer = '';
runPhdrecibe el contenido completo del archivo.phdcomo un string.code.split(/\r?\n/)lo divide en un array de líneas (funciona tanto con\ncomo con\r\n).insideProgramindica si ya estamos dentro del bloque entrehdp!y!hdp.buffersirve para ir acumulando texto hasta encontrar un!(fin de instrucción).- Se recorre cada línea del archivo.
trim()elimina espacios al principio y al final.
if (!insideProgram) {
if (line.toLowerCase() === 'hdp!') {
insideProgram = true;
}
continue;
}
- Mientras no estemos dentro del programa:
- si la línea es
hdp!(ignorando mayúsculas/minúsculas), activamosinsideProgram. - se hace
continuepara saltar a la siguiente línea y no procesar nada más.
- si la línea es
if (line.toLowerCase() === '!hdp') {
insideProgram = false;
break;
}
- Si ya estamos dentro y encontramos
!hdp, salimos del programa:- ponemos
insideProgramafalse breakpara dejar de leer más líneas (ignoramos lo que venga después).
- ponemos
// Acumular hasta encontrar "!"
buffer += (buffer ? ' ' : '') + line;
while (buffer.includes('!')) {
const idx = buffer.indexOf('!');
const stmt = buffer.slice(0, idx).trim();
buffer = buffer.slice(idx + 1).trim();
if (stmt) executeStatement(stmt);
}
- Se añade la línea actual al
buffer. Si elbufferno está vacío, se mete un espacio antes para separar. while (buffer.includes('!')):- puede haber más de una instrucción en una misma línea, o una instrucción que continúa desde la línea anterior.
idxes la posición del primer!.stmtes el texto desde el principio delbufferhasta el!(sin incluirlo).- Luego se recorta el
bufferpara quitar esa parte+el!y se hacetrim(). - Si
stmtno está vacío, se llama aexecuteStatement(stmt)para ejecutar esa instrucción PHD. bufferse vuelve:"hola nombre = 'Johnny'! holaDevelopers 'Hola'!".- Primera vuelta del
while:idxapunta al primer!(después de'Johnny').stmt="hola nombre = ``'Johnny'".- Se ejecuta
executeStatement("hola nombre = 'Johnny'").
- Luego se recorta
bufferpara que quede:"holaDevelopers 'Hola'!", y elwhilevuelve a hacer lo mismo para la siguiente instrucción.
Decidir qué tipo de instrucción es
function executeStatement(stmt) {
// Declaración de variable: hola nombre = 'Johnny'
if (stmt.toLowerCase().startsWith('hola ')) {
handleVarDeclaration(stmt);
} else if (stmt.toLowerCase().startsWith('holadevelopers ')) {
handlePrint(stmt);
} else {
// Instrucción desconocida: ignorar
}
}
executeStatementrecibe una instrucción ya sin el!.- Si empieza por hola (con espacio) → se considera una declaración de variable y se llama a
handleVarDeclaration. - Si empieza por
holadevelopers→ se considera un console.log especial y se llama ahandlePrint.
❗ Cualquier otro comando se ignora silenciosamente (no lanza error, simplemente no hace nada).
Declaración de variables (hola)
function handleVarDeclaration(stmt) {
// quitar "hola "
const rest = stmt.slice(5).trim();
// formato esperado: nombre = valor
const match = /^([a-zA-Z_]\w\*)\s\*=\s\*(.+)$/.exec(rest);
if (!match) return;
stmt.slice(5)quita los primeros 5 caracteres (hola).restdebería quedar algo como:nombre = 'Johnny'.- El regex:
^([a-zA-Z_]\w*)captura el nombre de la variable: empieza por letra o_, seguido de letras, números o_.\s\*=\s*permite espacios alrededor del=.(.+)$captura el valor tal cual hasta el final de la línea.
- Si no coincide con este patrón, se sale de la función (
return) y no se hace nada. namees el nombre de la variable (nombreen el ejemplo).valueRawes el texto del valor ('Johnny',25, otraVariable, etc.), sin espacios alrededor.- Si el valor empieza y acaba con
'o", se trata como string literal. - Se quitan las comillas
(slice(1, -1))y se guarda el texto dentro enenv[name]. - Si no tiene comillas, se intenta convertir a número.
Number(valueRaw)intenta parsear el string a número.!Number.isNaN(...)comprueba que realmente es un número y no algo inválido.- Si es un número válido, se guarda como número en
env[name]. - Si no es string ni número, se asume que es el nombre de otra variable.
- Se copia
env[otraVariable]aenv[name]. - Si esa variable no existe, se guarda
null.
Ejemplos:
hola edad = 30! → env.edad = 30.
hola saludo = 'Hola'! → env.saludo = "Hola".
hola x = y! → env.x = env.y (o null si y no existe).
Imprimir con holadevelopers
function handlePrint(stmt) {
// quitar "holadevelopers "
const rest = stmt.slice('holadevelopers'.length).trim();
let text = rest;
- Se quita la palabra clave
holadevelopersdel principio de la instrucción. restes lo que viene después, normalmente un texto con comillas.- Si lo que queda empieza y termina con
'o", se consideran comillas externas. - Se eliminan para quedarnos solo con el contenido.
// Reemplazar //var por su valor y si no pues imprime text
const result = text.replace(/\/\/([a-zA-Z_]\w*)/g, (\_, varName) => {
const val = env[varName];
return val !== undefined && val !== null ? String(val) : '';
});
console.log(result);
- El regex
/\/\/([a-zA-Z_]\w*)/gbusca patrones como//nombre,//edad, etc.://literal.([a-zA-Z\_]\\w\*)nombre de variable válido.
- Cada vez que encuentra uno, ejecuta la función callback:
_es el texto completo que coincidió (no lo usamos).varNamees el nombre de la variable capturado (por ejemplonombre).
- Busca
env[varName]:- Si existe y no es
null, lo convierte a texto conString(val)y lo inserta. - Si no existe o es
null, inserta una cadena vacía.
- Si existe y no es
- Al final hace
console.log(result);para mostrar en la consola el texto con las variables ya sustituidas. - En (_, varName) => { ... }:
_es simplemente un nombre de variable. Equivale a escribir (matchCompleto, varName) => { ... }.
Leer el archivo .phd y arrancar
// Leer archivo .phd y ejecutar
const fullPath = path.resolve(phdPath);
fs.readFile(fullPath, 'utf8', (err, data) => {
if (err) {
console.error('Error al leer el archivo:', err.message);
process.exit(1);
}
runPhd(data);
});
path.resolve(phdPath)convierte la ruta que le pasas en una ruta absoluta.fs.readFilelee el archivo:- primer argumento: ruta absoluta.
- segundo: codificación ('
utf8' para obtener un string). - tercero: callback que recibe un error (
err) y los datos (data).
- Si hay error al leer (archivo no existe, permisos, etc.), se muestra el mensaje y se termina el programa.
- Si todo va bien, se llama a
runPhd(data), que procesa el contenido línea a línea y ejecuta tus comandos esotéricos.
Vídeo Tutorial
Para la investigación y para la documentación se ha usado IA generativa. Revisado y corregido por un humano.
← Volver a los proyectos