¿Cómo creo mi propio lenguaje de programación y un compilador para él? [Cerrado]


427

Soy minucioso con la programación y he encontrado lenguajes que incluyen BASIC, FORTRAN, COBOL, LISP, LOGO, Java, C ++, C, MATLAB, Mathematica, Python, Ruby, Perl, JavaScript, Assembly, etc. No puedo entender cómo las personas crean lenguajes de programación y diseñan compiladores para ello. Tampoco podía entender cómo las personas crean sistemas operativos como Windows, Mac, UNIX, DOS, etc. La otra cosa que es misteriosa para mí es cómo las personas crean bibliotecas como OpenGL, OpenCL, OpenCV, Cocoa, MFC, etc. Lo último que no puedo entender es cómo los científicos diseñan un lenguaje ensamblador y un ensamblador para un microprocesador. Realmente me gustaría aprender todas estas cosas y tengo 15 años. Siempre quise ser un informático, alguien como Babbage, Turing, Shannon o Dennis Ritchie.


Ya leí el libro de conceptos de Aho's Compiler Design y Tanenbaum OS y todos ellos solo discuten conceptos y códigos en un alto nivel. No entran en detalles y matices y cómo diseñar un compilador o sistema operativo. Quiero una comprensión concreta para poder crear una yo mismo y no solo una comprensión de lo que es un hilo, semáforo, proceso o análisis. Le pregunté a mi hermano sobre todo esto. Él es un estudiante de SB en EECS en el MIT y no tiene idea de cómo crear realmente todas estas cosas en el mundo real. Todo lo que sabe es solo una comprensión del diseño del compilador y los conceptos del sistema operativo como los que ustedes han mencionado (es decir, como subprocesos, sincronización, concurrencia, administración de memoria, análisis léxico, generación de código intermedio, etc.)


Si está en Unix / Linux, puede obtener información acerca de las herramientas dedicadas: lex, yaccy bison.
Mouviciel

Mi primera sugerencia sería Leer el Libro del Dragón de Aho. amazon.com/Compilers-Principles-Techniques-Alfred-Aho/dp/…
Julian

1
Tal vez no sea demasiado útil, pero recomiendo visitar sites.google.com/site/steveyegge2/blog-rants (blog de Steve Yegge) y steve-yegge.blogspot.com/ (otro blog de Steve Yegge).
KK.

3
Aprende tantos lenguajes de programación como puedas. De esa manera, aprenderá de sus conceptos y de sus errores. ¿Por qué contentarse con los enanos, cuando puedes pararte sobre el hombro de gigantes?
sbi

1
pista: un intérprete es más fácil que un compilador; es solo una clase que "hace algo" en base al texto de entrada que lee línea por línea. Otra pista: ata esto a la reflexión y puedes controlar objetos arbitrarios con tu script.
Dave Cousineau

Respuestas:


407

Básicamente, su pregunta es "¿cómo se diseñan e implementan los chips de computadora, los conjuntos de instrucciones, los sistemas operativos, los idiomas, las bibliotecas y las aplicaciones?" Esa es una industria mundial multimillonaria que emplea a millones de personas, muchas de las cuales son especialistas. Es posible que desee centrar su pregunta un poco más.

Dicho esto, puedo echar un vistazo a:

No puedo entender cómo las personas crean lenguajes de programación y diseñan compiladores para ello.

Me sorprende, pero mucha gente considera que los lenguajes de programación son mágicos. Cuando me encuentro con personas en fiestas o lo que sea, si me preguntan qué hago, les digo que diseño lenguajes de programación e implemento los compiladores y herramientas, y es sorprendente la cantidad de veces que las personas, programadores profesionales, lo entienden. "wow, nunca lo pensé, pero sí, alguien tiene que diseñar esas cosas". Es como si pensaran que los idiomas simplemente surgen totalmente formados con infraestructuras de herramientas a su alrededor.

No solo aparecen. Los idiomas están diseñados como cualquier otro producto: haciendo cuidadosamente una serie de compensaciones entre las posibilidades de la competencia. Los compiladores y las herramientas se crean como cualquier otro producto de software profesional: analizando el problema, escribiendo una línea de código a la vez y luego probando el programa resultante.

El diseño del lenguaje es un gran tema. Si está interesado en diseñar un idioma, un buen lugar para comenzar es pensar en cuáles son las deficiencias en un idioma que ya conoce. Las decisiones de diseño a menudo surgen al considerar un defecto de diseño en otro producto.

Alternativamente, considere un dominio que le interese y luego diseñe un lenguaje específico de dominio (DSL) que especifique soluciones a problemas en ese dominio. Usted mencionó LOGO; ese es un gran ejemplo de un DSL para el dominio de "dibujo lineal". Las expresiones regulares son un DSL para el dominio "buscar un patrón en una cadena". LINQ en C # / VB es un DSL para el dominio "filtrar, unir, ordenar y proyectar datos". HTML es un DSL para el dominio "describir el diseño del texto en una página", y así sucesivamente. Hay muchos dominios que son aptos para soluciones basadas en el lenguaje. Uno de mis favoritos es Inform7, que es un DSL para el dominio "juego de aventura basado en texto"; Es probablemente el lenguaje de programación serio de más alto nivel que he visto.

Una vez que haya esbozado cómo quiere que se vea su idioma, intente escribir con precisión cuáles son las reglas para determinar qué es un programa legal e ilegal. Por lo general, querrá hacer esto en tres niveles:

  1. léxico : cuáles son las reglas para las palabras en el idioma, qué caracteres son legales, cómo son los números, etc.
  2. sintáctico : ¿cómo se combinan las palabras del idioma en unidades más grandes? En C #, las unidades más grandes son cosas como expresiones, declaraciones, métodos, clases, etc.
  3. semántica : dado un programa sintácticamente legal, ¿cómo averiguar lo que el programa hace ?

Escriba estas reglas con la mayor precisión posible . Si hace un buen trabajo, puede usarlo como base para escribir un compilador o un intérprete. Eche un vistazo a la especificación C # o la especificación ECMAScript para ver a qué me refiero; Están repletos de reglas muy precisas que describen lo que hace un programa legal y cómo averiguar qué hace.

Una de las mejores maneras de comenzar a escribir un compilador es escribir un compilador de lenguaje de alto nivel a lenguaje de alto nivel . Escriba un compilador que incluya cadenas en su idioma y escupe cadenas en C # o JavaScript o cualquier idioma que conozca; deje que el compilador para ese idioma se encargue del trabajo pesado de convertirlo en código ejecutable.

Escribo un blog sobre el diseño de C #, VB, VBScript, JavaScript y otros lenguajes y herramientas; si este tema te interesa, échale un vistazo. http://blogs.msdn.com/ericlippert (histórico) y http://ericlippert.com (actual)

En particular, puede encontrar esta publicación interesante; Aquí enumero la mayoría de las tareas que el compilador de C # realiza para usted durante su análisis semántico. Como puede ver, hay muchos pasos. Dividimos el gran problema de análisis en una serie de problemas que podemos resolver individualmente.

http://blogs.msdn.com/b/ericlippert/archive/2010/02/04/how-many-passes.aspx

Finalmente, si está buscando un trabajo para hacer estas cosas cuando sea mayor, considere venir a Microsoft como pasante universitario e intentar ingresar a la división de desarrolladores. ¡Así es como terminé con mi trabajo hoy!


¿Ha escrito hasta qué punto las optimizaciones del compilador ya no se realizan ya que el CLR puede hacerlas automáticamente?

