Hola Habr! Ve directo al grano. En este momento estoy leyendo "The Dragon Book" y estoy desarrollando un compilador para mi lenguaje de programación llamado Lolo (en honor al pingüino de la caricatura soviético-japonesa). Planeo terminar dentro de un año si nada duele. Paralelamente, publicaré extractos interesantes de la experiencia de la traducción, la creación de código intermedio, la optimización, etc. Bueno, hoy les presentaré el idioma. Siéntate y vete.
El lenguaje es compilado, imperativo, no orientado a objetos; la semántica ha sido descaradamente descartada de C y complementada con muchas características útiles. Comencemos con ellos.
Modificaciones semánticas
Punteros seguros
Es posible que haya pensado en los punteros inteligentes de Rust en este momento, pero no lo son. En mi idioma, la seguridad de acceder a la memoria es proporcionada por dos modismos. Primero: la falta de una operación de desreferenciación de punteros. En cambio, al acceder al puntero declarado, se hace referencia al objeto en sí. Es decir, puedes y debes escribir así:
int # pointer ~~ new int(5) int variable ~ pointer + 7
La variable variable ahora contiene el número 12. Ahora ve una sintaxis desconocida y, tal vez, está un poco perplejo, pero explicaré todo en el transcurso del artículo. Segundo modismo: falta de operaciones en punteros. Nuevamente: todas las operaciones al acceder a punteros, incluidas la asignación, el incremento y la disminución, se realizan en los objetos. La única operación que se relaciona directamente con el puntero es la asignación por dirección o, como lo llamo, identificación. En el ejemplo de código anterior, en la primera línea, es precisamente identificación. Cualquier puntero se puede establecer en la dirección de solo el área de memoria ya asignada, que es la nueva operación devuelta. También puede poner un puntero a la dirección de otra variable asignada incluso en el montón, incluso en la pila. Aquí hay un ejemplo:
int variable ~ 5 int # pointer ~~ variable
Aquí "~" es la operación de asignación habitual. También puede identificar punteros con un puntero nulo especial. Actúa como un puntero que se refiere a una dirección nula. Después de identificar las operaciones de comparación y comparación de identidad (direcciones idénticas) con nulo, producirán verdadero:
int # pointer ~~ null if (pointer = null) nop ;; true if (pointer == nul) nop ;; true
Aquí "=" es una comparación de valores, "==" es una comparación por direcciones, "nop" es una operación vacía y después de ";;" - comentar. Y sí, null es la única operación de puntero con la que es posible sin verificar la compatibilidad de tipos.
Por lo tanto, los punteros solo se pueden asignar a la memoria asignada o áreas nulas y no se pueden mover a ninguna parte. Sin embargo, estas medidas no protegen completamente contra errores de fallas de segmentación. Para obtenerlo, solo sigue estos pasos:
int # pointer1 ~~ new int(5) int # pointer2 ~~ pointer1 delete pointer1 int variable ~ pointer2 ;; segmentation fault!
Creo que todo está claro aquí. Pero cometer tal error solo se puede hacer a propósito, y luego, haber trabajado duro. Después de todo, la operación de eliminación hace lo mismo que el recolector de basura, solo que de manera menos segura. Hablando de él ...
Recolector de basura
Recolector de basura: también es recolector en Lolo. Probablemente no sea necesario explicar de qué se trata. Solo puedo decir que puede deshabilitarse mediante una opción especial durante la compilación. Probamos el programa con el recopilador, todo funciona como debería: puede ingresar la opción e intentar optimizar el programa utilizando la administración manual de memoria.
Arreglos incorporados
Aunque dije que la semántica del lenguaje está descartada de C, las diferencias son bastante significativas. Aquí las matrices son punteros. Las matrices tienen su propia sintaxis y direccionamiento seguro. No, no con una verificación de rango. Con ellos es básicamente difícil obtener un error de tiempo de ejecución. Esto se debe a que cada matriz almacena la longitud en el tamaño variable, como en Java, y con cada indexación del índice ... ¡existe el resto de la división por este tamaño! Una decisión estúpida, a primera vista, hasta que miramos los índices negativos. Si encuentra el resto de dividir -1 entre la longitud de la matriz, obtendrá un número igual a tamaño-1, es decir, el elemento más finito. Mediante tal maniobra, podemos acceder a los índices no solo desde el principio, sino también desde el final de la matriz. Otro truco es lanzar cualquier tipo primitivo a la matriz de bytes []. Pero, ¿cómo se obtiene un error de tiempo de ejecución? Te dejaré esta pregunta como un acertijo fácil.
Referencias
No sé con certeza si el estándar C actual incluye enlaces, pero definitivamente estarán en Lolo. Tal vez la falta de referencias en versiones anteriores de C es una de las principales razones de los punteros a punteros. Son necesarios para pasar argumentos a la dirección, para devolver valores de funciones sin copiar. Los punteros y las matrices también se pueden pasar por referencia (ya que al pasar por valor, las matrices se copiarán por completo y los punteros establecidos en una nueva ubicación por la operación ~~ no lo guardarán).
Multithreading
Todo es más bello y más bello. Ya estoy enamorado de mi idioma. Su siguiente pasatiempo es multihilo. Honestamente, no he decidido completamente qué herramientas se proporcionarán. Lo más probable es que la palabra clave sincronizada con todas las propiedades de ala-Java y, posiblemente, la palabra clave concurrente frente a funciones no en línea, lo que significa "ejecutar estas funciones en subprocesos paralelos".
Cuerdas en línea
Son cadenas, no literales de cadena, como en C ++. Cada línea tendrá su propia longitud, la indexación ocurrirá al encontrar el resto. En general, las cadenas en Lolo son muy similares a las matrices de caracteres, excepto que las matrices no tienen concatenación a través de "+", animación a través de "*" y comparaciones a través de "<" y ">". Y como estamos hablando de líneas, debemos mencionar los personajes. Los símbolos en Lolo no son números, como en C ++. Y contienen no un byte, sino 4 para caracteres DKOTI y 6 para caracteres UTF. Hablaré sobre DKOTI la próxima vez, pero por ahora, solo sé que Lolo admite caracteres y cadenas en dos codificaciones. Y sí, la propiedad de longitud incluso se puede tomar de constantes:
int len ~ "Hello, world!".length ;; len = 13
Tipo booleano con tres valores
La gran mayoría de los lenguajes de programación que tienen un tipo de datos lógico utilizan lógica binaria. Pero en Lolo será ternario, o más bien, ternario difuso. Tres valores: verdadero - verdadero, falso - falso y ninguno - nada. Hasta ahora no he encontrado en el lenguaje de operaciones que no devuelvan ninguno, pero recuerdo muchos ejemplos de la práctica cuando las banderas con tres valores serían muy útiles. Tuve que usar enumeraciones o un tipo entero. Ya no tienes que hacerlo. Ese es solo el nombre de este tipo que no puedo elegir. El lugar más común es "lógico", pero demasiado largo. Otras opciones son "luk" en honor a Jan Lukasevich, "brus" en honor a N. P. Brusnetsov y "trit", pero estrictamente hablando, este tipo no es un trit. En general, la encuesta se encuentra al final del artículo.
Listas para inicializar estructuras y listas
Si, después de declarar una variable estructural, coloca el signo ~ y abre los corchetes, puedes establecer los valores de sus campos a su vez o en forma de diccionario. Si realiza dicho procedimiento con una matriz, puede establecer los valores de sus celdas, solo sin un diccionario. No hay nada especial que contar, solo mira el código:
struct { int i; real r; str s; } variable ~ [ i: 5, r: 3.14, s: "Hello!" ] int[5] arr ~ [ 1, 2, 3, 4, 5 ]
Devuelve múltiples valores de funciones
Justo como en Go! Puede escribir varios nombres de variables separados por comas y asignarles todos los valores devueltos por la función a la vez:
int, real function() { return 5, 3.14 } byte § { int i; real r i, r ~ function }
Módulos en lugar de encabezados
Todo está claro aquí. En lugar de encabezados C-shy - módulos de Java.
para (elemento automático: matriz)
Nuevamente Java nativo. Como tenemos matrices con longitud, es un pecado no usar la expresión para cada una.
El operador de selección no es solo para int
No sé acerca de usted, pero en C y C ++ estoy terriblemente enfurecido por la falta de la capacidad de usar la operación de cambio de mayúsculas y minúsculas para variables no enteras. Y la sintaxis también enfurece. Aquí en Pascal hay otro asunto. Y ahora en Lolo:
case variable { "hello", "HELLO": nop "world": { nop; nop } "WORLD": nop }
Operadores de alimentación y división
Y esto es de Python.
real r ~ 3.14 ** 2 int i ~ r // 3
Tuplas de parámetros de función
¿Recuerda que todas las operaciones con punteros están prohibidas en Lolo, excepto la identificación? Ahora recordemos cómo acceder a los parámetros de función desde las listas de parámetros de longitud variable. Debe declarar un puntero al primer elemento y luego incrementarlo hasta que la verificación de la verdad devuelva verdadero. No puedes incrementar en Lolo. Pero eso está bien. Después de todo, la lista de parámetros aquí se presenta en forma de una tupla de una longitud fija (dependiente de la llamada), con índice seguro, como en las matrices. Su nombre es "?" La verificación de tipo se realiza solo para los parámetros establecidos en la definición de la función. El resto de los parámetros ("multipunto") se reducen a cualquier tipo, y con un movimiento incómodo su comportamiento no se define. Pero aún así, tal tupla es mucho más segura y conveniente que las macros en C.
void function(...) { if (?.size > 1) { int i ~ ?[0] real r ~ ?[1] } }
Intervalos numéricos
Y otro personaje: una familia de tipos de intervalo (rango, urange, lrange, etc.). Están dados por dos enteros a través de dos puntos (..) y pueden cortar una matriz de una matriz, una cadena de una cadena, en general, una cosa útil, creo.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] int[3] subarr = arr[1..3] ;; [ 2, 3, 4 ]
En operador
De Pascal. Funciona con cadenas, matrices, tuplas? y rangos.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] if (4 in arr) nop
Diccionario de parámetros de funciones
Honestamente, ya estoy confundido sobre cómo se llama correctamente a esta cosa, con ella puedes especificar directamente los argumentos de las funciones no puras:
int pos = str_find(string, npos: -1)
Opciones predeterminadas
De C ++. Aquí, incluso un ejemplo no es necesario dar, por lo que todo está claro.
Excepciones
¿Y dónde sin ellos?
try { raise SEGMENTATION_FAULT_EXCEPTION } except (Exception e) { print(e.rus) }
Sin salto incondicional
Porque en 2019, usar el operador GOTO de la muerte es similar.
Sintaxis
Bueno, una pequeña charla sobre la sintaxis. Como notó, el punto y coma es poco profundo. Los lenguajes de programación modernos funcionan muy bien sin esta fuente de error. Ejemplos son Python, Kotlin. El operador de flecha (->) se combina con el operador de punto. Cuando se llaman funciones sin argumentos, los corchetes son opcionales. Las cadenas se dan en números y viceversa. Los operadores lógicos y bit a bit se combinan. Hay modificadores de función para la tabulación. Funciones anidadas tipo_de. Y lo más importante: multilingüismo. Sí, voy a duplicar palabras clave, propiedades de cadenas y matrices y todos los identificadores de la biblioteca estándar en todos los idiomas de comunicación internacional, a saber: inglés, ruso, japonés, chino, español, portugués, árabe, francés, alemán y latín.
De hecho, todo lo anterior no incluye la mitad de las capacidades de Lolo. Simplemente no puedo recordar de inmediato todas sus características. Agregaré cuando el compilador esté listo.