Escribiendo un compilador en vivo

Iniciado por Yuki, 18 Abril 2018, 23:52 PM

0 Miembros y 1 Visitante están viendo este tema.

Yuki

Bueno, ya queriendo iniciar un proyecto nuevo, les aviso que a partir de mañana 19/04/2018 (3:00 pm - Hora Argentina) voy a empezar a escribir un compilador en vivo desde Twitch, el proyecto será programado en el viejo VB6 bajo Windows 10 de 64 bits.

¡Los espero!

Yuki

Bueno, cumpliendo con mi post y sin animo de crear uno nuevo, hice el primer vídeo en vivo donde escribo un compilador, para los interesados, pueden verlo desde aquí.

Saludos!

engel lex

#2
no lo vi completo (ni tampoco en vivo) pero si una buena parte, :P está bien pero te recomiendo tener un guion a mano para que no te pierdas mucho... sobre el tono de voz te recomiendo hablar más solido, siento que intentas hablar suave y se te escucha casi como tímido, esto tambien te permitiría bajar el volumen del micro para que se sientan menos los ruidos de fondo

por que los se veían esos artefactos visuales? (las manchas negras en las barras amarillas y azules)

sobre los operadores usualmente el xor es  ^, puedes aplicar potencia como en python ** , así mismo podrías aplicar división entera, raiz, etc...

de resto me parece cool :P


tambien te reomciendo... has la publicación aquí unos 5 minutos antes de empezar... muchos no estamos pendientes del horario de Argentina XD
El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.

srWhiteSkull

No está mal para ser algo casi improvisado, pero muy largo para mi  :-\

Yuki

Cita de: srWhiteSkull en 20 Abril 2018, 00:04 AM
No está mal para ser algo casi improvisado, pero muy largo para mi  :-\

Me sorprende un poco, si es un poco largo, pero 1 hora al día creo que es razonable, voy a ver si puedo grabar los vídeos en local también y hago algunas ediciones "resumen".

engel lex, me gustó tu comentario
Sobre mi voz, lamento decir que así es mi voz, traté de hablar fuerte y claro; pero es así de gastada... Aún así voy a tratar de probar unas mentas o algo para aclarar.

Voy a ver como puedo corregir esos borrones negros que también me desagradaron.

Si no es problema, voy a publicar en este mismo post 5 minutos antes de la emisión.

Gracias por sus comentarios

engel lex

CitarSi no es problema, voy a publicar en este mismo post 5 minutos antes de la emisión.
es lo más apropiado
El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.

Serapis

#6
Se me hace muy tarde ya, solo he visto hasta el minuto 27...

Hay varias cosas que comentar, pero ahora antes de nada te comento sobre lo que decías en esos minutos previos... (y ya a la tarde, te comento más cosas).

Señalas que procesas todo desde un bucle y que desde ahí tras procesar los tokens de una línea ya conviertes a ensamblador, y que como querías que el compilador sirviera para más lenguajes y/o para más procesadores destino, no te va bien...

en efecto así es... el esquema para compilar en fases, es más o menos el que sigue:

0a manejador de errores (esto se conecta a todas las fases)
0b tabla de símbolos (ídem, se conecta a todas las fases).
1 analizador léxico
2 analizador sintáctico
3 analizador semántico
--------------------------
Esas tres fases, pueden servir para tratar en origen cualquier lenguaje de origen
aunque en general cuanto más flexible es el compilador de cara a más lenguajes, menos óptimo puede resultar de cara a todos ellos (o bien resultar excesivamente cmplejo hasta resultar inmanejable.
La solución es que esas tres partes pueden implemetar interfaces, y según el lenguaje crear una implementación específica para él (no todos los lenguajes podrían requerirlo, pero si lenguajes cuya 'familia' de origen sea muy distinta.

4 Creador de código intermedio:
Si es muy cercano al ensamblador, será costoso traducirlo a máquinas my diferentes como los ARM de x86, ni debe ser muy 'humano' pués de nuevo requerirá fases de aproximación al final... en general es algo intermedio y se recurre a sistemas llamados "código de 3 direcciones" que son parecidos al ensamblador, un mnemónico de instrucción seguido de 3 operandos, uno es un operando origen, otro el opeando destino y el tercero una direccion (como si fuera un registro).
Probablemente no tuvieras pensado una fase, así, pero precisamente esta fase es la que facilita pasar como destino a  una u otra plataforma.

5 Optimización del código: siempre es posible simplificar algo, por ejemplo multiplicaciones por una potencia de 2 o divisiones (cuando son valores constantes, lógicamente), pueden convertirse a desplazamientos (shift) que son más rápidas... y consumen menos ciclos de reloj.
6 Genera el código: para la maquina destino.
Las fases 5 y 6, al igual que las fases 1-3, pueden implementar una interfz específica para cada plataforma... con lo que al final estaremos usando clases y no tantos módulos como veo.

Fíjateq ue las 3 primeras están centradas en el lenguaje de origen, en tanto las dos últimas en la plataforma de destino (hardware), en cambio la 4 es el punto medio del compilador, es donde se une un lenguaje diferente y de donde podrá derivarse hacia una plataforma distinta... es el centro del embudo....

...ya comentaré más cosas, cuando termine de ver el vídeo en la tarde...


Serapis

#7
Habría mucho por comentar, pero tampoco quiero hacer un mensaje demasiado largo, así que he obviado cosas, respondiendo simplemente a algunas de tus dudas...



1 - Preprocesamiento (directivas de preprocesado):
El preprocesador, actúa en la fase primera de la compilación, de hecho empieza en el analizador léxico.

En VB6 hay algunas directivas de preprocesameinto, seguramente las hayas visto en algún código:


Supongamos que tienes que acceder a la hora del sistema y para ello debes invocar una librería, la cual dependerá del sistema operativo destino, en éste caso, la directiva tiene su fundamento, en que compilará la parte perteneciente al sistema operativo vigente a la hora de solicitar la compilación.
Tú decides si vas a compilar para Linux, o para Microsoft (por ejemplo), cambiando el valor de la contante antes de compilar...

#const Os as string = "Microsoft"

public Function GetLocalTime as string
   dim hora as string

   #If Os = "Microsoft" then
      hora = ...
   #Elseif Os = "Linux" then
      hora = ...
   #end if

   GetLocalTime = hora
End function


No sólo eso, también por ejemplo para incluir macros...
Supongamos que facilitas la posibilidad de tener 'snippets' (pequeños trozos de código, para tareas frecuentes, pero demasiado largas para escribirlas cada vez).
Una forma sencilla de preprocesamiento, sería porejemplo crear directivas de snippets.
Así esto:

#snippet = "C:\Mi lenguaje\snippets\copypaste.snippet"

Podría incluir ahí código de forma automática, durante la compilación, yendo a la ruta concreta, leyendo el fichero y pegando el contenido íntegro en donde aparece esa directiva.

Otra forma podría ser funciones que no tienes en librerías, sino que se adjuntan con el ejecutable, así por ejemplo podría tener una función en forma de directiva de esta forma:
#funcion = Ordenar (elarray)
que básicamente podría ser una función (o sub) llamada ordenar, que recibe como parámetro un array...
Y lo que hará el compilador será remplazar esa línea por el código de la función con el identificador correcto del parámetro (que tiene la línea) a la hora de compilar.
Y esto como alternativa a invocar una función ordenar en una librería externa. Uno sabrá qué razones serían precisas para elegir dicha forma... (que a veces son precisas).
Fíjate que en este último caso, no se indica ruta, el formato lo decides tú, podría constar en un fichero, pero quizás si sean pocas (funciones), decidas cargarlas en memoria todas y por tanto luego basta el nombre para localizarla en una colección en vez de en un fichero.
Las directivas, en definitiva facilitan escribir el código, y se le informa a la fase de compilación que debe hacer algo especial distinto al resto del código con el contenido de la directiva.


2 - Continuadores de línea:
Concuerdo contigo, que los continuadores de línea, son desagradables, sin embargo es preciso incluirlos si respondes afirmativamente a esta cuestión:
Nunca hay que olvidar de vista, si al hacer un compilador lo haces exclusivamente para tí, o para que también sea usado por otros. En tu caso es lo segundo?. Si es esto segundo, debes hacerlo porque habrá mucha gente que lo heche en falta (aunque otros lo odien), si es lo primero, puedes excluirlo.
De todos modos, para un ejercicio de publicación (como es los vídeos que vas a realizar) puedes omitirlo, y ya lo dejarás pendiente para otra ocasión. En general un compilador es siempre la mejora de una versión previa...
En BV6, por ejemplo, el continuador de línea es " _" esto es un espacio seguido de un guión bajo, esto no solo es para las expresiones (líneas de código9, es así incluso para los comentarios (cosa que el 99'995 ignora).
Así esto es válido en vb6:
' Aquí empieza un comentario multilínea _
 _
esto también sigue la línea de comentarios _
continuador de línea espacio y _
_
y aquí acaba, a que parece extraño ver una línea de comentario que no empieza por un apóstrofo?

OJO: los continuadores de línea, no deben dejar líneas en blanco, sin su correspondiente 'token':
continuadorLinea = " _" CRLF
Una línea en blanco, siempre se debe interpretar como espacios, como comentario, excepto que esté entre comillas, porque entonces forma parte del valor de dicha cadena de texto.
Es decir esto otro:
"valor de los días d ela semana
Lunes
Martes
...
Domingo"
No es continuador de línea es el valor de una cadena de texto, que contiene caracteres CRLF, igual que podría contener cualquier otro carácter...

3 - Operadores:
Los operadores limítalo a los propios del procesador (incluyendo shifts), el resto considero conveniente dejarlo como funciones internas añadidas al lenguaje (quizás en una clase Math):
Porciento(Valor, Porcentaje)
Logaritmo(Base, valor)
Random(Maximo, Minimo)
Coseno(angulo)
...etc...
Más preciso aún... (ya que tratas de hacer un compilador orientado a objetos), es que cada función pueda ser nativa del tipo de datos específico que es.
No tengas apuros en crear operadores sinónimos:
éste: ">=", lo mismo que éste otro: "=>"
y éste: "><" puede ser lo mismo que "<>" y lo mismo que "!="
...esto de cara al analizador léxico, luego internamente será solo uno cuando se convierta a código intermedio.

4 - Tabla de atributos en tu lenguaje para todos los caracteres ASCII: Es adecuado, que antes de nada, crees un array con todos los caracteres ASCII, y que a cada uno le des valores conforme al tipo de carácter que es, así, por ejemplo "A", es un carácter alfabético, es un caracter mayúsculas y también es un figito en la base hexadecimal. Esto facilita, que debes hacer después con cada carácter, si evaluas un identificador, "A" es un carácter válido, y si evaluas un número "A" puede o no ser un dígito válido en función de si se trata o no de un número en base hexadecimal.

5 - Especificación del lenguaje: Por último y más importante, lo primero a la hora de crear un compilador es crear la especificación del lenguaje. Es una tarea que puede llevar meses, ahora bien si la basas en otro lenguaje ya conocido, al que sólo cambias determinadas cosas (como creo que es el caso), se puede omitir casi todo, y tan solo especificar esa lista de cambios... por ejemplo como señalabas para utilizar ciertos caracteres diferentes como remplazo de los operadores que incorpora VB6, por ejemplo...
la especifciación de un lenguaje empieza por cosas simples como lo que es un dígito, un número, y para ello lo mejor es el BNF.


digito = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
digitos = digito [digitos]
numero = ["+"|"-"] digitos
numeroFlotante = numero ["." numero]  *** podría tambén admitirse la notación científica de E.
digitoHexadecimal = digito|"A"|"B"|"C"|"D"|"E"|"F"|"a"|"b"|"c"|"d"|"e"|"f"
digitosHexadecimal = digitoHexadecimal [digitosHexadecimal]
numeroHexadecimal = "&Hx" digitosHexadecimal    *** esto es, ahora sabrás que "A" es un dígito válido, porque hay un 'prefijo' para indicar la base numerica hexadecimal, en ausencia debe considerarse decimal.

caracterMayuscula = A-Z, a-z    *** es otra forma de indicar "A"|"B"|...."Y"|"Z"
caracterMinuscula = a-z
caracterAlfabetico = caracterMayuscula|caracterMinuscula
caracterAlfanumerico = caracterAlfabetico|digito
caracteresAlfanumericos = caracterAlfanumerico [caracteresAlfanumericos]

identificador = caracterAlfabetico caracteresAlfanumericos   *** esto es debe empezar obligatoriamente por una letra y ser lo largo que sea.  


Entonces puedes llegar a la declaración de variables así:

byte = identificador "byte" ["=" numero]

El analizador kléxico, verifica que se escribe bien, el sintáctico, que todo está en orden, que no tiene nada extraño, ni le falta algo, el semántico, verifica que si se inicializa un valor esté en el rango del byte (0-255), y si no se inicializa expresamente con un valor le asigna el valor 0 (el valor por defecto)

valorDefectoByte = "0"


Y así vas especificando todo el lenguaje, pero como mínimo si te basas en otro, especificas aquello que cambie, o aquello donde necesites claridad para en adelante no equivocarte...

Por ejemplo puede definir un bucle 'For... next' en español, así:
Citar
hasta = "Hasta"  exprNumerica ["Salta" exprNumerica] comentFinLinea   
itera = "Itera" identificador "Desde" exprNumerica  
itera1 = itera hasta
itera2 = itera comentFinLinea
declaracionItera = (itera1|itera2)
repite1 = "Repetir" comentFinLinea
repite2 = "Repetir" hasta
*** básicamente señala que el cuerpo interno del bucle puede contener expresiones y también la sentencia "SalirDeAqui" (del bucle), en cualquier parte.... expresiones debe definirse en otra parte, tal que sea la suma de expresiones admisiblea estar dentor del cuerpo de un bucle (incluso otro bucle, pero no una función, por ejemplo)
exprSalirDeAqui = ["SalirDeAqui"] expresiones ["SalirDeAqui"] [exprSalirDeAqui]
bloqueItera = (itera1 exprSalirDeAqui repite1|itera2 exprSalirDeAqui repite2)
Y conviene adjuntar ejemplos de lo que se admite y comentar si algo en espacila no se admite.
Estos ejemplos serían validos con la especificación recién dada:
Citar
   Itera k Desde 5 Hasta 40
      SalirDeAqui  *** esta producción no se ha descrito...
      Mostrar k     *** esta producción no se ha descrito...
      SalirDeAqui  *** es el típico 'exit for', un goto a la dirección siguiente a 'repetir (un jump incondicional a una dirección)
   Repetir

   Itera cuenta Desde 40 Hasta 5 Salta -2
      Mostrar cuenta
   Repetir

   Itera k Desde 5
      Mostrar k
   Repetir Hasta 40 Salta 5

   Itera k Desde 40
      Mostrar k
            Si ((k and X) = j) luego SalirDeAqui
   Repetir Hasta 5 Salta -8
En BNF, cada 'variable' a la izquierda, se llama producción, y es una equivalencia de todo lo que está a la parte derecha del "=". Hay cosas que nunca están en la parte izquierda, se llaman símbolos terminales, y no se describen porque están al punto sumplificados que son 'autoentendibles', típicamente son los caracteres, o alguna secuencia especial de ellos, basta ponerlo entre comillas o en negrita (yo prefiero ambas cosas, porque al portarlo a otro sitio que no admite negritas, desaparece el formato, si queda entre comillas, todavía es claro...

...y bueno, al final me he extendido, más de lo que pretendía...

Yuki

En 5 minutos comenzará la emisión del nuevo streaming

saludos!