66
@ Thorbjørn: Seamos claros acerca de la terminología. Un "compilador" es cualquier dispositivo que se traduce de un lenguaje de programación a otro. Una de las cosas buenas de tener un compilador de C # que convierte C # en IL, y un compilador de IL (el "jitter") que convierte IL en código de máquina, es que puede escribir el compilador de C # en IL (¡fácil!), Y ponga las optimizaciones específicas del procesador en el jitter. No es que las optimizaciones del compilador "no se estén haciendo", es que el equipo del compilador jit las hace por nosotros. Ver blogs.msdn.com/b/ericlippert/archive/2009/06/11/…
Eric Lippert

66
@ Cyclotis04: Inform6 compila en código Z, que es un famoso ejemplo muy temprano de una máquina virtual basada en bytecode. Así es como todos esos juegos de Infocom en la década de 1980 podrían ser más grandes que la memoria y portátiles para múltiples arquitecturas; los juegos se compilaron en código z y luego se implementaron intérpretes de código z con paginación de memoria de código para múltiples máquinas. Hoy en día, por supuesto, puede ejecutar un intérprete de zcode en un reloj de pulsera si es necesario, pero en el pasado eso era de alta tecnología . Ver en.wikipedia.org/wiki/Z-machine para más detalles.
Eric Lippert

@EricLippert compilador no es un dispositivo, el dispositivo es algo contiene hardware.we puede decir un programa predefinido que tiene un conjunto de reglas para convertir los datos de entrada en código máquina
dharam

2
@dhams: Un dispositivo es cualquier cosa hecha para un propósito particular. Todos los compiladores que he escrito se ejecutan en un hardware diseñado específicamente para permitir que existan los compiladores.
Eric Lippert

127

Puede encontrar Lets Build a Compiler de Jack Crenshaw una introducción interesante para escribir compiladores y lenguaje ensamblador.

El autor lo mantuvo muy simple y se centró en la creación de funcionalidad real.


2
Lo interesante de la introducción de Crenshaw es que termina (spoiler: está incompleto) justo cuando te topaste con los problemas que te harían darte cuenta, oye, realmente debería haber diseñado mi lenguaje completamente antes de comenzar a implementarlo. Y luego dices, oye, si tengo que escribir una especificación de lenguaje completo, ¿por qué no hacerlo en una notación formal que luego pueda alimentar en una herramienta para generar un analizador sintáctico? Y luego lo estás haciendo como todos los demás.
poco

3
@kindall, debes haberlo hecho a mano para darte cuenta de que hay una razón para usar las herramientas.

72

"Realmente me gustaría aprender estas cosas". Si usted es serio a largo plazo:

  • Ir a la universidad, especializarse en ingeniería de software. Tome cada clase de compilador que pueda obtener. Las personas que imparten las clases tienen mejor educación y más experiencia que usted; es bueno que sus perspectivas expertas se utilicen para presentarle la información de maneras que nunca obtendrá al leer el código.

  • Seguir con las clases de matemáticas hasta la escuela secundaria y continuar en la universidad durante los 4 años. Centrarse en las matemáticas no estándar: lógica, teoría de grupos, metamatemáticas. Esto te obligará a pensar de manera abstracta. Le permitirá leer los documentos teóricos avanzados sobre la compilación y comprender por qué esas teorías son interesantes y útiles. Puede ignorar esas teorías avanzadas, si siempre quiere estar detrás del estado del arte.

  • Recopile / lea los textos estándar del compilador: Aho / Ullman, etc. Contienen lo que la comunidad generalmente acepta es algo fundamental. Es posible que no use todo de esos libros, pero debe saber que existe, y debe saber por qué no lo está usando. Pensé que Muchnick era genial, pero es para temas bastante avanzados.

  • Construye un compilador. Comience AHORA construyendo uno podrido. Esto te enseñará algunos problemas. Construye una segunda. Repetir. Esta experiencia genera una gran sinergia con el aprendizaje de su libro.

  • Un buen lugar para comenzar es aprender sobre BNF (Backus Naur Form), analizadores y generadores de analizadores. BNF se utiliza de manera universal y universal en tierra compiladora, y no puede hablar de manera realista con sus compañeros compiladores si no lo sabe.

