A menudo he fantaseado con intentar construir (otro más) lenguaje de computadora de alto nivel. El objetivo sería tratar de impulsar la envolvente de la rapidez del desarrollo y la ejecución del resultado. Intentaría crear bibliotecas de operaciones mínimas, bastante optimizadas, y luego tratar de desarrollar las reglas del lenguaje de tal manera que cualquier enunciado o expresión expresable en el lenguaje resultaría en un código óptimo ... a menos que lo que se estaba expresando fuera simplemente inherentemente por debajo de lo óptimo.
Compilaría el código de bytes, que se distribuiría, y luego el código de máquina cuando se instalara, o cuando el entorno del procesador cambiara. Entonces, cuando se carga un ejecutable, habría una pieza de cargador que verificaría el procesador y unos pocos bytes de datos de control en el objeto, y si los dos coincidían, entonces la parte ejecutable del objeto podría cargarse directamente, pero si no , entonces el código de bytes para ese objeto debería ser recompilado y la parte ejecutable actualizada. (Por lo tanto, no se trata de la compilación Just In Time, sino de la instalación del programa o de la compilación de la CPU modificada). La parte del cargador sería muy corta y agradable, estaría en el código '386 por lo que no sería necesario compilarla. Solo cargaría el compilador de código de bytes si fuera necesario y, de ser así, cargaría un objeto de compilación pequeño y ajustado, y se optimizaría para la arquitectura detectada. Idealmente, el cargador y el compilador permanecerían como residentes, una vez cargados, y solo habría una instancia de ambos.
De todos modos, quería responder a la idea de que tienes que tener al menos dos pases, no creo que esté completamente de acuerdo. Sí, utilizaría una segunda pasada a través del código compilado, pero no a través del código fuente.
Lo que debes hacer es, cuando te encuentres con un símbolo, ver la tabla hash de símbolos, y si no hay ninguna entrada allí, crea una y almacena un marcador de referencia directa en tu código compilado con un puntero a la tabla entrada. Cuando encuentre las definiciones de etiquetas y símbolos, actualice (o coloque datos nuevos) su tabla de símbolos.
Los objetos compilados individualmente nunca deben ser tan grandes que ocupen mucha memoria, por lo tanto, definitivamente todo el código compilado debe mantenerse en la memoria hasta que todo esté listo para ser escrito. La forma de mantener la huella de la memoria pequeña es simplemente tratando con un objeto a la vez, y nunca guardando más de un pequeño búfer lleno de código fuente en la memoria a la vez. Tal vez 64k o 128k o algo así. (Algo lo suficientemente grande como para que la sobrecarga involucrada al realizar la llamada para cargar el búfer desde el disco sea pequeña en comparación con el tiempo que lleva leer los datos del disco, para que la transmisión esté optimizada)
Entonces, un pase a través de la secuencia fuente para un objeto, luego encadena sus piezas, recopilando la información de referencia hacia adelante necesaria de la tabla hash sobre la marcha, y si los datos no están allí, eso es un error de compilación. Ese es el proceso que estaría tentado de probar.
Esto me recuerda a la prueba programador de la década de 1980, debido a un disquete con sólo el command.com y debug.com en él, qué tipo de un entorno de desarrollo crearía para ti. Sé cómo respondieron los chicos de Forth. – zumalifeguard