Si desea una excelente primera introducción a la compilación, y el valor directo de BNF no solo para la documentación sino como un metalenguaje procesable por herramienta, consulte este tutorial (no el mío) sobre la construcción de compiladores "meta" (compiladores que compilan compiladores) basados ​​en un artículo de 1964 (sí, lo leíste bien) ["META II, un lenguaje de escritura de compilación orientado a la sintaxis" de Val Schorre. (http://doi.acm.org/10.1145/800257.808896)] Este en mi humilde opinión es uno de los mejores documentos de comp-sci jamás escritos: te enseña a construir compiladores en 10 páginas. Aprendí inicialmente de este artículo.

Lo que escribí anteriormente es mucho de la experiencia personal, y creo que me ha servido bastante bien. YMMV, pero en mi humilde opinión, no por mucho.


54
-1 Ninguno de los anteriores es necesario.
Neil Butterworth

77
@nbt Ninguno de los anteriores es necesario. Pero todo lo anterior ayuda. Realmente mucho
Konrad Rudolph

1
Estoy particularmente en desacuerdo con el "¡Aprende matemáticas para pensar de manera abstracta!" sugerencia. Incluso si crees que "aprender a pensar de manera abstracta" es particularmente útil para crear tu propio lenguaje de programación y compilador (no lo creo, me resulta mucho más útil aprender haciendo que tomando estas indirectas, rutas increíblemente indirectas) ¡Las matemáticas no son el único campo con pensamiento abstracto! (Soy un matemático por cierto, así que no estoy negando el uso de las matemáticas en general, solo su aplicabilidad en este caso particular ...)
Grautur

26
Si desea leer los documentos técnicos avanzados sobre la teoría del compilador, es mejor que sea matemáticamente competente. Puede decidir ignorar esa literatura, y su teoría y, por lo tanto, los compiladores serán más pobres. Todos los detractores aquí señalan que puedes construir un compilador sin mucha educación formal, y estoy de acuerdo. Parecen implicar que puedes construir compiladores realmente buenos sin él. Esa no es una apuesta que me gustaría tomar.
Ira Baxter

77
CS es una disciplina que es realmente útil para el diseño y la implementación del lenguaje. No es obligatorio, por supuesto, pero ha habido décadas de investigación que pueden y deben aprovecharse, y no hay ninguna razón para repetir otros errores.
Donal Fellows

46

Aquí hay un libro / curso en línea que puede seguir llamado Los elementos de los sistemas informáticos: construcción de una computadora moderna a partir de los primeros principios .

Usando simuladores, en realidad construyes un sistema informático completo desde cero. Si bien muchos comentaristas han declarado que su pregunta es demasiado amplia, este libro en realidad la responde mientras se mantiene muy manejable. Cuando haya terminado, habrá escrito un juego en un lenguaje de alto nivel (que usted diseñó), que utiliza la funcionalidad de su propio sistema operativo, que su compilador compila en un lenguaje VM (que diseñó). traducido a un lenguaje ensamblador (que usted diseñó) por su traductor de VM, que se ensambla en el código de máquina (que usted diseñó) por su ensamblador, que se ejecuta en su sistema informático y lo ensambla a partir de chips que diseñó utilizando lógica booleana y Un lenguaje de descripción de hardware simple.

Los capítulos:

  1. Resumen del curso
  2. Lógica booleana
  3. Chips combinatorios
  4. Chips secuenciales
  5. Lenguaje de máquina
  6. Arquitectura de Computadores
  7. Ensamblador
  8. Máquina virtual I: aritmética
  9. Máquina virtual II: control
  10. Lenguaje de programación
  11. Compilador I: Análisis de sintaxis
  12. Compilador II: Generación de Código
  13. Sistema operativo
  14. Elemento de la lista

Más diversión para llevar


Gracias por las ediciones, persona desconocida. Lo intenté un par de veces, pero no pude concentrar mis pensamientos lo suficiente para la descripción ... pero no quería no mencionar el libro. El libro ahora está en línea en el enlace del Plan de Estudio: www1.idc.ac.il/tecs/plan.html . También tiene un precio muy razonable en línea. Disfruta a todos.
Joe Internet

Iba a sugerir esto yo mismo ... para los perezosos, echa un vistazo a la introducción de 10 minutos: De NAND a Tetris en 12 Pasos @ youtube.com/watch?v=JtXvUoPx4Qs
Richard Anthony Hein

46

Da un paso atrás. Un compilador es simplemente un programa que traduce un documento en un idioma a un documento en otro idioma. Ambos idiomas deben estar bien definidos y específicos.

Los lenguajes no tienen que ser lenguajes de programación. Pueden ser cualquier idioma cuyas reglas se puedan escribir. Probablemente hayas visto Google Translate ; es un compilador porque puede traducir un idioma (por ejemplo, alemán) a otro (japonés, tal vez).

Otro ejemplo de un compilador es un motor de representación HTML. Su entrada es un archivo HTML y la salida es una serie de instrucciones para dibujar los píxeles en la pantalla.

Cuando la mayoría de las personas hablan de un compilador, generalmente se refieren a un programa que traduce un lenguaje de programación de alto nivel (como Java, C, Prolog) a uno de bajo nivel (código ensamblador o de máquina). Eso puede ser desalentador. Pero no es tan malo cuando consideras que un compilador es un programa que traduce un idioma a otro.

¿Puedes escribir un programa que invierta cada palabra en una cadena? Por ejemplo:

When the cat's away, the mice will play.

se convierte

nehW eht s'tac yawa, eht ecim lliw yalp.

Ese no es un programa difícil de escribir, pero debes pensar en algunas cosas:

  • ¿Qué es una "palabra"? ¿Puedes definir qué caracteres forman una palabra?
  • ¿Dónde comienzan y terminan las palabras?
  • ¿Las palabras están separadas por un solo espacio, o puede haber más o menos?
  • ¿Es necesario revertir la puntuación también?
  • ¿Qué pasa con la puntuación dentro de una palabra?
  • ¿Qué pasa con las mayúsculas?

Las respuestas a estas preguntas ayudan a que el lenguaje esté bien definido. Ahora ve y escribe el programa. Felicitaciones, acabas de escribir un compilador.

¿Qué tal esto? ¿Puedes escribir un programa que tome una serie de instrucciones de dibujo y genere un archivo PNG (o JPEG)? Tal vez algo como esto:

image 100 100
background black
color red
line 20 55 93 105
color green
box 0 0 99 99

Nuevamente, necesitará pensar un poco para definir el lenguaje:

  • ¿Cuáles son las instrucciones primitivas?
  • ¿Qué viene después de la palabra "línea"? ¿Qué viene después del "color"? Del mismo modo para "fondo", "cuadro", etc.
  • ¿Qué es un número?
  • ¿Se permite un archivo de entrada vacío?
  • ¿Está bien capitalizar las palabras?
  • ¿Se permiten números negativos?
  • ¿Qué sucede si no le das la directiva "imagen"?
  • ¿Está bien no especificar un color?

Por supuesto, hay más preguntas que responder, pero si puede concretarlas, ha definido un idioma. El programa que escribes para hacer la traducción es, supongo, un compilador.

Verá, escribir un compilador no es tan difícil. Los compiladores que ha utilizado en Java o C son versiones más grandes de estos dos ejemplos. ¡Así que adelante! Defina un lenguaje simple y escriba un programa para que ese idioma haga algo. Tarde o temprano querrás ampliar tu idioma. Por ejemplo, es posible que desee agregar variables o expresiones aritméticas. Su compilador se volverá más complejo, pero lo entenderá todo porque lo escribió usted mismo. Así es como surgen los lenguajes y los compiladores.


77
myFirstCompiler = (str) -> ("" + (str || "")). split (''). reverse (). join (''); jsfiddle.net/L7qSr
Larry Battle

21

Si está interesado en el diseño del compilador, consulte el Libro del Dragón (título oficial: Compiladores: Principios, Técnicas y Herramientas). Es ampliamente considerado como un libro clásico sobre este tema.


44
Tenga en cuenta que es posible que necesite un poco más de experiencia real para aprovechar al máximo este libro. Gran referencia, sin embargo.

13
-1 Solo alguien que no lo haya leído puede pensar que el libro del dragón es bueno. y en particular no aborda la cuestión.
Neil Butterworth

33
El libro del dragón? ¿Para un entusiasta de quince años? Prefiero que mantenga su entusiasmo un poco más.
David Thornley

1
Una alternativa más accesible: 'Pragmática del lenguaje de programación' 3e .
willjcroz

@DavidThornley No lo descartes por completo (Sí, me doy cuenta de que esta es una publicación muy antigua). Comencé a investigar cómo funcionan los idiomas a los 15 años y me concentré específicamente en máquinas virtuales. Ahora tengo 16 años y después de meses de investigación, redacción y reescritura, tengo un intérprete y un compilador que me satisfacen.
David


10

No creas que hay algo mágico en un compilador o un sistema operativo: no lo hay. ¿Recuerdas los programas que escribiste para contar todas las vocales en una cadena o sumar los números en una matriz? Un compilador no es diferente en concepto; Es mucho más grande.

Cada programa tiene tres fases:

  1. lee algunas cosas
  2. procesar esas cosas: traducir los datos de entrada a los datos de salida
  3. escribir algunas otras cosas: los datos de salida

Piénselo: ¿qué es la entrada al compilador? Una cadena de caracteres de un archivo fuente.

¿Qué es la salida del compilador? Una cadena de bytes que representan las instrucciones de la máquina para la computadora de destino.

Entonces, ¿cuál es la fase de "proceso" del compilador? ¿Qué hace esa fase?

Si tenemos en cuenta que el compilador - como cualquier otro programa - tiene que incluir estos tres fases, tendrá una buena idea de cómo se construye un compilador.


3
Como dijo Neil, cierto pero no útil. Los aspectos fundamentales del compilador, como una gramática recursiva y tablas de símbolos, no son intuitivamente obvios.
Mason Wheeler

1
@Mason Wheeler: Creo que cualquiera que aspire de manera realista a escribir un compilador (y diseñar el idioma de destino) probablemente piense que la gramática recursiva y las tablas de símbolos son conceptos bastante básicos.
FumbleFingers

8

No soy un experto, pero aquí está mi puñalada:

No pareces preguntar sobre escribir un compilador, solo un ensamblador. Esto no es realmente mágico.

Robando la respuesta de alguien más de SO ( https://stackoverflow.com/questions/3826692/how-do-i-translate-assembly-to-binary ), el ensamblaje se ve así:

label:  LDA #$00
        JMP label

Luego lo ejecuta a través de un ensamblador y se convierte en algo como esto:

$A9 $00
$4C $10 $00

Solo que está todo aplastado, así:

$A9 $00 $4C $10 $00

Realmente no es magia.

No puede escribir eso en el bloc de notas, porque el bloc de notas utiliza ASCII (no hexadecimal). Usaría un editor hexadecimal o simplemente escribiría los bytes programáticamente. Escribe ese hexadecimal en un archivo, asígnele el nombre "a.exe" o "a.out" y luego le dice al sistema operativo que lo ejecute.

Por supuesto, las CPU y los sistemas operativos modernos son realmente bastante complicados, pero esa es la idea básica.

Si desea escribir un nuevo compilador, así es como se hace:

1) Escriba un lenguaje interpretado usando algo como el ejemplo de la calculadora en pyparsing (o cualquier otro buen marco de análisis). Eso lo pondrá al día sobre los conceptos básicos del análisis.

2) Escribe un traductor. Traduce tu idioma a, digamos, Javascript. Ahora su idioma se ejecutará en un navegador.

3) Escriba un traductor a un nivel inferior, como LLVM, C o Assembly.

Puedes parar aquí, este es un compilador. No es un compilador optimizador, pero esa no era la pregunta. Es posible que también deba considerar escribir un enlazador y ensamblador, pero ¿realmente desea hacerlo?

4) (Loco) Escribe un optimizador. Grandes equipos trabajan durante décadas en esto.

4) (Sane) Participe en una comunidad existente. GCC, LLVM, PyPy, el equipo central que trabaja en cualquier intérprete.


8

Varios otros han dado excelentes respuestas. Solo agregaré algunas sugerencias más. Primero, un buen libro para lo que está tratando de hacer son los textos de implementación del compilador moderno de Appel (elija C , Java o ML estándar ). Este libro lo lleva a través de una implementación completa de un compilador para un lenguaje simple, Tiger, para ensamblar MIPS que se puede ejecutar en un emulador, junto con una biblioteca de soporte de tiempo de ejecución mínimo. Para un solo paso por todo lo necesario para que un lenguaje compilado funcione, es un libro bastante bueno 1 .

Appel lo guiará a través de cómo compilar un lenguaje que viene prediseñado, pero no pasa mucho tiempo en lo que significan las diversas características del lenguaje o cómo pensar en ellas en términos de sus méritos relativos para diseñar el suyo. Para ese aspecto, los lenguajes de programación: conceptos y construcciones son decentes. Conceptos, técnicas y modelos de programación de computadoras también es un buen libro para pensar profundamente sobre el diseño del lenguaje, aunque lo hace en el contexto de un solo lenguaje ( Oz ).

Finalmente, mencioné que Appel tiene su texto en C, Java y ML estándar: si usted es serio sobre la construcción del compilador y los lenguajes de programación, le recomiendo aprender ML y usar esa versión de Appel. Los idiomas de la familia ML tienen sistemas de tipo fuerte y son predominantemente funcionales, características que serán diferentes de muchos otros idiomas, por lo que aprenderlos si aún no conoce un idioma funcional perfeccionará su oficio lingüístico. Además, su mentalidad funcional y de coincidencia de patrones es extremadamente adecuada para los tipos de manipulaciones que necesita hacer a menudo en un compilador, por lo que los compiladores escritos en lenguajes basados ​​en ML suelen ser mucho más cortos y fáciles de entender que los compiladores escritos en C, Java, o lenguajes similares. El libro de Harperon Standard ML es una guía bastante buena para comenzar; trabajar con eso debería prepararlo para asumir el libro de implementación del compilador ML estándar de Appel. Si aprende ML estándar, también será bastante fácil elegir OCaml para un trabajo posterior; En mi opinión, tiene mejores herramientas para el programador que trabaja (se integra de manera más limpia con el entorno del sistema operativo circundante, produce programas ejecutables fácilmente y tiene algunas herramientas espectaculares de compilación como ulex y Menhir).


1 Para referencia a largo plazo, prefiero el Libro del Dragón, ya que tiene más detalles sobre las cosas a las que probablemente me referiré, como el funcionamiento interno de los algoritmos analizadores y tiene una cobertura más amplia de diferentes enfoques, pero el libro de Appel es muy bueno. para un primer pase Básicamente, Appel te enseña una forma de hacer las cosas a través del compilador y te guía a través de él. Dragon Book cubre diferentes alternativas de diseño con más detalle, pero proporciona mucha menos orientación sobre cómo hacer que algo funcione.


Editado : reemplace la referencia incorrecta de Aho con Sethi, mencione CTMCP.


Ugh, tenía Essentials Of Programming Language para mi clase de intérpretes universitarios. Fue horrible. Incluso me gusta el esquema personalmente y no me importa la sintaxis, fueron las pobres explicaciones de los autores las que me arruinaron.
Greg Guida

Me gusta la compilación de Appel con continuaciones, pero encontré que sus libros suponían muchos conocimientos previos.
Jon Harrop

6

Tuve que crear un compilador para la clase en la universidad.

Los principios básicos para hacerlo no son tan complicados como parece. El primer paso es crear tu gramática. Piensa en la gramática del idioma inglés. Del mismo modo, puede analizar una oración si tiene un sujeto y un predicado. Para más información sobre eso, lea sobre Gramáticas sin contexto .

Una vez que tiene la gramática abajo (las reglas de su idioma), escribir un compilador es tan simple como seguir esas reglas. Los compiladores generalmente se traducen al código de la máquina, pero a menos que desee aprender x86, le sugiero que mire MIPS o haga su propia máquina virtual.

Los compiladores suelen tener dos partes, un escáner y un analizador sintáctico. Básicamente, el escáner lee el código y lo separa en tokens. El analizador analiza la estructura de esas fichas. Luego, el compilador revisa y sigue algunas reglas bastante simples para convertirlo a cualquier código que necesite (ensamblado, código intermedio como bytecode, etc.). Si lo divide en partes cada vez más pequeñas, esto eventualmente no es desalentador en absoluto.

¡Buena suerte!


8
¿Conceptualmente simple? Si. En realidad simple? No.
Neil Butterworth

77
Uhm El compilador, después de escanear / analizar necesita hacer una verificación de tipo / inferencia, optimización, asignación de registros, etc., etc. Estos pasos son cualquier cosa menos simples. (Al usar el código interpretado, simplemente diferir estas partes a la etapa de tiempo de ejecución.)
Macke

No tengo ningún voto: mientras los compiladores tienen dos partes básicas, una de ellas es construir una descripción abstracta del programa (que generalmente se divide en escaneo y análisis) y la otra para escribir una versión de esa descripción abstracta nuevamente en algunos otra forma (por ejemplo, código de máquina). (Nota al margen : los compiladores de optimización generalmente intentan mejorar la descripción del resumen antes de escribirlo, pero eso es un refinamiento).
Donal Fellows

6

El código del libro de Petzold es una gran introducción a los no técnicos y técnicos, comenzando por los primeros principios. Es altamente legible y amplio en su alcance sin atascarse demasiado.

Ahora que he escrito esto, tendré que volver a leerlo.



5

Hay excelentes respuestas en este hilo, pero solo quería agregar las mías ya que una vez tuve la misma pregunta. (Además, me gustaría señalar que el libro sugerido por Joe-Internet es un excelente recurso).

Primero está la cuestión de cómo funciona una computadora. Así es como: Entrada -> Calcular -> Salida.

Primero considere la parte de "Calcular". Veremos cómo funciona Entrada y Salida más adelante.

Una computadora consiste esencialmente en un procesador (o CPU) y algo de memoria (o RAM). La memoria es una colección de ubicaciones, cada una de las cuales puede almacenar un número finito de bits, y cada una de esas ubicaciones de memoria puede ser referenciada por un número, esto se llama la dirección de la ubicación de la memoria. El procesador es un dispositivo que puede obtener datos desde la memoria, realice algunas operaciones basadas en los datos y vuelva a escribir algunos datos en la memoria. ¿Cómo determina el procesador qué leer y qué hacer después de leer los datos de la memoria?

Para responder a esto, necesitamos comprender la estructura de un procesador. La siguiente es una vista bastante simple. Un procesador consta esencialmente de dos partes. Uno es un conjunto de ubicaciones de memoria integradas dentro del procesador que sirven como memoria de trabajo. Estos se llaman "registros". El segundo es un montón de maquinaria electrónica construida para realizar ciertas operaciones utilizando los datos en los registros. Hay dos registros especiales llamados "Contador de programa" o la PC y el "Registro de instrucciones" o ir. El procesador considera que la memoria está dividida en tres partes. La primera parte es la "memoria de programa", que almacena el programa de computadora que se está ejecutando. El segundo es la "memoria de datos". El tercero se usa para algunos propósitos especiales, hablaremos de eso más adelante. El contador de programa contiene la ubicación de la siguiente instrucción para leer de la memoria de programa. El contador de instrucciones contiene un número que se refiere a la operación actual que se realiza. Cada operación que puede realizar un procesador se refiere a un número llamado código operativo de la operación. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. El contador de instrucciones contiene un número que se refiere a la operación actual que se realiza. Cada operación que puede realizar un procesador se refiere a un número llamado código operativo de la operación. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. El contador de instrucciones contiene un número que se refiere a la operación actual que se realiza. Cada operación que puede realizar un procesador se refiere a un número llamado código operativo de la operación. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. Cada operación que puede realizar un procesador se refiere a un número llamado código operativo de la operación. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. Cada operación que puede realizar un procesador se refiere a un número llamado código operativo de la operación. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro. El funcionamiento esencial de una computadora es leer la ubicación de la memoria a la que hace referencia el contador de programas en el registro de instrucciones (e incrementa el contador de programas para que apunte a la ubicación de la memoria de la siguiente instrucción). A continuación, lee el Registro de instrucciones y realiza la operación deseada. Por ejemplo, la instrucción podría ser leer una ubicación de memoria específica en un registro, o escribir en algún registro o realizar alguna operación utilizando los valores de dos registros y escribir la salida en un tercer registro.

Ahora, ¿cómo realiza la computadora Entrada / Salida? Proporcionaré una respuesta muy simplificada. Ver http://en.wikipedia.org/wiki/Input/output y http://en.wikipedia.org/wiki/Interrupt. para más. Utiliza dos cosas, esa tercera parte de la memoria y algo llamado Interrupciones. Todos los dispositivos conectados a una computadora deben poder intercambiar datos con el procesador. Lo hace utilizando la tercera parte de la memoria mencionada anteriormente. El procesador asigna una porción de memoria a cada dispositivo y el dispositivo y el procesador se comunican a través de esa porción de memoria. Pero, ¿cómo sabe el procesador qué ubicación se refiere a qué dispositivo y cuándo necesita un dispositivo para intercambiar datos? Aquí es donde entran las interrupciones. Una interrupción es esencialmente una señal para que el procesador pause lo que es actualmente y guarde todos sus registros en una ubicación conocida y luego comience a hacer otra cosa. Hay muchas interrupciones, cada una se identifica por un número único. Para cada interrupción, hay un programa especial asociado a ella. Cuando ocurre la interrupción, El procesador ejecuta el programa correspondiente a la interrupción. Ahora, dependiendo de la BIOS y de cómo están conectados los dispositivos de hardware a la placa base de la computadora, cada dispositivo tiene una interrupción única y una porción de memoria. Al arrancar el sistema operativo con la ayuda de la BIOS determina la interrupción y la ubicación de la memoria de cada dispositivo y configura los programas especiales para que la interrupción maneje adecuadamente los dispositivos. Entonces, cuando un dispositivo necesita algunos datos o quiere enviar algunos datos, indica una interrupción. El procesador pausa lo que está haciendo, maneja la interrupción y luego vuelve a lo que está haciendo. Hay muchos tipos de interrupciones, como el disco duro, el teclado, etc. Una importante es el temporizador del sistema, que invoca una interrupción a intervalos regulares. También hay códigos de operación que pueden provocar interrupciones, llamadas interrupciones de software.

Ahora casi podemos entender cómo funciona un sistema operativo. Cuando se inicia, el sistema operativo configura una interrupción del temporizador, de modo que le da control al sistema operativo a intervalos regulares. También configura otras interrupciones para manejar otros dispositivos, etc. Ahora, cuando la computadora está ejecutando un montón de programas, y ocurre la interrupción del temporizador, el sistema operativo toma el control y realiza tareas importantes como la gestión de procesos, la gestión de memoria, etc. También un sistema operativo generalmente proporciona Una forma abstracta para que los programas accedan a los dispositivos de hardware, en lugar de permitirles acceder a los dispositivos directamente. Cuando un programa quiere acceder a un dispositivo, llama a un código proporcionado por el sistema operativo que luego se comunica con el dispositivo. Hay mucha teoría involucrada en estos que trata con la concurrencia, hilos, bloqueos, gestión de memoria, etc.

Ahora, uno puede en teoría escribir un programa directamente usando códigos de operación. Esto es lo que se llama código de máquina. Esto obviamente es muy doloroso. Ahora, un lenguaje ensamblador para el procesador no es más que mnemotecnia para estos códigos de operación, lo que facilita la escritura de programas. Un ensamblador simple es un programa que toma un programa escrito en ensamblador y reemplaza los mnemónicos con los códigos de operación apropiados.

¿Cómo se puede diseñar un procesador y lenguaje ensamblador? Para saber que tienes que leer algunos libros sobre arquitectura de computadoras. (véanse los capítulos 1-7 del libro referido por joe-internet). Esto implica aprender sobre álgebra booleana, cómo construir circuitos combinatorios simples para sumar, multiplicar, etc., cómo construir memoria y circuitos secuenciales, cómo construir un microprocesador, etc.

Ahora, ¿cómo se escriben los idiomas de la computadora? Uno podría comenzar escribiendo un ensamblador simple en código máquina. Luego use ese ensamblador para escribir un compilador para un subconjunto simple de C. Luego use ese subconjunto de C para escribir una versión más completa de C. Finalmente use C para escribir un lenguaje más complicado como python o C ++. Por supuesto, para escribir un idioma primero debe diseñarlo (de la misma manera que desea un procesador). Nuevamente mira algunos libros de texto sobre eso.

¿Y cómo se escribe un sistema operativo? Primero apunta a una plataforma como x86. Luego descubres cómo arranca y cuándo se invocará tu sistema operativo. Una PC típica arranca de esta manera. Se inicia y BIOS realiza algunas pruebas. Luego, la BIOS lee el primer sector del disco duro y carga el contenido en una ubicación específica de la memoria. Luego configura la CPU para comenzar a ejecutar estos datos cargados. Este es el punto en el que se invoca. Un sistema operativo típico en este punto carga el resto de la memoria. Luego inicializa los dispositivos y configura otras cosas y finalmente te saluda con la pantalla de inicio de sesión.

Entonces, para escribir un sistema operativo, debe escribir el “cargador de arranque”. Luego debe escribir código para manejar las interrupciones y dispositivos. Luego debe escribir todo el código para la gestión de procesos, gestión de dispositivos, etc. Luego debe escribir una API que permita que los programas que se ejecutan en su sistema operativo accedan a dispositivos y otros recursos. Y finalmente debe escribir código que lea un programa desde el disco, lo configure como un proceso y comience a ejecutarlo.

Por supuesto, mi respuesta es abiertamente simplificada y probablemente de poco uso práctico. En mi defensa, ahora soy un estudiante graduado en teoría, así que he olvidado muchas de estas cosas. Pero puedes buscar en Google muchas de estas cosas y obtener más información.


4

Puedo recordar un punto en mi carrera de programación cuando estaba en un estado de confusión similar al tuyo: había leído bastante sobre la teoría, el libro del Dragón, el libro del Tigre (rojo), pero todavía no tenía mucho de Una pista de cómo poner todo junto.

Lo que lo unió fue encontrar un proyecto concreto que hacer (y luego descubrir que solo necesitaba un pequeño subconjunto de toda la teoría).

La máquina virtual Java me proporcionó un buen punto de partida: conceptualmente es un "procesador" pero está muy abstraído de los detalles desordenados de las CPU reales. También ofrece una parte importante y a menudo pasada por alto del proceso de aprendizaje: desarmar las cosas antes de volver a armarlas (como solían hacer los niños con los aparatos de radio en los viejos tiempos).

Juega con un descompilador y la clase Hello, World en Java. Lea las especificaciones de JVM e intente comprender lo que está sucediendo. Esto le dará una perspectiva sólida de lo que está haciendo el compilador .

Luego jugar con el código que crea la clase Hola, mundo. (De hecho, está creando un compilador específico de la aplicación, para un lenguaje altamente especializado en el que solo puede decir Hello, World).

Intente escribir código que podrá leer en Hello, World escrito en algún otro idioma y generar la misma clase. Hágalo para que pueda cambiar la cadena de "Hola, Mundo" a otra cosa.

Ahora intente compilar (en Java) una clase que calcule alguna expresión aritmética, como "2 * (3 + 4)". Desarme esta clase, escriba un "compilador de juguetes" que pueda armarlo nuevamente.


3

1) Grandes video conferencias de la Universidad de Washington:

Construcción del compilador CSE P 501 - Otoño 2009 www.cs.washington.edu/education/courses/csep501/09au/lectures/video.html *

2) SICP http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/ Y el libro con el mismo nombre. Esto es realmente obligatorio para cualquier ingeniero de software.

3) Además, sobre programación funcional, Haskell, cálculo lambda, semántica (incluida la denotación) e implementación del compilador para lenguajes funcionales. Puede comenzar desde 2005-SS-FP.V10.2005-05-24.HDV si ya conoce a Haskell. Los videos Uxx son respuestas. Por favor, siga primero los videos Vxx .

http://video.s-inf.de/#FP.2005-SS-Giesl.(COt).HD_Videoaufzeichnung

(los videos están en inglés, aunque otros cursos están en alemán).

  • Los nuevos usuarios solo pueden publicar un máximo de dos hipervínculos.

3

ANTLR es un buen punto de partida. Es un marco generador de lenguaje, similar a Lex y Yacc. Hay una interfaz gráfica de usuario llamada ANTLRWorks que simplifica el proceso.

En el mundo .NET existe el Dynamic Language Runtime que se puede usar para generar código en el mundo .NET. He escrito un lenguaje de expresión llamado Zentrum que genera código usando el DLR. Le mostrará cómo analizar y ejecutar expresiones escritas de forma estática y dinámica.


2

Para una introducción simple sobre cómo funcionan los compiladores y cómo crear su propio lenguaje de programación, recomendaría el nuevo libro http://createyourproglang.com que se enfoca más en la teoría del diseño del lenguaje sin tener que saber acerca de los componentes internos del sistema operativo / CPU, es decir, lexers, analizadores , intérpretes, etc.

Utiliza las mismas herramientas que se usaron para crear los populares lenguajes de programación Coffee Script y Fancy .


2

Si todo lo que dice es cierto, tiene el perfil de un investigador prometedor, y una comprensión concreta solo se puede obtener de una manera: estudiando. Y no digo "¡ Lee todos estos libros de informática de alto nivel (especialmente estos ) escritos por este genio !"; Quiero decir: debes estar con personas de alto nivel para ser un informático como Charles Babbage, Alan Turing, Claude Shannon o Dennis Ritchie. No desprecio a las personas autodidactas (soy una de ellas) pero no hay muchas personas como tú por ahí. Recomiendo seriamente el Programa de Sistemas Simbólicos (SSP) en la Universidad de Stanford . Como dice su sitio web:

El Programa de Sistemas Simbólicos (SSP) en la Universidad de Stanford se enfoca en computadoras y mentes: sistemas artificiales y naturales que usan símbolos para representar información. SSP reúne a estudiantes y profesores interesados ​​en diferentes aspectos de la relación humano-computadora, incluyendo ...

  • ciencia cognitiva : estudio de la inteligencia humana, los lenguajes naturales y el cerebro como procesos computacionales;
  • inteligencia artificial : dotar a las computadoras de comportamiento y comprensión similares a los humanos; y
  • interacción humano-computadora : diseño de software e interfaces informáticos que funcionan bien con usuarios humanos.

2

Voy a sugerir algo un poco fuera del campo izquierdo: aprender Python (o quizás Ruby, pero tengo mucha más experiencia en Python, así que eso es lo que discutiré). Y no solo incursionar en ello, sino realmente llegar a conocerlo a un nivel profundo.

Hay varias razones por las que sugiero esto:

  1. Python es un lenguaje excepcionalmente bien diseñado. Si bien tiene algunas verrugas, tiene menos IMHO que muchos otros idiomas. Si es un diseñador de idiomas en ciernes, es bueno exponerse a tantos idiomas buenos como sea posible.

  2. La implementación estándar de Python (CPython) es de código abierto y está bien documentada, lo que hace que sea más fácil entender cómo funciona el lenguaje bajo el capó.

  3. Python se compila en un código de byte simple que es más fácil de entender que el ensamblado y que funciona igual en todas las plataformas en las que se ejecuta Python. Entonces aprenderá sobre la compilación (ya que Python compila su código fuente en código de bytes) y la interpretación (ya que este código de bytes se interpreta en la máquina virtual de Python).

  4. Python tiene muchas características nuevas propuestas, documentadas en PEP numeradas (propuestas de mejora de Python). PEP interesantes para leer para ver cómo los diseñadores de idiomas consideraron implementar una característica antes de elegir la forma en que realmente lo hicieron. (Las PEP que todavía están bajo consideración son especialmente interesantes a este respecto).

  5. Python tiene una combinación de características de varios paradigmas de programación, por lo que aprenderá sobre diversas formas de abordar la resolución de problemas y tendrá una gama más amplia de herramientas para considerar, incluso en su propio lenguaje.

  6. Python hace que sea bastante fácil extender el lenguaje de varias maneras con decoradores, metaclases, ganchos de importación, etc. para que pueda jugar con nuevas características del lenguaje hasta cierto punto sin abandonar el idioma. (Como comentario: ¡los bloques de código son objetos de primera clase en Ruby, por lo que en realidad puedes escribir nuevas estructuras de control como bucles! Tengo la impresión de que los programadores de Ruby no necesariamente consideran que extender el lenguaje, es solo cómo programa en Ruby. Pero es genial).

  7. En Python, puedes desmontar el código de bytes generado por el compilador, o incluso escribir el tuyo desde cero y hacer que el intérprete lo ejecute (lo hice yo mismo, y fue alucinante pero divertido).

  8. Python tiene buenas bibliotecas para analizar. Puede analizar el código Python en un árbol de sintaxis abstracta y luego manipularlo utilizando el módulo AST. El módulo PyParsing es útil para analizar lenguajes arbitrarios, como los que diseñas. En teoría, podría escribir su primer compilador de lenguaje en Python si lo desea (y podría generar C, ensamblaje o incluso salida de Python).

Este enfoque de investigación podría ir bien con un enfoque más formal, ya que comenzará a reconocer los conceptos que ha estudiado en el idioma con el que está trabajando, y viceversa.

¡Que te diviertas!


No cavar en python, pero no viene al caso. El niño ya tiene N idiomas para N grande; incrementar N no hará mucha diferencia. Tome C, por ejemplo. Es estándar. Tiene muchas bibliotecas. Es multiplataforma (cuando te apegas al estándar). Puede desmontar la salida. Puedes escribir CFront. Etc. Entonces ahí.
Ian

1

Bueno, creo que su pregunta podría reescribirse para ser: "¿Cuáles son los conceptos prácticos básicos de una licenciatura en informática?", Y la respuesta total es, por supuesto, obtener su propia licenciatura en informática.

Básicamente, crea su propio compilador de lenguaje de programación leyendo un archivo de texto, extrayendo información de él y realizando transformaciones en el texto en función de la información que ha leído de él, hasta que lo haya transformado en bytes que pueden leerse el cargador (cf, Linkers and Loaders de Levine). Un compilador trivial es un proyecto bastante riguroso cuando se realiza por primera vez.

El corazón de un sistema operativo es el núcleo, que gestiona los recursos (p. Ej., Asignación de memoria / desasignación) y cambia entre tareas / procesos / programas.

Un ensamblador es una transformación de texto-> byte.

Si está interesado en estas cosas, le sugiero que escriba un ensamblador X86, en Linux, que admita algún subconjunto del ensamblaje X86 estándar. Ese será un punto de entrada bastante sencillo y le presentará estos problemas. No es un proyecto para bebés, y le enseñará muchas cosas.

Yo recomendaría escribirlo en C; C es la lengua franca para ese nivel de trabajo.


1
Por otro lado, este es un buen lugar para un lenguaje de muy alto nivel. Siempre que pueda dictar los bytes individuales en un archivo, puede hacer un compilador / ensamblador (que es más fácil) en cualquier idioma. Di perl. O VBA. ¡Cielos, las posibilidades!
Ian

1

Ver el libro de Kenneth Louden, "Construcción del compilador"

http://www.cs.sjsu.edu/~louden/cmptext/

Proporciona un mejor enfoque práctico para el desarrollo del compilador.

La gente aprende haciendo. Solo un pequeño número puede ver símbolos garabateados en el tablero y saltar inmediatamente de la teoría a la práctica. Desafortunadamente, esas personas son a menudo dogmáticas, fundamentalistas y las más ruidosas al respecto.


1

Tuve la suerte de estar expuesto al PDP-8 como mi primer lenguaje ensamblador. El PDP-8 tenía solo seis instrucciones, que eran tan simples que era fácil imaginar que fueran implementadas por unos pocos componentes discretos, que de hecho eran. Realmente eliminó la "magia" de las computadoras.

Otra puerta de entrada a la misma revelación es el lenguaje ensamblador "mixto" que Knuth usa en sus ejemplos. "Mix" parece arcaico hoy, pero todavía tiene ese efecto de-mistificación.


0

Los compiladores y los lenguajes de programación (y todo lo relacionado con la construcción de uno, como la definición de una gramática finita y la conversión a ensamblaje) es una tarea muy compleja que requiere una gran comprensión sobre los sistemas en su conjunto. Este tipo de curso generalmente se ofrece como una clase de compilación de 3er / 4to año en la Universidad.

Recomiendo encarecidamente que primero comprenda mejor los sistemas operativos en general y cómo se compilan / ejecutan los lenguajes existentes (es decir, de forma nativa (C / C ++), en una VM (Java) o por un intérprete (Python / Javascript)).

Creo que usamos el libro Conceptos del sistema operativo de Abraham Silberschatz, Peter B. Galvin, Greg Gagne en mi curso de Sistemas operativos (en el segundo año). Este fue un excelente libro que dio un recorrido exhaustivo de cada componente de un sistema operativo: un poco caro pero valió la pena y las copias antiguas / usadas deberían estar flotando.


Conceptos del sistema operativo? Se necesita muy poco de eso para construir un compilador. Lo que se necesita es comprender las arquitecturas de software: espacios de direcciones, pilas, hilos (si quiere aprender compiladores, es mejor que aprenda sobre el paralelismo, es su futuro).
Ira Baxter

Inmediatamente después de decir que quería aprender el diseño del lenguaje y los compiladores, dijo que quería aprender sobre los sistemas operativos.
David Thornley

@Ira - estuvo de acuerdo. Nunca dije que se necesita comprender el sistema operativo para construir un compilador / lenguaje, simplemente expliqué que podría ser un punto de partida más fácil. Todos se están centrando en el aspecto 'compilador' de su pregunta, pero también mencionó que quiere una mejor comprensión del sistema operativo y las bibliotecas. Para un niño de 15 años que todavía está aprendiendo sobre arquitecturas, sería mucho más útil comprender la gestión de la memoria, el enhebrado, el bloqueo, la E / S, etc. que aprender a definir una gramática con yacc (IMHO)
plafond

Lo siento ... perdí el punto de querer aprender sobre los sistemas operativos (¿de construcción?). Mi punto es que no necesita mucho conocimiento del sistema operativo para los compiladores. De hecho, es un tema completamente diferente, excepto donde el compilador y el sistema operativo interactúan para lograr algún propósito colectivo. (Multics requirió que sus compiladores PL / 1 construyeran llamadas a funciones de ciertas maneras para habilitar una VM global, por ejemplo).
Ira Baxter

0

Es un gran tema, pero en lugar de ignorarlo con un pomposo "ve a leer un libro, niño", en su lugar, con gusto te daré consejos para ayudarte a entenderlo.

La mayoría de los compiladores y / o intérpretes trabajan así:

Tokenizar : escanee el texto del código y divídalo en una lista de tokens.

Este paso puede ser complicado porque no puede simplemente dividir la cadena en espacios, debe reconocer que if (bar) foo += "a string";es una lista de 8 tokens: WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR. Como puede ver, simplemente dividir el código fuente en los espacios no funcionará, debe leer cada carácter como una secuencia, por lo que si encuentra un carácter alfanumérico, sigue leyendo los caracteres hasta que toque un carácter no alfanumérico y esa cadena Acabo de leer es una PALABRA para ser clasificada más adelante Puedes decidir por ti mismo cuán granular es tu tokenizer: si se traga "a string"como un token llamado STRING_LITERAL para analizarlo más adelante, o si ve"a string" como OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE, o lo que sea, esta es solo una de las muchas opciones que tiene que decidir por sí mismo mientras lo codifica.

Lex : Entonces ahora tienes una lista de tokens. Probablemente etiquetó algunos tokens con una clasificación ambigua como WORD porque durante la primera pasada no gasta demasiado esfuerzo tratando de descubrir el contexto de cada cadena de caracteres. Así que ahora lea nuevamente su lista de tokens de origen y reclasifique cada uno de los tokens ambiguos con un tipo de token más específico basado en las palabras clave en su idioma. Por lo tanto, tiene una PALABRA como "if" y "if" está en su lista de palabras clave especiales llamadas símbolo IF, por lo que cambia el tipo de símbolo de ese token de WORD a IF, y cualquier WORD que no esté en su lista de palabras clave especiales , como WORD foo, es un IDENTIFICADOR.

Parse : Así que ahora giró if (bar) foo += "a string";una lista de tokens lexed que se ve así: IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. El paso es reconocer secuencias de tokens como declaraciones. Esto está analizando. Lo haces usando una gramática como:

DECLARACIÓN: = ASIGN_EXPRESSION | IF_STATEMENT

IF_STATEMENT: = IF, PAREN_EXPRESSION, STATEMENT

ASIGN_EXPRESSION: = IDENTIFICADOR, ASIGN_OP, VALOR

PAREN_EXPRESSSION: = OPEN_PAREN, VALUE, CLOSE_PAREN

VALOR: = IDENTIFICADOR | STRING_LITERAL | PAREN_EXPRESSION

ASIGN_OP: = IGUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT

Las producciones que usan "|" entre términos significa "coincidir con cualquiera de estos", si hay comas entre términos significa "coincidir con esta secuencia de términos"

¿Cómo usas esto? Comenzando con el primer token, intente hacer coincidir su secuencia de tokens con estas producciones. Entonces, primero intenta hacer coincidir su lista de tokens con STATEMENT, así que lee la regla para STATEMENT y dice "una STATEMENT es ASIGN_EXPRESSION o IF_STATEMENT", por lo que intenta hacer coincidir ASIGN_EXPRESSION primero, así que busca la regla gramatical de ASIGN_EXPRESSION y dice "ASIGN_EXPRESSION es un IDENTIFICADOR seguido de un ASIGN_OP seguido de un VALOR, por lo que busca la regla gramatical para IDENTIFICADOR y ve que no hay un gramatical para IDENTIFICADOR, lo que significa que IDENTIFICADOR es un" terminal ", lo que significa que no requiere más análisis para que coincida para que pueda intentar hacerlo directamente con su token, pero su primer token de origen es un IF, y IF no es lo mismo que un IDENTIFICADOR, por lo que falló la coincidencia. ¿Ahora que? Vuelve a la regla STATEMENT e intenta hacer coincidir el siguiente término: IF_STATEMENT. Busca IF_STATEMENT, comienza con IF, busca IF, IF es un terminal, compara el terminal con tu primer token, si el token coincide, es increíble, el próximo término es PAREN_EXPRESSION, busca PAREN_EXPRESSION, no es un terminal, cuál es el primer término, PAREN_EXPRESSION comienza con OPEN_PAREN, busca OPEN_PAREN, es una terminal, combina OPEN_PAREN con tu próximo token, coincide, .... y así sucesivamente.

La forma más fácil de abordar este paso es tener una función llamada parse () a la que le pasa el token de código fuente que está tratando de hacer coincidir y el término gramatical con el que está tratando de hacerlo coincidir. Si el término de gramática no es una terminal, entonces recurre: llama a parse () nuevamente y le pasa el mismo token de origen y el primer término de esta regla de gramática. Es por eso que se llama un "analizador de descenso recursivo". La función parse () devuelve (o modifica) su posición actual en la lectura de los tokens de origen, esencialmente devuelve el último token en la secuencia coincidente, y continúa la siguiente llamada a analizar () desde allí.

Cada vez que parse () coincide con una producción como ASIGN_EXPRESSION, crea una estructura que representa ese fragmento de código. Esta estructura contiene referencias a los tokens de origen originales. Empiezas a construir una lista de estas estructuras. Llamaremos a toda esta estructura el Árbol de sintaxis abstracta (AST)

Compilar y / o ejecutar : Para ciertas producciones en su gramática, ha creado funciones de controlador que, si se les da una estructura AST, compilarían o ejecutarían esa porción de AST.

Así que echemos un vistazo a la parte de su AST que tiene el tipo ASIGN_ADD. Entonces, como intérprete, tiene una función ASIGN_ADD_execute (). Esta función se pasa como parte del AST que corresponde al árbol de análisis, por foo += "a string"lo que esta función mira esa estructura y sabe que el primer término en la estructura debe ser un IDENTIFICADOR, y el segundo término es el VALOR, por lo que ASIGN_ADD_execute () pasa el término VALOR a una función VALOR_eval () que devuelve un objeto que representa el valor evaluado en la memoria, luego ASIGN_ADD_execute () realiza una búsqueda de "foo" en la tabla de variables y almacena una referencia a lo que devuelve eval_value () función.

Eso es un intérprete. En cambio, un compilador tendría funciones de controlador que traducen el AST en código de bytes o código de máquina en lugar de ejecutarlo.

Los pasos 1 a 3, y algunos 4, se pueden facilitar con herramientas como Flex y Bison. (también conocido como Lex y Yacc), pero escribir un intérprete desde cero es probablemente el ejercicio más enriquecedor que cualquier programador podría lograr. Todos los demás desafíos de programación parecen triviales después de la cumbre de este.

Mi consejo es comenzar poco a poco: un lenguaje pequeño, con una gramática pequeña, e intente analizar y ejecutar algunas declaraciones simples, luego crezca a partir de ahí.

¡Lee esto y buena suerte!

http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c

http://en.wikipedia.org/wiki/Recursive_descent_parser


2
Cometes lo que considero un error clásico cuando la gente piensa en compilar: eso es creer que el problema es analizar. PARSING ES TÉCNICAMENTE FÁCIL; Hay grandes tecnologías para hacerlo. La parte difícil de compilar es el análisis semántico, la optimización en los niveles alto y bajo de representación de programas y la generación de código, con un énfasis creciente en estos días en el código PARALELO. Trivializa esto completamente en su respuesta: "un compilador tendría funciones de controlador para traducir el AST en código de bytes". Hay 50 años transcurridos de teoría de compiladores e ingeniería escondidos allí.
Ira Baxter

0

El campo de la computadora solo es complicado porque ha tenido tiempo de evolucionar en muchas direcciones. En esencia, se trata solo de máquinas que computan.

Mi computadora muy básica favorita es la computadora de retransmisión de Harry Porter . Da una idea de cómo funciona una computadora en el nivel base. Entonces puede comenzar a apreciar por qué se necesitan cosas como idiomas y sistemas operativos.

La cuestión es que es difícil entender algo sin entender lo que lo necesita . Buena suerte y no solo leas cosas. Haz cosas



-1

Otro buen libro introductorio es el "Compilerbau" de N. Wirth de 1986 (construcción del compilador) que tiene aproximadamente 100 páginas y explica un código conciso y bien diseñado para el lenguaje de juguetes PL / 0, que incluye analizador, generador de códigos y máquina virtual. También muestra cómo escribir un analizador sintáctico que se lee en la gramática para analizar en notación EBNF. El libro está en alemán, pero escribí un resumen y traduje el código a Python como ejercicio, consulte http://www.d12k.org/cmplr/w86/intro.html .


-1

Si está interesado en comprender la esencia de los lenguajes de programación, le sugiero que trabaje a través del libro PLAI (http://www.cs.brown.edu/~sk/Publications/Books/ProgLangs/) para comprender los conceptos y su implementación También lo ayudará con el diseño de su propio idioma.


-1

Si realmente tiene interés en el compilador, y nunca antes lo había hecho, podría comenzar diseñando una calculadora para calcular fórmulas aritméticas (una especie de DSL como mencionó Eric). Hay muchos aspectos que debe considerar para este tipo de compilador:

  • Números permitidos
  • Operadores permitidos
  • Las prioridades del operador
  • Validación de sintaxis
  • Mecanismo de búsqueda variable
  • Detección de ciclo
  • Mejoramiento

Por ejemplo, tiene las siguientes fórmulas, su calculadora debería poder calcular el valor de x:

a = 1
b = 2
c = a + b
d = (3 + b) * c
x = a - d / b

Para empezar, no es un compilador extremadamente difícil, pero podría hacerte pensar más en algunas ideas básicas de lo que es un compilador, y también ayudarte a mejorar tus habilidades de programación y controlar la calidad de tu código (este es un problema perfecto que Test Driven Development TDD podría aplicarse para mejorar la calidad del software).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.