Vous êtes sur la page 1sur 211

Construcción de

compilador
Niklaus Wirth
Esto es una versión ligeramente revisada del libro publicado por Addison-Wesley en

1996 ISBN 0-201-40353-6

Zürich, noviembre 2005

1
Teoría y Técnicas de Construcción de Compilador
Una Introducción
Niklaus Wirth

Prefacio
Este libro ha emergido de mis notas de conferencia para un curso introductorio en diseño de compilador
en ETH Zürich. Muchos cronometra he sido preguntado para justificar este curso, desde entonces diseño
de compilador está considerado un tema un poco esotérico , practicó sólo en unos cuantos software
altamente especializado casas. Porque hoy en día todo cuál no cede los beneficios inmediatos tiene que
ser justificados, intentaré explicar por qué considero este tema como importante y pertinente a
alumnado de informática en general..
Es la esencia de cualquier educación académica aquello no conocimiento único, y, en el caso de una
educación de ingeniería, saber-cómo está transmitido, pero también entendiendo e idea. En particular,
conocimiento aproximadamente superficies de sistema sólo es insuficientes en informática; qué está
necesitado es un entendiendo de contenidos. Cada ordenador académicamente educado el científico tiene
que saber cómo unas funciones de ordenador, y tiene que entender las maneras y los métodos en qué
programas están representados e interpretó. Los compiladores convierten textos de programa a código
interno. Por ello constituyen el puente entre software y hardware.
Ahora, uno puede interject aquel conocimiento sobre el método de traducción es innecesario para un
entendiendo de la relación entre programa de fuente y código de objeto, e incluso mucho menos
pertinente está sabiendo cómo a de hecho construir un compilador. Aun así, de mi experiencia como
profesor, genuino entendiendo de un tema es más adquirido de un en-implicación de profundidad con
ambos conceptos y detalles. En este caso, esta implicación es nada menos de la construcción de un
compilador real.
Naturalmente tenemos que concentrar en el essentials. Después de todo, este libro es una introducción , y
no un libro de referencia para expertos. Nuestra primera restricción al essentials se preocupa la lengua de
fuente. Sea junto al punto para presentar el diseño de un compilador para una lengua grande. La lengua
tendría que ser pequeña, pero no obstante tenga que contener todo los elementos verdaderamente
fundamentales de lenguajes de programación. Hemos escogido un subconjunto de la lengua Oberon para
nuestros propósitos. La segunda restricción se preocupa el ordenador de objetivo. Tenga que presentar
una estructura regular y un conjunto de instrucción sencillo. La mayoría de importante es el practicality
de los conceptos enseñaron. Oberon Es un general-propósito, lengua flexible y potente, y nuestro
ordenador de objetivo refleja el exitoso RISC-arquitectura en una manera ideal. Y finalmente, las terceras
mentiras de restricción en renunciar técnicas sofisticadas para optimización de código. Con estos
premisses, es posible de explicar un compilador entero en detalle, e incluso para construirlo dentro el
tiempo limitado de un curso.
Capítulos 2 y 3 trata el basics de lengua y sintaxis. Capítulo 4 está preocupado con análisis de sintaxis,
aquello es el método de parsing frases y programas. Concentramos en el sencillos pero
sorprendentemente método potente de descenso recursivo, el cual está utilizado en nuestro compilador
ejemplar. Consideramos análisis de sintaxis como medios a un fin, pero no como el objetivo definitivo.
En Capítulo 5, la transición de un parser a un compilador está preparado. El método depende de el uso de
atributos para sintácticos construye.
Después de la presentación de la lengua Oberon-0, Capítulo 7 espectáculos el desarrollo de su parser
según el método de descenso recursivo. Para razones prácticas, el manejando de syntactically las frases
erróneas es también habladas. En Capítulo 8 explicamos por qué lenguas qué contener declarations, y
cuál por tanto introduce dependencia encima contexto, no obstante puede ser tratado como syntactically
el contexto libre.
Hasta este punto ninguna consideración del ordenador de objetivo y su conjunto de instrucción ha sido
necesarios. Desde los capítulos subsiguientes están dedicados al tema de generación de código, la
especificación de un objetivo deviene inevitable (Capítulo 9). Es un RISC arquitectura con un conjunto
2
de instrucción pequeño y un conjunto de registros. El tema central de diseño de compilador, la
generación de secuencias de instrucción, es después distribuido encima tres capítulos: código para
expresiones y asignaciones a variables (Capítulo 10), para

3
Condicional y repitió declaraciones (Capítulo 11) y para procedimiento declarations y llamadas (Capítulo
12). Junto cubren todo el construye de Oberon-0.
Los capítulos subsiguientes están dedicados a varios adicionales, importantes construye de lenguajes de
programación de propósito general. Su tratamiento es más cursory en naturaleza y menos preocupado con
detalles, pero son referenced por varios sugeridos ejercita al final de los capítulos respectivos. Estos
temas son más allá tipos de dato elemental (Capítulo 13), y el construye de variedades abiertas, de
estructuras de dato dinámico, y de tipos de procedimiento llamaron métodos en objeto-terminología
orientada (Capítulo 14).
Capítulo 15 está preocupado con el módulo construye y el principio de la información que esconde. Estas
ventajas al tema de desarrollo de software en equipos, basados en la definición de interfaces y la
implementación subsiguiente, independiente de las partes (módulos). La base técnica es la recopilación
separada de módulos con controles completos de la compatibilidad de los tipos de todos componentes de
interfaz. Esta técnica es de importancia primordial para ingeniería de software en general, y para
lenguajes de programación modernos en particular.
Finalmente, Capítulo 16 da una visión general breve de problemas de optimización de código. Es
necesario debido al vacío semántico entre lenguas de fuente y arquitecturas de ordenador por un lado, y
nuestro deseo de utilizar los recursos disponibles así como posibles en el otro.

Acknowledgements
Expreso mi sincero gracias a todo quién contribuyó con sus sugerencias y critizism a este reservar cuál
maduró sobre los muchos años en qué he enseñado el curso de diseño del compilador en ETH Zürich. En
particular, soy indebted a Hanspeter Mössenböck y Michael Franz quién cuidadosamente leído el
manuscrito y lo sometió a su escrutinio. Además, doy las gracias a Stephan Gehring, Stefan Ludwig y
Josef Templ para sus comentarios valiosos y cooperación en enseñar el curso.
N. W. Diciembre 1995

4
Contenidos
Prefacio
1. Introducción
2. Lengua y Sintaxis.
2.1. Ejercicios
3. Lenguas regulares
4. Análisis de Contexto-Lenguas libres
4.1. El método de descenso recursivo
4.2. Mesa-conducido superior-abajo parsing
4.3. Inferior-arriba de parsing.
4.4. Ejercicios
5. Atribuyó Gramáticas y Semantics
5.1. Reglas de tipo
5.2. Reglas de evaluación
5.3. Reglas de traducción
5.4. Ejercicios
6. El Lenguaje de programación Oberon-0
7. Un Parser para Oberon-0
7.1. El escáner
7.2. El parser
7.3. Soportando errores sintácticos.
7.4. Ejercicios
8. Consideración del contexto Especificado por Declarations
8.1. Declarations
8.2. Entradas para tipos de datos
8.3. Representación de dato en corrido-tiempo
8.4. Ejercicios
9. Un RISC Arquitectura cuando Objetivo
10. Expresiones y Asignaciones.
10.1. Generación de código recto según el stack principio
10.2. Generación de código retrasado
10.3. Indexed Variables y campos récord
10.4. Ejercicios
11. Condicional y Repitió Declaraciones y Booleanos Epressions
11.1. Comparaciones y saltos.
11.2. Condicional y repitió declaraciones
11.3. Operaciones booleanas
11.4. Asignaciones a variables Booleanas
11.5. Ejercicios
12. Procedimientos y el Concepto de Locality.
12.1. Carrera-organización de tiempo de la tienda
12.2. Dirigiendo de variables.
12.3. Parámetros
12.4. Procedimiento declarations y llamadas.
12.5. Procedimientos estándares
12.6. Procedimientos de función
12.7. Ejercicios

5
13. Tipos de Dato elemental
13.1. Los tipos REALES y LONGREAL.
13.2. Compatibilidad entre tipos de dato numérico
13.3. El tipo de dato PUSO
13.4. Ejercicios
14. Variedades abiertas, Punteros y Tipos de Procedimiento
14.1. Variedades abiertas
14.2. Estructuras de dato dinámico y punteros.
14.3. Tipos de procedimiento
14.5. Ejercicios
15. Módulos y Recopilación Separada
15.1. El principio de la información que esconde
15.2. Recopilación separada
15.3. Implementación de archivos de símbolo
15.4. Dirigiendo objetos externos
15.5. Comprobando consistencia de configuración
15.6. Ejercicios
16. Optimizaciones de código y el Frontend/backend Estructura
16.1. Consideraciones generales
16.2. Optimizaciones sencillas
16.3. Evitando repitió evaluaciones
16.4. Asignación de registro
16.5. El frontend/backend estructura de compilador
16.6. Ejercicios
Apéndice Un: Sintaxis
A.1. Oberon-0
A.2. Oberon
A.3. Archivos de símbolo
Apéndice B: El ASCII Apéndice de
conjunto del carácter C: El Oberon-0
compilador
C.1. El escáner
C.2. El parser
C.3. Las Referencias de
generador del código

6
1. Introducción
Programas de ordenador están formulados en un lenguaje de programación y especificar clases de
computar procesos. Ordenadores, aun así, interpretar secuencias de instrucciones particulares, pero no
programar textos. Por tanto, el texto de programa tiene que ser traducido a una secuencia de instrucción
adecuada antes de que pueda ser procesado por un ordenador. Esta traducción puede ser automatizada, el
cual implica que pueda ser formulado como lo programar. El programa de traducción se apellida un
compilador, y el texto para ser traducido se apellida texto de fuente (o a veces código de fuente).
No es difícil de ver que este proceso de traducción de texto de fuente a secuencia de instrucción requiere
esfuerzo considerable y sigue reglas complejas. La construcción del primer compilador para la lengua
Fortran (traductor de fórmula) alrededor 1956 era una empresa de osar, cuyo éxito era en absoluto
aseguró. Implique aproximadamente 18 manyears de esfuerzo, y por tanto representado entre los
proyectos de programación más grandes del tiempo.
El intricacy y la complejidad del proceso de traducción podría ser reducida sólo por escoger un
claramente definido, fuente estructurada bien lengua. Esto ocurrió por primera vez en 1960 con el
advenimiento de la lengua Algol 60, el cual estableció las fundaciones técnicas de diseño de compilador
que todavía es válido hoy. Por primera vez, una notación formal era también utilizada para la definición
de la estructura de la lengua (Naur, 1960).
El proceso de traducción es ahora guiado por la estructura del texto analizado. El texto está
descompuesto, parsed a sus componentes según la sintaxis dada. Para los componentes más elementales,
su semantics está reconocido, y el significado (semantics) del composite las partes es el resultado del
semantics de sus componentes. Naturalmente, el significado del texto de fuente tiene que ser preservado
por la traducción.
El proceso de traducción esencialmente consta de las partes siguientes:
1. La secuencia de los caracteres de un texto de fuente está traducido a una secuencia correspondiente de
símbolos del vocabulario de la lengua. Para caso, los identificadores que constan de letras y dígitos,
numera constar de dígitos, los delimitadores y los operadores que constan de los caracteres especiales
están reconocidos en esta fase, el cual se apellida análisis léxico.
2. La secuencia de símbolos está transformada a una representación que directamente espejos la
estructura sintáctica del texto de fuente y deja esta estructura fácilmente ser reconocido. Esta fase se
apellida análisis de sintaxis (parsing).
3. Alto-lenguas de nivel están caracterizadas por el hecho que objetos de programas, por ejemplo
variables y funciones, está clasificado según su tipo. Por tanto, además de reglas sintácticas, reglas de
compatibilidad entre los tipos de operadores y operandos definen la lengua. De ahí, verificación de si
estas reglas de compatibilidad están observadas por un programa es un deber adicional de un
compilador. Esta verificación se apellida tipo comprobando.
4. En la base de la representación que resulta de paso 2, una secuencia de las instrucciones tomadas de la
instrucción puesta del ordenador de objetivo está generado. Esta fase se apellida generación de código.
En general es la parte más implicada , no menos porque los conjuntos de instrucción de muchos
ordenadores carecen de la regularidad deseable. A menudo, la parte de generación del código es por
tanto subdivided más allá.
Un partitioning del proceso de recopilación a tan muchos separa tan posibles era la técnica predominante
hasta que aproximadamente 1980, porque hasta entonces la tienda disponible era demasiado pequeña de
acomodar el compilador entero. Partes de compilador individuales únicas cabrían, y podrían ser cargados
uno tras otro en secuencia. Las partes se apellidaron pases, y el enteros se apellidó un multipass
compilador. El número de pases era típicamente 4 - 6, pero logró 70 en un caso particular (para PL/yo)
sabido al autor. Típicamente, la producción de pase k sirvió tan entrada de pase k+1, y el disco sirvió
almacenamiento tan intermedio (Figura 1.1). El acceso muy frecuente a almacenamiento de disco
resultado en tiempo de recopilación larga.

7
Análisi Análisi Genera
s léxico s de ción de
sintaxis código

Figura 1.1. Multipass Recopilación.


Ordenadores modernos con su aparentemente unlimited las tiendas lo hacen factibles de evitar
almacenamiento intermedio encima disco. Y con él, el proceso complicado de serializing una estructura
de dato para producción, y su reconstrucción encima la entrada puede ser discarded también. Con
compiladores de pase solo, aumentos en velocidad por los factores de varios miles son por tanto posibles.
En vez de ser emprendido uno tras otro en estrictamente moda secuencial, las varias partes (tareas) es
interleaved. Por ejemplo, generación de código no es retrasada hasta que todas las tareas preparatorias
están completadas, pero empieza ya después del reconocimiento de la primera estructura oracional del
texto de fuente.
Un sensato compromise existe en la forma de un compilador con dos partes, concretamente un fin de
frente y un fin posterior. La primera parte comprende léxica y análisis de sintaxis y el tipo que
comprueba, y genera un árbol que representa la estructura sintáctica del texto de fuente. Este árbol está
aguantado en tienda principal y constituye la interfaz a la segunda parte qué generación de código de los
mangos. La ventaja principal de estas mentiras de solución en la independencia del fin de frente del
ordenador de objetivo y su conjunto de instrucción. Esta ventaja es inestimable si compiladores para la
misma lengua y para varios ordenadores tienen que ser construidos, porque el mismo fin de frente les
sirve todo.
La idea de desacoplar lengua de fuente y arquitectura de objetivo también ha dirigido a los proyectos que
crean varios fines de frente para las lenguas diferentes que generan árboles para un fin posterior solo.
Mientras que para la implementación de m lenguas para n ordenadores m * n los compiladores habían
sido necesarios, ahora m fines de frente y n los fines posteriores bastan (Figura 1.2).

Pascal Modula Oberon

Árbol de
sintaxis

MIPS SPARC BRAZO

Figura 1.2. Fines de frente y fines posteriores.


Esta solución moderna al problema de porting un compilador nos recuerdo de la técnica qué jugado una
función significativa en la propagación de Pascal alrededor 1975 (Wirth, 1971). La función del árbol
estructural estuvo supuesta por un linearized forma, una secuencia de órdenes de un ordenador abstracto.
El fin posterior constó de un intérprete programa cuál era implementable con esfuerzo pequeño, y la
secuencia de instrucción lineal se apellidó P-código. El drawback de esta solución era la pérdida
inherente de la eficacia común a intérpretes.
Frecuentemente, uno encuentra compiladores que no directamente generar código binario, sino texto de
ensamblador. Para una traducción completa un ensamblador es también implicado después del
compilador. De ahí, tiempos de traducción más larga son inevitables. Desde este esquema difícilmente
ofrece cualesquier ventajas, no recomendamos esta aproximación.

8
Cada vez más, alto-lenguas de nivel son también empleadas para la programación de microcontroladores
utilizó en embedded aplicaciones. Tales sistemas son principalmente utilizados para adquisición de datos
y control automático de maquinaria. En estos casos, la tienda es típicamente pequeña y es insuficiente de
llevar un compilador. En cambio, el software está generado con la ayuda de otros ordenadores capaces de
compilar. Un compilador qué genera código para un ordenador diferente del ejecutando el compilador se
apellida un compilador de cruz. El código generado es entonces transferido - descargado - vía una línea
de transmisión del dato.
En los capítulos siguientes concentraremos en las fundaciones teóricas de diseño de compilador, y
después en el desarrollo de un compilador de pase solo real.

9
2. Lengua y Sintaxis.
Cada lengua muestra una estructura llamó su gramática o sintaxis. Por ejemplo, una frase correcta
siempre consta de un subject seguido por un predicado, corrige aquí significando bien formó. Este hecho
puede ser descrito por la fórmula siguiente:
Frase = predicado subject.
Si añadimos a esta fórmula las dos fórmulas más
lejanas tema = "John" | "Mary".
El predicado = "come" | "charlas".
Entonces definimos herewith exactamente cuatro frases
posibles, concretamente John come Mary come
John habla Mary charlas
Dónde el símbolo | es para ser pronunciado cuando o . Llamamos estas reglas de sintaxis de las fórmulas,
producciones, o sencillamente ecuaciones sintácticas. Subject y el predicado es clases sintácticas .
Notación más a escasa para el encima omite identificadores significativos:
S = AB. L = {ac, anuncio,
bc, bd} Un = "un" | "b".
B = "c" | "d".
Utilizaremos esta notación de taquigrafía en los ejemplos subsiguientes, cortos. El conjunto L de frases
cuáles pueden ser generados de este modo, aquello es, por sustitución repetida de los lados izquierdos por
los lados derechos de las ecuaciones, se apellida la lengua .
El ejemplo encima evidentemente define una lengua que consta de sólo cuatro frases. Típicamente, aun
así, una lengua contiene infinitamente muchas frases. El ejemplo siguiente muestra que un conjunto
infinito puede muy bien ser definido con un número finito de ecuaciones. Las posiciones  de símbolo
para la secuencia vacía.
S = Un. L = {, un, aa, aaa, aaaa, ...
} Un = "un" Un | .
El medio para hacer así que es recursión qué deja una sustitución (aquí de Un por ""un Un) ser repetido
arbitrariamente a menudo.
Nuestro tercer ejemplo es otra vez basado en el uso de recursión. Pero genera no sólo sentencia constar
de una secuencia arbitraria del mismo símbolo, pero también nested frases:
S = Un. L = {b, abc, aabcc, aaabccc, ... }
Un = "un" Un "" c | "b".
Es claro que arbitrariamente imbricaciones profundas (aquí de Cuando) puede ser expresado, una
propiedad particularmente importante en la definición de estructuró lenguas.
Nuestro cuarto y último ejemplo exhibe la estructura de expresiones. Los símbolos E, T, F, y V posición
para expresión, plazo, factor, y variable..
E = T | Un "+" T.
T = F | T "*" F.
F = V | "(" E ")".
V = "un" | "b" | "c" | "d".
De este ejemplo es evidente que una sintaxis no sólo define el conjunto de frases de una lengua, pero
también les proporciona con una estructura. La sintaxis descompone frases en sus electores cuando
mostrados en el ejemplo de Figura 2.1. Las representaciones gráficas se apellidan sintaxis o árboles
estructurales árboles.

10
Un * b + c Un + b * c (Un+b)*(c+d)

Un Un Un

+ + *

* c Un * ( Un ) ( Un )

Un b b c + +

Un b c d

Figura 2.1. La estructura de


expresiones nos Dejé ahora formular los conceptos presentaron
encima más rigurosamente:
Una lengua está definida por el siguiente:
1. El conjunto de símbolos terminales. Estos son los símbolos que ocurre en sus frases. Están dichos
para ser terminales, porque no pueden ser sustituidos por cualesquier otros símbolos. Las parones de
proceso de la sustitución con símbolos terminales. En nuestro primer ejemplo este conjunto consta de
los elementos un, b, c y d. El conjunto es vocabulario llamado también .
2. El conjunto de nonterminal símbolos. Denotan clases sintácticas y puede ser sustituido. En nuestro
primer ejemplo este conjunto consta de los elementos S, Un y B..
3. El conjunto de ecuaciones sintácticas (también llamó producciones). Estos definen las sustituciones
posibles de nonterminal símbolos. Una ecuación está especificada para cada nonterminal símbolo.
4. El símbolo de inicio. Es un nonterminal símbolo, en los ejemplos encima denotados por S.
Una lengua es, por tanto, el conjunto de secuencias de símbolos terminales qué, empezando con el
símbolo de inicio, puede ser generado por aplicación repetida de ecuaciones sintácticas, aquello es,
sustituciones.
También deseamos definir rigurosamente y precisamente la notación en qué ecuaciones sintácticas está
especificada. Dejado nonterminal los símbolos son identificadores cuando les sabemos de lenguajes de
programación, aquello es, cuando secuencias de letras (y posiblemente dígitos), por ejemplo, expresión,
plazo. Los símbolos terminales dejados son secuencias de carácter encerrados en cita (cuerdas), por
ejemplo, "=", "|". Para la definición de la estructura de estas ecuaciones es conveniente de utilizar la
herramienta el ser justo se definió:
Sintaxis = Sintaxis de producción | .
Producción = Expresión "=" de identificador "." .
Expresión = Plazo | de expresión "|" del plazo.
Plazo = Factor | de plazo del factor.
Factor = Cuerda | de identificador.
Identificado = Identificador | de letra de identificador
r | de letra dígito.
Cuerda = stringhead """.

11
stringhead = """ | stringhead Carácter.

12
Letra = "Un" | ... |
"Z".
Dígito = "0" | ... | "9".
Esta notación estuvo introducida en 1960 por J. Backus Y P. Naur En casi forma idéntica para la
descripción formal de la sintaxis de la lengua Algol 60. Es por tanto llamado Backus Naur Forma (BNF)
(Naur, 1960). Cuando nuestros espectáculos de ejemplo, utilizando recursión para expresar las
repeticiones sencillas es bastante detrimental a readability. Por tanto, extendemos esta notación por dos
construye para expresar repetición y optionality. Además, dejamos expresiones para ser encerradas
dentro de paréntesis. Así una extensión de BNF llamó EBNF (Wirth, 1977) es postulated, el cual otra vez
nosotros inmediatamente uso para su definición propia, precisa:
Sintaxis = {Producción}.
Producción = Expresión "=" de identificador "." .
Expresión = Plazo {"|" de plazo}.
Plazo = Factor {de factor}.
Factor = Cuerda | de identificador | "(" expresión ")" | "[" expresión "]" | "{"
expresión "}".
Identificado = Dígito {de letra | de la letra}.
r
Cuerda = """ {Carácter} """.
Letra = "Un" | ... | "Z".
Dígito = "0" | ... | "9".
Un factor de la forma {x} es equivalente a una secuencia arbitrariamente larga de x, incluyendo la
secuencia vacía. Una producción de la forma
Un = AB | .
Es ahora formulado más brevemente como = {B}. Un factor de la forma [x] es equivalente a x "o nada",
aquello es, expresa optionality. De ahí, la necesidad para el símbolo especial  para la secuencia vacía
desaparece.
La idea de definir lenguas y su gramática con precisión matemática vuelve a N. Chomsky. Devenga
claro, aun así, que el esquema presentado, sencillo de reglas de sustitución era insuficiente de representar
la complejidad de lenguas habladas. Esto quedó cierto incluso después de los formalismos eran
considerablemente expandió. En contraste, este trabajo probó extremadamente fructífero para la teoría de
lenguajes de programación y formalismos matemáticos. Con él, Algol 60 devenía el primer lenguaje de
programación para ser definido formalmente y precisamente. De paso, enfatizamos que este rigor aplicó
a la sintaxis sólo, no al semantics.
El uso del Chomsky el formalismo es también responsable para el lenguaje de programación de plazo,
porque los lenguajes de programación parecían para exhibir una estructura similar a lenguas habladas.
Creemos que este plazo es bastante unfortunate en general, porque un lenguaje de programación no es
hablado, y por tanto no es una lengua en el sentido cierto de la palabra. El formalismo o la notación
formal habrían sido plazos más apropiados.
Uno se pregunta por qué una definición exacta de las frases que pertenecen a una lengua tendría que ser
de cualquier importancia grande. De hecho, no es realmente. Aun así, es importante de saber si o no una
frase es bien formó. Pero incluso aquí uno puede pedir una justificación. Finalmente, la estructura de un
(bien formado) la frase es pertinente, porque es instrumental en establecer el significado de la frase. A
causa de la estructura sintáctica, las partes individuales de la frase y su significado pueden ser
reconocidos independientemente, y juntos ceden el significado de la totalidad.
Dejado nos ilustrar este punto que utiliza el ejemplo siguiente, trivial de una expresión con el símbolo de
adición. Dejado E posición para expresión, y N para número:
E = N | E "+" E.
N = "1" | "2" | "3" | "4" .
Evidentemente, "4 + 2 + 1" es una expresión bien formada. Incluso pueda ser derivado en varias maneras,
cada correspondientes a una estructura diferente, cuando mostrado en Figura 2.2.

13
Un Un

Un + Un Un + Un

Un + Un 1 4 Un + Un

4 2 2 1

Figura 2.2. Difiriendo árboles estructurales para la misma expresión.


El dos que difiere las estructuras también pueden ser expresadas con paréntesis apropiados,
concretamente cuando (4 + 2) + 1 y cuando 4 + (2 + 1), respectivamente. Afortunadamente, gracias al
associativity de adición tanto ceder el mismo valor 7. Pero esto necesita no siempre ser el caso. El uso
mero de sustracción en el sitio de adición cede un ejemplo de contador qué espectáculos que el dos que
difiere las estructuras también ceden una interpretación diferente y resultado: (4 - 2) - 1 = 1, 4 - (2 - 1) =
3. El ejemplo ilustra dos hechos:
1. Interpretación de frases siempre restos en el reconocimiento de su estructura sintáctica.
2. Cada frase tiene que tener una estructura sola para ser inequívoco.
Si el segundo requisito no es frases satisfechas , ambiguas surgen. Estos pueden enriquecer lenguas
habladas; lenguajes de programación ambiguos, aun así, es sencillamente inútil.
Llamamos una clase sintáctica ambigua si pueda ser atribuido varias estructuras. Una lengua es ambigua
si contiene al menos uno clase sintáctica ambigua (construye).

2.1. Ejercicios
2.1. El Algol 60 Informe contiene la sintaxis siguiente (traducido a EBNF):
primario = unsignedNumber | variable | "(" arithmeticExpression ")" | ... .
Factor = el factor | primario "" primario.
Plazo = de factor | del plazo ("×" | "/" | "÷") factor.
simpleArithmeticExpression = Plazo | ("+" | "-") plazo | simpleArithmeticExpression ("+" | "-") plazo.
arithmeticExpression = simpleArithmeticExpression |
"SI" BooleanExpression "ENTONCES" simpleArithmeticExpression "MÁS"
arithmeticExpression. relationalOperator = "=" | "" | "" | "<" | "" | ">" .
Relación = arithmeticExpression relationalOperator arithmeticExpression.
BooleanPrimary = logicalValue | Relación | variable | "(" BooleanExpression ")" | ...
.
BooleanSecondary = BooleanPrimary | "¬" BooleanPrimary.
BooleanFactor = BooleanSecondary | BooleanFactor "" BooleanSecondary.
BooleanTerm = BooleanFactor | BooleanTerm "" BooleanFactor.
Implicación = BooleanTerm | implicación "" BooleanTerm.
simpleBoolean = Implicación | simpleBoolean "" implicación.
BooleanExpression = simpleBoolean |
"SI" BooleanExpression "ENTONCES" simpleBoolean "MÁS" BooleanExpression.
Determinar los árboles de sintaxis de las expresiones siguientes, en qué letras son para ser tomados
como variables: x + y + z
x×y+z
x+y×z
(x - y) × (x + y)

14
-x ÷ y
Un + b < c + d
Un + b < c  d  e  ¬ f  g > h  i × j = k  l  m - n + p  q
2.2. Las producciones siguientes también son parte de la definición original de Algol 60.
Contienen ambigüedades qué estuvo eliminado en el Informe Revisado.
forListElement = arithmeticExpression |
arithmeticExpression "PASO" arithmeticExpression "HASTA"
arithmeticExpression | arithmeticExpression "MIENTRAS" BooleanExpression.
forList = forListElement | forList "," forListElement.
forClause = "PARA" variable ":=" forList "HACER"
. forStatement = forClause Declaración.
compoundTail = Declaración "de FIN" | de la declaración ";"
compoundTail. compoundStatement = "EMPIEZA"
compoundTail.
Declaración incondicional = basicStatement | forStatement | compoundStatement | ... .
ifStatement = "SI" BooleanExpression "ENTONCES"
unconditionalStatement. conditionalStatement = ifStatement | ifStatement
"MÁS" declaración. Declaración = unconditionalStatement |
conditionalStatement.
Encuentra al menos dos estructuras diferentes para las expresiones siguientes y declaraciones. Dejado Un
y B posición para "declaraciones básicas".
SI un ENTONCES b MÁS c = d
SI un ENTONCES SI b ENTONCES Un MÁS B
SI un ENTONCES PARA HACER SI b ENTONCES Un MÁS B
Proponer una sintaxis alternativa qué es inequívoco.
2.3. Considerar el siguiente construye y descubrir cuál unos son correctos en Algol, y cuál unos en
Oberon (ve Apéndice Un.2):
Un + b = c +
d un * -b
Un < & b c < d
Evaluar las expresiones siguientes:
5 * 13 DIV 4 =
13 DIV 5*4 =

15
3. Lenguas regulares
Ecuaciones sintácticas de la forma definida en EBNF generar contexto-lenguas libres. El contexto "de
plazo- libre" se debe a Chomsky y raíces del hecho que la sustitución del símbolo dejó de = por una
secuencia derivada de la expresión a la derecha de = es siempre permitted, a toda costa del contexto en
qué el símbolo es embedded dentro de la frase. Ha resultado que esta restricción a libertad de contexto
(en el sentido de Chomsky) es bastante aceptable para lenguajes de programación, y que es incluso
deseable. Dependencia de contexto en otro sentido, aun así, es indispensible. Regresaremos a este tema
en Capítulo 8.
Aquí deseamos investigar una subclase más que una generalización de contexto-lenguas libres. Esta
subclase, sabido como lenguas regulares, juega una función significativa en el reino de lenguajes de
programación. En esencia, son el contexto -lenguas libres cuya sintaxis no contiene ninguna recursión
excepto la especificación de repetición. Desde entonces en EBNF la repetición está especificada
directamente y sin el uso de recursión, la definición siguiente, sencilla puede ser dada:
Una lengua es regular, si su sintaxis puede ser expresada por un solo EBNF expresión.
El requisito que unos sufijos de ecuación solos también implica que sólo los símbolos terminales ocurren
en la expresión. Tal una expresión se apellida una expresión regular.
Dos ejemplos breves de lenguas regulares pueden bastar. El primero define identificadores cuando son
comunes en más lenguas; y el segundo define enteros en notación decimal. Utilizamos el nonterminal
letra de símbolos y dígito por el bien de brevity. Pueden ser eliminados por sustitución, por el cual unos
resultados de expresión regulares para ambos identificador y entero .
Dígito = de letra {de letra | de
identificador}. Dígito = de dígito
{del entero}.
Letra = "Un" | "B" | ... | "Z".
Dígito = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
La razón para nuestro interés en mentiras de lenguas regulares en el hecho que programas para el
reconocimiento de frases regulares es particularmente sencillo y eficaz. Por "reconocimiento"
significamos la determinación de la estructura de la frase, y así naturalmente la determinación de si la
frase es bien formada, aquello es, pertenece a la lengua. Reconocimiento de frase se apellida análisis de
sintaxis.
Para el reconocimiento de frases regulares un autómata finito, también llamó una máquina estatal, es
necesario y suficiente. En cada paso la máquina estatal lee el símbolo próximo y cambia estatal. El estado
resultante es sólo determinado por el estado anterior y el símbolo leyeron. Si el estado resultante es
único, la máquina estatal es determinista, otherwise nondeterministic. Si la máquina estatal está
formulada como programa, el estado está representado por el punto actual de ejecución de programa.
El programa de analizar puede ser derivado directamente de la sintaxis de definir en EBNF. Para cada
EBNF construye K allí existe una traducción gobierna cuál cede un fragmento de programa Pr(K). Las
reglas de traducción de EBNF para programar el texto está mostrado abajo. Therein sym Denota una
variable global siempre representando el símbolo último leído del texto de fuente por una llamada a
procedimiento luego. Error de procedimiento rescinde ejecución de programa, señalando que la secuencia
de símbolo leída tan lejos no pertenece a la lengua.
K Pr(K)
"x" SI sym = "x" ENTONCES luego MÁS FIN de error
(exp) Pr(exp)
[exp] SI sym EN primero(exp) ENTONCES Pr(exp) FIN
{exp} MIENTRAS sym EN primero(exp) Pr(exp) FIN
fac0 fac1 ... facn Pr(fac0); Pr(fac1); ... Pr(facn
Plazo0 | plazo1 | ... | termn CASO sym DE
Primero(plazo0): Pr(plazo0)
| Primero(plazo1): Pr(plazo1)
16
...

17
| Primero(termn): Pr(termn)
FIN
El conjunto primero(K) contiene todos los símbolos con qué una frase derivada de construir K puede
empezar. Es el conjunto de símbolos de inicio de K . Para los dos ejemplos de identificadores y enteros
son:
Primero(entero) = dígitos = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
Primero(identificador) = letras = {"Un", "B", ... , "Z"}
La aplicación de estas traducciones sencillas gobierna generar un parser de una sintaxis dada es, aun así,
tema a la sintaxis que es determinista. Este precondition puede ser formulado más concretely como sigue:
K Cond(K)
Plazo0 | plazo1 Los plazos no tienen que tener cualesquier símbolos de inicio comunes.
fac0 fac1 Si fac0 contiene la secuencia vacía, entonces los factores
no tienen que tener cualesquier símbolos de inicio
comunes.
[exp] O {exp} Los conjuntos de símbolos de inicio de exp y
de símbolos que puede seguir K tiene que ser
disjunto.
Estas condiciones son satisfechas trivially en los ejemplos de identificadores y enteros, y por lo tanto
obtenemos los programas siguientes para su reconocimiento:
SI s EN letras ENTONCES luego MÁS FIN
de error ; MIENTRAS sym EN dígitos + de
letras HACEN
CASO sym DE
"" Un .. "Z":
Luego
| "0" .. "9": FIN
próximo
FIN
SI sym EN dígitos ENTONCES luego MÁS
FIN de error ; MIENTRAS sym EN dígitos
FIN próximo
Frecuentemente, el programa obtenido por aplicar las reglas de traducción pueden ser simplificadas por
eliminar condiciona cuáles son evidentemente establecidos por preceder condiciones. Las condiciones
sym EN letras y sym EN los dígitos son típicamente formulados como sigue:
("Un" <= sym) & (sym <= "Z") ("0" <= sym) & (sym <= "9")
La importancia de lenguas regulares en conexión con raíces de lenguajes de programación del hecho que
el último es típicamente definido en dos etapas. Primero, su sintaxis está definida en plazos de un
vocabulario de símbolos terminales abstractos. Segundo, estos símbolos abstractos están definidos en
plazos de secuencias de símbolos terminales concretos, como ASCII caracteres. Esta segunda definición
típicamente tiene una sintaxis regular. La separación a dos etapas ofrece la ventaja que la definición de
los símbolos abstractos, y así de la lengua, es independiente de cualquier representación concreta en los
plazos de cualesquier conjuntos de carácter particular utilizaron por cualquier equipamiento particular.
Esta separación también tiene consecuencias en la estructura de un compilador. El proceso de análisis de
sintaxis está basado en un procedimiento para obtener el símbolo próximo. Este procedimiento en vuelta
está basado en la definición de símbolos en plazos de secuencias de uno o más caracteres. Este
procedimiento último se apellida un escáner, y análisis de sintaxis en este segundo, nivel más bajo,
análisis léxico. La definición de símbolos en los plazos de caracteres es típicamente dados en plazos de
una lengua regular, y por lo tanto el escáner es típicamente una máquina estatal.
Nosotros summarize las diferencias entre los dos niveles como sigue:
Proceso Elemento de Algoritmo Sintaxis
18
entrada
Análisis léxico Carácter Escáner Regular
Análisis de Símbolo Parser El contexto
sintaxis libre

19
Cuando un ejemplo muestramos un escáner para un parser de EBNF. Sus símbolos terminales y su
definición en los plazos de caracteres son
Símbolo = {Espacio} (cuerda | de identificador | "(" | ")" | "[" | "]" | "{" | "}" | "|" |
"=" | ".") .
Identifica = Dígito {de letra | de la letra}.
dor
Cuerda = """ {Carácter} """.
De este derivamos el procedimiento GetSym cuál, a cada llamada, asigna un valor numérico que
representa el símbolo próximo leído al global variable sym. Si el símbolo es un identificador o una
cuerda, la secuencia de carácter real está asignada al más lejano global variable id. Tenga que ser notado
que típicamente un escáner también tiene en cuenta reglas sobre espacios y fines de líneas.
Mayoritariamente estas reglas dicen: los espacios y los fines de líneas separan símbolos consecutivos,
pero otherwise es de ninguna importancia. Procedimiento GetSym, formulado en Oberon, uso de marcas
del siguiente declarations.
CONST IdLen = 32;
ident = 0; literal = 2; lparen = 3; lbrak = 4; lbrace = 5; barra = 6; eql = 7;
rparen = 8; rbrak = 9; rbrace = 10; periodo = 11; otro = 12;
VARIEDAD de Identificador = del TIPO IdLen DE CHAR;
VAR ch: CHAR;
sym: ENTERO;
id: Identificador;
R: Textos.Lector;
Nota que la operación de lectura abstracta es ahora representada por los Textos de llamada
concretos.Leído(R, ch). R Es un Lector globalmente declarado que especifica el texto de fuente. También
nota que variable ch tiene que ser global, porque al final de GetSym pueda contener el primer carácter
que pertenece al símbolo próximo. Esto tiene que ser tenido en cuenta a la llamada subsiguiente de
GetSym .
PROCEDIMIENTO
GetSym; VAR i:
ENTERO;
EMPIEZA
MIENTRAS ~R.eot & (ch <= " ") HACER Textos.Leído(R, ch) FIN ; (*skip espacios*)
CASO ch DE
"Un" .. "Z", "un" .. "z": sym := ident; i := 0;
REPITE id[i] := ch; INC(i); Textos.Leído(R,
ch) HASTA QUE (GORRA(ch) < "Un") O
(GORRA(ch) > "Z");
id[i] := 0X
| 22X: (*cita*)
Textos.Leído(R, ch); sym := literal; i := 0;
MIENTRAS (ch # 22X) & (ch > " ")
id[i] := ch; INC(i); Textos.Leído(R,
ch) FIN ;
SI ch <= " " ENTONCES error(1) FIN ;
id[i] := 0X; Textos.Leído(R, ch)
| "=" : sym := eql; Textos.Leído(R, ch)
| "(" : sym := lparen; Textos.Leído(R, ch)
| ")" : sym := rparen; Textos.Leído(R, ch)
| "[" : sym := lbrak; Textos.Leído(R, ch)
| "]" : sym := rbrak; Textos.Leído(R, ch)
| "{" : sym := lbrace; Textos.Leído(R, ch)
| "}" : sym := rbrace; Textos.Leído(R, ch)
| "|" : sym := Barra; Textos.Leído(R, ch)
| "." : sym := Periodo; Textos.Leído(R,
ch) MÁS sym := otro; Textos.Leído(R,
20
ch) FIN
FIN GetSym

21
3.1. Ejercicio
Las frases de lenguas regulares pueden ser reconocidas por máquinas estatales finitas. Son normalmente
descritos por transistion esquemas. Cada nodo representa un estado, y cada borde una transición estatal.
El borde está etiquetado por el símbolo que está leído por la transición. Considerar los esquemas
siguientes y describir la sintaxis de las lenguas correspondientes en EBNF.

o b

U ( x ) U +
n n
. c *

22
4. Análisis de Contexto-Lenguas libres
4.1. El método de Descenso Recursivo
Las lenguas regulares son subject a la restricción que no nested las estructuras pueden ser
expresadas. Nested Las estructuras pueden ser expresadas con la ayuda de recursión sólo (ve
Capítulo 2).
Una máquina estatal finita por tanto no puede bastar para el reconocimiento de frases de contexto lenguas
libres. No obstante intentaremos derivar un parser programa para el tercer ejemplo en Capítulo 2, por
utilizar los métodos explicaron en Capítulo 3. Wherever El método fallará - y tenga que fallar - mentiras
la pista para una generalización posible. De hecho está sorprendiendo qué pequeño el esfuerzo de
programación adicional necesario resulta para ser.
El construir
Un = "un" Un "" c | "b".
Ventajas, después de simplificación adecuada y el uso de un SI en vez de una declaración de CASO, a la
pieza siguiente de programa:
SI sym = "un"
ENTONCES
luego;
SI sym = Un ENTONCES luego MÁS
FIN de error ; SI sym = "c" ENTONCES
luego MÁS FIN de error
ELSIF sym = "b"
ENTONCES luego MÁS
error
FIN
Aquí hemos a ciegas trató el nonterminal símbolo Un en la misma moda como símbolos terminales. Esto
es naturalmente no aceptable. El propósito de la tercera línea del programa es a parse un construir de la
forma Un (más que para leer un símbolo Un). Aun así, esto es precisamente el propósito de nuestro
programa también. Por tanto, la solución sencilla a nuestro problema es para dar el programa un nombre,
aquello es, para darlo la forma de un procedimiento, y para sustituir la tercera línea de programa por una
llamada a este procedimiento. Tan en la sintaxis el construir Un es recursivo, así que es el procedimiento
Un recursivo:
PROCEDIMIEN
TO Un;
EMPIEZA
SI sym = "un"
ENTONCES
luego; Un;
SI sym = "c" ENTONCES luego MÁS
FIN de error ELSIF sym = "b" ENTONCES
luego
MÁS FIN
de error
FIN Un
La extensión necesaria del conjunto de reglas de traducción es extremadamente sencillo. La regla adicional única
es:
Un parsing el algoritmo está derivado para cada nonterminal símbolo, y está formulado como el
procedimiento que lleva el nombre del símbolo. La ocurrencia del símbolo en la sintaxis está
traducido a una llamada del procedimiento correspondiente.
Nota: estos controles de regla a toda costa de si el procedimiento es recursivo o no.
Es importante de verificar que las condiciones para un algoritmo determinista están satisfechas. Esto
23
implica entre otras cosas que en una expresión de la forma
Plazo0 | plazo1
Los plazos no tienen que presentar cualesquier símbolos de inicio comunes. Este requisito excluye
recursión izquierda. Si consideramos la producción recursiva izquierda
Un = Un "un" | "b".

24
Reconocemos que el requisito está violado, sencillamente porque b es un símbolo de inicio de Un (b
EN primero(Un)), y porque por tanto primero(Un"un") y primero("b") no es disjunto. "b" Es el elemento
común.
La consecuencia sencilla es: lata de recursión izquierda y tiene que ser reemplazado por repetición. En el
ejemplo por encima de Un = Un "un" | "b" está reemplazado por Un = "b" {"un"}.
Otra manera de mirar en nuestro paso de la máquina estatal a su generalización es para considerar el
último como puesto de máquinas estatales qué llamada a cada otro y a ellos. En principio, la condición
nueva única es que el estado de la máquina de llamar es resumed después de que terminación de la
máquina estatal llamada. El mosto estatal por tanto ser preservado. Desde las máquinas estatales son
nested, un stack es la forma apropiada de tienda. Nuestra extensión de la máquina estatal es por tanto
llamó un pushdown autómata. Teóricamente pertinente es el hecho que el stack (pushdown tienda) tiene
que ser arbitrariamente profundo. Esto es la diferencia esencial entre la máquina estatal finita y el
infinito pushdown autómata.
El principio general qué está sugerido aquí es el siguiente: considerar el reconocimiento del oracional
construir cuál empieza con el símbolo de inicio de la sintaxis subyacente como el encima objetivo. Si
durante la búsqueda de este objetivo, aquello es, mientras la producción está siendo parsed, un
nonterminal el símbolo está encontrado, entonces el reconocimiento de un construir correspondiendo a
este símbolo está considerado como subordinar objetivo para ser perseguido primero, mientras el objetivo
más alto es temporalmente suspendió. Esta estrategia es por tanto objetivo llamado también-orientado
parsing. Si miramos en el árbol estructural del parsed frase reconocemos que objetivos (símbolos) más
altos en el árbol está emprendido primero, objetivos más bajos (símbolos) después. El método es por
tanto llamado superior-abajo parsing (Knuth, 1971; Aho y Ullman, 1977). Además, la implementación
presentada de esta estrategia basada en los procedimientos recursivos es sabidos como descenso
recursivo parsing.
Finalmente, recordamos que decisiones sobre los pasos para ser tomados es siempre hecho en la base de
la entrada sola, próxima símbolo sólo. El parser miradas adelante por un símbolo. Un lookahead de
varios símbolos complicarían el proceso de decisión considerablemente, y así también irlo más despacio.
Por esta razón restringiremos nuestra atención a lenguas cuáles pueden ser parsed con un lookahead de
un símbolo solo.
Como ejemplo más lejano para demostrar la técnica de descenso recursivo parsing, dejado nos considerar
un parser para EBNF, cuya sintaxis es summarized aquí una vez más:
Sintaxis = {Producción}.
Producción = Expresión "=" de identificador "." .
Expresión = Plazo {"|" de plazo}.
Plazo = Factor {de factor}.
Factor = Cuerda | de identificador | "(" expresión ")" | "[" expresión "]" | "{"
expresión "}".
Por aplicación de las reglas de traducción dadas y simplificación subsiguiente el siguiente parser
resultados. Está formulado como un Oberon módulo:
MÓDULO EBNF;
Espectadores de IMPORTACIÓN, Textos, TextFrames, Oberon;
CONST IdLen = 32;
ident = 0; literal = 2; lparen = 3; lbrak = 4; lbrace = 5; barra = 6; eql = 7;
rparen = 8; rbrak = 9; rbrace = 10; periodo = 11; otro = 12;
VARIEDAD de Identificador = del TIPO IdLen DE CHAR;
VAR ch: CHAR;
sym: ENTERO;
lastpos: LONGINT;
id: Identificador;
R:
Textos.Lector;
W:

25
Textos.Escritor;
Error de PROCEDIMIENTO(n:
ENTERO); VAR pos:
LONGINT;
EMPIEZA pos := Textos.Pos(R);
SI > pos lastpos+4 ENTONCES (*evita spurious mensajes de error*)

26
Textos.WriteString(W, " pos"); Textos.WriteInt(W, pos, 6);
Textos.WriteString(W, " err"); Textos.WriteInt(W, n, 4); lastpos := pos;
Textos.WriteString(W, " sym "); Textos.WriteInt(W, sym, 4);
Textos.WriteLn(W); Textos.Anexa(Oberon.Registro, W.buf)
FIN
Error de FIN;
PROCEDIMIENTO GetSym;
EMPIEZA ... (*Ve Capítulo
3*) FIN GetSym;
Expresión de
PROCEDIMIENTO;
plazo de
PROCEDIMIENTO;
Factor de
PROCEDIMIENTO;
EMPIEZA
SI sym = ident ENTONCES registro(T0, id, 1);
GetSym ELSIF sym = literal ENTONCES récord(T1,
id, 0); GetSym ELSIF sym = lparen ENTONCES
GetSym; Expresión;
SI sym = rparen ENTONCES GetSym MÁS error(2)
FIN ELSIF sym = lbrak ENTONCES
GetSym; Expresión;
SI sym = rbrak ENTONCES GetSym MÁS error(3)
FIN ELSIF sym = lbrace ENTONCES
GetSym; Expresión;
SI sym = rbrace ENTONCES GetSym MÁS error(4)
ACABA MÁS error(5)
FIN
Factor de FIN;
EMPIEZA (*plazo*) factor;
MIENTRAS sym < la barra HACE
plazo de FIN de FIN de factor;
EMPIEZA (*expresión*) plazo;
MIENTRAS sym = barra GetSym;
expresión de FIN de FIN de plazo;
Producción de
PROCEDIMIENTO; EMPIEZA
(*sym = ident*) GetSym;
SI sym = eql ENTONCES GetSym MÁS error(7)
FIN ; expresión;
SI sym = periodo ENTONCES GetSym MÁS
error(8) producción de FIN del FIN;
Sintaxis de
PROCEDIMIENTO;
EMPIEZA
MIENTRAS sym = ident HACER
sintaxis de FIN de FIN de producción;
El PROCEDIMIENTO Compila*;
EMPIEZA (*conjunto R al principio del texto para ser
compilado*) lastpos := 0; Textos.Leído(R, ch); GetSym;
sintaxis; Textos.Anexa(Oberon.Registro, W.buf)

27
El FIN Compila;
EMPIEZA
Textos.OpenWriter(W) FIN
EBNF.

28
4.2. Mesa-conducido Superior-abajo Parsing
El método de descenso recursivo es sólo uno de varias técnicas para darse cuenta el superiores-abajo
parsing principio. Aquí presentaremos otra técnica: mesa-conducido parsing.
La idea de construir un algoritmo general para superior-abajo parsing para qué una sintaxis concreta está
suministrada como el parámetro es difícilmente lejos-fetched. La sintaxis toma la forma de unos datos
estructura cuál es típicamente representado como graph o mesa. Esta estructura de dato es entonces
interpretada por el general parser. Si la estructura está representada como graph, podemos considerar su
interpretación como traversal del graph, guiado por el ser de texto de la fuente parsed.
Primero, tenemos que determinar una representación de dato del estructural graph. Sabemos que EBNF
contiene dos repetitivo construye, concretamente secuencias de factores y secuencias de plazos.
Naturalmente, están representados como listas. Cada elemento de la estructura de dato representa un
(terminal) símbolo. De ahí, cada elemento tiene que ser capaz de denotar dos sucesores representaron por
punteros. Les llamamos luego para el factor consecutivo próximo y alt para el plazo alternativo próximo.
Formulado en la lengua Oberon, declaramos los tipos de dato siguientes:
PUNTERO = de símbolo A SymDesc;
SymDesc = RÉCORD alt, luego: FIN de
Símbolo
Entonces formular este tipo de dato abstracto para terminal y nonterminal símbolos por utilizar Oberon
característica de extensión del tipo (Reiser y Wirth, 1992). Los registros que denotan los símbolos
terminales especifican el símbolo por el atributo adicional sym:
PUNTERO = terminal A TSDesc;.
TSDesc = REGISTRO (SymDesc) sym: FIN de ENTERO
Los elementos que representan un nonterminal el símbolo contiene una referencia (puntero) a la
estructura de dato que representa que símbolo. Fuera de consideraciones prácticas introducimos una
referencia indirecta: el puntero refiere a un elemento de encabezamiento adicional, el cual en vuelta
refiere a la estructura de dato. El encabezamiento también contiene el nombre de la estructura, aquello es,
del nonterminal símbolo. Estrictamente hablando, esta adición es innecesaria; su utilidad devendrá
aparente más tarde.
Nonterminal = PUNTERO A NTSDesc;
NTSDesc = REGISTRO (SymDesc) esto: FIN de Encabezamiento
Encabezamie = PUNTERO A HDesc;
nto
HDesc = RÉCORD sym: Símbolo; nombre: VARIEDAD n DE
CHAR FIN
Cuando un ejemplo escogemos la sintaxis siguiente para expresiones sencillas. Figura 4.1 exhibiciones
la estructura de dato correspondiente como graph. Los bordes horizontales son punteros próximos , los
bordes verticales son alt punteros.
Expresión = Plazo {("+" | "-") plazo}.
Plazo = Factor {("*" | "/") factor}.
Factor = id | "(" Expresión ")" .
Ahora somos en una posición para formular el general parsing algoritmo en la forma de un
procedimiento concreto: PROCEDIMIENTO Parsed(hd: Encabezamiento): BOOLEANO;
VAR x: Símbolo; partido: BOOLEANO;
EMPIEZA x := hd.sym; Textos.WriteString(Wr,
hd.name); REPITE
SI x ES Terminal ENTONCES
SI x(Terminal).sym = sym ENTONCES partido :=
CIERTO; GetSym MÁS partido := (x = vacío)
FIN
MÁS partido :=
Parsed(x(Nonterminal).Este) FIN ;
SI partido ENTONCES x := x.Luego MÁS x :=
29
x.alt FIN HASTA x = CERO;

30
FIN de partido
del REGRESO Parsed

expr ession +



t rm *



f ctor id

( )

Error

Figura 4.1. Sintaxis cuando estructura de dato

Los comentarios siguientes tienen que ser mantenidos en mente:


1. Nosotros tacitly suponer que los plazos siempre son
de la forma T = f0 | f1 | ... | fn
donde Todos los factores excepto el último inicio con un símbolo distinto, terminal. Sólo el último
factor puede empezar con cualquier un terminal o un nonterminal símbolo. Bajo esta condición es
posible a traverse la lista de alternativas y en cada paso para hacer sólo una comparación sola.
2. La estructura de dato puede ser derivada de la sintaxis (en EBNF) automáticamente, aquello es, por un
programar cuál compila la sintaxis.
3. En el procedimiento por encima del nombre de cada nonterminal símbolo para ser reconocido es
producción. El elemento de encabezamiento sirve precisamente este propósito.
4. Vacío es un símbolo terminal especial y el elemento que representa la secuencia vacía. Está necesitado
para marcar la salida de repeticiones (bucles).

31
4.3. Inferior-arriba de Parsing
Ambos el recursivos-descenso y mesa-conducidos parsing mostrados aquí es las técnicas basaron en el
principio de superior-abajo parsing. El objetivo primario es para mostrar que el texto para ser analizado
es derivable del símbolo de inicio. Cualquier nonterminal los símbolos encontraron está considerado
como subgoals. El parsing el proceso construye el principio de árbol de la sintaxis con el símbolo de
inicio como su raíz, aquello es, en el superior-abajo dirección.
Aun así, es también posible de proceder según un principio complementario en el inferior-arriba
dirección. El texto está leído sin búsqueda de un objetivo concreto. Después de que cada cual da un paso
unos controles de prueba si el leídos subsequence corresponde a algunos oracionales construye, aquello
es, la parte correcta de una producción. Si esto es el caso , el leído subsequence está reemplazado por el
correspondiente nonterminal símbolo. El proceso de reconocimiento otra vez consta de pasos
consecutivos, de los cuales allí son dos clases distintas:
1. Cambiando el símbolo de entrada próximo a un stack (paso de cambio),
2. Reduciendo un stacked secuencia de símbolos a un solos nonterminal símbolo según una producción
(reduce paso).
Parsing En el inferior-arriba la dirección es cambio llamado también -reducir parsing. El sintáctico
construye está construido arriba y entonces redujo; el árbol de sintaxis crece del inferior hasta arriba
(Knuth, 1965; Aho y Ullman, 1977; Kasten, 1990).
Una vez más, demuestramos el proceso con el ejemplo de expresiones sencillas. Dejado la sintaxis ser
como sigue:
E = T | E "+" T. Expresión
T = F | T "*" F. Plazo
F = id | "(" E ")". Factor
Y dejar la frase para ser reconocida ser x * (y + z). Para mostrar el proceso, el texto de fuente restante
está mostrado a la derecha, mientras que a la izquierda el - inicialmente vacío - secuencia de reconocido
construye está listado. En la izquierda lejana, las letras S y R indicar la clase de dar un paso tomado
x * (y + z)
S x * (y + z)
R F * (y + z)
R T * (y + z)
S T* (y + z)
S T*( y + z)
S T*(y + z)
R T*(F + z)
R T*(T + z)
R T*(E + z)
S T*(E+ z)
S T*(E + z )
R T*(E + F )
R T*(E + T )
R T*(E )
S T*(E)
R T*F
R T
R E
Al final, el texto de fuente inicial está reducido al símbolo de inicio E, el cual aquí mejor apellidarse el
símbolo de parón. Cuando mencionado más temprano, la tienda intermedia a la izquierda es un stack.
En analogía a esta representación, el proceso de parsing la misma entrada según el superior-abajo el
principio está mostrado abajo. Las dos clases de pasos están denotadas por M (partido) y P (producto,
expandir). El símbolo de inicio es E.

32
E x * (y + z)
P T x * (y + z)
P T* F x * (y + z)
P F*F x * (y + z)
P id * F x * (y + z)
M *F * (y + z)
M F (y + z)
P (E) (y + z)
M E) y + z)
P E + T) y + z)
P T + T) y + z)
P F + T) y + z)
P id + T) y + z)
M + T) + z)
M T) z)
P F) z)
P id) z)
M ) )
M
Evidentemente, en el inferior-arriba método la secuencia de los símbolos leídos es siempre reducido en
su fin correcto, mientras que en el superior-abajo método es siempre el leftmost nonterminal símbolo qué
está expandido. Según Knuth el inferior-arriba el método es por tanto llamado LR-parsing, y el superior-
abajo método LL- parsing. El primer L expresa el hecho que el texto está siendo leído de izquierdo a
correcto. Normalmente, esta denotación está dada un parámetro k (LL(k), LR(k)). Indica la extensión
del lookahead el ser utilizó. Siempre implícitamente suponemos k = 1.
Dejado nos brevemente regresa hasta abajo-arriba principio. Las mentiras de problema concretas en
determinar qué clase de paso es para ser tomado luego, y, en el caso de un reducir paso, cuántos símbolos
en el stack es para ser implicado en el paso. Esta cuestión no es fácilmente contestó. Nosotros meramente
estatales que para garantizar un eficaz parsing proceso, la información en qué la decisión es para ser
basado tiene que ser presente en una manera apropiadamente compilada. Inferior-arriba de parsers
siempre mesas de uso, aquello es, el dato estructurado en una manera análoga a la mesa-conducido
superior-abajo parser presentó encima. Además de la representación de la sintaxis como estructura de
datos, las mesas más lejanas están requeridas para dejarnos para determinar el paso próximo en una
manera eficaz. Inferior-arriba de parsing es por tanto en general más intrincado y complejo que superior-
abajo parsing.
Allí existir varios LR parsing algoritmos. Imponen condiciones de frontera diferente en la sintaxis para
ser procesada. El más lenient estas condiciones son, el más complejos el parsing proceso. Mencionamos
aquí el SLR (DeRemer, 1971) y LALR (LaLonde et al., 1971) métodos sin explicarles en cualquier
detalle más lejano.

4. 4. Ejercicios
4.1. Algol 60 contiene una asignación múltiple de la forma v1 := v2 := ... vn := e. Está definido por la
sintaxis siguiente:
Asignación = leftpartlist expresión.
leftpartlist = leftpart | leftpartlist leftpart.
leftpart = Variable ":=" .
Expresión = variable | de expresión "+" variable.
Variable = ident | ident "[" expresión "]" .
Cuál es el grado de lookahead necesario a parse esta sintaxis según el superior-abajo principio? Proponer
una sintaxis alternativa para las asignaciones múltiples que requieren un lookahead de un símbolo sólo.
4.2. Determinar los conjuntos de símbolo primero y seguir del EBNF construye producción, expresión,
plazo, y factor. Utilizando estos conjuntos, verifica que EBNF es determinista.

33
Producción = {de sintaxis}.
Producción= id "=" expresión "." .
Plazo = de plazo {"|" de la
expresión}.
Factor = de factor {del plazo}.
Factor = id | cuerda | "(" expresión ")" | "[" expresión "]" | "{" expresión "}".
id= Dígito {de letra | de la letra}.
Carácter = """ {de cuerda} """.
4.3. Escribir un parser para EBNF y extender él con las declaraciones que generan la estructura de dato
(para mesa-conducido persing) correspondiendo a la sintaxis leída.

34
5. Atribuyó Gramáticas y Semantics.
En atribuyó gramáticas los atributos seguros están asociados con individuales construye, aquello es, con
nonterminal símbolos. Los símbolos son parameterized y representar clases enteras de variantes. Esto
sirve para simplificar la sintaxis, pero es, en práctica, indispensible para extender un parser a un traductor
genuino (Rechenberg y Mössenböck, 1985). El proceso de traducción está caracterizado por la asociación
de un (posiblemente vacío) producción con cada reconocimiento de un oracional construye. Cada
ecuación sintáctica (producción) está acompañado por las reglas adicionales que definen el relatonship
entre los valores de atributo de los símbolos qué está reducido, los valores de atributo para el resultantes
nonterminal símbolo, y la producción emitida. Presentamos tres aplicaciones para atributos y reglas de
atributo.

5.1. Reglas de tipo


Como ejemplo sencillo consideraremos una lengua que presenta varios tipos de dato. En vez de
especificar reglas de sintaxis separada para expresiones de cada tipo (cuando estuvo hecho en Algol 60),
definimos expresiones exactamente una vez, y asociar el tipo de dato T tan atributo con cada construir
implicado. Por ejemplo, una expresión de tipo T está denotado como exp(T), aquello es, cuando exp con
valor de atributo T. Reglas aproximadamente compatibilidad de tipo es entonces considerada como
adiciones a las ecuaciones sintácticas individuales. Para caso, los requisitos que ambos operandos de
adición y sustracción tienen que ser del mismo tipo, y que el tipo de resultado es el mismo tan aquello de
los operandos, está especificado por tales reglas de atributo adicionales:
Contexto de regla de Atributo de sintaxis condición
exp(T0) = plazo(T1) | T0 := T1
exp(T1) "+" plazo(T2) | T0 := T1 T1 = T2
exp(T1) "-" plazo(T2). T0 := T1 T1 = T2
Si operandos del ENTERO de tipos y REAL es para ser admisible en expresiones mixtas, las reglas
devienen más relajadas, pero también más complicados:
T0 := si (T1 = ENTERO) & (T2 = ENTERO) entonces el ENTERO más
REAL, T1 = ENTERO o T1 = REAL
T2 = ENTERO o T2 = REAL
Reglas aproximadamente compatibilidad de tipo es de hecho también estático en el sentido que pueden
ser verificados sin ejecución del programa. De ahí, su separación de puramente las reglas sintácticas
aparece bastante arbitrarias, y su integración a la sintaxis en la forma de reglas de atributo es enteramente
apropiado. Aun así, notamos aquello atribuyó las gramáticas obtienen una dimensión nueva, si los
valores de atributo posibles (aquí, tipos) y su número no es sabido a priori.
Si una ecuación sintáctica contiene una repetición, entonces es apropiado respecto a reglas de atributo
para expresar él con la ayuda de recursión. En el caso de una opción, es más para expresar los dos casos
por separado. Esto está mostrado por el ejemplo siguiente donde las dos expresiones
exp(T0) = plazo(T1) {"+" plazo(T2)}. exp(T0) = ["-"]
plazo(T1). Está partido a pares de plazos, concretamente
exp(T0) = plazo(T1) | exp(T0) = plazo(T1) |
exp(T1) "+" plazo(T2). "-" Plazo(T1).
Las reglas de tipo asociaron con una producción venida a efectuar siempre que un construir
correspondiendo a la producción está reconocida. Esta asociación es sencilla de implementar en el caso
de un descenso recursivo parser: declaraciones de programa que implementan las reglas de atributo son
sencillamente interspersed dentro del parsing declaraciones, y los atributos ocurren tan parámetros al
parser los procedimientos que están para el sintácticos construye (nonterminal símbolos). El
procedimiento para reconocer las expresiones pueden servir como primer ejemplo para demostrar este
proceso de extensión, donde el original parsing el procedimiento sirve como el scaffolding:

35
Expresión de
PROCEDIMIENTO;
EMPIEZA plazo;
MIENTRAS (sym = "+") O (sym = "-")
GetSym; FIN
de plazo
Expresión de FIN
Está extendido para implementar su atributo (tipo)
reglas: expresión de
PROCEDIMIENTO(VAR typ0: Tipo);
VAR typ1, typ2: Tipo;
EMPIEZA plazo(typ1);
MIENTRAS (sym = "+") O (sym = "-")
GetSym; Plazo(typ2);
typ1 := ResType(typ1, typ2)
FIN ;
typ0 := typ1
expresión de
FIN

5.2. Reglas de evaluación


Cuando nuestro segundo ejemplo consideramos una lengua que consta de expresiones cuyos factores son
números sólo. Es paso a escaso para extender el parser a un programa no sólo reconociendo, pero al
mismo tiempo también evaluando expresiones. Asociamos con cada construir su valor a través de un
atributo llamó val. En analogía a las reglas de compatibilidad del tipo en nuestro ejemplo anterior, ahora
tenemos que procesar reglas de evaluación mientras parsing. Así hemos implícitamente introdujo la idea
de semantics:.
Sintaxis Regla de atributo
(semantics)
exp(v0) = Plazo(v1) | v0 := v1
exp(v1) "+" plazo(v2) | v0 := v1 + v2
exp(v1) "-" plazo(v2). v0 := v1 - v2
Plazo(v0) = Factor(v1) | v0 := v1
Plazo(v1) "*" factor(v2) | v0 := v1 * v2
Plazo(v1) "/" factor(v2). v0 := v1 / v2
Factor(v0) = Número(v1) | v0 := v1
"(" exp(v1) ")". v0 := v1
Aquí, el atributo es el valor computado, numérico del reconocido construye. La extensión necesaria del
correspondiente parsing ventajas de procedimiento al procedimiento siguiente para expresiones:
Expresión de PROCEDIMIENTO(VAR val0:
ENTERO); VAR val1, val2: ENTERO; op:
CHAR;
EMPIEZA plazo(val1);
MIENTRAS (sym = "+") O (sym = "-")
op : = sym; GetSym; Plazo(val2);
SI op = "+" ENTONCES val1 : = val1 + val2 MÁS val1 := val1 - val2
FIN de FIN ;
val0 := val1
expresión de
FIN

5.3. Reglas de traducción


Un tercer ejemplo de la aplicación de atribuyó las gramáticas exhibe la estructura básica de un
36
compilador. Las reglas adicionales asociaron con una producción aquí no gobierna atributos de símbolos,
pero especificar la producción (código) emitido cuándo la producción está aplicada en el parsing proceso.
La generación de producción puede ser considerada como lado-efecto de parsing. Típicamente, la
producción es una secuencia de instrucciones. En este ejemplo, las instrucciones están reemplazadas por
símbolos abstractos, y su producción está especificada por el operador puso.

37
Sintaxis Regla de producción
(semantics)
exp = Plazo
-
exp "+" Plazo Puesto("+")
exp "-" Plazo. Puesto("-")
Plazo = Factor -
Factor "*" de plazo Puesto("*")
Factor "/" de plazo. Puesto("/")
Factor = Número Puesto(número)
"(" exp ")". -
Cuando fácilmente puede ser verificado, la secuencia de símbolos de producción corresponde al parsed
expresión en postfix notación. El parser ha sido extendido a un traductor.
Infix Notación Postfix notación
2+3 23+
2*3+4 23*4+
2+3*4 234*+
(5 - 4) * (3 + 2) 54-32+*
El procedimiento parsing y traduciendo las expresiones es
como sigue: expresión de PROCEDIMIENTO;
VAR op: CHAR;
EMPIEZA plazo;
MIENTRAS (sym = "+") O (sym = "-")
op := sym; GetSym; Plazo;
puesto(op) FIN
Expresión de FIN
Cuándo utilizando una mesa-conducido parser, las mesas que expresan la sintaxis fácilmente puede ser
extendida también para representar las reglas de atributo. Si la evaluación y reglas de traducción son
también contenido en asoció mesas, uno está tentado para hablar sobre una definición formal de la
lengua. El general, mesa-conducido parser crece a un general, mesa-compilador conducido. Esto, aun así,
tiene tan lejos quedó una utopía, pero la idea vuelve al 1960s. Está representado schematically por
Figura 5.1.

Sintaxis Reglas de Semantics


tipo
Resultado de programa
Compilador
genérico
Figura 5.1. Schema De un general, parametrized compilador.
Finalmente, la idea básica detrás de cada lengua es que tenga que servir como medios para
comunicación. Esto significa que los socios tienen que utilizar y entender la misma lengua. Promoviendo
la facilidad por qué una lengua puede ser modificada y extendida por tanto puede ser bastante
counterproductive. No obstante, ha devenido consuetudinario de construir los compiladores que utilizan
mesa-conducido parsers, y para derivar estas mesas de la sintaxis automáticamente con la ayuda de
herramientas. El semantics está expresado por procedimientos cuyas llamadas son también integradas
automáticamente al parser. Compiladores así no sólo devenir más voluminoso y menos eficaz que es
warranted, pero también mucho menos transparente. La propiedad última queda uno de nuestras
preocupaciones principales, y por lo tanto no perseguiremos este curso muy más allá.

5.4. Ejercicio
5.1. Extender el programa para análisis sintáctico de EBNF textos de tal manera que genera (1) una lista
de símbolos terminales, (2) una lista de nonterminal símbolos, y (3) para cada nonterminal símbolo los

38
conjuntos de su inicio y seguir símbolos. Basado en estos conjuntos, el programa es entonces para
determinar si la sintaxis dada

39
Puede ser parsed superior-abajo con un lookahead de un símbolo solo. Si esto no es tan, el programa
muestra las producciones de chocar en una manera adecuada.
Pista: Uso Warshall algoritmo (R. W. Floyd, Algoritmo 96, Comm. ACM, junio 1962).
TIPO VARIEDAD = matricial [1..n],[1..n] DE BOOLEANO;
Antepasado de PROCEDIMIENTO(VAR m: matricial; n: ENTERO);
(* Inicialmente m[i,j] es CIERTO, si individual i es un padre de
individual j. En conclusión, m[i,j] es CIERTO, si i es un
antepasado de j *)
VAR i, j, k: ENTERO;
EMPIEZA
PARA i := 1 A n
HACER PARA j :=
1 A n HACER
SI m[j, i] ENTONCES
PARA k := 1 A n HACER
SI m[i, k] ENTONCES m[j, k] := FIN
de FIN CIERTO
FIN
de FIN
FIN
Antepasado de FIN
Pueda ser supuesto que los números de terminales y nonterminal símbolos del analizó las lenguas no
superan un límite dado (por ejemplo, 32).

40
6. El Lenguaje de programación Oberon-0
Para evitar consiguiendo perdido en generalidades y teorías abstractas, construiremos un compilador
concreto, concreto, y explicamos los varios problemas que surge durante el proyecto. Para esto,
tenemos que postulado una lengua de fuente concreta.
Naturalmente tenemos que mantener este compilador, y por tanto también la lengua, suficientemente
sencillo para quedar dentro del alcance de un introductorio preceptoral. Por otro lado, deseamos explicar
tan muchos del fundamentales construye de lenguas y técnicas de recopilación como posibles. Fuera de
estas consideraciones han crecido las condiciones de frontera para la elección de la lengua: tenga que ser
sencillo, aún así representante. Hemos escogido un subconjunto de la lengua Oberon (Reiser y Wirth,
1992), el cual es una condensación de sus antepasados Modula-2 (Wirth, 1982) y Pascal (Wirth, 1971)
a sus características esenciales. Oberon Puede ser dicho para ser la descendencia más tardía en la
tradición de Algol 60 (Naur, 1960). Nuestro subconjunto se apellida Oberon-0, y es suficientemente
potente de enseñar y ejercitar las fundaciones de métodos de programación moderna.
Preocupándose estructuras de programa, Oberon-0 es razonablemente bien desarrolló. La declaración
elemental es la asignación . Composite Las declaraciones incorporan los conceptos de la secuencia de
declaración y ejecución condicional y repetitiva, el último en la forma del convencional si- y mientras-
declaraciones. Oberon-0 también contiene el concepto importante del subprogram, representado por el
procedimiento declaration y la llamada de procedimiento. Su poder principalmente restos en la
posibilidad de parameterizing procedimientos. En Oberon, distinguimos entre valor y parámetros
variables.
Con respetar a tipos de dato, aun así, Oberon-0 es bastante frugal. Los tipos de dato elementales únicos
son enteros y los valores lógicos, denotados por ENTERO y BOOLEANO. Es así posible de declarar
entero-valoró constantes y variables, y para construir expresiones con operadores de aritmética. Las
comparaciones de expresiones ceden valores Booleanos, los cuales pueden ser sometidos a operaciones
lógicas.
Las estructuras de dato disponibles son la variedad y el registro. Pueden ser nested arbitrariamente.
Punteros, aun así, está omitido.
Los procedimientos representan unidades funcionales de declaraciones. Es por tanto apropiado de asociar
el concepto de locality de nombres con la idea del procedimiento. Oberon-0 ofertas la posibilidad de
declarar los identificadores locales a un procedimiento, aquello es, de tal manera que los identificadores
son válidos (visibles) sólo dentro del procedimiento él.
Esto visión general muy breve de Oberon-0 es principalmente para proporcionar el lector con el contexto
necesario de entender la sintaxis subsiguiente, definido en plazos de EBNF.
ident = Dígito {de letra | de
la letra}. Dígito = de dígito
{del entero}.
Selector = {"." ident | "[" Expresión "]"}.
Entero = de número.
Factor = ident número | de selector | "(" expresión ")" | "~"
factor. Factor = de plazo {("*" | "DIV" | "MOD" | "&") factor}.
SimpleExpression = ["+"|"-"] Plazo {("+"|"-" | "O )"plazo}.
Expresión = SimpleExpression
[("=" | "#" | "<" | "<=" | ">" | ">=") SimpleExpression].
Asignación = ident selector ":=" expresión.
ActualParameters = "(" [Expresión {"," expresión}] ")" .
ProcedureCall = ident Selector [ActualParameters].
IfStatement = "SI" expresión "ENTONCES" StatementSequence
{"ELSIF" Expresión "ENTONCES"
StatementSequence} ["MÁS" StatementSequence]
"FIN".
WhileStatement = "MIENTRAS" expresión "" StatementSequence "FIN".
Declaración = [asignación | ProcedureCall | IfStatement | WhileStatement].
41
StatementSequence = Declaración {";" declaración}.

42
IdentList = ident {"," ident}.
ArrayType = "Expresión" de VARIEDAD "DE
tipo." FieldList = [IdentList ":" Tipo].
RecordType = "RÉCORD" FieldList {";" FieldList} "FIN".
Tipo = ident | ArrayType | RecordType.
FPSection = ["VAR"] IdentList ":" Tipo.
FormalParameters = "(" [FPSection {";" FPSection}] ")".
ProcedureHeading = "PROCEDIMIENTO" ident [FormalParameters].
ProcedureBody = declarations ["EMPIEZA" StatementSequence] "FIN" ident.
ProcedureDeclaration = ProcedureHeading ";" ProcedureBody.
declarations = ["CONST" {ident "=" Expresión ";"}]
["TIPO" {ident "=" tipo ";"}]
["VAR" {IdentList ":" Tipo ";"}]
{ProcedureDeclaration ";"}.
MÓDULO = "de módulo" ident ";" declarations
["EMPIEZA" StatementSequence] "FIN" ident
"." .
El ejemplo siguiente de un módulo puede ayudar el lector para apreciar el carácter de la lengua. El
módulo contiene varios, muestra bien sabida procedimientos cuyos nombres son self-explicativos.

Muestra de MÓDULO; el
PROCEDIMIENTO
Multiplica;
VAR x, y, z: ENTERO;
EMPIEZA Leído(x); Leído(y); z
:= 0; MIENTRAS x > 0
SI x MOD 2 = 1 ENTONCES z := z + y FIN ;
y := 2*y; x := x DIV 2
FIN ;
Escribe(x); Escribe(y); Escribe(z);
WriteLn el FIN Multiplica;
El PROCEDIMIENTO Divide;
VAR x, y, r, q, w: ENTERO;
EMPIEZA Leído(x); Leído(y); r := x; q := 0; w
:= y; MIENTRAS w <= r w := 2*w FIN ;
MIENTRAS w > y HACER
q := 2*q; w := w DIV 2;
SI w <= r ENTONCES r := r - w; q := q +
1 FIN de FIN ;
Escribe(x); Escribe(y); Escribe(q); Escribe(r);
WriteLn el FIN Divide;
PROCEDIMIENTO BinSearch;
VAR i, j, k, n, x: ENTERO;
Un: VARIEDAD 32 DE
ENTERO; EMPIEZA Leído(n); k := 0;
MIENTRAS k < n Lee(un[k]); k := k + 1 FIN ;
Leído(x); i := 0; j := n;
MIENTRAS i < j HACER
k := (i+j) DIV 2;
SI x < un[k] ENTONCES j := k MÁS i :=
k+1 FIN de FIN ;
Escribe(i); Escribe(j); Escribe(un[j]);
WriteLn FIN BinSearch;
Muestra de FIN.

43
6.1. Ejercicio
6.1. Determinar el código para el ordenador definido en Capítulo 9, generado del programa listó al final
de este Capítulo.

44
7. Un Parser para Oberon-0
7.1. El Escáner
Antes de empezar para desarrollar un parser, primero giramos nuestra atención al diseño de su escáner.
El escáner tiene que reconocer símbolos terminales en el texto de fuente. Primero, listamos su
vocabulario:
* DIV MOD & + - O
= # < <= > >= . , : ) ]
DE ENTONCES ( [ ~ := ;
FIN MÁS ELSIF SI MIENTRAS
La VARIEDAD RÉCORD CONST TIPO VAR el PROCEDIMIENTO EMPIEZA MÓDULO
Las palabras escritas en las letras mayúsculas representan símbolos solos , terminales, y se apellidan
reservó palabras. Tienen que ser reconocidos por el escáner, y por tanto no puede ser utilizado como
identificadores. Además de los símbolos listaron, los identificadores y los números son también tratados
como símbolos terminales. Por lo tanto el escáner es también responsable para reconocer identifers y
números..
Es apropiado de formular el escáner como módulo. De hecho, los escáners son un ejemplo clásico del
uso del concepto de módulo. Deja detalles seguros para ser escondidos del cliente, el parser, y para hacer
accesible (para exportar) sólo aquellos presenta cuáles son pertinentes al cliente. El exportó las
instalaciones son summarized en plazos de la definición de interfaz del módulo:
DEFINICIÓN OSS; (*Oberon Escáner de
Subconjunto*) Textos de IMPORTACIÓN;
CONST IdLen = 16;
(*Símbolos*) null = 0;
Tiempo = 1; div = 3; mod = 4; y = 5; plus = 6; minus = 7; o = 8;
eql = 9; neq = 10; lss = 11; geq = 12; leq = 13; gtr = 14;
Periodo = 18; coma = 19; colon = 20; rparen = 22; rbrak = 23;
De = 25; entonces = 26; hacer = 27;
lparen = 29; lbrak = 30; no = 32; deviene = 33; número = 34; ident = 37;
Punto y coma = 38; fin = 40; más = 41; elsif = 42;
Si = 44; mientras = 46;
Variedad = 54; récord = 55;
const = 57; tipo = 58; var = 59; procedimiento = 60; empieza = 61; módulo = 63;
eof = 64; TIPO Ident = VARIEDAD IdLen DE CHAR;
VAR val: LONGINT;
id: Ident;
Error: BOOLEANO;
PROCEDIMIENTO Mark(msg: VARIEDAD
DE CHAR); el PROCEDIMIENTO
Consigue(VAR sym: ENTERO);
PROCEDIMIENTO Init(T: Textos.Texto; pos:
LONGINT); FIN OSS.
Los símbolos son mapped a enteros. El mapeo está definido por un conjunto de definiciones constantes.
Procedimiento Mark sirve a diagnósticos de producción sobre errores descubrieron en el texto de fuente.
Típicamente, la explicación a escasa está escrita a un texto de registro junto con la posición del error
descubierto. El procedimiento Consigue representa el escáner real. Entrega para cada llamada el símbolo
próximo reconoció. El procedimiento actúa las tareas siguientes (el listado completo está mostrado en
Apéndice C):
1. Espacios y fines de línea son skipped.
2. Reservó palabras, como EMPEZAR y FIN, está reconocido.
3. Secuencias de las letras y los dígitos que empiezan con una letra, los cuales no son reservó palabras,
está reconocido como identificadores. El parámetro sym está dado el valor ident, y la secuencia de
45
carácter él está asignado al global variable id.

46
4. Las secuencias de dígitos están reconocidas como números. El parámetro sym está dado el número de
valor, y el número él está asignado al global variable val.
5. Combinaciones de caracteres especiales, como := y =, <está reconocido como símbolo.
6. Comentarios, representados por secuencias de los caracteres arbitrarios que empiezan con (* y
acabando con *) es skipped.
7. El símbolo null está regresado, si el escáner lee un carácter ilegal (como $ o %). El símbolo eof está
regresado si el fin del texto está logrado. Tampoco de estos símbolos ocurren en un programa bien
formado texto.

7.2. El parser
La construcción del parser estrictamente sigue las reglas explicaron en Capítulos 3 y 4. Aun así, antes de
la construcción está emprendida, es necesario de comprobar si la sintaxis satisface las reglas de restringir
que garantizan determinismo con un lookahead de un símbolo. Para este propósito, primero construimos
los conjuntos de inicio y seguir símbolos. Están listados en las mesas siguientes.
S Primero(S)
Plazo .[ *
de ( ~ Entero ident
factor ( ~ entero ident
del + - ( ~ Entero ident
selector + - ( ~ Entero ident
SimpleExpression ident
Asignación de ident
expresión ident SI MIENTRAS *
ProcedureCall
Declaración
StatementSequence ident SI MIENTRAS *
FieldList ident *
Tipo ident REGISTRO de
FPSection VARIEDAD ident VAR
FormalParameters (
ProcedureHeading PROCEDIMI
ProcedureBody ENTO
ProcedureDeclaration FIN CONST ESCRIBE VAR el PROCEDIMIENTO EMPIEZA
declarations PROCEDIMIENTO *
Módulo CONST TIPO VAR MÓDULO de
PROCEDIMIENTO
S Sigue(S)
Selector * DIV MOD + - = # < <= > >= , ) ] DE ENTONCES
HACER ; FIN MÁS ELSIF
Plazo * DIV MOD + - = # < <= > >= , ) ] DE ENTONCES
HACER ; FIN MÁS ELSIF
de + - = # < <= > >= , ) ] DE ENTONCES HACER ; FIN MÁS ELSIF

factor

47
SimpleExpression = # < <= > >= , ) ] DE ENTONCES HACER ; FIN MÁS ELSIF
Asignación de , ) ] DE ENTONCES HACER ; FIN MÁS ELSIF
expresión ; FIN MÁS ELSIF
ProcedureCall ; FIN MÁS ELSIF
declaración ; FIN MÁS ELSIF FIN
StatementSequence MÁS ELSIF
FieldList ; FIN
Tipo ) ;
FPSection ) ;
FormalParameters ) ;
ProcedureHeading ;
ProcedureBody ident

48
ProcedureDeclaration ;
declarations El FIN EMPIEZA
Los controles subsiguientes de las reglas para espectáculo de determinismo que esta sintaxis de Oberon-0
de hecho puede ser manejado por el método del descenso recursivo que utiliza un lookahead de un
símbolo. Un procedimiento está construido correspondiendo a cada nonterminal símbolo. Antes de los
procedimientos están formulados, es útil de investigar cómo dependen de cada cual otro. Para este
propósito diseñamos una dependencia graph (Figura 7.1). Cada procedimiento está representado como
nodo, y un borde está dibujado a todos los nodos en qué el procedimiento depende, aquello es, llama
directamente o indirectamente. Nota que algunos nonterminal los símbolos no ocurren en este graph,
porque están incluidos en otros símbolos en una manera trivial. Por ejemplo, ArrayType y RecordType
está contenido en escribir sólo y es por tanto no explícitamente dibujado. Además recordamos que los
símbolos ident y el entero ocurre símbolos tan terminales, porque están tratados como tal por el escáner.
Módulo

FPsection declarations StatSequence

IdentList Tipo Expresión

ProcedureDeclaration SimpleExpression

Plazo

Factor

Selector

Figura 7.1. Esquema de dependencia de parsing procedimientos


Cada bucle en el esquema corresponde a una recursión. Es evidente que el parser tiene que ser
formulado en una lengua que deja procedimientos recursivos. Además, el esquema revela qué los
procedimientos posiblemente pueden ser nested. El procedimiento único qué no es llamado por otro
procedimiento es Módulo . La estructura de los espejos de programa este esquema. El programa está
listado en Apéndice C en una forma ya aumentada. El parser, como el escáner, es también formulado
como módulo.

7.3. Soportando errores sintácticos.


Tan lejos hemos considerado sólo la tarea bastante sencilla de determinar si o no un texto de fuente es
bien formado según la sintaxis subyacente. Como lado-efecto, el parser también reconoce la estructura
del texto leyó. Apenas un inacceptable el símbolo aparece, la tarea del parser está completado, y el
proceso de análisis de sintaxis está rescindido. Para aplicaciones prácticas, aun así, esta proposición es
inaceptable. Un compilador genuino tiene que indicar un mensaje de diagnóstico del error y después
proceder con el análisis. Es entonces bastante probablemente que los errores más lejanos serán
detectados. Continuación de parsing después de una detección de error es, aun así, posible sólo bajo la
suposición de hipótesis seguras sobre la naturaleza del error. Dependiendo de esta suposición, una parte
del texto subsiguiente tiene que ser skipped, o los símbolos seguros tienen que ser insertados. Tales
medidas son necesarias incluso cuándo no hay ninguna intención de corregir o ejecutando el programa de
fuente erróneo. Sin un al menos parcialmente hipótesis correcta, continuación del parsing el proceso es
vano (Graham y Rhodes, 1975; Rechenberg y Mössenböck, 1985).

49
La técnica de escoger las hipótesis buenas está complicada. Él finalmente restos a heuristics, cuando el
problema tiene tan lejos eluded tratamiento formal. La razón principal para este es que la sintaxis formal
ignora factores qué es esencial para el reconocimiento humano de una frase. Para caso, un símbolo de
puntuación desaparecido es una equivocación frecuente , no sólo en textos de programa, pero un símbolo
de operador es raramente omitido en una expresión de aritmética. A un parser, aun así, ambas clases de
símbolos son símbolos sintácticos sin distinción, mientras que al programador el punto y coma aparece
tan casi redundando, y un símbolo de plus como la esencia de la expresión. Esta clase de diferencia tiene
que ser tenida en cuenta si los errores son para ser tratados sensibly. A summarize, nosotros postulado los
criterios de calidad siguientes para el error que maneja:
1. Cuando muchos errores como posibles tiene que ser detectado en un escáner solo a través del texto.
2. Cuando pocos suposiciones adicionales como posibles sobre la lengua es para ser hecho.
3. El error que maneja las características no tendrían que ir más despacio el parser apreciablemente.
4. El parser el programa no tendría que crecer en medida significativamente.
Podemos concluir aquel error que maneja fuertemente depende de un caso concreto, y que lo puede ser
descrito por reglas generales sólo con éxito limitado. No obstante, hay unos cuantos heurístico gobierna
cuáles parecen para tener pertinencia allende nuestra lengua concreta, Oberon. Notablemente, se
preocupan el diseño de una lengua tan mucho como la técnica de tratamiento de error. Sin duda, una
estructura de lengua sencilla significativamente simplifica diagnósticos de error, o, en otras palabras,, una
sintaxis complicada complica error manejando innecesariamente.
Dejado nos diferenciar entre dos casos de incorrect texto. El primer caso es donde los símbolos faltan.
Esto es relativamente fácil de manejar. El parser, reconociendo la situación, procede por omitir un o
muchos llama al escáner. Un ejemplo es la declaración al final de factor, donde un paréntesis de encierro
está esperado. Si falta, parsing es resumed después de que emitting un mensaje de error:
SI sym = rparen ENTONCES Conseguir(sym) MÁS Mark(" ) desaparecido") FIN
Virtualmente sin excepción, sólo los símbolos débiles están omitidos, símbolos qué es principalmente de
una naturaleza sintáctica, como la coma, punto y coma y cerrando símbolos. Un caso de uso incorrecto es
una señal de igualdad en vez de un operador de asignación, el cual es también fácilmente manejó.
El segundo caso es donde los símbolos incorrectos son presentes. Aquí es unavoiable a skip les y a
resume parsing en un punto más tardío en el texto. Para facilitar reanudación, Oberon presenta seguro
construye empezar con símbolos señalados qué, por su naturaleza, es raramente misused. Por ejemplo, un
declaration la secuencia siempre empieza con el símbolo CONST, TIPO, VAR, o PROCEDIMIENTO, y
una declaración estructurada siempre empieza con SI, MIENTRAS, REPITE, CASO, y tan encima. Tales
símbolos fuertes son por tanto nunca skipped. Sirven tan puntos de sincronización en el texto, donde
parsing puede ser resumed con una probabilidad alta de éxito. En Oberon sintaxis, establecemos cuatro
puntos de sincronización, concretamente en factor, declaración, declarations y tipo. A principios del
correspondiente parser símbolos de procedimientos están siendo skipped. El proceso es resumed cuándo
cualquiera un símbolo de inicio correcto o un símbolo fuerte está leído.
Factor de
PROCEDIMIENTO;
EMPIEZA (*sync*)
SI sym < lparen ENTONCES
Mark("ident?"); REPETIR
Conseguir(sym) HASTA sym >= lparen
FIN ;
...
Factor de FIN;
PROCEDIMIENTO
StatSequence; EMPIEZA
(*sync*)
SI sym < ident ENTONCES
Mark("Declaración?"); REPETIR
Conseguir(sym) HASTA sym >= ident
FIN ;
...
50
FIN StatSequence;

51
Tipo de PROCEDIMIENTO;
EMPIEZA (*sync*)
SI (sym # ident) & (sym >= const) ENTONCES Mark("tipo?");
REPETIR Conseguir(sym) HASTA QUE (sym = ident) O
(sym >= variedad)
FIN ;
...
Tipo de FIN;
PROCEDIMIENTO
declarations; EMPIEZA
(*sync*)
SI sym < const ENTONCES
Mark("declaration?"); REPETIR
Conseguir(sym) HASTA sym >= const
FIN ;
...
FIN declarations;
Evidentemente, un seguro ordenando entre los símbolos está supuesto al llegar a este punto. Esto
ordenando había sido escogido tal que los símbolos están agrupados para dejar gama sencilla y eficaz
pruebas. Símbolos fuertes no para ser skipped está asignado un alto ranking (número ordinal) cuando
mostrado en la definición de la interfaz del escáner.
En general, la regla aguanta que el parser el programa está derivado de la sintaxis según el método de
descenso recursivo y las reglas de traducción explicadas. Si un símbolo leído no conoce expectativas, un
error está indicado por una llamada de procedimiento Mark, y el análisis es resumed en el punto de
sincronización próximo. Frecuentemente, errores de seguimiento están diagnosticados, cuya indicación
puede ser omitida, porque son meramente consecuencias de un error anteriormente indicado. La
declaración qué resultados para cada punto de sincronización pueden ser formulados generalmente como
sigue:
SI ~(sym EN seguir(SYNC)) ENTONCES
Mark(msg); REPETIR Conseguir(sym) HASTA
sym EN seguir(SYNC)
FIN
Dónde seguir(SYNC) denota el conjunto de símbolos cuáles correctamente pueden ocurrir al llegar a este punto.
En casos seguros es ventajoso a depart de la declaración derivada por este método. Un ejemplo es el
construir de secuencia de declaración. En vez de
Declaración;
MIENTRAS sym = el punto y coma Consigue(sym);
FIN de Declaración utilizamos la formulación.
BUCLE (*sync*)
SI sym < ident ENTONCES Mark("ident?"); ... FIN ;
Declaración;
SI sym = el punto y coma ENTONCES Consigue(sym)
ELSIF sym EN seguir(StatSequence)
ENTONCES SALE MÁS Mark("punto y coma?")
FIN
de FIN
Esto reemplaza las dos llamadas de Declaración por una llamada sola, por el cual esta llamada puede ser
reemplazada por el cuerpo de procedimiento él, haciéndolo innecesario de declarar un procedimiento
explícito. Las dos pruebas después de que la declaración corresponde a los casos legales dónde, después
de leer el punto y coma, cualquiera la declaración próxima está analizada o la secuencia rescinde. En vez
de la condición sym EN seguir(StatSequence) utilizamos una expresión Booleana qué otra vez hace uso
del específicamente escogido ordenando de símbolos:

52
(sym >= Punto y coma) & (sym < si) O (sym >= variedad)
El construir encima es un ejemplo del caso general donde una secuencia de idéntico subconstructs cuáles
pueden ser vacíos (aquí, declaraciones) está separado por un símbolo débil (aquí, punto y coma). Un
segundo, el caso similar es manifestar en la lista de parámetro de llamadas de procedimiento. La
declaración

53
SI sym = lparen
ENTONCES
Conseguir(sym);
expresión;
MIENTRAS sym = la coma Consigue(sym); FIN de
expresión ; SI sym = rparen ENTONCES Conseguir(sym)
MÁS Mark(") ?") FIN
FIN
Está siendo reemplazado por
SI sym = lparen ENTONCES
Conseguir(sym); expresión de
BUCLE;
SI sym = la coma ENTONCES Consigue(sym)
ELSIF (sym = rparen) O (sym >= punto y coma)
ENTONCES SALE MÁS Mark(") o , ?")
FIN
de FIN
;
SI sym = rparen ENTONCES Conseguir(sym) MÁS
Mark(") ?") FIN de FIN
Un caso más lejano de esta clase es el declaration secuencia. En
vez de SI sym = const ENTONCES ... FIN ;
SI sym = tipo ENTONCES ...
FIN ; SI sym = var
ENTONCES ... FIN ;
Empleamos la formulación más liberal
BUCLE
SI sym = const ENTONCES ... FIN ;
SI sym = tipo ENTONCES ...
FIN ; SI sym = var
ENTONCES ... FIN ;
SI (sym >= const) & (sym <= var) ENTONCES
Mark("malo declaration secuencia") MÁS
SALIDA
FIN
de FIN
La razón para deviating del método anteriormente dado es que declarations en un orden incorrecto (por
ejemplo variables antes de constantes) tiene que provocar un mensaje de error, pero al mismo tiempo
puede ser parsed individualmente sin dificultad. Un caso más lejano, similar puede ser encontrado en
Tipo. En todos estos casos, es absolutamente obligatorio de asegurar que el parser nunca puede conseguir
cogido en el bucle. La manera más fácil de conseguir esto es para hacer seguro que en cada repetición al
menos un símbolo está siendo leído, aquello es, que cada camino contiene al menos uno llama de
Conseguir . Así, en el caso peor, el parser logra el fin del texto de fuente y parones. Referimos al listado
en Apéndice C para detalles más lejanos.
Ahora tenga que haber devenido claro que hay no tal cosa como estrategia perfecta del error que maneja
cuál traduciría todas las frases correctas con eficacia grande y también sensibly diagnosticar todos los
errores en enfermos- formó textos. Cada estrategia manejará frases abstrusas seguras en una manera que
aparece inesperado a su autor. Las características esenciales de un compilador bueno, a toda costa de
detalles, es que (1) ninguna secuencia de símbolos dirige a su accidente, y (2) frecuentemente encontró
los errores son correctamente diagnosticados y posteriormente generar no, o pocos adicionales, spurious
mensajes de error. La estrategia presentó aquí opera satisfactoriamente, albeit con posibilidades para
mejora. La estrategia tiene de particular en el sentido que el error que maneja parser está derivado según
unos cuantos, reglas sencillas del rectos parser. Las reglas están aumentadas por el judicious elección de
unos cuantos parámetros qué está determinado por ample experiencia en el uso del langauge.
54
7.4. Ejercicios

55
7.1. El escáner del compilador listado en Apéndice C usos una búsqueda lineal de variedad KeyTab para
determinar si o no una secuencia de letras es una palabra clave. Cuando esta búsqueda ocurre muy
frecuentemente, un método de búsqueda mejorado ciertamente resultado en eficacia aumentada.
Reemplazar la búsqueda lineal en la variedad por
1. Una búsqueda binaria en una variedad ordenada.
2. Una búsqueda en un árbol binario.
3. Una búsqueda de un hash mesa. Escoger el hash función de modo que como máximo dos
comparaciones son necesarias de descubrir si o no la secuencia de letra es una palabra clave .
Determinar el beneficio global en velocidad de recopilación para las tres soluciones.
7.2. Dónde es el Oberon sintaxis no LL(1), aquello es, dónde es un lookahead de más de un
símbolo necesario? Cambio la sintaxis de tal manera que satisface el LL(1) propiedad.
7.3. Extender el escáner de tal manera que acepta números reales cuando especificados por el Oberon
sintaxis (ve Apéndice Un.2).

56
8. Consideración del contexto Especificado por Declarations
8.1. Declarations
A pesar de que los lenguajes de programación están basados encima contexto-lenguas libres en el sentido
de Chomsky, son de ninguna manera contexto libres en el sentido normal del plazo. La sensibilidad de
contexto es manifestar en el hecho que cada identificador en un programa tiene que ser declarado. Así
está asociado con un objeto de la informática procesa cuál lleva propiedades permanentes seguras. Por
ejemplo, un identificador está asociado con una variable, y esta variable tiene un tipo de dato concreto
cuando especificado en el identificador declaration. Un identificador que ocurre en una declaración
refiere al objeto especificado en su declaration, y este declaration mentiras fuera de la declaración.
Decimos que el declaration mentiras en el contexto de la declaración.
Consideración de contexto evidentemente mentiras allende la capacidad de contexto-libre parsing. A
pesar de este, es fácilmente manejó. El contexto está representado por unos datos estructuran cuál
contiene una entrada para cada identificador declarado. Esta entrada asocia el identificador con el objeto
denotado y sus propiedades. La estructura de dato es sabida por la mesa de símbolo del nombre. Estas
fechas de plazo atrás al tiempo de ensambladores, cuándo los identificadores se apellidaron símbolos.
También, la estructura es típicamente más compleja que una variedad sencilla.
El parser ahora será extendido de tal manera que, cuándo parsing un declaration, la mesa de símbolo es
suitably aumentó. Una entrada está insertada para cada identificador declarado. A summarize:
- Cada declaration resultados en una entrada de mesa de símbolo nueva.
- Cada ocurrencia de un identificador en una declaración requiere una búsqueda de la mesa de
símbolo para determinar los atributos (propiedades) del objeto denotado por el identificador.
Un atributo típico es el objeto clase. Indica si el identificador denota una constante, una variable, un
tipo o un procedimiento. Un atributo más lejano en todas las lenguas con tipos de datos es el objeto tipo.
La forma más sencilla de estructura de datos para representar un conjunto de elementos es la lista. Su
desventaja importante es una búsqueda relativamente lenta proceso, porque tiene que ser traversed de su
raíz al elemento deseado. Por el bien de simplicidad - estructuras de datos no son el tema de este texto -
declaramos el dato siguiente escribe representar listas lineales:
PUNTERO = de objeto A ObjDesc;
ObjDesc = Nombre
RÉCORD: Ident;
clase: ENTERO;
tipo: Tipo;
Luego: Objeto;
val: LONGINT
FIN
El siguiente declarations es, por ejemplo, representado por la lista mostrada en Figura 8.1.
CONST N = 10;
TIPO T = VARIEDAD N DE
ENTERO; VAR x, y: T

topScope
Tipo “N” “T” “x” “y”
de Const Tipo Var Var
clase Int T T
del 10
nom CE
bre RO
val
Figura 8.1. Mesa de símbolo que representa objetos con nombres y atributos.
luego

57
Para la generación de entradas nuevas introducimos el procedimiento NewObj con la clase de parámetro
explícita, el parámetro implicado id y el resultado obj. Los controles de procedimiento si el identificador
nuevo (id) es ya presente en la lista. Esto signify una definición múltiple y constituir un error de
programación. La entrada nueva está anexada al final de la lista, de modo que los espejos de lista el orden
del declarations en el texto de fuente. El fin de la lista está marcado por un elemento de guardia (sentinel)
al cual el identificador nuevo está asignado antes de la lista traversal inicios. Esta medida simplifica la
condición de terminación del rato- declaración.
PROCEDIMIENTO NewObj(VAR obj: Objeto; clase:
ENTERO); VAR nuevo, x: Objeto;
EMPIEZA x := origen; guard.name := id;
MIENTRAS x.next.name # id x := x.FIN
próximo ; SI x.Guardia = próximo ENTONCES
NUEVO(nuevo); new.name := id; nuevo.Clase := clase;
nuevo.Luego := guardia; x.Luego := nuevo; obj := nuevo
MÁS obj := x.Luego; Mark("múltiple
declaration") FIN
FIN NewObj
Para solicitar el proceso de búsqueda, la lista es a menudo reemplazada por una estructura de árbol. Su
ventaja deviene noticeable sólo con un número bastante grande de entradas. Para estructuró lenguas con
alcances locales, aquello es, gamas de visibilidad de identificadores, la mesa de símbolo tiene que ser
estructurada consiguientemente, y el número de las entradas en cada alcance deviene relativamente
pequeños. La experiencia muestra que como resultar la estructura de árbol cede no beneficio sustancial
sobre la lista, a pesar de que requiere una búsqueda más complicada proceso y la presencia de tres
punteros de sucesor por entrada en vez de uno. Nota que el lineal ordenando de las entradas también
tienen que ser grabadas, porque es significativo en el caso de parámetros de procedimiento.

8.2. Entradas para tipos de datos


En las lenguas que presentan tipos de dato, su consistencia que comprueba es uno de las tareas más
importantes de un compilador. Los controles están basados en el atributo de tipo grabado en cada entrada
de mesa del símbolo. Desde los datos se escribe puede ser declarado, un puntero a la entrada de tipo
respectiva aparece para ser la solución obvia. Aun así, los tipos también pueden ser especificados
anónimamente, cuando ejemplificados por el siguientes declaration:
VAR Un: VARIEDAD 10 DE ENTERO
El tipo de variable un tiene ningún nombre. Una solución fácil al problema es para introducir un tipo de
dato apropiado en el compilador para representar tipos como tal. Nombrado escribe entonces está
representado en la mesa de símbolo por una entrada de Objeto de tipo, el cual en vuelta refiere a un
elemento de Tipo de tipo.
PUNTERO = de tipo A TypDesc;
TypDesc = Forma
RÉCORD, len:
ENTERO; campos:
Objeto;
Base: FIN
de Tipo
La forma de atributo diferencia entre tipos elementales (ENTERO, BOOLEANO) y estructuró tipos
(variedades, registros). Los atributos más lejanos están añadidos según las formas individuales. La
característica para variedades es su longitud (número de elementos) y el tipo de elemento (base). Para
registros, una lista que representa los campos tienen que ser proporcionados. Sus elementos son del
Campo de clase. Cuando un ejemplo, Figura 8.2. Espectáculos la mesa de símbolo que resulta del
siguiente declarations:
TIPO R = RÉCORD f, g: FIN de ENTERO
; VAR x: ENTERO;
Un: VARIEDAD 10 DE ENTERO;
r, s: R;
58
Clase “R” “x” “U “r” “s”
de Tipo Var n” Var Var
nom Var CE
bre
Tip RO
o

Form Rec Variedadde


a len forma
camp len 10
os Base

Clase “f” “CE


de Cam RO”
nom po de
bre Cam Int
tipo po
próxi del g
mo
Figura 8.2. Mesa de símbolo que representa declaró objetos.

Según lo que programando la metodología está preocupada, sea preferible de introducir un tipo de dato
extendido para cada clase de objetos, utilizando un tipo de base con los campos id, tipo y luego sólo.
Refrenamos de hacer tan, no menos porque todos tales tipos serían declarados dentro del mismo módulo,
y porque el uso de un numérico discrimation valor (clase) en vez de los tipos individuales evita la
necesidad para tipo numeroso , redundando guardias y así aumenta eficacia. Después de todo, no
deseamos para promover una proliferación indebida de tipos de datos.

8.3. Representación de dato en corrido-tiempo


Tan lejos, todos los aspectos del ordenador de objetivo y su arquitectura, aquello es, del ordenador para
qué código es para ser generado, ha sido ignorado, porque nuestra tarea única era para reconocer texto de
fuente y para comprobar su conformidad con la sintaxis. Aun así, apenas el parser está extendido a un
compilador, el conocimiento sobre el ordenador de objetivo deviene obligatorio.
Primero, tenemos que determinar el formato en qué dato es para ser representado en corrido-tiempo en la
tienda. La elección inherently depende de la arquitectura de objetivo, a pesar de que este hecho es menos
aparente debido a la semejanza de virtualmente todos los ordenadores al respecto. Aquí, referimos a la
forma generalmente aceptada de la tienda como secuencia de individualmente células de byte
direccionable, aquello es, de byte-orientó memorias. Consecutivamente declaró las variables son
entonces destinadas con monotonically crecientes o direcciones de decrecimiento. Esto se apellida
asignación secuencial.
Cada ordenador presenta tipos de dato elementales seguros junto con instrucciones correspondientes,
como adición de entero y adición de punto flotante. Estos tipos son invariablemente tipos escalares, y
ocupan un número pequeño de ubicaciones de memoria consecutiva (bytes). Un ejemplo de una
arquitectura con un conjunto bastante rico de tipos es Semiconductor Nacional familia de NS32000
procesadores:
Tipo de dato Número de bytes Tipo de dato Número de bytes
ENTERO 2 LONGREAL 8
LONGINT 4 CHAR 1
SHORTINT 1 BOOLEANO 1
REAL 4 SET 4
Del foregoing concluimos que cada tipo tiene una medida, y cada variable tiene una dirección.

59
Estos atributos, tipo.Medida y obj.adr, está determinado cuándo los procesos de compilador declarations.
Las medidas de los tipos elementales están dadas por la arquitectura de máquina, y las entradas
correspondientes están generadas cuándo el compilador es cargado e inicializó. Para estructurado, declaró
tipos, su medida tiene que ser computada.
La medida de una variedad es su medida de elemento multiplicado por el número de sus elementos. La
dirección de un elemento es la suma de la dirección de la variedad y el índice del elemento multiplicado
por la medida de elemento. Dejado el siguiente general declarations ser dado:
TIPO T = VARIEDAD n
DE T0 VAR un: T
Entonces medida de tipo y dirección de elemento están obtenidas por las
ecuaciones siguientes: medida(T) = n * medida(T0)
adr(Un[x]) = adr(un) + x * medida(T0)
Para multi-variedades dimensionales, las fórmulas correspondientes (ve Figura 8.3) es:
TIPO T = VARIEDAD n k-1, ... , n1, n0 DE T0
Medida(T) = nk-1 * ... * n1 * n0 *
medida(T0) adr(un[xk-1, ... , x1, x0]) =
adr(un)
+ xk-1 * nk-2 * ... * n0 * medida(T0) + ...
+ x2 * n1 * n0 * medida(T0) + x1 * n0 * medida(T0) + x0 * medida(T0)
= adr(Un) + ((( ... xk-1 * nk-2 + ... + x2) * n1 + x1) * n0 + x0) * medida(T0) (Horner schema)
Nota que para la computación de la medida las longitudes de la variedad en todas las dimensiones son
sabidas, porque ocurren tan constantes en el texto de programa. Aun así, los valores de índice
necesitaron para la computación de la dirección de un elemento es típicamente no sabido antes de que
ejecución de programa.

Un: VARIEDAD 2 DE VARIEDAD 2 DE REAL.

Un[0, 0] 0
Un[0, 1] 4
Un[1, 0] 8
Un[1, 1] 12

Figura 8.3. Representación de una matriz.


En contraste, para estructuras récord, ambas medida de tipo y dirección de campo son sabidas en
compilar tiempo. Dejado nos considerar el siguiente declarations:
TIPO T = RÉCORD f0: T0; f1: T1; ... ; fk-1: Tk-1 FIN
VAR r: T
Entonces la medida del tipo y las direcciones de campo están computadas según las fórmulas
siguientes: medida(T) = medida(T0) + ... + Medida(Tk-1)
adr(r.fi) = adr(r) + Offset(fi)
Offset(fi) = medida(T0) + ... + Medida(Ti-1)
Las direcciones absolutas de variables son normalmente desconocidos en el tiempo de recopilación. Todo
generó las direcciones tienen que ser consideradas como relativos a una dirección base común qué está
dado en corrido-tiempo. La dirección eficaz es entonces la suma de esta dirección base y la dirección
determinada por el compilador.
Si la tienda de un ordenador es byte-dirigido, cuando es bastante común, un punto más lejano tiene que
ser considerado. A pesar de que los bytes pueden ser accedidos individualmente, típicamente un número
pequeño de bytes (dice 4 o 8) está transferido de o a memoria como paquete, un tan-palabra llamada. Si
60
la asignación ocurre estrictamente en orden secuencial es posible que una variable puede ocupar (partes
de) varias palabras (ve Figura 8.4). Pero esto sin duda tendría que ser evitado,

61
Porque otherwise un acceso variable implicaría varios accesos de memoria, resultando en un apreciables
slowdown. Un método sencillo de vencer este problema es a ronda arriba (o abajo) la dirección de cada
variable al múltiplo próximo de su medida. Este proceso se apellida alineación. Los controles de regla
para tipos de dato elemental. Para variedades, la medida de su tipo de elemento es pertinente, y para
registros nosotros sencillamente ronda hasta la medida de palabra del ordenador. El precio de alineación
es la pérdida de algunos bytes en memoria, el cual es bastante insignificante.

VAR Un: CHAR; b, c: ENTERO; d: REAL

3 2 1 0 3 2 1 0
c b Un 0 b Un 0
d c 4 c 4
d 8 d 8

No alineado, campos de ruptura alinearon

Figura 8.4. Alineación en computación de dirección.


Las adiciones siguientes al parsing procedimiento para declarations es necesario de generar las entradas
de mesa de símbolo requeridas:
SI sym = tipo ENTONCES (* "TIPO" ident "=" tipo *)
Consigue(sym);
MIENTRAS sym = ident
NewObj(obj, Typ);
Consigue(sym);
SI sym = eql ENTONCES Conseguir(sym) MÁS
Mark("= ?") FIN ; Tipo1(obj.Tipo);
SI sym = el punto y coma ENTONCES Consigue(sym) MÁS
Mark("; ?") FIN de FIN
FIN ;
SI sym = var ENTONCES (* "VAR" ident {"," ident} ":" tipo *)
Consigue(sym);
MIENTRAS sym = ident HACER
IdentList(Var, primero); Tipo1(tp); obj := primero;
MIENTRAS obj # el guardia HACE
obj.Tipo := tp; INC(adr, obj.Tipo.Medida); obj.val := -adr; obj :=
obj.FIN próximo ;
SI sym = el punto y coma ENTONCES Consigue(sym) MÁS
Mark("; ?") FIN de FIN
FIN ;
Aquí, procedimiento IdentList suele proceso una lista de identificador, y el Tipo de procedimiento
recursivo1 sirve para compilar un tipo declaration.
PROCEDIMIENTO IdentList(clase: ENTERO; VAR primero:
Objeto); VAR obj: Objeto;
EMPIEZA
SI sym = ident ENTONCES
NewObj(primero, clase);
Consigue(sym); MIENTRAS
sym = la coma HACE
Consigue(sym);
SI sym = ident ENTONCES NewObj(obj, clase); Consigue(sym) MÁS
Mark("ident?") FIN de FIN;
SI sym = el colon ENTONCES Consigue(sym) MÁS Mark(":?") FIN

62
FIN
FIN IdentList;
Tipo de PROCEDIMIENTO1(VAR
tipo: Tipo); VAR n: ENTERO;
obj, primero: Objeto; tp:
Tipo; EMPIEZA tipo := intType;
(*sync*)
SI (sym # ident) & (sym < variedad) ENTONCES
Mark("ident?"); REPETIR Conseguir(sym) HASTA QUE
(sym = ident) O (sym >= variedad)
FIN ;
SI sym = ident
ENTONCES
encontrar(obj);
Consigue(sym);
SI obj.Clase = Typ ENTONCES tipo := obj.Tipo MÁS
Mark("tipo?") FIN ELSIF sym = variedad ENTONCES
Consigue(sym);
SI sym = número ENTONCES n := val; Consigue(sym) MÁS Mark("número?"); n
:= 1 FIN ; SI sym = de ENTONCES Conseguir(sym) MÁS Mark("DE?") FIN ;
Tipo1(tp); NUEVO(tipo); tipo.Forma := Variedad;
tipo.Base := tp; tipo.len := n; Tipo.Medida := tipo.len *
tp.Medida
ELSIF sym = Graba ENTONCES
Consigue(sym); NUEVO(tipo); tipo.Forma := Récord; tipo.Medida := 0;
OpenScope; BUCLE
SI sym = ident ENTONCES
IdentList(Fld, primero); Tipo1(tp); obj := primero;
MIENTRAS obj # el guardia HACE
obj.Tipo := tp; obj.val := Tipo.Medida; INC(tipo.Medida, obj.Tipo.Medida);
obj := obj.FIN próximo
FIN ;
SI sym = el punto y coma
ENTONCES Consigue(sym) ELSIF
sym = ident ENTONCES Mark("; ?")
MÁS SALIDA
FIN
de FIN
;
Tipo.Campos := topScope.Luego; CloseScope;
SI sym = el fin ENTONCES Consigue(sym) MÁS
Mark("FIN?") ACABA MÁS Mark("ident?")
FIN
Tipo de FIN1;
Siguiendo un longstanding tradición, las direcciones de variables están asignadas valores negativos,
aquello es, offsets negativos a la dirección base común determinada durante ejecución de programa. Los
procedimientos auxiliares OpenScope y CloseScope asegura que la lista de los campos récord no es
intermixed con la lista de variables. Cada récord declaration establece un alcance nuevo de visibilidad de
identificadores de campo, cuando requeridos por la definición de la lengua Oberon. Nota que la lista a
qué entradas nuevas está insertada está arraigado en el global variable topScope.

8.4. Ejercicios
8.1. El alcance de identificadores está definido para extender del sitio de declaration hasta el final del
procedimiento en qué el declaration ocurre. Qué sería necesario de dejar esta gama extiende desde el
principio hasta el final del procedimiento?
8.2. Considera puntero declarations tan definido en Oberon. Especifican un tipo al cual el puntero
63
declarado está atado, y este tipo puede ocurrir más tarde en el texto. Qué es necesario de acomodar esta
relajación de la regla que todo referenced las entidades tienen que ser declaradas con anterioridad a su
uso?

64
9. Un RISC-Arquitectura cuando Objetivo
Vale notar que nuestro compilador, hasta este punto, podría ser desarrollado sin referencia al ordenador
de objetivo para qué es para generar código. Pero por qué de hecho tener que la influencia de estructura
de la máquina de objetivo error y análisis sintácticos que manejan? Al contrario, tal una influencia
conscientemente tendría que ser evitada. Como resultado, generación de código para un ordenador
arbitrario puede ser añadida según el principio de stepwise refinamiento al existiendo, la máquina
independiente parser, el cual sirve como un scaffolding. Antes de emprender esta tarea, aun así, una
arquitectura de objetivo concreta tiene que ser seleccionada.
Para mantener ambos el compilador resultante razonablemente sencillo y el desarrollo aclara de detalles
que es de pertinencia sólo para una máquina concreta y sus idiosincrasias, nosotros postulado una
arquitectura según nuestra elección propia. Así obtenemos la ventaja considerable que lo puede ser
tailored a las necesidades de la lengua de fuente. Esta arquitectura no existe como máquina real; es por
tanto virtual. Pero desde cada ordenador ejecuta instrucciones según un algoritmo fijo, fácilmente pueda
ser especificado por un programa. Un ordenador real entonces puede soler ejecutar esto programa cuál
interpreta el código generado. El programa se apellida un intérprete, y emula la máquina virtual, los
cuales por tanto pueden ser dichos para tener un semi-existencia real.
No es el objetivo de este texto para presentar las motivaciones para nuestra elección de la arquitectura
virtual concreta con todos sus detalles. Este capítulo es bastante pretendido para servir como descriptivo
manual constando de una introducción informal y una definición formal del ordenador en la forma del
interpretive programa. Esta formalización incluso puede ser considerada como un ejemplo de una
especificación exacta de un procesador.
En la definición de este ordenador intencionadamente seguimos estrechamente la línea de RISC-
arquitecturas. El acrónimo RISC posiciones para ordenador de conjunto de instrucción reducido, dónde
"reducido" es para ser entendido como relativo a arquitecturas con conjuntos grandes de instrucciones
complejas, cuando estos eran dominantes hasta que aproximadamente 1980. Esto es evidentemente no el
sitio para explicar la esencia del RISC arquitectura, ni a expound en sus varias ventajas. Aquí es
evidentemente atractivo debido a su simplicidad y claridad de conceptos, los cuales simplifican la
descripción del conjunto de instrucción y la elección de secuencias de instrucción que corresponden a la
lengua concreta construye. La arquitectura escogida aquí es casi idéntico al presentado por Hennessy y
Patterson (1990) bajo el nombre DLX. Las desviaciones pequeñas se deben a nuestro deseo para
regularidad aumentada. Entre productos comerciales, el MIPS y arquitecturas de BRAZO son más
cercanas a nuestra máquina virtual.

Recursos y registros
De los puntos de vista del programador y el diseñador de compilador el ordenador consta de una unidad
de aritmética, una unidad de control y una tienda. La unidad de aritmética contiene 16 registros R0 –
R15, con 32 bits cada. La unidad de control consta de el registro de instrucción IR, aguantando la
instrucción actualmente siendo ejecutado, y el PC de contador del programa, aguantando la dirección de
la instrucción para ser fetched próximo (Figura 9.1). El contador de programa está incluido en el conjunto
de registros de datos: PC = R15. Instrucciones de rama a subrutinas implícitamente registro de uso R14
para almacenar la dirección de regreso. La memoria consta de 32-mordió palabras, y es byte -dirigido,
aquello es, direcciones de palabra son múltiplos de 4.
Hay tres tipos de instrucciones y formatos de instrucción. Instrucciones de registro operan en registros
sólo y alimentar dato a través de un shifter y la unidad de lógica de la aritmética ALU. Instrucciones de
memoria fetch y dato de tienda en memoria. Instrucciones de rama afectan el contador de programa.
1. Instrucciones de registro (formatos F0 y F1).
MOV Un, c R.Un := Cambio(R.c, b) MOVI Un, R.Un :=
MVN Un, c R.Un := - Cambio(R.c, b) MVNI im Cambio(im, b)
AÑADIR un, R.Un := R.b + R.c ADDI un, R.Un := -
b, c im Cambio(im, b)
Un, b, R.Un := R.b + im
im
SUB Un, b, c R.Un := R.b – R.c SUBI Un, b, R.Un := R.b - im
65
im
MUL Un, b, c R.Un := R.b * R.c MULI Un, b, R.Un := R.b * im
im
DIV un, b, c R.Un := R.b DIV R.c DIVI Un, b, R.Un := R.b DIV im
im
MOD Un, b, c R.Un := R.b MOD R.c MODI Un, b, R.Un := R.b MOD
im im
CMP b, c Z := R.b = R.c CMPI b, im Z := R.b = im
N := R.b < R.c N := R.b < im

66
U
n incrementer

Registro

Programa
b c
Desco
difica
i Instr Reg
AL
U Shifter
adr

Memori
a
Figura 9.1.Esquema de bloque del RISC estructura

4 4 4 4
F0 00 op Un b c
18
F1 01 op Un b im

F2 10 op Un b disp
26
F3 11 op disp

Figura 9.2. Formatos de instrucción.


En el caso de instrucciones de registro allí son dos variantes. Cualquiera el segundo operando es un valor
inmediato (F1), y el 18-mordió constante im es la señal extendió a 32 bits. O el segundo operando es un
registro (F0). La instrucción de comparación CMP afecta los bits de estado Z y N..

2. Menory (Formato F2)


Instrucciones
LDW Un, b, im R.Un := Mem[R.b +disp] Palabra de
carga
LDB Un, b, im R.Un := Mem[R.b + disp] MOD 100H Byte de
carga
POP un, b, im R.b := R.b - disp; R.Un := Mem[R.b] Pop
STW Un, b, im Mem[R.b + disp] := R.Un Palabra de
tienda
STB Un, b, im Mem[R.b + disp] := … Byte de
tienda
PSH Un, b, im Mem[R.b] := R.Un; R.b := R.b + disp Empujón

67
3. Instrucciones de rama (Formato F3, PC de dirección de la palabra-relativo)
BEQ disp PC := PC+disp*4, si Z BNE disp PC := PC+disp*4, si ~Z
BLT disp PC := PC+disp*4, si N BGE disp PC := PC+disp*4, si ~N
BLE disp PC := PC+disp*4, si Z o N BGT disp PC := PC+disp*4, si ~(Z o N)
BR disp PC := PC+disp*4
BSR disp R14 := PC; PC := PC + disp*4 (PC de dirección-
relativo) RET disp PC := R.c
El ordenador virtual está definido por el programa de intérprete siguiente con más detalle. Nota que
palabra de controles de PC de registro direcciones en vez de direcciones de byte, y que Z y N es bits de
estado ponen ser instrucciones de comparación.
MÓDULO RISC; (*NW 27. 11. 05*)
SISTEMA de IMPORTACIÓN,
Textos;
CONST MemSize* = 4096; ProgOrg = 2048; (*en bytes*)
MOV = 0; MVN = 1; AÑADE = 2; SUB = 3; MUL = 4; Div = 5; Mod = 6; CMP = 7;
MOVI = 16; MVNI = 17; ADDI = 18; SUBI = 19; MULI = 20; DIVI = 21; MODI = 22; CMPI = 23;
CHKI = 24;
LDW = 32; LDB = 33; POP = 34; STW = 36; STB = 37; PSH = 38;
RD = 40; WRD= 41; WRH = 42; WRL = 43;
BEQ = 48; BNE = 49; BLT = 50; BGE = 51; BLE = 52; BGT = 53; BR = 56; BSR = 57; RET = 58;
VAR IR: LONGINT;
N, Z:
BOOLEANO;
R*: VARIEDAD 16 DE LONGINT;
M*: VARIEDAD MemSize DIV 4 DE LONGINT;
W: Textos.Escritor;
(* R15] es PC, R[14] utilizó tan registro de enlace por BSR instrucción*)
El PROCEDIMIENTO Ejecuta*(inicio: LONGINT; VAR en: Textos.Escáner; fuera:
Textos.Texto); VAR opc, un, b, c, nxt: LONGINT;
EMPIEZA R[14] := 0; R[15] := inicio + ProgOrg;
BUCLE (*ciclo de interpretación*)
nxt := R[15] + 4; IR := M[R[15] DIV 4];
opc := IR DIV 4000000H MOD 40H;
Un := IR DIV 400000H MOD
10H; b := IR DIV 40000H MOD
10H; c := IR MOD 40000H;
SI opc < MOVI ENTONCES (*F0*) c := R[IR MOD 10H]
ELSIF opc < BEQ ENTONCES
(*F1, F2*) c := IR MOD 40000H;
SI c >= 20000H ENTONCES DEC(c, 40000H) FIN (*extensión de
señal*) MÁS (*F3*) c := IR MOD 4000000H;
SI c >= 2000000H ENTONCES DEC(c, 4000000H) FIN (*extensión de
señal*) FIN ;
CASO opc DE
MOV, MOVI: R[un] := CENIZA(c, b) (*cambio de aritmética*)
| MVN, MVNI: R[un] := -CENIZA(c, b)
| AÑADE, ADDI: R[un] := R[b] + c
| SUB, SUBI: R[un] := R[b] - c
| MUL, MULI: R[un] := R[b] * c
| Div, DIVI: R[un] := R[b] DIV c
| Mod, MODI: R[un] := R[b] MOD c
| CMP, CMPI: Z := R[b] = c; N := R[b] < c
| CHKI: SI (R[un] < 0) O (R[un] >= c) ENTONCES R[un] := 0 FIN
| LDW: R[Un] := M[(R[b] + c) DIV 4]

68
| LDB: (*No implementado*)
| POP: R[un] := M[(R[b]) DIV 4]; INC(R[b], c)
| STW: M[(R[b] + c) DIV 4] := R[un]
| STB: (*No implementado*)
| PSH: DEC(R[b], c); M[(R[b]) DIV 4] := R[un]
| RD: Textos.Escáner(en); R[un] := en.i
| WRD: Textos.Escribe(W, " "); Textos.WriteInt(W, R[c], 1)
| WRH: Textos.WriteHex(W, R[c])
| WRL: Textos.WriteLn(W); Textos.Anexa(fuera, W.buf)
| BEQ: SI Z ENTONCES nxt := R[15] + c*4 FIN
| BNE: SI ~Z ENTONCES nxt := R[15] + c*4 FIN
| BLT: SI N ENTONCES nxt := R[15] + c*4 FIN
| BGE: SI ~N ENTONCES nxt := R[15] + c*4 FIN
| BLE: SI Z O N ENTONCES nxt := R[15] + c*4 FIN
| BGT: SI ~Z & ~N ENTONCES nxt := R[15] + c*4 FIN
| BR: nxt := R[15] + c*4
| BSR: nxt := R[15] + c*4; R[14] := R[15] + 4
| RET: nxt := R[c MOD 10H];
SI nxt = 0 ENTONCES FIN de SALIDA
FIN ; R[15]
:= nxt
FIN
El FIN Ejecuta;
Carga de PROCEDIMIENTO*(VAR código: VARIEDAD DE LONGINT;
len: LONGINT); VAR i: ENTERO;
EMPIEZA i := 0;
MIENTRAS i < len M[i + ProgOrg DIV 4] := código[i]; INC(i)
Carga de FIN del FIN;
EMPIEZA
Textos.OpenWriter(W) FIN
RISC.

Notas adicionales:
1. Instrucciones RD, WRD, WRH y WRL no es instrucciones de ordenador típico. Les hemos añadido
aquí para proporcionar una manera sencilla y eficaz para entrada y producción. Compilado e interpretó
los programas así pueden ser probados y obtener una realidad segura.
2. Instrucciones LDB y STB carga y almacenar un byte solo. Sin ellos, no haga sentido para hablar sobre
un byte-ordenador orientado. Aun así, refrenamos de especificarles aquí, porque tales declaraciones de
programa difícilmente espejo su implementación en hardware truthfully.
3. Instrucciones PSH y POP behave como STW y LDW, por el cual el valor del registro de base R.b Es
incremented o decremented por la cantidad c. Dejarán el manejando de parámetros de procedimiento en
una manera conveniente (ve Capítulo 12).
4. El CHKI la instrucción sencillamente reemplaza un índice valora cuál es fuera del índice bounds por 0,
porque el RISC no proporciona una facilidad de trampa.

69
10. Expresiones y Asignaciones.
10.1. Generación de código recto según el stack principio
El tercer ejemplo en Capítulo 5 mostrado cómo para convertir una expresión de convencional infix forma
a su equivalente postfix forma. Nuestro ordenador ideal sería capaz de directamente interpretando postfix
notación. Tan también mostrado, tal un ordenador ideal requiere un stack para aguantar resultados
intermedios. Tal arquitectura de ordenador se apellida un stack arquitectura.
Los ordenadores basaron en un stack la arquitectura no es en común uso. Conjuntos de explícitamente los
registros direccionables están preferidos a un stack. Naturalmente, un conjunto de los registros fácilmente
pueden soler emular un stack. Su elemento superior está indicado por un global variable representando el
stack puntero (SP) en el compilador. Esto es factible, desde el número de resultados intermedios es
sabido en compilar tiempo, y el uso de una variable global está justificado porque el stack constituye un
recurso global.
Para derivar el programa para generar el código que corresponde a concreto construye, nosotros primero
postulado los patrones de código deseados. Este método también será exitosamente empleado más tarde
para otro construye allende expresiones y asignaciones. Dejado el código para un dado construye K ser
dado por la mesa siguiente:
K Código(K) Efecto de
lado
ident LDW i, 0, adr(ident) INC(SP)
Número MOVI i, 0, valor INC(SP)
( exp ) Código(exp)
fac0 * fac1 Código(fa DEC(SP)
c0)
código(fac
1)
MUL i, i, i+1
Plazo0 + plazo1 código(plazo0) DEC(SP)
Código(plazo1)
AÑADE i, i, i+1
ident := exp Código(exp) DEC(SP)
STW i, adr(ident)
Para empezar, restringimos nuestra atención a variables sencillas como operandos, y omitimos selectors
para estructuró variables. Primero, considerar la asignación u := x*y + z*w:
Significado de instrucción stack stack puntero
LDW R0, base, x R0 := x x SP = 1
LDW R1, base, y R1 := y x, y 2
MUL R0, R1, R2 R0 := R0*R1 x*y 1
LDW R1, base, z R1 := z x*y, z 2
LDW R2, base, w R2 := w x*y, z, w 3
MUL R1, R1, R2 R1 := R1 * R2 x*y, z*w 2
AÑAD R0, R0, R1 R1 := R1 + R2 x*y + z*w 1
E
STW R0, base, u u := R1 - 0
De este es bastante evidente cómo el corresonding parser los procedimientos tienen
que ser extendidos: factor de PROCEDIMIENTO;
VAR obj: Objeto;
EMPIEZA
SI sym = ident ENTONCES encontrar(obj); Consigue(sym); INC(RX);
Puesto(LDW, RX, 0, -obj.val) ELSIF sym = Número ENTONCES INC(RX);
70
Puesto(MOVI, RX, 0, val); Consigue(sym) ELSIF sym = lparen ENTONCES
Consigue(sym); expresión;
SI sym = rparen ENTONCES Conseguir(sym) MÁS Mark(" ) desaparecido") FIN

71
ELSIF ...
FIN
Factor de FIN;
Plazo de
PROCEDIMIENTO;
VAR op: ENTERO;
EMPIEZA factor;
MIENTRAS (sym = tiempo) O (sym =
div) op := sym; Consigue(sym); factor;
DEC(RX);
SI op = el tiempo ENTONCES Puesto(MUL,
RX, RX, RX+1) ELSIF op = div ENTONCES
Puesto(DIV, RX, RX, RX+1) FIN
FIN
Plazo de FIN;
PROCEDIMIENTO
SimpleExpression; VAR op:
ENTERO;
EMPIEZA
SI sym = el plus ENTONCES Consigue(sym); plazo
ELSIF sym = minus ENTONCES Conseguir(sym); plazo;
Puesto(SUB, RX, 0, RX) MÁS plazo
FIN ;
MIENTRAS (sym = plus) O (sym = minus)
op := sym; Consigue(sym); plazo;
DEC(RX);
SI op = el plus ENTONCES Puesto(AÑADE, RX,
RX, RX+1) ELSIF op = minus ENTONCES
Puesto(SUB, RX, RX, RX+1) FIN
FIN
FIN SimpleExpression;
Declaración de
PROCEDIMIENTO;
VAR obj: Objeto;
EMPIEZA
SI sym = ident
ENTONCES
encontrar(obj);
Consigue(sym);
SI sym = deviene ENTONCES Conseguir(sym); expresión; Puesto(STW, RX, 0,
obj.val); DEC(RX) ELSIF ...
FIN
ELSIF ...
FIN
Declaración de FIN;
Aquí hemos introducido el procedimiento de generador Puso. Pueda ser considerado como el
counterpart del procedimiento de escáner Consigue. Suponemos que deposita una instrucción en una
variedad global, utilizando el variable pc cuando el índice que denota la ubicación libre próxima en la
variedad. Con esta suposición, el procedimiento Puesto está formulado en Oberon como sigue, por el
cual LSH(x, n) es una función que cede el valor x dejó cambiado por n mordió posiciones:
El PROCEDIMIENTO Puesto(op, un, b,
d: ENTERO); EMPIEZA
Código[pc] := LSH(LSH(LSH(op, 4) + un, 4) + b, 18) + (d MOD 40000H); INC(pc)
El FIN Puso
Las direcciones de variables están indicadas aquí por sencillamente utilizando su identificador. En
72
realidad, los valores de dirección obtuvieron de la posición de mesa del símbolo en sitio de los
identificadores. Son offsets a una dirección base computada en tiempo corrido, aquello es, los offsets
están añadidos a la dirección base para ceder la dirección eficaz. Esto aguanta no sólo para nuestro RISC
máquina, pero también para virtualmente todos los ordenadores comunes. Tomamos este hecho a cuenta
por especificar las direcciones que utilizan los pares que constan de el offset un y la base (registro) r.

73
10.2. Generación de código retrasado
Considera como segundo ejemplo la expresión x + 1. Según el esquema presentado en Sección 10.1,
obtenemos el código correspondiente
LDW 0, base, x R0 := x
MOVI 1, 0, 1 R1 := 1
AÑAD 0, 0, 1 R0 := R0 + R1
E
Esto muestra que el código generado es correcto, pero ciertamente no optimal. Las mentiras de defecto en
el hecho que la constante 1 está cargado a un registro, a pesar de que esto es innecesario, porque nuestro
ordenador presenta una instrucción qué deja constantes ser añadidos inmediatamente a un registro
(inmediato dirigiendo modo). Evidentemente algún código ha sido emitted prematuramente. El remedio
tiene que ser para retrasar emisión de código en casos seguros hasta que es sin duda sabido que hay no
solución mejor. Cómo es tal generación de código retrasada para ser implementado?
En general, el método consiste en asociar la información cuál habría sido utilizado para la selección del
emitted código con el resultante sintáctico construye. Del principio de atribuyó las gramáticas
presentaron en Capítulo 5, esta información está retenida en la forma de atributos. Generación de código
por tanto depende no sólo en el syntactically redujo símbolos, pero también en los valores de sus
atributos. Esta extensión conceptual está reflejada por el hecho que parser los procedimientos obtienen un
parámetro de resultado qué representa estos atributos. Porque hay normalmente varios atributos, una
estructura récord está escogida para estos parámetros; llamamos su Elemento de tipo (Wirth y
Gutknecht, 1992).
En el caso de nuestro segundo ejemplo, es necesario de indicar si el valor de un factor, el plazo o la
expresión está aguantado (en tiempo corrido) en un registro, cuando ha sido el caso tan lejos, o si el valor
es una constante sabida . El caso último ventaja bastante probable a una instrucción más tardía con modo
inmediato. Ahora deviene claro que el atributo tiene que indicar el modo en qué el factor, el plazo o la
expresión es, aquello es, donde el valor está almacenado y cómo es para ser accedido. Este modo de
atributo corresponde al modo de dirigir de instrucciones de ordenador, y su gama de valores posibles
depende de el conjunto de dirigir modos qué el ordenador de objetivo características. Para cada modo de
dirigir ofreció, hay un modo de elemento correspondiente. Un modo es también implícitamente
introducido por clases de objeto. Clases de objeto y modos de elemento parcialmente overlap. En el caso
de nuestro RISC arquitectura, hay sólo tres modos:
Modo de Clase de Dirigiendo modo Atributos adicionales
elemento objeto
Var Var Directo Un Valor en memoria en dirigir
un
Const Const Inmediato Un Valor es el constante un
Reg - Registro r El valor aguantado en registro
R[r]
Con este en mente, declaramos el Elemento de tipo del dato como estructura récord con modo de
campos, tipo, un y r . Evidentemente, el tipo del elemento es también un atributo. No sea mencionado
muy más allá abajo, porque consideraremos sólo el Entero de tipo solo.
El parser los procedimientos ahora emergen tan funciones con Elemento de tipo del resultado.
Programando consideraciones, aun así, sugiere para utilizar procedimientos apropiados con un parámetro
de resultado en vez de procedimientos de función.
REGISTRO = de elemento
Modo: ENTERO;
tipo: Tipo;
Un, r: LONGINT;
FIN
Dejado nos ahora regresar a nuestro ejemplo para demostrar la generación de código para la expresión
x+1. El proceso está mostrado en Figura 10.1. La transformación de un Var-Elemento a un Reg-el
elemento está acompañado por la emisión de un LDW instrucción, y la transformación de un Reg-
Elemento y un Const-Elemento a un Reg-el elemento está acompañado por emitting un ADDI
74
instrucción.

75
+ + + +

x 1 x 1 x 1 x 1

Var Reg Reg Const Reg


x x x 1 1

Figura 10.1. Generando elementos e instrucciones para la expresión x+1.


Nota la semejanza del dos Elemento de tipos y Objeto . Ambos describen objetos, pero mientras que
Objetos representar declarado, nombró objetos, cuya visibilidad logra allende el construir de su
declaration, Elementos describe objeta cuáles son siempre estrictamente atados a su sintácticos construye.
Por tanto, es fuertemente recomendado no para destinar Elementos dinámicamente (en un heap), sino
para declararles parámetros tan locales y variables..
Factor de PROCEDIMIENTO(VAR
x: Elemento); EMPIEZA
SI sym = ident ENTONCES encontrar(obj); Consigue(sym); x.Modo := obj.Clase;
x.Un := obj.adr; x.r := 0 ELSIF sym = número ENTONCES x.Modo := Const; x.Un
:= val; Consigue(sym)
ELSIF sym = lparen
ENTONCES
Conseguir(sym);
expresión(x);
SI sym = rparen ENTONCES Conseguir(sym) MÁS Mark(" )
desaparecido") FIN ELSIF ...
FIN
Factor de FIN;
Plazo de PROCEDIMIENTO(VAR
x: Elemento); VAR y: Elemento;
op: ENTERO;
EMPIEZA factor(x);
MIENTRAS (sym = tiempo) O (sym = div)
op := sym; Consigue(sym); factor(y);
Op2(op, x, y) FIN
Plazo de FIN;
PROCEDIMIENTO SimpleExpression(VAR x:
Elemento); VAR y: Elemento; op: ENTERO;
EMPIEZA
SI sym = el plus ENTONCES Consigue(sym); plazo(x)
ELSIF sym = minus ENTONCES Conseguir(sym); plazo(x);
Op1(minus, x) MÁS plazo(x)
FIN ;
MIENTRAS (sym = plus) O (sym = minus)
op := sym; Consigue(sym); plazo(y);
Op2(op, x, y)
FIN
FIN SimpleExpression;
Declaración de
PROCEDIMIENTO; VAR
obj: Objeto; x, y: Elemento;
EMPIEZA
76
SI sym = ident ENTONCES
Encuentra(obj); Consigue(sym); x.Modo := obj.Clase; x.Un :=
obj.adr; x.r := 0; SI sym = deviene ENTONCES

77
Consigue(sym); expresión(y);
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(STW, y.r, 0,
x.Un)
ELSIF ...
FIN
ELSIF ...
FIN
Declaración de FIN;
El código que genera las declaraciones son ahora fusionadas a dos procedimientos, Op1 y Op2 . El
principio de emisión de código retrasado es también utilizado aquí para evitar la emisión de instrucciones
de aritmética si el compilador puede actuar la operación él. Esto es el caso cuándo ambos operandos son
constantes . La técnica es sabida como constante plegando.
PROCEDIMIENTO Op1(op: ENTERO; VAR x: Elemento); (* x
:= op x *) VAR t: LONGINT;
EMPIEZA
SI op = minus ENTONCES
SI x.Modo = Const ENTONCES
x.Un := -x.Un MÁS
SI x.Modo = Var ENTONCES
carga(x) FIN ; Puesto(MVN, x.r, 0,
x.r)
FIN
...
FIN
FIN Op1;
PROCEDIMIENTO Op2(op: ENTERO; VAR x, y: Elemento); (* x := x
op y *) EMPIEZA
SI (x.Modo = Const) & (y.Modo = Const)
ENTONCES SI op = plus ENTONCES x.Un
:= x.Un + y.Un
ELSIF op = minus ENTONCES x.Un := x.Un - y.Un
...
FIN
MÁS
SI op = plus ENTONCES PutOp(AÑADE,
x, y) ELSIF op = minus ENTONCES
PutOp(SUB, x, y)
...
FIN
FIN de
FIN Op2;
PROCEDIMIENTO PutOp(cd: LONGINT; VAR x, y:
Elemento); EMPIEZA
SI x.Modo # Reg ENTONCES carga(x) FIN ;
SI y.Modo = Const ENTONCES Puesto(cd+MVI,
x.r, x.r, y.Un) MÁS
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(cd, x.r, x.r, y.r);
EXCL(regs, y.r)
FIN
FIN PutOp;
Carga de
PROCEDIMIENTO(VAR x:
Elemento); VAR r: ENTERO;

78
EMPIEZA (*x.Modo # Reg*)
SI x.Modo = Var ENTONCES GetReg(r); Puesto(LDW, r, x.r,
x.Un); x.r := r ELSIF x.Modo = Const ENTONCES
SI x.Un = 0 ENTONCES x.r := 0 MÁS GetReg(x.r); Puesto(MOVI, x.r, 0, x.Un) FIN

79
FIN ;
x.Modo :=
Reg carga de
FIN;
Siempre que expresiones de aritmética están evaluadas, el peligro inherente de desbordamiento existe.
Las declaraciones de evaluar por tanto tendrían que ser suitably guarded. En el caso de guardias de
adición puede ser formulado como sigue:
SI x.Un >= 0 ENTONCES
SI y.Un <= MAX(ENTERO) - x.Un ENTONCES x.Un := x.Un + y.Un MÁS
Mark("desbordamiento") ACABA MÁS
SI y.Un >= MIN(ENTERO) - x.Un ENTONCES x.Un := x.Un + y.Un MÁS
Mark("underflow") FIN de FIN
La esencia de generación de código retrasado es que el código no es emitted antes de que es claro que no
la solución mejor existe. Por ejemplo, un operando no es cargado a un registro antes de que esto es
sabido de ser inevitable.
También abandonamos asignación de registros según el rígidos stack principio. Esto es ventajoso en
casos seguros cuál será explicado más tarde. Procedimiento GetReg entrega y reserva cualquiera de los
registros libres. El conjunto de registros libres es suitably representado por un global variable regs.
Naturalmente, el cuidado tiene que ser tomado para liberar registros siempre que su valor es ya no
pertinente.
PROCEDIMIENTO GetReg(VAR r:
LONGINT); VAR i: ENTERO;
EMPIEZA i := 1;
MIENTRAS (i < 15) & (i EN regs) INC(i) FIN ;
INCL(regs, i); r := i
FIN GetReg;
El principio de generación de código retrasado es también útil en muchos otros casos, pero deviene
indispensible cuándo considerando ordenadores con complejos dirigiendo modos, para qué código
razonablemente eficaz tiene que ser generado por hacer uso bueno de los modos complejos disponibles.
Cuando un ejemplo consideramos emisión de código para un CISC arquitectura. Típicamente ofrece
instrucciones con dos operandos, uno de ellos también representando el resultado. Dejado nos considerar
la expresión u := x + y*z y obtener la secuencia de instrucción siguiente:
MOV y, R0 R0 := y
MUL z, R0 R0 := R0 * z
AÑAD x, R0 R0 := R0 + x
E
MOV R0, u u := R0
Esto está obtenido por retrasar el cargando de variables hasta que son para ser unidos con otro operando.
Porque la instrucción reemplaza el primer operando con el resultado de la operación, la operación no
puede ser actuada en la ubicación original de la variable, pero sólo en una ubicación intermedia,
típicamente un registro. La instrucción de copia no es emitida antes de que esto está reconocido como
inevitable. Un efecto de lado de esta medida es que, por ejemplo, la asignación sencilla x := y no
transfiere vía un registro en absoluto, pero ocurre directamente a través de una instrucción de copia, el
cual ambas eficacia de aumentos y longitud de código de las disminuciones:
MOV y, x x := y

10.3. Indexed Variables y campos récord


Tan lejos hemos considerado variables sencillas sólo en expresiones y asignaciones. Acceso a elementos
de estructuró variables, variedades y registros, necessitates la selección del elemento según un índice
computado o un identificador de campo, respectivamente. Syntactically, el identificador de la variable
está seguido por uno o varios selectors. Esto es mirrored en el parser por una llamada del selector de
procedimiento dentro factor y también en declaración:

80
Encuentra(obj); Consigue(sym); x.Modo := obj.Clase; x.Un := obj.adr; x.r := 0; selector(x)

81
Selector de procedimiento procesa no sólo una selección sola, pero si necesitó una secuencia entera de
selecciones. La formulación siguiente muestra que el tipo de atributo del operando x es también
pertinente.
Selector de PROCEDIMIENTO(VAR
x: Elemento); VAR y: Elemento;
obj: Objeto;
EMPIEZA
MIENTRAS (sym = lbrak) O (sym = periodo)
SI sym = lbrak ENTONCES
Consigue(sym); expresión(y);
SI x.Tipo.Variedad = de forma ENTONCES Índice(x, y) MÁS Mark("no una
variedad") FIN ; SI sym = rbrak ENTONCES Conseguir(sym) MÁS
Mark("]?") FIN
MÁS Conseguir(sym);
SI sym = ident ENTONCES
SI x.Tipo.Registro = de forma
ENTONCES FindField(obj,
x.Tipo.Campos); Consigue(sym);
SI obj # guardia ENTONCES Campo(x, obj) MÁS
Mark("undef") ACABA MÁS Mark("no un récord")
FIN
MÁS Mark("ident?")
FIN
FIN
de FIN
Selector de FIN;
La dirección del elemento seleccionado está dada por las fórmulas derivaron en Sección 8.3. En el caso
de un identificador de campo la computación de dirección está actuada por el compilador. La dirección es
la suma de la dirección de la variable y el offset del campo.
Campo de PROCEDIMIENTO(VAR x: Elemento; y:
Objeto); (* x := x.y *) EMPIEZA INC(x.Un, y.val); x.Tipo
:= y.Tipo
Campo de FIN;
En el caso de un indexed variable, el código es emitted según la fórmula
adr(un[k]) = adr(un) + k * medida(T)
Aquí un denota la variable de variedad, k el índice, y T el tipo de los elementos de la variedad.
Computación de índice requiere dos instrucciones; el scaled el índice está añadido al componente de
registro de la dirección. Dejado el índice ser almacenado en registro R.j, y dejar la dirección de variedad
ser almacenado en registro R.i.
MULI j, j, medida(T)
AÑADE j, i, j
Índice de procedimiento emits el encima código de índice, controles si el indexed la variable es de hecho
una variedad, y computa la dirección del elemento directamente si el índice es una constante .
Índice de PROCEDIMIENTO(VAR x, y: Elemento); (*
x := x[y] *) VAR z: Elemento;
EMPIEZA
SI y.Tipo # intType ENTONCES Mark("índice no entero")
FIN ; SI y.Modo = Const ENTONCES
SI (y.Un < 0) O (y.Un >= x.Tipo.len) ENTONCES Mark("índice fuera de gama") FIN ;
x.Un := x.Un + y.Un *
x.Tipo.Base.Medida MÁS
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(MULI, y.r, y.r,

82
x.Tipo.Base.Medida);
Puesto(AÑADE, y.r, x.r, y.r); EXCL(regs, x.r);
x.r := y.r FIN;
x.Tipo :=
x.Tipo.Índice de FIN
de la base;

83
Ahora podemos mostrar el código que resulta del fragmento de programa siguiente que contiene un- y
dos- variedades dimensionales:
PROCEDIMIENTO P1;
VAR i, j: ENTERO; adr -4, -8
Un: VARIEDAD 4 DE ENTERO; adr -24
b: VARIEDAD 3 DE VARIEDAD 5 DE ENTERO; adr -84
EMPIEZA i := un[j]; i := un[2]; i := un[i+j]; i := b[i][j]; i := b[2][4]; i
:= un[un[i]] FIN P1.
LDW 0, base, -8 i := Un[j]
MULI 0, 0, 4
AÑAD 0, base, 0
E
LDW 1, 0, -24 Un
STW 1, base, -4 i
LDW 0, base, -16 i := Un[2]
STW 0, base, -4
LDW 0, base, -4 i :=
Un[i+j];
LDW 1, base, -8
AÑAD 0, 0, 1 i+j
E
MULI 0, 0, 4
AÑAD 0, base, 0
E
LDW 1, 0, -24
STW 1, base, -4 i
LDW 1, base, -4 i := b[i][j]
MULI 0, 0, 20
AÑAD 0, base, 0
E
LDW 1, base, -8 j
MULI 1, 1, 4
AÑAD 1, 0, 1
E
LDW 0, 1, -84 b
STW 0, base, -4 i
LDW 0, base, -28 i := b[2][4]
STW 0, base, -4
LDW 0, base, -4 i :=
Un[un[i]]
MULI 0, 0, 4
AÑAD 0, base, 0
E
LDW 1, 0, -24
MULI 1, 1, 4
AÑAD 1, base, 1
E
LDW 0, 1, -24
STW 0, base, -4
Nota que la validez del índice puede ser comprobada sólo si el índice es una constante , aquello es, es de
valor sabido. Otherwise El índice no puede ser comprobado hasta que tiempo corrido. A pesar de que la
prueba es naturalmente redundanda en programas correctos, su omisión no es recomendada. Para
safeguard la abstracción de la variedad estructura la prueba es completamente justificó. Aun así, el
diseñador de compilador tendría que intentar para conseguir eficacia suma. La prueba toma la forma de la
declaración

84
SI (k < 0) O (k >= n) ENTONCES PARAR FIN
Dónde k es el índice y n la longitud de la variedad. Para nuestro ordenador virtual, nosotros
sencillamente postulado una instrucción correspondiente. En otros casos una secuencia de instrucción
adecuada tiene que ser encontrada, por el cual el siguiendo tiene que ser considerado: desde entonces en
Oberon la variedad más baja ató es siempre 0, unos sufijos de comparación solos si el valor de índice está
considerado como un unsigned entero. Esto es tan porque valores negativos en

85
Complementa la representación aparece con una señal mordió valor 1, cediendo un unsigned el valor más
grande que el más alto (firmado) valor de entero.
Índice de procedimiento está extendido consiguientemente, generando un CHK instrucción, el cual
resulta en terminación de la computación en caso de un índice nulo.
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(CHKI, y.r, 0,
x.Tipo.Base.len);
Puesto(MULI, y.r, y.r, x.Tipo.Base.Medida);
Finalmente, un ejemplo de un programa está mostrado con nested estructuras de dato. Demuestra
claramente cómo el tratamiento especial de las constantes en selectors simplifica el código para
computaciones de dirección. Comparar el código que resulta para variables indexed por expresiones con
aquellos indexed por constantes. CHK Las instrucciones han sido omitidas por el bien de brevity..
PROCEDIMIENTO P2;
TIPO R0 = RÉCORD x, y: FIN de ENTERO ;
R1 = RÉCORD u: ENTERO; offset 0
v: VARIEDAD 4 DE R0; offset 4
w: Offset de ENTERO 36
FIN ;
VAR i, j, k: ENTERO; adr -4, -8, -12
s: VARIEDAD 2 DE R1; adr -92
EMPIEZA k := s[i].u; k := s[1].w;
k := s[i].v[j].x; k := s[1].v[2].y;
s[0].v[i].y := k
FIN P2.
LDW 0, base, -4 i
MULI 0, 0, 40
AÑADE 0, base, 0
LDW 1, 0, -92 s[i].u
STW 1, base, -12 k
LDW 0, base, -16 s[1].w
STW 0, base, -12
LDW 0, base, -4 i
MULI 0, 0, 40
AÑADE 0, base, 0
LDW 1, base, -8 j
MULI 1, 1, 8
AÑADE 1, 0, 1
LDW 0, 1, -88 s[i].v[j].x
STW 0, base, -12
LDW 0, base, -28 s[1].v[2].y
STW 0, base, -12
LDW 0, base, -4 i
MULI 0, 0, 8
AÑADE 0, base, 0
LDW 1, base, -12 k
STW 1, 0, -84 s[0].v[i].y
Un deseo de mantener objetivo-partes dependientes del compilador separado de objetivo-las partes
independientes sugiere que el código que genera las declaraciones tendrían que ser recogidas en la forma
de procedimientos en un módulo separado. Llamaremos este módulo OSG y presentar su interfaz.
Contiene muchos de los procedimientos de generador encontraron tan lejos. El otros serán explicados en
Capítulos 11 y 12.

86
DEFINICIÓN OSG;
IMPORTACIÓN OSS, Textos, Fuentes, Exhibición;
CONST Cabeza = 0; Var = 1; Par = 2; Const = 3; Fld = 4; Typ = 5; Proc = 6; SProc = 7;
Booleano = 0; Entero = 1; Variedad = 2; Récord = 3;
PUNTERO de Objeto = del TIPO A ObjDesc;
ObjDesc = REGISTRO
Clase, lev: ENTERO;
luego, dsc: Objeto;
tipo: Tipo;
Nombre:
OSS.Ident; val:
LONGINT;
FIN ;
PUNTERO = de tipo A TypeDesc;
TypeDesc = REGISTRO
Forma:
ENTERO;
campos: Objeto;
base: Tipo;
Medida, len: ENTERO;
FIN ;
REGISTRO = de elemento
Modo, lev: ENTERO;
tipo: Tipo;
Un: LONGINT;
FIN ;
VAR boolType, intType: Tipo;
curlev, pc: ENTERO;
PROCEDIMIENTO FixLink (L:
LONGINT); PROCEDIMIENTO
IncLevel (n: ENTERO);
PROCEDIMIENTO MakeConstItem (VAR x: Elemento; typ: Tipo; val:
LONGINT); PROCEDIMIENTO MakeItem (VAR x: Elemento; y:
Objeto);
Campo de PROCEDIMIENTO (VAR x: Elemento; y:
Objeto); Índice de PROCEDIMIENTO (VAR x, y:
Elemento); PROCEDIMIENTO Op1 (op: ENTERO;
VAR x: Elemento); PROCEDIMIENTO Op2 (op:
ENTERO; VAR x, y: Elemento);
Relación de PROCEDIMIENTO (op: ENTERO; VAR x,
y: Elemento); Tienda de PROCEDIMIENTO (VAR x, y:
Elemento);
Parámetro de PROCEDIMIENTO (VAR x: Elemento; ftyp: Tipo;
clase: ENTERO); PROCEDIMIENTO CJump (VAR x: Elemento);
PROCEDIMIENTO BJump (L: LONGINT);
PROCEDIMIENTO FJump (VAR L:
LONGINT); Llamada de
PROCEDIMIENTO (VAR x: Elemento);
PROCEDIMIENTO IOCall (VAR x, y:
Elemento); Encabezamiento de
PROCEDIMIENTO (medida: LONGINT);
el PROCEDIMIENTO Introduce (medida:
LONGINT); Regreso de
PROCEDIMIENTO (medida: LONGINT);

87
el PROCEDIMIENTO Abierto;
El PROCEDIMIENTO Cercano (VAR S: Textos.Escáner; globals:
LONGINT); FIN OSG.

88
10.4. Ejercicios
10.1. Mejorar el Oberon-0 compilador de tal manera que multiplicación e instrucciones de división
están reemplazadas por máscara y cambio eficaces instrucciones, si un factor o el divisor es un
poder de 2..
10.2. Mejorar el Oberon-0 compilador de tal manera que el código de acceso para elementos de
variedad incluye una prueba que verifica que las mentiras de valor del índice dentro de la gama
dada por la variedad declaration.
10.3. Tenido la declaración de asignación en Oberon sido definido en una forma donde la expresión
asignada ocurre a la izquierda de la variable, aquello es por ejemplo por la forma e =: v,
recopilación de asignaciones ser más sencillos en cualquier manera?
10.4. Considerar la introducción de una asignación múltiple en Oberon de la forma e =: x0 =: x1 =: ... =:
xn . Lo implementa. Hace la definición de su semantics presente cualesquier problemas?
10.5. Cambio la definición de expresiones en Oberon a aquello de Algol 60 (ve Ejercicio 2.1)
e implementar los cambios. Hablar las ventajas y desventajas de las dos formas.

89
11. Condicional y Repitió Declaraciones y Expresiones Booleanas
11.1. Comparaciones y saltos
Condicional y repitió las declaraciones están implementadas con la ayuda de instrucciones de salto, también llamó
Instrucciones de rama. Como primer ejemplo, dejado nos considerar la forma más sencilla de
declaración condicional: SI x = y ENTONCES StatSequence FIN
Su mapeo a una secuencia de instrucciones es sincera:
SI x = y EQL x, y
BF L
ENTONCES StatSequence
código(StatSequence)
FIN L ...
Nuestras consideraciones están basadas una vez más en un stack arquitectura. Instrucción EQL prueba
los dos operandos para igualdad y les reemplaza encima el stack por el resultado Booleano. La
instrucción de rama subsiguiente BF (rama si FALSO) ventajas a la etiqueta de destino L si este resultado
es FALSO, y saca él del stack. De modo parecido a EQL, instrucciones de rama condicional son
postulated para las relaciones 9, <, 3, #, y .. >
Desafortunadamente, aun así, tal compilador_los ordenadores amistosos son difícilmente extendidos.
Bastante más común es ordenadores cuyas instrucciones de rama dependen de la comparación de un
valor de registro con 0. Les denotamos tan BNE (rama si no igual), BLT (rama si menos de), BGE (rama
si más grande o igual), BLE (rama si menos o igual), y BGT (rama si más grande que). La secuencia de
código que corresponde al encima el ejemplo es
SI x = y código (Ri := x - y)
BNE Ri, L
ENTONCES StatSequence
código(StatSequence)
FIN L ...
El uso de sustracción (x - y  0 estando para x  y) tiene un implícito pitfall: la sustracción puede dirigir
a desbordamiento, resultando en cualquier terminación de programa o un resultado incorrecto. Por lo
tanto una instrucción de comparación concreta CMP está utilizado en sitio de sustracción, el cual evita
desbordamiento, pero correctamente indica si la diferencia es cualquiera cero, positivo o negativo. El
resultado es típicamente almacenado en un registro especial código de condición llamada, constando de
los dos bits denotaron por N y Z, indicando si la diferencia es negativa o cero respectivamente. Todo
instrucciones de rama condicional entonces implícitamente refieren a este registro cuando argumento.
SI x = y CMP x, y
BNE L
ENTONCES StatSequence
código(StatSequence)
FIN L ...

11.2. Condicional y repitió declaraciones


La cuestión de cómo un valor Booleano es para ser representado por un elemento ahora surge. En el caso
de stack arquitectura la respuesta es fácil: desde el resultado de unas mentiras de comparación en el stack
como cualquier otro resultado, ningún modo de elemento especial es necesario. Un CMP instrucción, aun
así, requiere más allá pensó. Primero restringiremos nuestra consideración a los casos sencillos de
comparaciones puras sin operadores Booleanos más lejanos.
En el caso de una arquitectura con un CMP esquema, es necesario de indicar en el elemento resultante
qué registro aguanta la diferencia computada, y qué relación está representada por la comparación. Para
el último un atributo nuevo está requerido; llamamos el modo nuevo Cond y su atributo nuevo (campo
récord) c. El mapeo de relaciones a valores de c está definido por

90
= 0 # 1
< 2 >= 3
<= 4 > 5

91
El construir conteniendo las comparaciones es la expresión . Su sintaxis es
Expresión = SimpleExpression [("=" | "#" | "<" | "<=" | ">" | ">=") SimpleExpression].
El correspondiente, extendido parser el procedimiento es
fácilmente derivó: expresión de
PROCEDIMIENTO(VAR x: Elemento);
VAR y: Elemento; op: ENTERO;
EMPIEZA SimpleExpression(x);
SI (sym >= eql) & (sym <= gtr) ENTONCES
op := sym; Consigue(sym); SimpleExpression(y);
Relación(op, x, y) FIN
Expresión de FIN;
Relación de PROCEDIMIENTO(op: ENTERO; VAR x,
y: Elemento); EMPIEZA
SI (x.Tipo.Entero # de forma) O (y.Tipo.Entero # de forma) ENTONCES
Mark("tipo malo") MÁS
SI (y.Modo = Const) & (y.Un = 0) ENTONCES carga(x) MÁS PutOp(CMP, x, y) FIN ;
x.c := op - eql; EXCL(regs, x.r); EXCL(regs, y.r)
FIN ;
x.Modo := Cond; x.Tipo :=
boolType Relación de FIN;
El esquema de código presentado a principios de este capítulo cede el correspondiente parser programa
para manejar el SI construye en StatSequence:
ELSIF sym = Si ENTONCES
Consigue(sym); expresión(x); CJump(x);
SI sym = entonces ENTONCES Conseguir(sym) MÁS
Mark("ENTONCES?") FIN ; StatSequence; Fixup(x.Un)
SI sym = el fin ENTONCES Consigue(sym) MÁS Mark("FIN?") FIN
Procedimiento CJump(x) genera la instrucción de rama necesaria según su parámetro x.c De tal manera
que el salto está tomado si la condición especificada no es satisfecha.
Aquí una dificultad deviene aparente cuál es inherente en todos compiladores de pase solo. La ubicación
de destino de ramas es todavía desconocido cuándo la instrucción es para ser emitted. Este problema está
solucionado por añadir la ubicación de la instrucción de rama como un atributo al elemento generó. Este
atributo está utilizado más tarde cuándo el destino del salto deviene sabido para completo la rama con su
dirección cierta. Esto se apellida un fixup. La solución sencilla es posible sólo si el código está
depositado en una variedad global donde los elementos son accesibles en cualquier tiempo. No es
aplicable si el emitted el código es inmediatamente almacenado encima disco. Para representar la
dirección de la instrucción de rama incompleta utilizamos el campo de elemento un.
PROCEDIMIENTO CJump(VAR x:
Elemento); EMPIEZA
SI x.Tipo.La forma = Booleana ENTONCES
Puesto(BEQ + negated(x.c), x.r, 0, 0); EXCL(regs, x.r); x.Un :=
pc-1 MÁS OSS.Mark("Booleano?"); x.Un := pc
FIN
FIN CJump;
PROCEDIMIENTO negated(cond: LONGINT):
LONGINT; EMPIEZA
SI EXTRAÑO(cond) ENTONCES REGRESO cond-1 MÁS REGRESO cond+1 FIN
FIN negated;
PROCEDIMIENTO Fixup(L: LONGINT);
EMPIEZA código[L] := código[L] DIV 10000H * 10000H + pc - L
FIN Fixup;

92
Procedimiento CJump emite un mensaje de error si x no es de escribir BOOLEANO. Nota que uso de
instrucciones de la rama dirige relativo a la ubicación de la instrucción (PC-relativo); por lo tanto el
valor pc-L está utilizado.
Finalmente, tenemos que mostrar declaraciones qué condicionales en su forma general están
compiladas; la sintaxis es "SI" expresión "ENTONCES" StatSequence
{"ELSIF" Expresión "ENTONCES" StatSequence}
["MÁS" StatSequence]
"FIN"
Y el patrón de código correspondiente es
SI expresión ENTONCES
código(expresió
n) Bcond L0
StatSequence
Código(StatSe
quence) BR L
ELSIF Expresión ENTONCES L0 código(expresión)
Bcond L1
StatSequence
Código(StatSe
quence) BR L
ELSIF Expresión ENTONCES L1 código(expresión)
Bcond L2
StatSequence
Código(StatSe
quence) BR L
…..
MÁS StatSequence Ln código(StatSequence)
FIN L ...
De qué el parser las declaraciones pueden ser derivadas tan parte de procedimiento StatSeqence. A pesar
de que un número arbitrario de ELSIF construye puede ocurrir y así también un número arbitrario de
destinos de salto L1, L2,
... Puede resultar, un elemento solo variable x sufijos. Está asignado un valor nuevo para cada ELSIF caso.
ELSIF sym = Si ENTONCES
Consigue(sym); expresión(x); CJump(x);
SI sym = entonces ENTONCES Conseguir(sym) MÁS
Mark("ENTONCES ?") FIN ; StatSequence; L := 0;
MIENTRAS sym = elsif HACER
Consigue(sym); FJump(L); Fixup(x.Un); expresión(x); CJump(x);
SI sym = entonces ENTONCES Conseguir(sym) MÁS
Mark("ENTONCES ?") FIN ; StatSequence
FIN ;
SI sym = más ENTONCES Conseguir(sym); FJump(L); Fixup(x.Un); StatSequence MÁS
Fixup(x.Un) FIN ; FixLink(L);
SI sym = el fin ENTONCES Consigue(sym) MÁS Mark("FIN ?") FIN
...
PROCEDIMIENTO FJump(VAR L:
LONGINT); EMPIEZA Puesto(BEQ, 0, 0,
L); L := pc-1
FIN FJump
Aun así, una situación nueva surge en qué no sólo una rama sola refiere a la etiqueta de destino L al final,
pero un conjunto entero, concretamente cuando muchos tan hay SI y ELSIF ramas en la declaración. El
problema es elegantemente solucionado por almacenar los enlaces de la lista de instrucciones de rama
incompleta en estas instrucciones ellos, y para dejar variable L representa la raíz de esta lista. Los enlaces

93
están establecidos por el parámetro de la operación Puesta llamada en FJump. Basta para reemplazar
procedimiento Fixup por FixLink, en qué la lista entera de instrucciones para ser fijadas arriba es
traversed. Es esencial que variable L está declarado local al parser procedimiento StatSequence, porque
las declaraciones pueden ser nested, el cual dirige a activación recursiva. En este caso, varios casos de
variables L convive representar listas diferentes.

94
PROCEDIMIENTO FixLink(L:
LONGINT); VAR L1: LONGINT;
EMPIEZA
MIENTRAS L # 0
L1 := código[L] MOD 10000H; Fixup(L); L :=
L1 FIN
FIN FixLink;
La recopilación de la declaración de RATO es muy similar a aquello del sencillo SI declaración. Además
del salto de delantero condicional, un incondicional backward el salto es necesario. La sintaxis y el
patrón de código correspondiente son:
MIENTRAS expresión L0
código(expresió
n) Bcond L1
StatSequence Código(StatSequence)
FIN BR L0
L1 ...
De este derivamos el correspondientes, extendidos parser
procedimiento: ELSIF sym = mientras ENTONCES
Consigue(sym); L := pc; expresión(x); CJump(x);
SI sym = ENTONCES Consigue(sym) MÁS
Mark("HACER ?") FIN ; StatSequence; BJump(L);
Fixup(x.Un);
SI sym = el fin ENTONCES Consigue(sym) MÁS Mark("FIN ?") FIN
PROCEDIMIENTO BJump(L:
LONGINT); EMPIEZA Puesto(BEQ,
0, 0, L-pc)
FIN BJump;
A summarize, muestramos dos declaraciones que utilizan variables i y j , junto con el código
generado: SI i < j ENTONCES i := 0 ELSIF i = j ENTONCES i := 1 MÁS i := 2 FIN ;
MIENTRAS i > 0 i := i - 1 FIN
4 LDW 0, base, -4 i
8 LDW 1, base, -8 j
12 CMP 0, 0, 1
16 BGE 3 (Salta 3 instrucciones a 28)
20 STW 0, base, -4 i := 0
24 BEQ 10 (Salta 10 instrucciones a 64)
28 LDW 0, base -4
32 LDW 1, base, -8
36 CMP 0, 0, 1
40 BNE 4 (Salta 4 instrucciones a 56)
44 MOVI 0, 0, 1
48 STW 0, base, -4 i := 1
52 BEQ 3 (Salta 3 instrucciones a 64)
56 MOVI 0, 0, 2
60 STW 0, base, -4 i := 2
64 LDW 0, base, -4
68 BLE 5 (Salta 5 instrucciones a 88)
72 LDW 0, base, -4
76 SUBI 0, 0, 1
80 STW 0, base, -4 i := i - 1
84 BEQ -5 (El salto respalda encima 5
instrucciones a 64)
88 ...

95
11.3. Operaciones booleanas
Naturalmente está tentando para tratar expresiones Booleanas en la misma manera cuando expresiones
de aritmética. Desafortunadamente, aun así, esto en muchos casos dirige no sólo a inefficient, pero
incluso a código incorrecto.
Las mentiras de razón en la definición de operadores Booleanos, concretamente
pOq = si p entonces CIERTO
más q p & q = si p entonces q
más FALSO
Esta definición especifica que el segundo operando q necesita no ser evaluado si el resultado es
singularmente dado por el valor del primer operando p. Definiciones de lenguaje de programación
incluso van un paso más allá por especificar que en estos casos el segundo operando no tiene que ser
evaluado. Esta regla es postulated para que el segundo operando puede quedar undefined sin causar
ejecución de programa para ser rescindido. Un ejemplo frecuente que implica un puntero x es
(x # CERO) & (x^.Medida > 4)
Expresiones booleanas con los operadores Booleanos por tanto suponen la forma de declaraciones
condicionales (más precisamente, expresiones condicionales), y es apropiado de utilizar las mismas
técnicas de recopilación en cuanto a declaraciones condicionales. Las expresiones booleanas y las
declaraciones condicionales fusionan, cuando los espectáculos de ejemplo siguientes. La declaración
SI (x <= y) & (y < z) ENTONCES S FIN
Está compilado en la misma manera como su
formulación equivalente SI x <= y ENTONCES SI
< y z ENTONCES S FIN de FIN
Con la intención de derivar un patrón de código adecuado, dejado nos primero considerar la expresión
siguiente que contiene tres relaciones conectaron por & el operador. Nosotros postulado el patrón de
código deseado cuando mostrado abajo, considerando sólo el patrón a la izquierda de momento. Un, b, ...
, f denota valores numéricos. Las etiquetas T y F denotar los destinos para los casos cuándo la expresión
es cierta o falsa, respectivamente.
(Un < b) & (c < d) & (e < f)
CMP Un, b CMP un, b
BGE F BGE F
CMP c, d CMP c, d
BGE F BGE F
CMP e, f CMP e, f
BGE F BLT T
(T) (F)
Cuando los espectáculos de patrón de mano izquierdos, una instrucción de rama condicional es emitted
para cada & operador. El salto está ejecutado si la condición de preceder no es satisfecha (F-salto). Estos
resultados en las instrucciones BGE para representar la relación, < BNE para la = relación, y tan encima.
Si consideramos el problema de generar el código requerido, podemos ver que el parser plazo de
procedimiento, cuando es sabido para procesar plazos de aritmética, tiene que ser extendido ligeramente.
En particular, una instrucción de rama tiene que ser emitted antes del segundo operando está procesado,
mientras que al final la dirección de esta instrucción tiene que ser fijada arriba. La tarea anterior está
actuada por procedimiento Op1, el último por Op2.
Plazo de PROCEDIMIENTO(VAR
x: Elemento); VAR y: Elemento;
op: ENTERO;
EMPIEZA factor(x);
MIENTRAS (sym >= tiempo) & (sym <= y)
op := sym; Consigue(sym);
SI op = y ENTONCES Op1(op, x)
96
FIN ; factor(y); Op2(op, x, y)
FIN
Plazo de FIN;

97
PROCEDIMIENTO Op1(op: ENTERO; VAR x: Elemento); (* x :=
op x *) VAR t: LONGINT;
EMPIEZA
SI op = minus
ENTONCES ... ELSIF
op = Y ENTONCES
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
PutBR(BEQ + negated(x.c), x.Un); EXCL(regs, x.r); x.Un :=
pc-1 FIN
FIN Op1;
Si el primer factor Booleano está representado por elemento x en modo Cond, entonces en la posición
presente x es CIERTO y las instrucciones para la evaluación del segundo operando tiene que seguir. Aun
así, si no es en modo Cond, tenga que ser transferido a este modo. Esta tarea está ejecutada por
procedimiento loadBool. Suponemos que el valor FALSO está representado por 0. El valor de atributo c
= 1 por lo tanto causa la instrucción BEQ para devenir activo, si x equals 0.
PROCEDIMIENTO loadBool(VAR x:
Elemento); EMPIEZA
SI x.Tipo.La forma # Booleana ENTONCES
OSS.Mark("Booleano?") FIN ; carga(x); x.Modo := Cond; x.c
:= 1
FIN loadBool;
El O el operador está tratado análogamente, con la diferencia que los saltos están tomados si sus
condiciones respectivas están satisfechas (T-salto). Las instrucciones están listadas en la lista dual con
enlaces en el campo de elemento b. El postcondition de una secuencia de plazos conectó con O los
operadores es FALSOS. Considera otra vez el patrón de código izquierdo sólo:
(Un < b) O (c < d) O (e < f)
CMP Un, b CMP un, b
BLT T BLT T
CMP c, d CMP c, d
BLT T BLT T
CMP e, f CMP e, f
BLT T BGE F
(F) (T)
Luego, consideramos la implementación de negación. Aquí resulta que bajo el esquema no presentó
ninguna instrucción necesitar ser emitted cualquier cosa. Sólo el valor de condición representado por el
campo de elemento c tiene que ser negated, y las listas de F-saltos y T-los saltos necesitan ser
intercambiados. El resultado de negación está mostrado en los patrones de código en Figuras 11.1 y 11.2
en el lado derecho para ambas expresiones con y U & operadores. El afectó los procedimientos están
extendidos tan mostrados abajo:
PROCEDIMIENTO SimpleExpression(VAR x:
Elemento); VAR y: Elemento; op: ENTERO;
EMPIEZA plazo(x);
MIENTRAS (sym >= plus) & (sym <= o)
op := sym; Consigue(sym);
SI op = o ENTONCES Op1(op, x)
FIN ; plazo(y); Op2(op, x, y)
FIN
FIN SimpleExpression;
PROCEDIMIENTO Op1(op: ENTERO; VAR x: Elemento); (* x
:= op x *) VAR t: LONGINT;
EMPIEZA
SI op = minus
ENTONCES ... ELSIF
op = No ENTONCES
98
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
x.c := negated(x.c); t := x.Un; x.Un := x.b; x.b := t

99
ELSIF op = Y ENTONCES
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
PutBR(BEQ + negated(x.c), x.Un); EXCL(regs,
x.r);
x.a := pc-1; FixLink(x.b); x.b := 0
ELSIF op = o ENTONCES.
SI x.Modo # Cond ENTONCES loadBool(x)
FIN ; PutBR(BEQ + x.c, x.b); EXCL(regs,
x.r);
x.b := pc-1; FixLink(x.Un); x.Un
:= 0 FIN
FIN Op1;
Cuándo compilando expresiones con y U & operadores, el cuidado tiene que ser tomado que delante de
cada & condición P, y delante de cada O condición ~P, control de mosto. Las listas respectivas de
instrucciones de salto tienen que ser traversed (el T-lista para &, el F-lista para O), y el designó las
instrucciones tienen que ser fijadas arriba apropiadamente. Esto ocurre a través de llamadas de
procedimiento de FixLink en Op1. Cuando ejemplos, consideramos las expresiones
(Un < b) & (c < d)) O ((e < f) & (g < h)
(Un < b) O (c < d)) & ((e < f) O (g < h)
y los códigos resultantes:
CMP Un, b CMP Un,
b
BGE F0 BLT T0
CMP c, d CMP c, d
BLT T BGE F
F0 CMP e, f T0 CMP e, f
BGE F BLT T
CMP G, h CMP G, h
BGE F BGE F
(T) (T)
También pueda pasar que una lista de un subordinar la expresión puede fusionar con la lista de su
expresión de contener (ve F-enlace en el patrón para Q en Figura 11.3). Esta fusión está cumplida por el
procedimiento fusionado(un, b), cediendo como su valor la concatenación de sus listas de argumento. se
apellida de dentro procedimiento Op2.
PROCEDIMIENTO Op2(op: ENTERO; VAR x, y: Elemento); (* x := x
op y *) EMPIEZA
SI (x.Tipo.Entero = de forma) & (y.Tipo.Entero = de forma) ENTONCES
...
ELSIF (x.Tipo.La forma = Booleana) & (y.Tipo.La forma =
Booleana) ENTONCES SI y.Modo # Cond ENTONCES
loadBool(y) FIN ;
SI op = o ENTONCES x.Un := y.Un; x.b := Fusionado(y.b, x.b); x.c
:= y.c ELSIF op = Y ENTONCES x.Un := fusionado(y.Un, x.Un);
x.b := y.b; x.c := y.c FIN
MÁS ...
FIN ;
FIN Op2;

11.4. Asignaciones a variables Booleanas


Recopilación de una asignación a una variable Booleana q es ciertamente más complicado que
generalmente esperó. La razón es el modo de elemento Cond, los cuales tienen que ser convertidos a un
assignable valor 0 o 1. Esto está conseguido por el patrón de código siguiente:
T ADDI 0, 0, 1

100
BEQ L
F ADDI 0, 0, 0

101
L STW 0, q
Esto causa la asignación sencilla q := x < y para aparecer como disappointingly secuencia de código
largo. Tenemos que, aun así, ser conscientes que variables Booleanas (generalmente llamó banderas)
ocurre (tendría que ocurrir) infrequently, a pesar de que la idea del tipo Booleano es de hecho
fundamental. Es inapropiado a strive para optimal implementación de raramente ocurriendo construye en
el precio de un proceso intrincado. Aun así, es esencial que los casos frecuentes están manejados
optimally.
No obstante, manejamos asignaciones a un elemento Booleano no en el Cond modo como caso especial,
concretamente como la asignación convencional que evita la implicación de saltos. De ahí, la asignación
p := q resultados en la secuencia de código esperada
LDW 1, 0, q
STW 1, 0, p
Como consecuencia, la Tienda de procedimiento afectada resulta
como sigue: Tienda de PROCEDIMIENTO(VAR x, y:
Elemento); (* x := y *)
EMPIEZA ...
SI y.Modo = Cond ENTONCES
FixLink(y.b); GetReg(y.r); Puesto(MOVI, y.r, 0, 1); PutBR(BEQ, 2);
FixLink(y.Un); Puesto(MOVI,
y.r, 0, 0) ELSIF y.Modo # Reg
ENTONCES carga(y) FIN ;
SI x.Modo = Var ENTONCES Puesto(STW,
y.r, x.r, x.Un) MÁS Mark("asignación ilegal")
FIN ;
EXCL(regs, x.r); EXCL(regs, y.r)
Tienda de FIN;

11.5. Ejercicios
11.1. Mutar la lengua Oberon-0 a una variante Oberon-D por redefinir el condicional y la declaración
repetida como sigue:
Declaración = ...
"SI" guardedStatements {"|" guardedStatements} "FI" | ""
guardedStatements {"|" guardedStatements} "OD" .
guardedStatements = Condición "." Declaración {";" declaración} .
La forma nueva de declaración
SI B0 . S0 | B1 . S1 | ... | Bn . Sn FI
Significará que de todas las condiciones (expresiones Booleanas) Bi aquello es cierto, uno está
seleccionado arbitrariamente y su secuencia de declaración correspondiente Si está ejecutado. Si ninguno
es cierto, ejecución de programa está abortada. Cualquier secuencia de declaración Si será ejecutado sólo
cuándo el corrresponding condición Bi es cierto. Bi Es por tanto dicho para ser el guardia de Si .
La declaración
HACER B0 . S0 | B1 . S1 | ... | Bn . Sn OD
Significará que que mientras cualquiera de las condiciones Bi es cierto, uno de ellos está escogido
arbitrariamente, y su secuencia de declaración correspondiente Si está ejecutado. El proceso rescinde
apenas todo Bi es falso. Aquí también, el Bi función como guardias. El -OD construye es un repetitivo,
nondeterministic construye. Ajustar el compilador consiguientemente.
11.2. Extiende Oberon-0 y su compilador por un PARA
declaración: declaración = [asignación |
ProcedureCall |
102
IfStatement | WhileStatement | ForStatement.

103
ForStatement = "PARA" identificador ":=" expresión "A expresión" ["POR" expresión]
"" StatementSequence "FIN" .
La expresión que precede el símbolo A especifica el valor de empezar, el después el valor de final de la
variable de control denotada por el identificador. La expresión después de que POR indica el increment.
Si perdiendo, dejado 1 ser su default valor.
11.3. Considerar la implementación de la declaración de caso de Oberon (ve Apéndice Un.2). Su
propiedad esencial es que utiliza una mesa de direcciones de salto para los varios casos, y un indexed
instrucción de salto.

104
12. Procedimientos y el Concepto de Locality.
12.1. Organización_de tiempo corrido de la tienda
Procedimientos, los cuales son también sabidos como subrutinas, es quizás la herramienta más
importante para estructurar programas. Debido a su frecuencia de ocurrencia, es obligatorio que su
implementación es eficaz. La implementación está basada en la instrucción de rama qué salva el valor de
PC actual y así el punto de regresar después de que terminación del procedimiento, cuándo este valor es
reloaded al registro de PC.
La cuestión cuando a dónde la dirección de regreso tendría que ser salvada surge inmediatamente. En
muchos ordenadores está depositado en un registro, y hemos adoptado esta solución en nuestro RISC.
Esto garantiza la eficacia suma, porque ningún acceso de memoria adicional está implicado. Pero
teniendo que salvar el valor del registro a la memoria antes de la llamada de procedimiento próxima es
inevitable, porque otherwise la dirección de regreso vieja sería overwritten. Así la dirección de regreso de
la primera llamada sería perdida. En la implementación de un compilador este valor de registro del
enlace tiene que ser salvado a principios de cada llamada de procedimiento.
Para almacenar el enlace, un stack es la solución obvia. La razón es que activaciones de procedimiento
ocurren en un nested moda; los procedimientos rescinden en el orden inverso de sus llamadas. La tienda
para las direcciones de regreso por tanto tienen que operar según el primer-en último-fuera principio.
Estos resultados en el código siguiente, fijo secuencias al principio y fin de cada procedimiento. se
apellidan el prólogo y el epílogo del procedimiento. Aquí utilizaremos R13 para el stack puntero SP y
R14 cuando registro de enlace LNK. R15 está definido como el PC de contador del programa.
Llamada BSR P Rama a subrutina
Prólogo P PSH LNK, SP, 4 Enlace de empujón
Epílogo POP LNK, SP, 4 Enlace de pop
RET LNK Salto de regreso
Este patrón de código es válido bajo la suposición que el BSR la instrucción deposita la dirección de
regreso en R14. Nota que esto está especificado como característica de hardware (Capítulo 9), mientras
que el uso de R13 cuando stack el puntero es meramente una convención de software determinado por el
diseño de compilador o por el sistema operativo subyacente. Siempre que el sistema está empezado, R14
tiene que ser inicializado para señalar a una área de la memoria reservada para el stack.
Algol 60 introdujo el concepto muy fundamental de variables locales. Implique que cada identificador
declaró tenido una gama limitada de visibilidad y validez. En Pascal (y también en Oberon) esta gama es
el cuerpo de procedimiento . En plazos concretos, las variables pueden ser declaradas locales a un
procedimiento tal que son visibles y válidos dentro de este procedimiento sólo. La consecuencia
pretendida es que a la entrada a la memoria de procedimiento está destinada automáticamente para estas
variables locales, y está liberado a la terminación del procedimiento. Variables locales de los
procedimientos diferentes por tanto pueden compartir la misma área de almacenamiento, pero nunca
simultáneamente, naturalmente..
A primera vista este esquema parece para causar una pérdida segura de eficacia al mecanismo de llamada
del procedimiento. Afortunadamente, aun así, esto necesita no ser tan, porque los bloques de
almacenamiento para los conjuntos de variables locales pueden ser destinados, gusta direcciones de
regreso, según el stack principio. La dirección de regreso puede de hecho también ser considerado como
(escondido) variable local, y es sólo natural de utilizar el mismo stack para variables y direcciones de
regreso. Los bloques de almacenamiento se apellidan registros de activación del procedimiento o marcos
de activación. Liberación de un bloque a terminación de procedimiento está conseguido por
sencillamente resetting el stack puntero a su valor antes de la llamada de procedimiento. De ahí, la
asignación y la liberación de almacenamiento local es optimally eficaces.
Las direcciones de variables locales generaron por el compilador es siempre relativo a la dirección base
del marco de activación respectivo. Desde entonces en programas la mayoría de variables son locales, su
dirigiendo también tiene que ser altamente eficaz. Esto está conseguido por reservar un registro para
aguantar la dirección base, y para hacer uso del hecho que la dirección eficaz es la suma de un valor de
105
registro y el campo de dirección de la instrucción (pariente de registro que dirige modo). El registro
reservado se apellida el puntero de marco (FP). Estas consideraciones

106
Está tenido en cuenta por el prólogo siguiente y epílogo, donde R12 supone la función del puntero de
marco:
Prólogo P PSH LNK, SP, 4 Empujón
PSH FP, SP, 4 de enlace
MOV FP, 0, SP del
empujón
FP
FP := SP
SUBI SP, SP, n SP := SP-n (n = Medida de
marco)
Epilog MOV SP, 0, FP SP := FP
POP FP, SP, 4 Pop FP
POP LNK, SP, 4 Enlace de pop
RET LNK Salto de regreso
La activación enmarca resultar de llamadas de procedimiento consecutivo están enlazadas por una lista
de sus direcciones base. La lista se apellida el enlace dinámico, porque denota la secuencia dinámica de
activaciones de procedimiento. Sus mentiras de raíz en el registro de puntero del marco FP (ve Figura
12.1).

SP
Marco
FP

Marco

Frame

Figura 12.1. Lista de marcos de activación en el stack.


El estado del stack antes de que y después de una llamada de procedimiento está mostrada en Figura
12.2. Nota que el epílogo reverts el stack a su estado original por sacar dirección de regreso y entrada de
enlace dinámico.

Prólogo + de
llamada
SP
Regreso + de Marco
epílogo
FP
ret adr
SP
Marco Marco
FP

Figura 12.2. Estados del stack antes de que y después de que llamada de procedimiento.
Si cuidadosamente consideramos el necessity de los dos punteros SP y FP, podemos venir a la
107
conclusión que FP es de hecho superfluous, porque las variables' direcciones de offset podrían ser hechas
relativos a SP en vez de FP. Esta proposición, aun así, es válido sólo si las medidas de todas las variables
son sabidas en compilar tiempo. Esto no es tan en el caso de abierto (dinámico) variedades, cuando
devendrá aparente más tarde. Pero evidentemente la retención

108
De un segundo puntero (FP) requiere accesos de almacenamiento adicional a cada llamada de
procedimiento y regreso, los cuales son indeseables.
Para mejorar eficacia y en particular para reducir la longitud de las secuencias de instrucción para ambos
prólogo y epílogo, los ordenadores con instrucciones más complejas presentan las instrucciones
especiales que corresponden a prólogo y epílogo. Dos ejemplos pueden ayudar al llegar a este punto; las
segundas características especiales, dedicó registros para los punteros SP y FP. El número de requirió
instrucciones, aun así, queda igual.
Motorola 680x0 Semiconductor nacional 32x32
Llamada BSR P BSR P
Prólogo ENLACE D14, n INTRODUCE n
Epílogo UNLNK D14 SALIDA
Salto de regreso RTD RET

12.2. Dirigiendo de variables


Recordamos que la dirección de una variable local es relativa a la dirección base del marco de activación
que contiene la variable, y que esta dirección base está aguantada en registro FP. El último, aun así,
aguanta sólo para que así conste activado último, y así sólo para variables qué pertenecer al
procedimiento en qué son referenced. En mucho procedimiento de lenguajes de programación
declarations puede ser nested, dando aumento a referencias a variables qué es local a algún
procedimiento, pero no al procedimiento referencing les. El ejemplo siguiente demuestra la situación, con
R lugareño de ser a Q, y Q y S lugareño a P:
Objeto Nivel
PROCEDIMIENTO P; P 0
VAR x: ENTERO; x 1
PROCEDIMIENTO Q; Q 1
VAR y: ENTERO; y 2
PROCEDIMIENTO R; R 2
VAR z: ENTERO; z 3
EMPIEZA x := y
+ z FIN R;
EMPIEZ
A R FIN
Q;
PROCEDIMIENTO S; S 1
EMPIEZ
A Q FIN
S;
EMPIEZA
Q; S FIN P;
Dejado nos rastro la cadena de llamadas P → Q → R. Está tentando para creer que, cuándo accediendo
variables x, y, o z en R, su dirección base podría ser obtenida por traversing la lista de enlace dinámica.
El número de pasos sería la diferencia entre los niveles de la llamada y del declaration. Esta diferencia es
2 para x, 1 para y, y 0 para z. Pero esta suposición es mal. R También podría ser logrado a través de la
secuencia de llamada P → S
→ Q → R Cuando mostrado en Figura 12.3. Acceso a x entonces ventaja en dos pasos al marco de
activación de S en vez de P.
Evidentemente, una segunda lista de registros de activación es necesaria cuál espejos el orden estático de
imbricación más que el orden dinámico de llamadas. Por ello un segundo enlace tiene que ser establecido
a cada llamada de procedimiento. El tan-marca de procedimiento llamado ahora contiene, además de la
dirección de regreso y el enlace dinámico, un elemento de enlace estático. El enlace estático de un
procedimiento P señala al registro de activación del procedimiento qué contiene P, aquello es, en qué P
109
está declarado localmente. Tenga que ser notado que este puntero es superfluous para los procedimientos
declararon globalmente, si las variables globales están dirigidas directamente, aquello es, sin dirección
base. Desde este es típicamente el caso, y desde entonces más los procedimientos están declarados
globalmente, el adicionales

110
La complejidad causada por la cadena estática es aceptable. Con alguna justificación el absoluto
dirigiendo de las variables globales pueden ser consideradas como caso especial de local variable
dirigiendo principal a un aumento en eficacia.

Enla R Enlace
ce dinámic
estáti o
co
Q

Figura 12.3. Enlaces dinámicos y estáticos en el stack.


Finalmente, nota que acceso a variables vía la lista de enlace estática (variables de nivel intermedio) es
menos eficaz que acceso a estrictamente variables locales, porque cada paso a través de la lista requiere
un acceso de memoria adicional. Varias soluciones han sido propuestas e implementadas para eliminar
esta pérdida de eficacia. Finalmente siempre confían en el mapeo de la lista estática a un conjunto de
registros de base. Consideramos esto como una optimización en el sitio incorrecto. Primero, los registros
son recursos escasos cuál no tendría que ser dado fuera demasiado fácilmente. Y segundo, el copiando
de elementos de enlace a registros a cada llamada y el regreso pueden fácilmente costado más de salva,
en particular porque las referencias a variables de nivel intermedio ocurren bastante raramente en
práctica. La optimización por tanto puede resultar para ser bastante el revés.
En ordena no para realzar el readability del Oberon-0 compilador listado en Apéndice C, el manejando de
variables de nivel intermedio no ha sido implementadas.
Las variables globales han fijado dirige qué mosto también ser considerado relativo a una dirección de
marco. Sus valores absolutos están determinados a cargar el código, aquello es, después de que
recopilación pero antes de que ejecución de programa. El emitted código de objeto por tanto puede ser
acompañado por una lista de direcciones de las instrucciones que refieren a variables globales. El loader
el mosto entonces añade a estos dirige la dirección base del marco respectivo de variables globales. Este
fixup la operación puede ser omitida si el ordenador presenta el contador de programa como un registro
de dirección. Nuestro RISC hace exactamente esto por dejar el PC ser accesible como R15. El marco de
variables globales está colocado inmediatamente precediendo el marco de código. De ahí, direcciones de
uso de variables globales R15 cuando dirección base, y la dirección de la instrucción actual tiene que ser
restada del offset de la variable.

12.3. Parámetros
Los parámetros constituyen la interfaz entre el llamando y el llamó procedimientos. Los parámetros en el
lado de llamar están dichos para ser parámetros reales, y aquellos en el lado llamado parámetros
formales. El último es de hecho titulares de sitio único para qué los parámetros reales están sustituidos.
Básicamente, una sustitución es una asignación del valor real a la variable formal. Esto implica que cada
parámetro formal ser representado por un variable atado al procedimiento, y que cada llamada ser
acompañado por un número de asignaciones sustituciones de parámetro llamado.
La mayoría de lenguajes de programación distinguen entre al menos dos clases de parámetros. El primero
es el parámetro de valor dónde, como su propio nombre indica, el valor del parámetro real está asignado
a la variable formal. El parámetro real es syntactically una expresión. La segunda clase de parámetro es
el parámetro de referencia, dónde, también tan sugerido por su nombre, una referencia al parámetro real
está asignada a la variable formal. Evidentemente, el parámetro real en este caso tiene que ser una
111
variable, porque una asignación al parámetro formal es permisible, y esta asignación tiene que referir a la
variable real.

112
(En Pascal, Modula, y Oberon, el parámetro de referencia es por tanto parámetro variable llamado). El
valor de la variable formal es en este caso un puntero escondido, aquello es, una dirección.
Naturalmente, el parámetro real tiene que ser evaluado antes de la sustitución tiene lugar. En el caso de
parámetros variables, la evaluación toma la forma de una identificación de la variable, implicando, por
ejemplo, la evaluación del índice en el caso de indexed variables. Pero cómo es el destino de esta
sustitución para ser determinada? Aquí el stack la organización de la tienda viene a juego. Los valores
reales son sencillamente depositados en secuencia en la parte superior del stack; ninguno direcciones de
destino explícitas están requeridas. Figura 12.4 espectáculos el estado del stack después de la deposición
de los parámetros, y después de la llamada y el prólogo.

Regreso de llamada

SP
Variab
les
locales FP

ret adr
SP
Parámetros Parámetros

SP

Figura 12.4. Sustitución de parámetro.


Ahora deviene evidente que los parámetros pueden ser dirigidos relativos a la dirección de marco FP,
como variables locales. Si las variables locales tienen offsets negativos, los parámetros tienen offsets
positivos. Es particularmente valor notando que los parámetros de referencias de procedimiento llamados
exactamente dónde estuvieron depositados por el procedimiento de llamar. El espacial destinado para los
parámetros está recuperado por el epílogo sencillamente por creciente el valor de SP..
Epílogo MOV SP, 0, FP SP := FP
POP FP, SP, 4 pop FP
POP LNK, SP, m+4 enlace de pop y
parámetros RET LNK salto de regreso
En el caso de CISC ordenadores con el prólogo y el epílogo representados por instrucciones especiales, el
requeridos increment de SP está incluido en la instrucción de regreso que especifica la medida del bloque
de parámetro cuando parámetro (RET m).

12.4. Procedimiento declarations y llamadas


El procedimiento para procesar procedimiento declarations es fácilmente derivado de la sintaxis con la
ayuda del parser reglas de construcción. La entrada nueva en la mesa de símbolo generada para un
procedimiento declaration obtiene el atributo de clase Proc, y su atributo un está dado el valor actual de
pc, el cual es la dirección de entrada del prólogo del procedimiento. Después, un alcance nuevo está
abierto en la mesa de símbolo de tal manera que (1) las entradas nuevas para objetos locales son
automáticamente insertados en el alcance nuevo, y (2) al final del procedimiento los objetos locales son
fácilmente discarded y el alcance anterior reaparece. Aquí también, los dos procedimientos OpenScope y
CloseScope encarnar el stack principio, y la conexión está establecida por un elemento de
encabezamiento (Cabeza de clase, campo dsc). Los objetos están dados un atributo adicional lev
denotando el nivel de imbricación del objeto declarado. Considerar el siguiente declarations:
CONST N = 10;
VAR x: T;
PROCEDIMIENTO P(x, y: ENTERO); ...

113
La mesa de símbolo resultante está mostrada en Figura 12.5. El dsc el puntero refiere a P parámetros x y y.

Alcance x y Tipo
Cabe Par Par de
za Int Int clase
12 8 del
Guar nom
Nivel dia bre
k+1 val
próxi
mo
dsc

Cabe N x P Tipo
za Const Var Proc de
Int T Ning clase
10 uno del
Nivel adr nom
k guard bre
ia val
próxi
mo
dsc

Figura 12.5. Mesa de símbolo que representa dos alcances.

PROCEDIMIENTO
ProcedureDecl; VAR proc,
obj: Objeto;
procid: Ident;
locblksize, parblksize: LONGINT;
PROCEDIMIENTO FPSection;
VAR obj, primero: Objeto; tp: Tipo; parsize:
LONGINT; EMPIEZA
SI sym = var ENTONCES Conseguir(sym); IdentList(Par, primero) MÁS IdentList(Var, primero) FIN
;
SI sym = ident
ENTONCES
encontrar(obj);
Consigue(sym);
SI obj.Clase = Typ ENTONCES tp := obj.Tipo MÁS Mark("tipo?"); tp :=
intType FIN MÁS Mark("ident?"); tp := intType
FIN ;
SI primero.Clase = Var ENTONCES parsize := tp.Medida MÁS
parsize := 4 FIN ; obj := primero;
MIENTRAS obj # guardia obj.Tipo := tp; INC(parblksize, parsize); obj := obj.FIN
de FIN próximo FPSection;
EMPIEZA (* ProcedureDecl *) Consigue(sym);
SI sym = ident
ENTONCES
procid := id;
NewObj(proc, Proc); Consigue(sym);
114
parblksize := 8; INC(nivel); OpenScope;
proc.val := -1;
SI sym = lparen ENTONCES
Conseguir(sym); SI sym = rparen
ENTONCES Conseguir(sym)
MÁS FPSection;
MIENTRAS sym = el punto y coma Consigue(sym);
FPSection FIN ; SI sym = rparen ENTONCES
Conseguir(sym) MÁS Mark(")?") FIN
FIN
de FIN
;
obj := topScope.Luego; locblksize :=
parblksize; MIENTRAS obj # el guardia
HACE

115
obj.lev := curlev;
SI obj.Clase = Par ENTONCES
DEC(locblksize, 4) MÁS locblksize := locblksize - obj.Tipo.FIN
de medida ;
obj.val := locblksize; obj := obj.FIN
próximo ;
proc.dsc := topScope.Luego;
SI sym = el punto y coma ENTONCES Consigue(sym) MÁS
Mark(";?") FIN; locblksize := 0; declarations(locblksize);
MIENTRAS sym =
procedimiento
ProcedureDecl;
SI sym = el punto y coma ENTONCES Consigue(sym) MÁS
Mark(";?") FIN de FIN ;
proc.val := pc; Introduce(locblksize);
SI sym = empezar ENTONCES Conseguir(sym); StatSequence FIN ;
SI sym = el fin ENTONCES Consigue(sym) MÁS
Mark("FIN?") FIN ; SI sym = ident ENTONCES
SI procid # id ENTONCES Mark("ningún
partido") FIN ; Consigue(sym)
MÁS Mark("ident?")
FIN ;
Regreso(parblksize - 8); CloseScope;
DEC(nivel) FIN
FIN ProcedureDecl;
Dentro del cuerpo de procedimiento, parámetros de valor están tratados exactamente como variables
locales. Sus entradas en la mesa de símbolo son de clase Var. Una clase nueva Par está introducido para
representar parámetros de referencia. Las direcciones (offsets) de los parámetros formales están
derivados según la fórmula siguiente, por el cual el último parámetro pn obtiene el menos offset,
concretamente la medida de la marca de procedimiento (8). La medida de parámetros variables es
siempre 4, el cual es la medida de una dirección.
adr(pi) = Medida(pi+1) + ... + Medida(pn) + 8
Desafortunadamente, esto implica que los offsets no pueden ser determinados antes de la lista de
parámetro entera ha sido reconocida. En el caso de byte-dirigió tiendas es además ventajoso siempre a
increment o decrement el stack puntero por múltiplos de 4, tal que los parámetros son siempre alineados
a fronteras de palabra. En el caso de Oberon-0 atención especial a esta regla es innecesaria, porque todos
tipos de datos presentan una medida de múltiplos de 4 en todo caso.
Local declarations está procesado por el parser procedimiento declarations. El código para el prólogo es
emitted por el procedimiento Introduce después del procesamiento de local declarations. La emisión del
epílogo está actuada por Regreso de procedimiento al final de ProcedureDec l.
El PROCEDIMIENTO
Introduce(medida: LONGINT);
EMPIEZA
Puesto(PSH, LNK, SP, 4);
Puesto(PSH, FP, SP, 4);
Puesto(MOV, FP, 0,
SP);
Puesto(SUBI, SP, SP,
medida)
El FIN Introduce;
Regreso de PROCEDIMIENTO(medida:
LONGINT); EMPIEZA
Puesto(MOV, SP, 0, FP);
Puesto(POP, FP, SP, 4);
Puesto(POP, LNK, SP,
116
medida+4); PutBR(RET,
LNK)
Regreso de FIN;

117
Procedimiento MakeItem genera un Elemento que corresponde a un Objeto dado. Al llegar a este punto,
la diferencia entre el dirigiendo de variables locales y globales tienen que ser tenidas en cuenta. (Cuando
ya mencionado, el manejando de variables de nivel intermedio no es tratadas aquí.) Nota, aun así,
aquellos parámetros de referencia (clase = Par) requiere indirecto dirigiendo. Desde el RISC la
arquitectura no explícitamente presenta un indirecto dirigiendo modo, el valor del parámetro formal, el
cual es la dirección del parámetro real, está cargado a un registro. El parámetro real es entonces accedido
vía este registro, con offset 0.
PROCEDIMIENTO MakeItem(VAR x: Elemento;
y: Objeto); VAR r: LONGINT;
EMPIEZA x.Modo := y.Clase; x.Tipo := y.Tipo; x.Un := y.val;
SI y.lev = 0 ENTONCES x.r := PC ELSIF y.lev = curlev
ENTONCES x.r := FP MÁS Mark("nivel!"); x.r := 0
FIN ;
SI y.Clase = Par ENTONCES GetReg(r); Puesto(LDW, r, x.r, x.Un); x.Modo := Var; x.r := r;
x.Un := 0 FIN de FIN MakeItem;
Llamadas de procedimiento están generadas dentro del procedimiento ya encontrado StatSequence con la
ayuda de Parámetro de procedimientos auxiliares y Llamada :
SI sym = ident ENTONCES
Encuentra(obj); Consigue(sym); MakeItem(x, obj);
selector(x); SI sym = deviene ENTONCES ...
ELSIF x.Modo = Proc
ENTONCES par :=
obj.dsc;
SI sym = lparen ENTONCES
Conseguir(sym); SI sym = rparen
ENTONCES Conseguir(sym)
MÁS
Expresión de BUCLE(y);
SI IsParam(par) ENTONCES Parámetro(y, par.Tipo, par.Clase); par :=
par.Luego MÁS Mark("demasiados parámetros")
FIN ;
SI sym = la coma ENTONCES Consigue(sym)
ELSIF sym = rparen ENTONCES Conseguir(sym); SALIDA
ELSIF sym >= Punto y coma ENTONCES Mark(")
?"); SALIDA MÁS Mark(") o , ?")
FIN
de FIN
FIN
de FIN
;
SI obj.val < 0 ENTONCES
Mark("llamada de delantero") ELSIF
~IsParam(par) ENTONCES Llamada(x)
MÁS Mark("también pocos parámetros")
FIN
...
Parámetro de PROCEDIMIENTO(VAR x: Elemento; ftyp: Tipo;
clase: ENTERO); VAR r: LONGINT;
EMPIEZA
SI x.Tipo = ftyp ENTONCES
SI clase = Par ENTONCES (*Var
param*) SI x.Modo = Var
ENTONCES
SI x.Un # 0 ENTONCES GetReg(r); Puesto(ADDI, r, x.r, x.Un) MÁS
r := x.r FIN MÁS Mark("modo de parámetro ilegal")
FIN ;
118
Puesto(PSH, r, SP, 4); EXCL(regs, r)
(*empujón*) MÁS (*valor param*)
SI x.Modo # Reg ENTONCES
carga(x) FIN ; Puesto(PSH, x.r, SP, 4);
EXCL(regs, x.r)

119
FIN
MÁS Mark("tipo de parámetro
malo") FIN
Parámetro de FIN;
PROCEDIMIENTO IsParam(obj: Objeto): BOOLEANO;
EMPIEZA REGRESO (obj.Clase = Par) O (obj.Clase = Var) & (obj.val
> 0) FIN IsParam;
Llamada de
PROCEDIMIENTO(VAR x:
Elemento); EMPIEZA
PutBR(BSR, x.Un - pc)
Llamada de FIN;
Aquí nosotros tacitly suponer que las direcciones de entrada de procedimientos son sabidas cuándo una
llamada es para ser compilado. Así excluimos adelante referencias cuál puede, por ejemplo, surge en el
caso de mutuo, recursivo referencing. Si esta restricción es para ser lifted, las ubicaciones de adelante las
llamadas tienen que ser retenidas para que las instrucciones de rama pueden ser fijadas arriba cuándo sus
destinos devienen sabidos. Este caso es similar al fixups requirió para adelante saltos en condicionales y
repitió declaraciones.
En conclusión, muestramos el código generado para el procedimiento
siguiente, sencillo: PROCEDIMIENTO P(x: ENTERO; VAR y:
ENTERO);
EMPIEZA x := y; y := x; P(x, y);
P(y, x) FIN P
0 PSH LNK, SP, 4 prólogo
4 PSH FP, SP, 4
8 MOV FP, 0, SP
12 SUBI SP, SP, 0 Ninguna variable local
16 LDW 0, FP, 8
20 LDW 1, 0, 0
24 STW 1, FP, 12 x := y
28 LDW 0, FP, 8
32 LDW 1, FP, 12
36 STW 1, 0, 0 y := x
40 LDW 0, FP, 12 x
44 PSH 0, SP, 4
48 LDW 0, FP, 8 adr(y)
52 PSH 0, SP, 4
56 BSR -14 P(x, y)
60 LDW 0, FP, 8
64 LDW 1, 0, 0 y
68 PSH 1, SP, 4
72 ADDI 0, FP, 12 adr(x)
76 PSH 0, SP, 4
80 BSR -20 P(y, x)
84 MOV SP, 0, FP Epílogo
88 POP FP, SP, 4
92 POP LNK, SP, 12 Enlace de pop y
parámetros
96 RET LNK

12.5. Procedimientos estándares

120
La mayoría de lenguajes de programación presentan procedimientos seguros y funciona cuáles no
necesitan para ser declarados en un programa. Están dichos para ser predeclared y se pueden apellidar
de anywhere, cuando son dominantes. Estos son funciones bien sabidas , por ejemplo el valor absoluto
de un número (ABS), tipo

121
Conversiones (ENTIER, ORD), o frecuentemente encontró declaraciones qué mérito una abreviatura y es
disponible encima muchos ordenadores como instrucciones solas (INC, DEC). La propiedad común a
todo estos tan- los procedimientos estándares llamados es que corresponden directamente tampoco a una
instrucción sola o a secuencia a escasa de instrucciones. Por tanto, estos procedimientos están manejados
bastante de manera diferente por compiladores; ninguna llamada está generada. En cambio, las
instrucciones necesarias son emitted directamente al código. Estos procedimientos son por tanto también
llamados en-procedimientos de línea, un plazo que las marcas notan sólo si la técnica de implementación
subyacente está entendida.
Como consecuencia es ventajoso de considerar procedimientos estándares como una clase de objeto de
su propio. Así la necesidad para un tratamiento especial de llamadas deviene inmediatamente aparente.
Para Oberon-0 nosotros procedimientos de postulado Leídos, Escribe, WriteHex, y WriteLn, el cual por
un lado introduce producción y entrada elementales instalaciones, y por otro lado servir para demostrar el
propuesto manejando de predeclared procedimientos. En este caso, el estándar de plazo es ciertamente
misleading, mientras que predeclared y en_la línea refiere al núcleo del asunto subject. Las entradas
correspondientes en la mesa de símbolo están hechas cuándo el compilador está inicializado,
concretamente en un outermost alcance universo llamado qué siempre queda abierto (ve Apéndice C). El
atributo de clase nuevo está denotado por SProc, y atributo val (un en el caso de Elemento s) identifica el
procedimiento preocupado.
SI sym = ident ENTONCES
Encuentra(obj); Consigue(sym); MakeItem(x, obj);
selector(x); SI sym = deviene ENTONCES ...
ELSIF x.Modo = Proc
ENTONCES ... ELSIF x.Modo
= SProc ENTONCES
SI obj.val <= 3 ENTONCES param(y); TestInt(y)
FIN ; IOCall(x, y)
...
PROCEDIMIENTO IOCall(VAR x, y: Elemento);
VAR z: Elemento;
EMPIEZA (*x.Modo =
SProc*) SI x.Un = 1
ENTONCES (*Leído*)
GetReg(z.r); z.Modo := Reg; z.Tipo := intType; Puesto(RD, z.r, 0, 0); Tienda(y,
z) ELSIF x.Un = 2 ENTONCES (*Escribe*) carga(y); Puesto(WRD, 0, 0, y.r);
EXCL(regs, y.r) ELSIF x.Un = 3 ENTONCES (*WriteHex*) carga(y);
Puesto(WRH, 0, 0, y.r); EXCL(regs, y.r) MÁS (*WriteLn*) Puesto(WRL, 0, 0, 0)
FIN
FIN IOCall;
El ejemplo final muestra una secuencia de tres declaraciones y el código
resultante: Leído(x); Escribe(x); WriteLn
4 LEÍD 0, 0, 0
O
8 STW 0, 0, -4 x
12 LDW 0, 0, -4 x
16 WRD 0, 0, 0
32 WRL 0, 0, 0

12.6. Procedimientos de función


Un procedimiento de función es un procedimiento cuyo identificador simultáneamente denota ambos un
algoritmo y su resultado. Es activó no por una declaración de llamada pero por un factor de una
expresión. La llamada de un procedimiento de función tiene que por tanto también cuidar de regresar el
resultado de la función. La cuestión por tanto surge del cual los recursos tendrían que ser utilizados.
Si nuestro objetivo primario es la generación de código eficaz con el número mínimo de accesos de
memoria, entonces un registro es el candidato primo para temporalmente aguantando el resultado de la
122
función. Si esta solución está adoptada, tenemos que renunciar la capacidad de definir funciones con un
resultado estructurado, porque estructuró los valores no pueden ser aguantados en un registro.

123
Si esta restricción está considerada como inaceptable, un sitio en el stack tiene que ser reservado para
aguantar el resultado estructurado. Típicamente, está añadido al área de parámetro del registro de
activación. El resultado de función está considerado como un resultado implícito (variable) parámetro.
Correspondingly, el stack el puntero es incremented antes de que el código para el primer parámetro es
emitted.
Al llegar a este punto, todos los conceptos contuvieron en la lengua Oberon-0 e implementado en su
compilador ha sido presentado. El listado del compilador está contenido en lleno en Apéndice C.

12.7. Ejercicios.
12.1. Mejorar el Oberon-0 compilador de tal manera que la restricción que las variables tienen que
ser estrictamente locales o enteramente globales puede ser lifted.
12.2. Añade funciones estándares al Oberon-0 compilador, generando inline código. Considera
ABS, INC, Dic.
12.3. Reemplazar el VAR concepto de parámetro por la idea de un FUERA parámetro. Un FUERA el
parámetro representa una variable local cuyo valor está asignado a su parámetro real
correspondiente a terminación del procedimiento. Constituye el inverse del parámetro de valor,
donde el valor del parámetro real está asignado a la variable formal al inicio del procedimiento.

124
13. Tipos de Dato elemental
13.1. Los tipos REALES y LONGREAL.
Tan temprano cuando 1957 enteros y los números reales estuvieron tratados como tipos de dato distinto
en Fortran. Esto no fue sólo porque representaciones diferentes , internas eran necesarias, pero porque
esté reconocido que el programador tiene que ser consciente de cuándo las computaciones podrían ser
esperadas para ser exactos (concretamente para enteros), y cuándo únicos aproximados. El hecho que con
números reales sólo los resultados aproximados pueden ser obtenidos, puede ser entendido por considerar
que los números reales están representados por scaled enteros con un número fijo, finito de dígitos. Su
tipo se apellida REAL, y un valor real x está representado por el par de enteros e y m tan definidos por la
ecuación
x = Be-w × m 1  m < B
Esta forma se apellida representación de punto flotante; e está dicho para ser el exponente, m la mantisa.
La base B y el sesgo w es valores fijos para todos los valores REALES, caracterizando la representación
de número escogida. Los dos estándares de IEEE de representaciones de punto flotante presentan los
valores siguientes para B y w , y a los componentes e y m un poco s está añadido para la señal:
Tipo B w Número de bits para e Número de bits para m Total
REAL 2 127 8 23 32
LONGREAL 2 1023 11 52 64
Las formas exactas de los dos tipos, llamados REALES y LONGREAL en Oberon, está especificado por
las fórmulas siguientes:
x = (-1)s × 2e-127 × 1.m x = (-1)s × 2e-1023 × 1.m
Los ejemplos siguientes muestran la representación de punto flotante de algunas seleccionó números:
Decimal se 1.m Binario Hexadecimal
1.0 0 127 1.0 0 01111111 00000000000000000000000 3F80 0000
0.5 0 126 1.0 0 01111110 00000000000000000000000 3F00 0000
2.0 0 128 1.0 0 10000000 00000000000000000000000 4000 0000
10.0 0 130 1.25 0 10000010 01000000000000000000000 4120 0000
0.1 0 123 1.6 0 01111011 10011001100110011001101 3DC CCCD
-1.5 1 127 1.5 1 01111111 10000000000000000000000 BFC0 0000
Dos ejemplos ilustran el caso de LONGREAL:
1.0 0 1023 1.0 0 01111111111 00000000 ... 00000000 3FF0 0000 0000 0000
0.1 0 1019 1.6 0 01111111011 10011001 ... 10011010 3FB9 9999 9999 999Un
Esta forma logarítmica inherently excluye un valor para 0. El valor 0 tiene que ser tratado como caso
especial, y está representado por todos los bits que son 0. Respecto a propiedades numéricas constituye
un caso especial y una discontinuidad. Además, el postulado de estándares del IEEE dos valores
especiales adicionales: e = 0 (con m 
0) y e = 255 (resp. e = 1023) está considerado como los resultados nulos y ellos se apellidan NaN (no un número).
Normalmente, el programador no tiene que preocupación sobre estas especificaciones, y el diseñador de
compilador no es afectado por ellos. Los tipos REALES y LONGREAL constituir tipos de dato abstracto
normalmente integrados en el hardware qué características un conjunto de instrucciones adaptó a la
representación de punto flotante. Si este conjunto es completo, aquello es, cubre todas operaciones
numéricas básicas, la representación puede ser considerada tan escondido, desde ningún más lejano,
programó las operaciones prpers dependen. En muchos ordenadores, las instrucciones para operandos de
punto flotante utilizan un conjunto especial de registros. La razón detrás de este es que a menudo
coprocesadores separados, tan-llamados unidades de punto flotante (FPUs) está utilizado qué
implementar todas instrucciones de punto flotante y contener este conjunto de registros de punto flotante.

125
13.2. Compatibilidad entre tipos de dato numérico
Los valores de todas las variables con tipo de dato numérico son números. Por tanto hay no razón obvia
no para declararles todo tan la asignación compatible. Pero, tan ya perfilado, los números de tipos
diferentes son de manera diferente representados en plazos de mordió secuencias dentro del ordenador.
De ahí, siempre que un número de tipo T0 está asignado a una variable de tipo T1, una conversión de
representación tiene que ser actuada qué toma algunos tiempo finito. La cuestión entonces surge de si
este hecho tendría que quedar escondido del programador para evitar distracción, o si tenga que ser
hecho explícito porque afecta la eficacia del programa. La elección última está cumplida por declarar los
varios tipos como incompatibles y por proporcionar explícito, predefined funciones de conversión.
De todas formas, para ser completo, el conjunto de un ordenador de las instrucciones también tienen que
contener instrucciones de conversión qué convertir enteros a números de punto flotante y vicio-versa.
Los mismos controles en el nivel del lenguaje de programación.
En Oberon tipos de dato diferente pueden ser utilizados dentro de una expresión de aritmética, el cual es
entonces dicho para ser una expresión mixta. En este caso, el compilador tiene que insertar instrucciones
de conversión escondida que ceden la representación requerida para la operación de aritmética
especificada para ser aplicado.
La definición de Oberon proporciona no sólo dos, pero un conjunto entero de tipos de dato numérico.
Están ordenados en el sentido que el tipo más grande contiene todo valora pertenecer al tipo más
pequeño. Esta idea se apellida inclusión de tipo:

SHORTINT  ENTERO  LONGINT  REAL  LONGREAL


El tipo de resultado de una expresión está definido para ser igual al más grande de los operandos. Esta
regla determina las instrucciones de conversión para ser insertados por el compilador.
Para la asignación, compatibilidad de tipo relajada segura las reglas son también postulated, relajados
relativos a la regla estricta que el tipo de la variable de destino tiene que ser idéntico a aquello de la
expresión de fuente. Oberon Postulados que el tipo de la variable tiene que incluir el tipo de la expresión.
Los ejemplos siguientes son por tanto permitted:
VAR i, j: ENTERO; k: LONGINT; x, y: REAL; z: LONGREAL;
i := j; ENTERO M ENTERO (ninguna conversión)
k := i; ENTERO M LONGINT (ENTERO a LONGINT)
z := x; REAL M LONGREAL (REAL a LONGREAL) x
:= i; ENTERO M REAL (ENTERO a REAL).
Las conversiones entre tipos de entero son sencillas y eficaces, porque constan de una extensión de señal
sólo. Mucho más implicado es conversiones de entero a representación de punto flotante. Constan de una
normalización de la mantisa tal aquello 1  m < 2, y el empaquetando de señal, exponente y mantisa a
una palabra sola. Típicamente, una instrucción apropiada está proporcionada para esta tarea.
Aun así, si el tipo de la expresión es más grande que el tipo de la variable de destino, la asignación es a
veces imposible, cuando el valor asignado puede ser fuera de la gama especificada por el tipo de la
variable. Por tanto, una carrera-control de gama del tiempo es necesario antes de la conversión. Este
hecho tendría que ser hecho visible en la lengua de fuente por una función de conversión explícita.
Oberon Presenta las funciones siguientes:
ENTERO CORTO a SHORTINT.
LONGINT A ENTERO
LONGREAL a REAL
ENTIER REAL a LONGINT
LONGREAL A LONGINT
ENTIER(x) Cede el entero más grande no más grande que x.
Oberon reglas de conversión del tipo son sencillas y fácilmente explicó. No obstante ellos puerto un
pitfall. En una multiplicación de dos factores de ENTERO de tipo (o REAL) en algunos casos está
tentando para esperar un resultado de tipo LONGINT (resp. LONGREAL). Aun así, en las asignaciones

126
k := i*j + k z := x*y + z
El producto es, según el el dado gobierna, de ENTERO de tipo (resp. REAL), y está convertido a su
variante larga para la adición subsiguiente. Computación del producto con precisión más alta está
conseguido por forzar la conversión para ocurrir antes de que multiplicación:
k := MUCHO TIEMPO(i) * MUCHO TIEMPO(j) + k z := MUCHO TIEMPO(x) * MUCHO TIEMPO(y) +
z
La simplicidad de las reglas de compatibilidad es una ventaja significativa para el diseñador de
compilador. El principio de la expresión que maneja es claramente prescribió. Sólo los procedimientos de
recopilación para expresiones y plazos, y el para asignación tiene que ser ajustado por introducir
discriminaciones de caso. El más grande el número de tipos numéricos diferentes, el más los casos tienen
que ser distinguidos.

13.3. El tipo de dato PUSO


Las unidades del almacenamiento en ordenadores consta de un número pequeño de bits qué es
interpretable en maneras diferentes. Pueden representar enteros con o sin señal, números de punto
flotante o dato lógico. La cuestión sobre la manera de introducir secuencias de bit lógico en lenguajes de
programación más altos ha sido polémicas para un tiempo largo. La propuesta para introducirles tan los
conjuntos se debe a C. Un. R. Hoare (Hoare, 1972).
La propuesta es atractiva, porque el conjunto es un matemáticamente abstracción bien fundada. Es
apropiadamente representado en un ordenador por su función característica F. Si x es un conjunto de
elementos del conjunto de base ordenado M, F(x) es la secuencia de valores de verdad bi con el
significado "i está contenido en x". Si escogemos una palabra (constando de N bits) para representar los
valores de tipo PUSIERON, el conjunto de base consta de los enteros 0, 1, ... , N-1. N Es típicamente tan
pequeño que la gama de las aplicaciones para el tipo PONEN es bastante restringido. Aun así, las
operaciones de conjunto básicas de intersección, la unión y la diferencia son implementable
extremadamente efficiently. Los ejemplos de conjuntos representaron por mordió secuencias con
longitud de palabra N es:
x N-1 ... 76543210
{0, 2, 4, 6, ...} 0 ... 01010101
{0, 3, 6, ...} 0 ... 01001001
{} 0 ... 00000000
Oberon operadores de conjunto están implementados por las instrucciones lógicas disponibles en cada
ordenador. Esto es posible debido a las propiedades siguientes de la función característica. Nota que
utilizamos el Oberon notación para poner operaciones, aquello es, x+y para la unión y x*y para la
intersección:
i ((i  x+y ) : (i  x) O (i  y)) Unión
i ((i  x*y ) : (i  x) & (i  y)) Intersección
i ((i  x-y ) : (i  x) & ~(i  y)) Diferencia
i ((i  x/y ) : (i  x)  (i  y)) Symmetric Diferencia
Consiguientemente, el O la instrucción puede ser utilizada para unión puesta, Y para intersección puesta
y XOR para el symmetric diferencia. El resultado es una implementación muy eficaz , porque la
operación está ejecutada encima todos los elementos (bits) simultáneamente (en paralelo). Ejemplos con
el conjunto de base {0, 1, 2, 3} es:
{0, 1} + {0, 2} = {0, 1, 2} 0011 O 0101. = 0111
{0, 1} * {0, 2} = {0} 0011 & 0101 = 0001
{0, 1} - {0, 2} = {1} 0011 & ~ 0101 = 0010
{0, 1} / {0, 2} = {1, 2} 0011 XOR 0101 = 0110
Concluimos por mostrar el código que representa la expresión de conjunto (un+b) * (c+d)
LDW 1, 0,
un
LDW 2, 0, b
127
O 1, 1, 2
LDW 2, 0, c
LDW 3, 0, d
O 2, 2, 3
Y 1, 1, 2

128
La prueba de afiliación i EN x está implementado por un poco prueba. Si tal una instrucción no es
disponible, la secuencia de bit tiene que ser cambiada apropiadamente con la señal subsiguiente mordió
prueba.
El tipo PONE es particularmente útil si el conjunto de base incluye los números ordinales de un conjunto
de carácter (CHAR). La eficacia es en este caso un poco reducido, porque 256 bits (32 bytes) es
típicamente requerido para representar un valor de conjunto. Incluso en 32-mordió ordenadores 8
instrucciones lógicas están requeridas para la ejecución de una operación de conjunto.

13.4. Ejercicios
13.1 Extiende la lengua Oberon-0 y su compilador por el tipo de dato REAL (y/o LONGREAL) con sus
operadores de aritmética +, -, * y /. El RISC la arquitectura tiene que ser extendida consiguientemente
por un conjunto de instrucciones de punto flotante y un conjunto de registros de punto flotante. Escoge
uno de las alternativas siguientes:
a. El tipo de resultado de una operación es siempre que de los operandos. El ENTERO de tipos y REAL
no puede ser mezclado. Aun así, allí existir las dos funciones de transferencia ENTIER(x) y
REALES(i)..
b. Operandos del ENTERO de tipos y REAL (y LONGREAL) puede ser mezclado en
expresiones. Comparar las complejidades de los compiladores en los dos casos.
13.2. Extender la lengua Oberon-0 y su compilador por el tipo de dato PUSO con sus operadores +
(unión), * (intersección) y - (diferencia), y con la relación EN (afiliación). Además, constructores de
conjunto están introducidos por la sintaxis adicional siguiente. Cuando una opción, expresiones en poner
los constructores pueden ser limitados a constantes..
Conjunto = de número | del factor | ...
Conjunto = "{" [elemento {"," elemento}]
"}". Expresión = de elemento [".."
Expresión].
13.3. Extender la lengua Oberon-0 y su compilador por el tipo de dato CHAR con las funciones ORD(ch)
(número ordinal de ch en el conjunto de carácter) y CHR(k) (k-th carácter en el conjunto de carácter).
Una variable de tipo CHAR ocupa un byte solo en tienda.

129
14. Variedades abiertas, Punteros y Tipos de Procedimiento
14.1. Variedades abiertas
Una variedad abierta es un parámetro de variedad cuya longitud es desconocida (abierto) en el tiempo
de recopilación. Aquí encontramos por primera vez una situación donde la medida del bloque de
almacenamiento requerido no es dado. La solución es relativamente sencilla en el caso de un parámetro
de referencia, porque ningún almacenamiento tiene que ser destinado en todo caso, y meramente una
referencia a la variedad real está pasada al procedimiento llamado.
Aun así, para índice de control bounds cuándo accediendo elementos del parámetro de variedad abierto,
la longitud tiene que ser sabida. Por tanto, además de la dirección de la variedad, su longitud es también
pasada encima. En el caso de una variedad multidimensional, abierta la longitud es también necesaria de
computar direcciones de elemento. De ahí, la longitud de la variedad en cada dimensión está
suministrada. La unidad que consta de dirección de variedad y las longitudes se apellida un descriptor de
variedad. Considerar el ejemplo siguiente:
VAR Un: VARIEDAD 10 DE VARIEDAD 20 DE ENTERO;
PROCEDIMIENTO P(VAR x: VARIEDAD DE VARIEDAD
DE ENTERO); EMPIEZA k := x[i]
FIN P;
P(Un)
Un descriptor con tres entradas está empujado al stack cuando parámetro a P (s. Figura 14.1) y el
código correspondiente es como sigue:
MVI 1, 0, 20 R1 := 20
PSH 1, 30, 4 empujón len, R14 = SP
ADDI 1, 0, 10 R1 := 10
PSH 1, 30, 4 empujón len
ADDI 1, 0, un R1 := adr(un)
PSH 1, 30, 4 empujón adr
BSR P Llamada

adr(U x
n)
+4
10
+8
20

Figura 14.1. Descriptor de variedad para variedad abierta.


Si un parámetro de variedad abierto está pasado de largo valor , su valor tiene que ser copiado a su
ubicación formal proporcionada tan en el caso de un valor escalar. Esta operación puede, aun así, toma
esfuerzo considerable si la variedad es grande. En el caso de estructuró parámetros, los programadores
siempre tendrían que utilizar el VAR opción, a no ser que una copia es esencial.
Ciertamente el código para la operación de copia es mejor insertado después del prólogo del
procedimiento más que en el sitio de la llamada. Consiguientemente, el patrón de código para la llamada
es igual para valor y parámetros de referencia, con la excepción que para el anterior la operación de copia
está omitida del prólogo.
La ubicación formal aparentemente no aguanta la variedad, pero en cambio el descriptor de variedad,
cuya medida es sabida. El espacio para la copia está destinado en la parte superior del stack, y el stack el
puntero es incremented (o decremented) por la medida de la variedad. En el caso de variedades
multidimensionales, la medida está computada (en corrido- tiempo) como el producto de las longitudes

130
individuales y la medida de elemento.

131
Aquí SP está cambiado en tiempo corrido por cantidades qué es desconocido en compilar tiempo. Por lo
tanto es imposible en el caso general para operar con un registro de dirección solo (SP); el puntero de
marco FP es de hecho necesario.

14.2. Estructuras de dato dinámico y punteros.


Las dos formas de datos estructura proporcionadas en Oberon es la variedad (todos los elementos del
mismo tipo, homogeneous estructura) y el registro (estructura heterogénea). Estructuras más complejas
tienen que ser programadas individualmente, aquello es, tienen que ser generados durante ejecución de
programa. Por esta razón están dichos para ser estructuras dinámicas. Así los componentes de la
estructura están generados uno por uno; el almacenamiento está destinado para componentes
individualmente. Ellos no necesariamente mentira en ubicaciones contiguas en tienda. Las relaciones
entre componentes están expresadas explícitamente por punteros.
Para la implementación de este concepto un mecanismo tiene que ser disponible para la asignación de
almacenamiento en tiempo corrido. En Oberon, está representado por el procedimiento estándar
NUEVO(x). Esto destina almacenamiento a una variable dinámica, y asigna la dirección del bloque
destinado a la variable de puntero x. De este sigue que los punteros son direcciones . Acceso a un
variable referenced por un puntero es necesariamente indirecto cuando en el caso de VAR parámetros.
De hecho, un VAR el parámetro representa un puntero escondido. Considerar el siguiente declarations:
TIPO T = PUNTERO A TDesc;.
TDesc = RÉCORD x, y : LONGINT FIN;
VAR un, b : T;
El código para la asignación un.x := b.y Con acceso vía punteros un y b deviene
LDW 1, FP, b R1 := b
LDW 2, 1, y R2 := b.y
LDW 3, FP, un R3 := un
STW 2, 3, x Un.x :=
R2
El paso del referencing variable de puntero al referenced la variable récord se apellida dereferencing. En
Oberon el explícito dereferencing el operador está denotado por el símbolo . Un.x Es evidentemente
una abreviatura para la forma más explícita .x. El implícito dereferencing la operación es
reconocible cuándo el símbolo de selector (punto) es precedió no por un récord pero por una variable de
puntero.
Todo el mundo quién ha escrito programa cuál fuertemente implica el puntero que maneja sabe qué
fácilmente los errores pueden ser hechos con consecuencias catastróficas. Para explicar por qué,
considerar el tipo siguiente declarations:
T0 = RÉCORD x, y : LONGINT FIN ;
T1 = RÉCORD x, y, z : LONGINT FIN;
Dejado un y b ser variables de puntero, y dejar un punto a un registro de tipo T0, b a un registro de tipo
T1. Entonces el designator un.z Denota un undefined valor de una variable inexistente, y un.z : = b.x
Almacena un valor a algunos undefined ubicación, quizás corrompiendo otro variable destinado a esta
ubicación.
Esta situación peligrosa es elegantemente eliminada por punteros obligatorios a un tipo de dato. Esto
permite la validación de valores de puntero en el tiempo de recopilación sin pérdida de eficacia de tiempo
corrido. Esta idea brillante se debe a C. Un. R. Hoare Y estuvo implementado por primera vez en Algol
W (Hoare, 1972). El tipo al cual un puntero está atado se apellida su tipo de base.
P0 = PUNTERO A
T0;.
P1 = PUNTERO A
T1;.
Ahora el compilador puede comprobar y garantía que valores de puntero único pueden ser asignados a
una variable de puntero p cuál señala a una variable del tipo de base de p . El CERO de valor, señalando
a ningún variable en absoluto, está considerado cuando perteneciendo a todos tipos de puntero.
132
Refiriendo al ejemplo encima, ahora el designator un.z Está detectado tan incorrect, porque z no es un
campo del tipo T0 al cual un está atado. Si cada variable de puntero está inicializada a CERO, basta para
preceder cada acceso vía un puntero con una prueba para el CERO de valor del puntero. En este caso, los
puntos de puntero a ninguna variable, y cualquier designator tiene que ser erróneo.

133
Tal prueba es de hecho bastante sencilla, pero debido a su frecuencia reduce eficacia. La necesidad para
un patrón de código explícito puede ser circumvented por (ab)utilizando el mecanismo de protección del
almacenamiento disponible encima muchos ordenadores. En este caso, la prueba no correctamente
comprueba si un = CERO, sino si un.z Es una dirección válida, desprotegida. Si CERO tan habitual está
representado por la dirección 0, y si ubicaciones 0 ... N-1 está protegido, las referencias equivocadas vía
CERO están cogidas sólo si sus offsets de campo son menos de N . No obstante, el método parece para
ser satisfactorio en práctica.
La introducción de punteros requiere una clase nueva de objetos en la mesa de símbolo y también un
modo nuevo de elementos. Ambos son para implicar indirecto dirigiendo. Porque VAR los parámetros
también requieren indirectos dirigiendo, un modo que indica indirection es ya presente, y es sólo natural
de utilizar el mismo modo para acceso vía punteros. Aun así, el nombre Ind ahora aparecería tan más
apropiado que Par.
Designator Modo
x Var Directo dirigiendo
x^ Ind Indirecto dirigiendo
x^.y Ind Indirecto dirigiendo con offset
De ahí, el (normalmente implicado) dereferencing el operador convierte el modo de un elemento de
Var a Ind . A summarize:
1. La idea de un puntero es fácilmente integrado a nuestro sistema de compatibilidad de tipo que
comprueba. Cada tipo de puntero está atado a un tipo de base, concretamente el tipo del
referenced variable.
2. x^ Denota dereferencing, implementado por indirecto dirigiendo.
3. Los punteros son tipo seguros si el acceso está precedido por una prueba de CERO, y si variables de puntero
están inicializadas a CERO..
Asignación de variables referenced vía los punteros está obtenido por una llamada del procedimiento
NUEVO(p). Nosotros postulado su existencia cuando soporte de tiempo corrido en sistemas operativos.
La medida del bloque para ser destinado está dado por el tipo de base de p .
Tan lejos, hemos ignorado el problema de recuperación de almacenamiento. Es de hecho irrelevante para
programas abstractos; para concreto unos, aun así, es crucial, cuando las tiendas son inherently finitos.
Los sistemas operativos modernos ofrecen una administración de almacenamiento centralizada con
colección de basura. Hay varios esquemas para recuperación de almacenamiento; pero no les
explicaremos aquí. Prpers restringimos a la cuestión única pertinente al diseñador de compilador: qué
dato tiene que ser proporcionado al coleccionista de basura, de modo que en cualquier tiempo todo
bloques de almacenamiento irrelevante sin incidentes pueden ser identificados y reclaimed? Una variable
es ya no pertinente cuándo no hay ninguna referencia a él, las referencias que emanan de variables de
puntero declarado. Para determinar si tales referencias existen, el coleccionista de basura requiere el dato
siguiente:
1. Las direcciones de todo variables de puntero declarado,
2. Los offsets de todos campos de puntero en dinámicamente destinó registros, y
3. La medida de cada variable dinámicamente destinada.
Esta información es disponible en compilar tiempo, y tiene que ser "entregado abajo" de tal manera que
es disponible al coleccionista de basura en tiempo corrido. En este compilador de sentido y el sistema
tienen que ser integrados. El sistema es aquí supuesto para incluir administración de almacenamiento, en
particular el allocator NUEVO y el coleccionista de basura.
Para hacer esta información disponible en tiempo corrido, el procedimiento NUEVO no sólo destina un
bloque de almacenamiento, pero proporciona él con una descripción de tipo de la variable destinada.
Naturalmente, tal descriptor tiene que ser emitido sólo una vez, cuando necesite no ser duplicado para
cada caso (variable) del mismo tipo. Por tanto, el bloque está asignado meramente un puntero al
descriptor de tipo, y este puntero queda invisible al programador. El puntero se apellida una etiqueta de
tipo (s. Figura 14.2).
El descriptor de tipo aparentemente es una forma reducida del objeto que describe el tipo en la mesa de

134
símbolo del compilador, reducido al dato pertinente para recuperación de almacenamiento. Este concepto
tiene las consecuencias siguientes:
1. El compilador tiene que generar un descriptor para cada (récord) tipo, y lo tenga que añadir al archivo de objeto.

135
2. El procedimiento NUEVO(p) obtiene, además de la dirección de p, un parámetro adicional, escondido
que especifica la dirección del descriptor del tipo de base de p..
3. El programa loader tiene que interpretar la información de archivo de objeto añadida y generar descriptors de
tipo.

p
p Etiqueta

Descripto
r de tipo

Figura 14.2. Variable de puntero, referenced variable, y descriptor de tipo.

El descriptor de tipo especifica la medida del variable y el offset de todos campos de puntero (Figura 14.3).

Eti
que
Medida =
0 ta 32
4 Offset = 8
8 20
12 24
16
20 Descripto
24 r de tipo

Figura 14.3. Variable con descriptor de tipo.


Esto, aun así, es todavía insuficiente. Para que estructuras de dato pueden ser traversed, sus raíces tienen
que ser sabidas. Por tanto, el archivo de objeto es también proporcionado con una lista de todo variables
de puntero declarado. Esta lista está copiada a cargar a memoria. La lista también tiene que incluir los
punteros escondidos que designan descriptors de tipo. Para que los descriptors no tienen que ser
generados para todos tipos de datos, Oberon restringe punteros para referir a registros. Esto está
justificado cuándo considerando la función de registros en estructuras de dato dinámico.

14.3. Tipos de procedimiento


Si en unos procedimientos de lengua pueden ser pasados como parámetros, o si pueden ocurrir tan
valores de variables, deviene necesario de introducir tipos de procedimiento. Cuáles son las
características de tales tipos, aquello es, de los valores qué variables pueden suponer?
Tipos de procedimiento han sido en uso desde el advenimiento de Algol 60. Allí, ocurrieron
implícitamente sólo. Un parámetro en Algol 60 puede ser un procedimiento (procedimiento formal). Su
tipo, aun así, no es especificado; es meramente sabido que el parámetro denota algún procedimiento o
función. La especificación de tipo es incompleta o desaparecida, y esto constistutes un unfortunate
loophole en Algol sistema de tipo. En Pascal, esté retenido como concesión a Algol compatibilidad.
Modula-2, aun así, requiere un completo, tipo-especificación segura, y además parámetros, variables con
los procedimientos como sus valores son también dejó. Así tipos de procedimiento consiguen el mismo
estando como otros tipos de dato. Al respecto, Oberon ha adoptado el mismo concepto como Modula-2
(Wirth, 1982).
Qué este tipo-especificación segura, llamó la firma del procedimiento, consta de? Contiene todas las
especificaciones necesarios de validar la compatibilidad entre parámetros reales y formales,
concretamente su número, el tipo de cada parámetro, su clase (valor o referencia) y el tipo del resultado
en el caso de procedimientos de función. El ejemplo siguiente ilustra el caso:

136
PROCEDIMIENTO F(x, y : REAL):
REAL; EMPIEZA
... FIN
F
PROCEDIMIENTO H(f: PROCEDIMIENTO (u, v :
REAL): REAL); VAR un, b: REAL;
EMPEZAR un : = f(un +
b, un - b) FIN H
A recopilación del declaration de H la compatibilidad de tipo entre un + b y u , respectivamente que
entre un - b y v , está comprobado, así como si el tipo de resultado de f es assignable a un . En la
llamada H(F) la compatibilidad entre los parámetros, y que del tipo de resultado del real F y el formal f
está verificado, aquello es, entre x y u y entre y y v . Nota que los identificadores u y v no ocurre en el
programa, exceptúa como los nombres de los parámetros formales del procedimiento formal f. De ahí,
son de hecho superfluous, pero pueden ser útiles como comentarios a el lector si los nombres
significativos están escogidos.
Pascal, Modula y Oberon suponer compatibilidad de nombre como la base para establecer consistencia
de tipo. En el caso de parámetros de procedimiento, una excepción estuvo hecha; sufijos de
compatibilidad estructural. Si compatibilidad de nombre estuvo requerida, el tipo (firma) de cada
procedimiento utilizado como un parámetro real tendría que ser dado un nombre explícito. Esto estuvo
considerado tan demasiado pesado cuándo la lengua estuvo diseñada. Aun así, la compatibilidad
estructural requiere que un compilador ser capaz de comparar dos listas de parámetro para
correspondencia de tipo.
A El procedimiento así puede ser asignado a una variable bajo la condición que las dos listas de
parámetro corresponden. El procedimiento asignado está activado por referir a la variable de
procedimiento. La llamada es indirecta. Esto es de hecho la base de objeto-programación orientada,
donde los procedimientos están atados a los campos de variables récord llamaron objetos. Estos ataron
los procedimientos se apellidan métodos. En contraste a Oberon, métodos, una vez declarados y atados,
no puede ser alterado. Todos los casos de una clase refieren a los mismos métodos.
La implementación de tipos de procedimiento y los métodos resulta para ser sorprendentemente
sencillos, si el problema de compatibilidad de tipo que comprueba está ignorado. El valor de un campo
variable o récord con tipo de procedimiento es sencillamente la dirección de entrada del procedimiento
asignado. Esto aguanta sólo si requerimos que sólo procedimientos globales, aquello es, procedimientos
qué no es embedded en algún contexto, puede ser asignado. Esto fácilmente la restricción aceptable está
explicada con la ayuda del ejemplo siguiente qué incumple esta restricción. A ejecución de Q alias v el
contexto que contiene variables un y b falta.
TIPO T = PROCEDIMIENTO (u:
ENTERO); VAR v: T; r: ENTERO;
PROCEDIMIENTO P;
VAR Un, b: ENTERO;
PROCEDIMIENTO Q(VAR x:
ENTERO); EMPIEZA x := un+b FIN
Q;
EMPIEZA v
:= Q FIN P;
... P; v(r) ...

14.4. Ejercicios.
14.1. Extender la lengua Oberon-0 y su compilador con variedades abiertas:
a. Para unidimensional VAR parámetros,
b. Para multi-dimensional VAR parámetros,
c. Para parámetros de valor.
14.2. Extender la lengua Oberon-0 y su compilador con procedimientos de función:
137
a. Para tipos de resultado escalar (ENTERO, REAL, PUESTO),
b. Para cualquier tipo.

138
14.3. Un módulo seguro M dirige una estructura de dato cuyos detalles son para ser mantuvo escondido.
A pesar de este escondiéndolo tiene que ser posible de aplicar cualquier operación dada P encima todos
los elementos de la estructura. Para este propósito, un procedimiento Enumera está exportado de M , el
cual deja P para ser especificado tan parámetro. Como ejemplo sencillo, escogemos para P el contando
de los elementos actualmente en la estructura de dato y mostrar la solución deseada:
El PROCEDIMIENTO Enumera(P: PROCEDIMIENTO
(e: Elemento)); PROCEDIMIENTO CountElements*;
VAR n: ENTERO;
PROCEDIMIENTO Cnt(e: Elemento); EMPIEZA n := n+1
FIN Cnt; EMPIEZA n := 0; M.Enumera(Cnt);
Textos.WriteInt(W, n, 6) FIN CountElements;
Desafortunadamente, esta solución viola una restricción postulated para la lengua Oberon. La restricción
especifica que los procedimientos utilizaron como los parámetros tienen que ser declarados globalmente.
Esto nos fuerzo para declarar Cnt exterior de CountElements y así también la variable de contador n, a
pesar de que ambos sin duda tener ninguna función global.
Implementa tipos de procedimiento de tal manera que la restricción mencionada puede ser lifted, y que la
solución propuesta es admisible. Qué es el precio?
14.4. Nuestro RISC (ve Ch. 9) presenta un empujón y una instrucción de pop. Suelen implementar el
stack paradigma para marcos de activación del procedimiento. Si añadimos una instrucción de empujón
alternativa PSHu, lo podemos utilizar, junto con el pop de existir instrucción, para implementar fifo
buffers:
PSHu: M[R[b] div 4] := R[un]; INC(R[b], c)
Pero cómo es el concepto de buffer y el uso del buffer instrucciones para ser representadas en la lengua
Oberon-0? Sugerimos el concepto del jinete. Supone que un jinete r y un buffer B está declarado cuando
VAR B: VARIEDAD N DE
ENTERO; r: JINETE EN B;
Y que los jinetes están destinados en registros. Entonces dejado los operadores siguientes ser
definidos en jinetes: SET(r, B, i) jinete Dejado r punto en B[i]
r^ Designa el elemento poined en por r . Después de que acceso o asignación r está
adelantado al elemento próximo del buffer.
Escribir un módulo apropiado con un buffer y procedimientos para fetching y almacenando elementos.
Investigar las ventajas de este concepto, y también sus problemas. Implementa él por adiciones al
compilador.
14.5. Nuestro RISC (ve Ch. 9) está dicho para ser byte-orientó. Pero el dato es siempre movido a y de
memoria en grupos de 4 bytes, llamó palabras. Direcciones de palabra son siempre múltiplos de 4. El
menos dos bits de direcciones están ignorados.
Dejado nos uso de marca del byte que dirige por extender el concepto de jinetes (ve 14.4) a buffers de
bytes (caracteres). Extendemos el RISC la instrucción puesta por las dos instrucciones PSHB y POPB.
Utilizan un registro como 4-byte buffer, y transferir una palabra de de a memoey sólo después de cada
cuarto acceso. La instrucción de pop está definida como sigue:.
POPB: rotate R[Un] por 8 bits a la derecha;
SI R[b] MOD 4 = 0 ENTONCES R[un] := M[R.b DIV
4] FIN ; R[b] := R[b] + 1
Los estados de R[un] y R[b] antes de que y después de cada POPB la instrucción está mostrada abajo.
Después de cada instrucción, el byte próximo es disponible en el menos byte de registro R[un]:
R[Un] R[b] POPB R[Un] R[b]
0 Palabra de DCBA 1
carga
DCBA 1 rotate ADCB 2
139
ADCB 2 rotate MALO 3
C
BADC 3 rotate CBAD 0
CBAD 0 Palabra de ... 1
carga
De modo parecido, la instrucción de empujón está
definida como sigue: PSHB: rotate R[un] por
8 bits a la derecha;
SI R[b] MOD 4 = 3 ENTONCES M[R.b DIV 4] :=
R[un] FIN ; R[b] := R[b] + 1
Antes de cada instrucción de empujón, el byte próximo para ser almacenado está colocado en el menos
byte del buffer registro R[un]. Los estados de R[un] y R[b] antes de que y después de cada POPB la
instrucción está mostrada abajo:
R[Un] R[b] PSHB R[Un] R[b]
. . . Un 0 rotate Un . . . 1
Un . . B 1 rotate BA . . 2
BA . C 2 rotate CBA . 3
CBAD 3 Tienda rotated DCBA 0
palabra
Definir el jinete de byte construye en la lengua Oberon-0 e implementar él por extender el compilador.

140
15. Módulos y Recopilación Separada
15.1. El principio de la información que esconde
Algol 60 introdujo los principios de textuales locality de identificadores y que de limitados lifetime del
identificó objetos durante ejecución. La gama de visibilidad de un identificador en el texto se apellida
alcance (Naur, 1960), y extiende sobre el bloque en qué el identificador está declarado. Según la
sintaxis, los bloques pueden ser nested, con la consecuencia que las reglas aproximadamente la
visibilidad y los alcances tienen que ser refined. Algol 60 postulados que los identificadores declararon
en un bloque B es visible dentro de B y dentro todos los bloques contuvieron en B. Pero son invisibles
en el entorno de B.
De esta regla, el implementer concluye que el almacenamiento tiene que ser destinado a un variable x
lugareño a B apenas el control introduce B, y que el almacenamiento puede ser liberado apenas hojas de
control B. No sólo es x invisible exterior B, pero x cesa para existir cuándo el control es fuera de B. Esto
implica la ventaja significativa que el almacenamiento necesita no quedar destinado a todas las variables
de un programa.
En algunos casos, aun así, la existencia continuada de una variable durante un periodo de invisiblity es
altamente deseable. Variable x entonces parece para reaparecer con su valor anterior apenas control
reenters bloque B. Este caso especial estuvo cubierto en Algol 60 por la característica de variables
propias. Pero esta solución era pronto descubierta para ser bastante insatisfactorio, en particular en
conexión con procedimientos recursivos.
Una solución elegante y altamente útil al propio-el problema estuvo descubierto alrededor 1972 con la
estructura del módulo. Esté adoptado en las lenguas Modula (Wirth, 1977) y Mesa (Mitchell, Maybury y
Golosina, 1978), y más tarde bajo el paquete de nombre en Ada. Syntactically, un módulo se parece a un
procedimiento y consta de local declarations siguió por declaraciones. En contraste a un procedimiento,
aun así, un módulo no es llamado, pero sus declaraciones están ejecutadas una vez sólo, concretamente
cuándo el módulo está cargado. El localmente declaró los objetos son estáticos y mantenerse en vigor
mientras el módulo queda cargado. Las declaraciones en el cuerpo de módulo meramente sirve para
inicializar las variables del módulo. Estas variables son invisibles fuera del módulo; eficazmente están
escondidos. D. L. Parnas Ha acuñado la información de plazo que esconde, y ha devenido una idea
importante en construcción de software. Oberon Presenta la posibilidad de especificar seleccionó los
identificadores declararon en módulos como visibles en el entorno del módulo. Estos identificadores son
entonces dichos para ser exportados.
La variable propia x declaró dentro del Algol procedimiento P ahora será declarado, como P él, lugareño
a un módulo M. P Está exportado, pero no x. En el entorno de M los detalles de la implementación de P
así como el variable x está escondido, pero x retiene su existencia y su valor entre llamadas de P.
El deseo de esconder detalles y objetos seguros es particularmente justificados si un sistema consta de
varias partes cuyas tareas son relativamente bien separados, y si el los separa es de una complejidad
segura. Esto es típicamente el caso en una unidad organizativa qué dirige una estructura de dato.
Entonces la estructura de dato está escondida dentro de un módulo, y es accesible sólo vía exportó
procedimientos. El programador de estos postulados de módulo invariantes seguras, como condiciones de
consistencia, los cuales gobiernan la estructura de dato. Estas invariantes pueden ser guaranteed para
aguantar, porque no pueden ser violados por partes del sistema fuera del módulo. Como consecuencia, la
responsabilidad del programador es eficazmente limitado a los procedimientos dentro del módulo. Esta
encapsulación de los detalles sólo responsables para el especificó las invariantes es el propósito cierto de
la información que esconde y del concepto de módulo.
Ejemplos típicos de los módulos y la información que esconden es el sistema de archivo que esconde la
estructura de archivos y su diccionario, el escáner de un compilador que esconde el texto de fuente y su
lexicographic estructura, o el generador de código de un compilador que esconde el código generado y la
estructura de la arquitectura de objetivo.

15.2. Recopilación separada


Está tentando a postulado que módulos ser nestable como procedimientos. Esta facilidad está ofrecida
141
por ejemplo por la lengua Modula-2. En práctica, aun así, esta flexibilidad difícilmente ha sido fructífera.
Una estructura de módulo plana normalmente sufijos. De ahí, consideramos todos los módulos cuando
siendo globales, y su entorno como el universo.

142
Mucho más pertinente que su nestability es la posibilidad de en desarrollo y compilando módulos por
separado. El último es claramente factible sólo si los módulos son globales, aquello es, no nested. La
razón para esta demanda es sencillamente el hecho que el software nunca es planeado, implementado y
probado en secuencia recta, pero que está desarrollado en pasos, cada paso que incorpora algunas
adiciones o adaptaciones. El software no "es escrito", pero crece. El concepto de módulo es de
importancia fundamental en esta conexión, porque deja desarrollo de módulos individuales por separado
bajo la suposición de interfaces constantes de sus importaciones. El conjunto de exportó los objetos
eficazmente constituye la interfaz de un módulo con sus socios. Si una interfaz queda sin cambios, la
implementación de un módulo puede ser mejorada (y corregido) sin necesitar para adaptar y recompile
los clientes del módulo. Esto es la justificación real para recopilación separada.
La ventaja de este concepto deviene particularmente pertinente si el software está desarrollado por
equipos. Una vez el acuerdo está logrado sobre el partitioning de un sistema a módulos y sobre sus
interfaces, los miembros de equipo pueden proceder independientemente en implementar el módulo
asignó a ellos. Incluso si en practica resulta que cambios más tardíos en la especificación de interfaces es
evitable sólo raramente, la simplificación de teamwork a través del concepto de recopilación separada de
los módulos difícilmente pueden ser overestimated. El desarrollo exitoso de sistemas complejos crucially
depende de el concepto de módulos y su recopilación separada.
Al llegar a este punto, el lector puede creer que todo esto no es nuevo, que la programación
independiente de módulos y su atando por el programa loader, cuando simbolizado en Figura 15.1, ha
sido en común uso desde la era de ensambladores y el primer Fortran compiladores.

Un.Mod
Un.Mo Compilado
Compila Un.obj
Un.o
d rdor bj Linker
Códig
Cód
igo
o
B.Mod Compila
Compilado B.obj
rdor

Figura 15.1. Recopilación independiente de módulos Un y B.


Aun así, esto ignora el hecho que los lenguajes de programación más altos ofrecen protección aumentada
significativamente contra equivocaciones e incongruencias a través de su concepto de tipo estático. Este
inestimable - pero todo demasiado a menudo underestimated beneficio - está barrido aparte si controles
de consistencia del tipo son guaranteed sólo dentro de módulos, pero no a través de fronteras de módulo.
Esto implica que información de tipo aproximadamente todo importó los objetos tienen que ser
disponibles siempre que un módulo está compilado. En contraste a recopilación independiente (Figura
15.1), donde esta información no es disponible, recopilación cuando mostrado en Figura 15.2 con
controles de consistencia del tipo a través de fronteras de módulo se apellida recopilación separada.
Información sobre el importó los objetos es esencialmente un excerpt de la mesa de símbolo cuando
presentado en Capítulo 8. Este excerpt de la mesa de símbolo, transformado a una forma secuencial, se
apellida un archivo de símbolo. Recopilación de un módulo Unas qué importaciones (objetos de)
módulos B1 ... Bn Ahora requiere, además del texto de fuente de Un, los archivos de símbolo de B1 ...
Bn. Y además del código de objeto (Un.obj) También genera un archivo de símbolo (Un.sym).

Compilado Un.sym
Un.Mod r
Un.obj Linker
Códig
o
Compilador B.sym
B.Mod
B.obj

Figura 15.2. Recopilación separada de módulos Un y B.

143
15.3. Implementación de archivos de símbolo
Del foregoing consideraciones primero podemos concluir aquella recopilación de la lista de importación
de un módulo causa un archivo de símbolo para ser leído para cada identificador de módulo en la lista. La
mesa de símbolo del módulo compilado está inicializada por los archivos de símbolo importados.
Segundo, sigue que al final de recopilación la mesa de símbolo nueva es traversed, y un archivo de
símbolo es producción con una entrada que corresponde a cada elemento de mesa del símbolo marcado
para exportación. Figura 15.3 espectáculos como un ejemplo el pertinente excerpt de la mesa de símbolo
durante recopilación de un módulo Un importador B. Dentro de B, T y f está marcado con un asterisco
para exportación.

Un
MÓDULO Un;
IMPORTACIÓN
B; VAR x: B.T; Tipo
x
EMPIEZA x.f
:= 1;
… FIN
Un

Importaciones

MÓDULO B T Récord
B; TIPO T*
=
RÉCORD f*: FIN … de f
ENTERO; EMPIEZA …
FIN B

Figura 15.3. Mesa de símbolo de Un con importaciones de B.


Dejado nos primero considerar la generación del archivo de símbolo M.sym De un módulo M. A primera
vista, la tarea meramente consta de traversing la mesa y emitting una entrada que corresponde a cada
elemento marcado en un apropiadamente sequentialized forma. La mesa de símbolo es esencialmente
una lista de objetos con los punteros a tipo estructura cuáles son árboles . En este caso el
sequentialization de las estructuras que utilizan un prefijo característico para cada elemento es quizás la
técnica más apropiada. Está ilustrado por un ejemplo en Figura 15.4.
VAR x: VARIEDAD 10 DE ENTERO;
b: VARIEDAD 8 DE VARIEDAD 20 DE REAL

Tipo Var Archivo de símbolo


de “x” Variedad Integ
nom de
Base
forma Var x Variedad 10 Integ -40
bre -40
de la len 10
clase
adr
luego

144
Var Var y Variedad 8 Variedad 20 Real -
“y” Varie Varie 1000
dad dad
-1000 Real
8 20

Figura 15.4. Sequentialized Forma de una mesa de símbolo con dos variedades.

145
Un problema surge porque cada objeto contiene al menos un puntero que refiere a su tipo. Escribiendo
valores de puntero a un archivo es problemáticos, para decir el menos. Nuestra solución consiste en
escribir la descripción de tipo al archivo el primer tiempo está encontrado cuándo escaneando la mesa de
símbolo. Así la entrada de tipo es marcada y obtiene un número de referencia único. El número está
almacenado en un campo récord adicional del tipo ObjectDesc. Si el tipo es referenced otra vez más
tarde, el número de referencia es producción en vez de la estructura.
Esta técnica no sólo evita la escritura repetida de las mismas descripciones de tipo, pero también
soluciona el problema de referencias recursivas, cuando mostrados en Figura 15.5.
TIPO P = PUNTERO A R;
R = RÉCORD x, y: ENTERO; luego: P FIN

Tipo Tipo
de “P” Pntr
nom For
bre ma
Bas Rec Fld Fld Fld
de la “x” “y” Lue
clase e 4
len go
adr 8 0 4 8
luego CE
Tipo Int RO
“R”
CE
RO
4

Figura 15.5. Cyclic Referencia de nodo de tipo.


Los valores positivos están utilizados para números de referencia. Cuando una indicación que el número
de referencia está utilizado por primera vez, y que es por tanto inmediatamente seguido por la
descripción de tipo, el número está dado una señal negativa . Mientras leyendo un archivo de símbolo,
una mesa de tipo T está construido con referencias a las estructuras de tipo respectivas. Si un número de
referencia positivo r está leído, T[r] es el puntero necesitado; si r es negativo, el dato de tipo subsiguiente
está leído, y el puntero que refiere al descriptor nuevamente construido está asignado a T[-r].

main
C VAR y: Un.T
“C” Var
y := B.x
“y”
Anclas
B VAR x*: Un.T
“B” Var
“x”

Un TIPO T =
REGISTRO “Un Tipo
... ” T Rec

Figura 15.6. Re_Exportación de escribir Un.T De módulo B.


Información de tipo puede, en contraste a dato sobre otros objetos, ser importados y al mismo tiempo ser

146
re- exportó. Por lo tanto es necesario de especificar el módulo de qué el tipo exportado raíces. Para hacer
posible esto, utilizamos un tan-ancla de módulo llamado. En el encabezando de cada archivo de símbolo
allí es una lista

147
De objetos de ancla, uno para cada módulo importado qué es re_exportado, aquello es,. Cuál contiene un
tipo que es referenced por un objeto exportado. Figura 15.6 ilustra tal situación; módulo C módulos de
importaciones Un y B, por el cual un variables x está importado de B cuyas raíces de tipo de Un. El
control de compatibilidad del tipo para una asignación como y := x restos en la suposición que los
punteros de tipo de x y y ambos refieren al mismo descriptor de tipo. Si ellos no, un error está indicado.
Por ello concluimos que a recopilación de un módulo M, no sólo las mesas de símbolo del
explícitamente importó los módulos tienen que ser presentes, pero también aquellos de los módulos de
qué tipos son referenced tampoco directamente o indirectamente. Esto es una causa para preocupación,
porque la recopilación de cualquier módulo puede necessitate la lectura de archivos de símbolo de
jerarquías de módulo entero. Incluso pueda lograr abajo al nivel más profundo de un entorno operativo,
de donde tampoco las variables ni los procedimientos están importados, pero quizás sólo un tipo solo. El
resultado no sólo ser el superfluous cargando de cantidades grandes de datos, pero también unos
residuos de mucho espacio de memoria. Resulta, que a pesar de que nuestra preocupación está
justificada, las consecuencias son mucho menos dramáticas que podría ser esperado (Franz, 1993). La
razón es que la mayoría de mesas de símbolo pidieron es presente ya para otras razones. Como
consecuencia, el esfuerzo adicional queda pequeño. No obstante vale pondering sobre la posibilidad de
evitar el esfuerzo extra. De hecho, los primeros compiladores para Modula y Oberon ha adoptado la
técnica siguiente.
Dejado un módulo M tipos de importación de módulos M0, M1, y tan encima, tampoco directamente o
indirectamente. La solución consta de incluir en el archivo de símbolo de M descripciones completas del
importó tipos, así evitando referencias a los módulos secundarios M0, M1, y tan encima. Aun así, esto
solución bastante obvia complicación de causas. En el ejemplo ilustrado por Figura 15.6, el archivo de
símbolo de B evidentemente contiene una descripción completa de tipo T. El control de consistencia para
la asignación y := B.x, para ser altamente eficaz, meramente compara dos punteros de tipo. La
configuración mostrada en el correcto de Figura 15.6 mosto por tanto ser presente después de cargar.
Esto implica que en archivos de símbolo re-exportados escribe no sólo especificar su módulo de casa,
pero que cuando cargando un símbolo archiva una prueba tiene que verificar si o no el tipo leído es ya
presente. Esto puede ser el caso porque el archivo de símbolo del módulo que define el tipo ya ha sido
cargado, o porque el tipo ya ha sido leído cuándo cargando otros archivos de símbolo.
Al llegar a este punto también mencionamos otro, complicación pequeña en conexión con tipos que surge
porque los tipos pueden aparecer bajo nombres diferentes (alias). A pesar de que el uso de alias es raro, la
definición de lengua (desafortunadamente) lo deja. Son moderadamente significativos sólo si el
synonyms raíz de módulos diferentes, cuando mostrados en Figura 15.7.

TIPO T1 = Un.T0
B Typ
“T1”

U Typ
n “T0” obj
Tip
o

Figura 15.7. Tipo con alias.


Cuándo cargando el archivo de símbolo de B está reconocido que B.T1 y Un.T0, ambos señalando a un
objeto de tipo, el mosto de hecho señala al mismo descriptor de objeto. Para determinar cuál de los dos
descriptors tendría que ser discarded y cuál retuvo, nodos de tipo (Estructura de tipo) está suministrado
con un atrás-puntero al objeto de tipo original (Objeto de tipo), aquí a T0.

148
15.4. Dirigiendo objetos externos
La ventaja principal de recopilación separada es que cambios en un módulo M no invalida clientes de M,
si la interfaz de M restos no afectados. Recordamos que la interfaz consta de el conjunto entero de
exportado declarations. Cambia cuáles no afectan la interfaz puede ocurrir, así que para decir, en secreto,
y sin programadores de cliente que son conscientes de ellos. Tales cambios ni siquiera tienen que requerir
recompilation de los clientes que utilizan archivos de símbolo nuevo. Por el bien de honradez, acuciamos
para añadir aquello exportó mosto de procedimientos en su semantics no ha alterado, porque los
compiladores no podrían detectar tales cambios reliably. De ahí, si decimos que una interfaz queda sin
cambios, explícitamente referimos al declarations de tipos y variables, y a las firmas de procedimientos, y
sólo implícitamente a su semantics.
Si en un módulo seguro no-exportó los procedimientos y las variables están cambiados, añadidos o
eliminados, sus direcciones necesariamente también cambio, y como consecuencia así que aquellos de
otro, posiblemente exportó variables y procedimientos. Estas ventajas a un cambio de la mesa de
símbolo, y así también a una invalidación de módulos de cliente. Pero esto evidentemente contradice los
requisitos postulated para recopilación separada.
La solución a estas mentiras de dilema en evitar la inclusión de direcciones en un archivo de símbolo.
Esto tiene la consecuencia que las direcciones tienen que ser computadas cuándo cargando y atando un
módulo. De ahí, además de su dirección (para módulo-uso interno), un objeto exportado está dado un
número único . Este número supone el sitio de la dirección en el archivo de símbolo. Típicamente, estos
números están destinados estrictamente sequentially.
Como consecuencia, cuándo compilando un cliente, módulo único los números concretos son
disponibles, pero ninguna dirección. Estos numera mosto, cuando mencionó antes, ser convertido a
direcciones absolutas a cargar. Para esta tarea, conocimiento sobre las posiciones de tales campos de
dirección incompletos tienen que ser disponibles. En vez de suministrador el archivo de objeto con una
lista de todas las ubicaciones de tales direcciones, los elementos de este fixup la lista es embedded en las
instrucciones en el muy sitios del todavía desconocidos addreses. Estos espejos la técnica utilizada para
la conclusión de direcciones de adelante saltos (ve Capítulo 11). Si todas tales direcciones para ser
completados está recogido en un solo fixup lista, entonces esto corresponde a la Figura 15.8 (un). Cada
elemento tiene que ser identificado con un par que consta de un número de módulo (mno) y un número
de entrada (eno). Es más sencillo de proporcionar una lista separada para cada módulo. En el archivo de
objeto, no justo un solo fixup raíz, pero uno para cada lista está requerido. Esto corresponde a la Figura
15.8 (b). Parte (c) muestra la solución extrema donde un separado fixup la lista está especificada para
cada objeto importado. Cuál del tres presentó las soluciones está adoptada, depende de cuánta
información puede ser puesta al sitio de una dirección absoluta, por qué es finalmente reemplazó.

B.Q B.Q B.Q

B.x B.x B.x

B.Q B.Q B.Q

Un. Un. Un.


P P P

Un. Un. Un.


y y y
Un B Un.y Un.P B.Q B.x

mno eno eno

Lista de toda lista de importaciones por lista de módulo por objeto

149
Figura 15.8. Tres formas de fixup listas en archivos de objeto.

150
15.5. Comprobando consistencia de configuración
Pueda parecer tardío si ahora posamos la cuestión: Por qué es archivos de símbolo introdujeron en
absoluto? Dejado nos suponer que un módulo M es para ser compilado qué importaciones M0 y M1. Una
solución bastante sincera sería a recompile M0 y M1 inmediatamente precediendo la recopilación de M,
y para unir las tres mesas de símbolo obtuvieron. Las recopilaciones de M0 y M1 fácilmente podría ser
provocado por la recopilación de M leyendo la lista de importación.
A pesar de que la recopilación repetida del mismo texto de fuente es unos residuos de tiempo, esta
técnica está utilizada por varios compiladores comerciales para (extendidos) Pascal y Modula. El serio
shortcoming inherente en este método, aun así, no es tanto el esfuerzo adicional necesitó, pero la carencia
de una garantía para la consistencia del ser de módulos ató. Dejado nos suponer que M es para ser
recompiled después de que algunos cambios habían sido hechos en el texto de fuente. Entonces es
bastante probablemente que después de que la formulación original de M y después de su recopilación,
los cambios también han sido hechos a M0 y M1. Estos cambios pueden invalidar M. Quizás incluso las
versiones de fuente de M0 y M1 actualmente disponibles al programador de cliente M ya no comply con
los archivos de objeto reales de M0 y M1. Este hecho, aun así, no puede ser determinado por una
recopilación de M, pero casi ciertamente dirige a desastre cuándo el inconsistent las partes están atadas y
ejecutó.
Archivos de símbolo, aun así, no permite a cambios les gustan archivos de fuente; están codificados y no
visibles a través de un editor de texto. Sólo pueden ser reemplazados globalmente. Para establecer
consistencia, cada archivo de símbolo está proporcionado con una llave única. Archivos de símbolo así
prpers hacen para hacer los módulos disponibles sin dar fuera el texto de fuente. Un cliente puede confiar
en la definición de interfaz especificada y, gracias a la llave, la consistencia de la definición con las
implementaciones presentes es también guaranteed. A no ser que esta garantía está proporcionada, la idea
entera de los módulos y la recopilación separada es quizás enticing, pero difícilmente una herramienta
útil.

B PROC P; C B.P;
EMPEZAR
Un.Q(Un,b) 9144
FIN P 9691

U PROC Q(x, y: REAL); Un PROCF Q(x,y,z: REAL);


n EMPIEZA … EMPIEZA …
FIN Q 8325 FIN Q 8912

Figura 15.9. Incongruencia de versiones de módulo.


Cuando un ejemplo, Figura 15.9 espectáculos en su lado izquierdo la situación a recopilación de módulo
B, y en el lado correcto que a recopilación de módulo C. Entre las dos recopilaciones, Un estuvo
cambiado y recompiled. Los archivos de símbolo de B y C por tanto contener anclas de módulo de Un
con diferir llaves, concretamente 8325 en B y 8912 en C. El compilador comprueba las llaves, nota la
diferencia, y emite un mensaje de error. Si, aun así, módulo Un está cambiado después del recompilation
de C (con interfaz cambiada), entonces la lata de incongruencia y tiene que ser detectado a cargar y
atando la configuración de módulo. Para este propósito, las mismas llaves son también incluidas en los
archivos de objeto respectivos. Por lo tanto es posible de detectar la incongruencia de la importación de
Un en B y C antes de que la ejecución está intentada. Esto es absolutamente esencial.
El clave y el nombre está tomado como el par característico de cada módulo, y este par está contenido en
el encabezando de cada símbolo y archivo de objeto. Tan ya mencionado, los nombres de los módulos en
la lista de importación son también supplemented por su llave. Esta ventaja de consideraciones a la
estructura de símbolo archiva tan especificada en Apéndice Un.3.
Llaves de módulo de la Unique pueden ser generadas por varios algoritmos. El más sencillo es quizás el
uso de tiempo actual y datar cuál, suitably codificó, cosecha la llave deseada. Un drawback es que este
método no es enteramente fiable. Incluso si la resolución del reloj es una segundo, recopilaciones
151
simultáneas en diferentes

152
Los ordenadores pueden generar la misma llave. Un poco más significativo es el argumento que dos
recopilaciones del mismo texto de fuente siempre tendría que generar la misma llave; pero ellos no. De
ahí, si un cambio está hecho en un módulo qué es más tarde detectado para ser en error, recompilation de
la versión original no obstante resultados en un nuevos claves cuál deja los clientes viejos aparecen
cuando invalidó.
Un método mejor para generar una llave es para utilizar el símbolo se archiva tan argumento, gusta en la
computación de un checksum. Pero este método es también no enteramente seguro, porque archivos de
símbolo diferente pueden resultar en la misma llave. Pero presenta la ventaja que cada recompilation del
mismo texto genera la misma llave. Las llaves computaron de este modo se apellida fingerprints.

15.6. Ejercicios
15.1. Incorpora recopilación separada a vuestro Oberon-0 compilador. El langauge está extendido para
incluir una lista de importación (ve Apéndice Un.2) y un marcador en el identificador exportado
declaration. Uso la técnica de archivos de símbolo e introducir la regla que exportó las variables no
pueden ser asignadas valora de exterior, aquello es, en módulos importadores que está considerado para
ser leído-variables únicas.
15.2. Implementar un fingerprint facilidad para generar llaves de módulo.

153
16. Optimizaciones y el Frontend/Backend Estructura
16.1. Consideraciones generales
Si analizamos el código generado por el compilador desarrollado en los capítulos de preceder, fácilmente
podemos ver que es correcto y bastante sincero, pero en muchos casos también improvable. La razón
principalmente mentiras en el directness del algoritmo escogido qué traduce la lengua construye
independientemente de su contexto a patrones fijos de secuencias de instrucción. Difícilmente percibe
casos especiales y no les aprovecha. El directness de estas ventajas de esquema a resultados que es sólo
parcialmente satisfactorio según lo que la economía de almacenamiento y velocidad de ejecución está
preocupada. Esto no está sorprendiendo, cuando fuente y lenguas de objetivo no corresponden en
maneras sencillas. En esta conexión podemos observar el vacío semántico entre lenguaje de
programación por un lado y conjunto de instrucción y arquitectura de máquina en el otro.
Para generar código qué utiliza las instrucciones disponibles y recursos de máquina más eficazmente,
traducción más sofisticada los esquemas tienen que ser empleados. se apellidan optimizaciones, y los
compiladores que les utilizan está dicho para ser optimizando compiladores. Tenga que ser señalado
fuera que este plazo, a pesar de que en uso extendido, básicamente es un eufemismo. Nadie sería
dispuesto de reclamar que el código generado por ellos podrían ser optimal en todos los casos, aquello
es, de ningún modo improvable. El así que_llamó las optimizaciones son nada más de mejoras. Aun así,
nosotros comply con el vocabulario común y también utilizará la optimización de plazo.
Es bastante evidente que el más sofisticado el algoritmo, el mejor el código obtuvo. En general pueda
ser reclamado que el mejor el código generado y el más rápido su ejecución, el más complejo, más
grande y más lento será el compilador. En algunos casos, los compiladores han sido construidos qué dejar
una elección de un nivel de optimización: mientras un programa es debajo desarrollo, un bajo, y después
de su conclusión un alto, el grado de optimización está seleccionado para recopilación. Cuando un aparte,
nota que la optimización puede ser seleccionada con objetivos diferentes, como hacia ejecución más
rápida o hacia código más denso. Los dos criterios normalmente requieren algoritmos de generación de
código diferentes y es a menudo contradictorio, una indicación clara que hay no tal cosa como bien-
definido óptimo.
Difícilmente está sorprendiendo que las medidas seguras para mejora de código pueden ceder beneficios
considerables con esfuerzo modesto, mientras que otros pueden requerir aumentos grandes en
complejidad de compilador y medida mientras cediendo mejoras de código moderadas únicas,
sencillamente porque aplican en casos raros sólo. De hecho, hay diferencias enormes en la proporción
de esfuerzo para obtener. Antes del diseñador de compilador decide incorporar instalaciones de
optimización sofisticada, o antes de decidir para adquirir un altamente optimizando, compilador lento y
caro, vale mientras aclarando esta proporción, y si el prometió las mejoras son verdaderamente necesitó.
Además, tenemos que distinguir entre optimizaciones cuyos efectos también podrían ser obtenidos por
una formulación más apropiada del programa de fuente, y aquellos donde esto es imposible. La primera
clase de la optimización principalmente sirve el untalented o sloppy programador, pero meramente carga
todo los otros usuarios a través de la medida aumentada y decreased velocidad del compilador. Cuando
un ejemplo extremo, considerar el caso de un compilador qué elimina una multiplicación si un factor
tiene el valor 1. La situación es completamente diferente para la computación de la dirección de un
elemento de variedad, donde el índice tiene que ser multiplicado por la medida de los elementos. Aquí, el
caso de una medida igual a 1 es frecuente, y la multiplicación no puede ser eliminada por un truco listo
en el programa de fuente.
Un criterio más lejano en la clasificación de instalaciones de optimización es si o no dependen de una
arquitectura de objetivo dada. hay mide cuáles pueden ser explicados sólo en plazos de la lengua de
fuente, independiente de cualquier objetivo. Ejemplos de apuntar_las optimizaciones independientes
están sugeridas por el siguiendo bien identidades sabidas:
x+0=x
x*2=x+x
b & CIERTO = b
b & ~b = FALSO

154
SI CIERTO ENTONCES Un MÁS B
ACABAR = Un SI FALSO
ENTONCES Un MÁS B FIN = B
Por otro lado, hay optimizaciones que está justificado sólo a través de las propiedades de una
arquitectura dada. Por ejemplo, los ordenadores existen cuáles combinan una multiplicación y una
adición, o una adición, una comparación y una rama condicional en una instrucción sola. Un compilador
entonces tiene que reconocer el patrón de código qué deja el uso de un instrucción tan especial.
Finalmente, también tenemos que señalar fuera que el más optimizaciones con sizeable efectos que puede
ser incorporado en un compilador, el más pobre su versión original tiene que haber sido. En esta
conexión, las estructuras pesadas de muchos compiladores comerciales, cuyo origen es difícil a fathom,
ventaja a sorprendentemente rendimiento inicial pobre, el cual hace optimizar las características parecen
absolutamente indispensible.

16.2. Optimizaciones sencillas


Primero, dejado nos considerar optimizaciones que es implementable con esfuerzo modesto, y cuáles por
tanto son prácticamente obligatorios. Esta categoría incluye los casos cuáles pueden ser reconocidos por
inspección del contexto inmediato. Un ejemplo primo es la evaluación de expresiones con constantes.
Esto se apellida constante plegable y es ya contenido en el compilador presentó.
Otro ejemplo es multiplicación por un poder de 2, los cuales pueden ser reemplazados por un cambio
sencillo, eficaz instrucción. También, este caso puede ser reconocido sin considerar cualquier contexto:
SI (y.Modo = Const) & (y.Un # 0)
ENTONCES n := y.Un; k := 0;
MIENTRAS ~EXTRAÑO(n) n := n DIV 2; k := k+1 FIN ;
SI n = 1 ENTONCES PutShift(x, k) MÁS PutOp(MUL, x, y)
ACABA MÁS ...
FIN
División (de enteros) está tratado en la misma manera. Si el divisor es 2k para algún entero k, el
dividendo es meramente cambiado k bits a la derecha. Para el modulo operador, el menos significativo
k los bits son sencillamente enmascarados fuera.

16.3. Evitando evaluación repetida


Quizás el caso sabido mejor entre el objetivo las optimizaciones independientes es la eliminación de
comunes subexpressions. A primera vista, este caso puede ser clasificado entre las optimizaciones
electivas, porque el reevaluation del mismo subexpression puede ser conseguido por un cambio
sencillo del programa de fuente. Por ejemplo, las asignaciones
x := (Un+b)/c; y := (un+b)/d
Fácilmente puede ser reemplazado por tres asignaciones más sencillas cuándo
utilizando un auxiliares variables u: u := un+b; x := u/c; b := u/d
Ciertamente, esto es una optimización con respetar al número de operaciones de aritmética, pero no con
respetar al número de asignaciones o la claridad del texto de fuente. Por lo tanto los restos de cuestión
abren tan a si este cambio constituye una mejora en absoluto.
Más crítico es el caso donde la mejora es imposible de conseguir por un cambio del texto de fuente,
cuando está mostrado en el ejemplo siguiente:
Un[i, j] := un[i, j] + b[i, j]
Aquí, la misma computación de dirección está actuada tres tiempo , y cada vez implica al menos una
multiplicación y una adición. El común subexpressions es implícito y no directamente visible en la
fuente. Una optimización puede ser actuada sólo por el compilador.
La eliminación de expresiones comunes es valor único mientras si están evaluados repetidamente. Esto
incluso puede ser el caso si la expresión ocurre sólo una vez en la fuente:

155
MIENTRAS i > 0 z := x+y; i := i-1 FIN
Desde x y y quedar sin cambios durante la repetición, la necesidad de suma ser computado sólo una vez.
El compilador tiene que estirar la asignación a z fuera del bucle. El plazo técnico para esta hazaña es
bucle movimiento de código invariable.
En todos los casos últimos el código sólo puede ser mejorado por análisis selectivo de contexto. Pero esto
es precisamente lo que aumentos el esfuerzo durante recopilación muy significativamente. El compilador
presentado para Oberon-0 no constituye una base adecuada para esta clase de optimización..
Relacionado al sacando de las expresiones constantes de bucles es la técnica de simplificar expresiones
por utilizar los valores computaron en la repetición anterior, aquello es, por considerar recurrence
relaciones. Si, por ejemplo, la dirección de un elemento de variedad está dada por adr(un[i]) = k*i +
un0, entonces adr(un[i+1]) = adr(un[i])
+ k. Este caso es particularmente frecuente y por tanto pertinente. Para caso, las direcciones del indexed
variables en la declaración
PARA i := 0 A N-1 HACE un[i] := b[i] * c[i] FIN
Puede ser computado por una adición sencilla de un constante a sus valores anteriores. Estas ventajas de
optimización a reducciones significativas en tiempo de computación. Una prueba con el ejemplo
siguiente de una multiplicación matricial mostró sorprender resultados:
PARA i := 0 A 99
PARA j := 0 A 99
PARA k := 0 A 99 un[i, j] := un[i, j] + b[i, k] * c[k, j] FIN
de FIN
FIN
El uso de registros en vez de ubicaciones de memoria para aguantar valores de índice y sumas, y la
eliminación de índice ató las pruebas resultaron en una velocidad aumentada por un factor de 1.5. La
sustitución de indexed dirigiendo por progresión lineal de direcciones cuando describió encima cedió un
factor de 2.75. Y el uso adicional de una multiplicación combinada e instrucción de adición para
computar los productos escalares aumentaron el factor a 3.90.
Desafortunadamente, ni siquiera consideración de sufijos de información de contexto sencillos en este
caso. Un control sofisticado y análisis de flujo del dato está requerido, así como detección del hecho que
en cada repetición un índice es incremented monotonically por 1.

16.4. Asignación de registro


El tema dominante en el tema de optimización es el uso y asignación de registros de procesador. En el
Oberon-0 compilador presentó los registros están utilizados exclusivamente para aguantar resultados
intermedios anónimos durante la evaluación de expresiones. Para este propósito, normalmente unos
cuantos registros bastan. Procesadores modernos, aun así, característica un número significativo de
registros con tiempo de acceso considerablemente más corto que que de memoria principal. Utilizándoles
para los resultados intermedios sólo implicarían una utilización pobre de los recursos más valiosos. Un
objetivo primario de optimización de código bueno es el uso más eficaz de registros para reducir el
número de accesos a la memoria principal relativamente lenta. Una estrategia buena de uso de registro
cede más ventajas que cualquiera otra rama de optimización..
Una técnica extendida es asignación de registro utilizando graph coloración. Para cada valor que ocurre
en una computación, aquello es, para cada expresión el punto de su generación y el punto de su último
uso está determinado. Delimitan su gama de pertinencia. Evidentemente, los valores de expresiones
diferentes pueden ser almacenados en el mismo registro, si y sólo si sus gamas no overlap. Las gamas
están representadas por los nodos de un graph, en qué un borde entre dos nodos signifies que las dos
gamas overlap. La asignación de N registros disponibles a los valores de ocurrir entonces pueden ser
entendidos como la coloración del graph con N colores de tal manera que los nodos vecinos siempre
tienen colores diferentes. Esto implica que valores con overlapping las gamas son siempre destinadas a
registros diferentes.
Además, seleccionado, variables escalaras , locales son ya no destinadas en memoria en absoluto, sino en
dedicó registros. Para acercarse un optimal utilización de registro, los algoritmos sofisticados están
156
empleados para determinar qué variables están accedidas más frecuentemente. Evidentemente, el
necesario

157
bookkeeping Sobre los accesos variables crece, y así velocidad de recopilación padece. También, el
cuidado tiene que ser tomado que valores de registro están salvados en memoria antes de que llamadas de
procedimiento y está restaurado después del regreso de procedimiento. El lurking el peligro es que el
esfuerzo necesario para esta tarea supera los ahorros obtuvieron. En muchos compiladores, las variables
locales están destinadas a registros sólo en procedimientos que no contienen cualquiera los llama
(procedimientos de hoja), y cuáles por tanto son también llamados más frecuentemente, cuando
constituyen las hojas en el árbol que representa la jerarquía de llamada del procedimiento.
Un tratamiento detallado de todos estos problemas de optimización es allende el alcance de un texto
introductorio aproximadamente construcción de compilador. El encima el esbozo por tanto bastará. De
todas formas tales técnicas hacen aclare que para un casi optimal generación de código
significativamente más información aproximadamente el contexto tiene que ser considerado que es el
caso en nuestro relativamente sencillo Oberon-0 compilador. Su estructura no es bien-convenido a
conseguir un grado alto de optimización. Pero sirve excellently como el compilador rápido que produce
bastante aceptable, a pesar de que no optimal código, cuando es apropiado en la fase de desarrollo de un
sistema, particularmente para propósitos educativos. Sección 16.5 indica otro, un poco compilador más
complejo estructura cuál es mejor convenido para la incorporación de algoritmos de optimización.

16.5. El frontend/backend estructura de compilador


La característica más significativa del compilador desarrollado en Capítulos 7 _ 12 es que el texto de
fuente está leído exactamente una vez. El código es así generado en la mosca. En cada punto, la
información sobre los operandos está restringida a los elementos que denotan el operando y a la mesa de
símbolo que representa declarations. El tan- llamado frontend/backend estructura de compilador, el cual
era brevemente mencionado en Capítulo 1, deviates decisivamente al respecto. El frontend la parte
también lee el texto de fuente una vez sólo, pero en vez de generar código construye una estructura de
dato que representa el programa en una forma suitably organizó para procesamiento más lejano. Toda
información contenida en las declaraciones es mapped a esta estructura de dato. se apellida un árbol de
sintaxis, porque él también espejos la estructura sintáctica del texto. Un poco oversimplifying la
situación, podemos decir que el frontend compila declarations a la mesa de símbolo y declaraciones al
árbol de sintaxis. Estos dos estructuras de dato constituyen la interfaz al backend parte cuya tarea es
generación de código . El árbol de sintaxis deja acceso rápido a prácticamente todas las partes de un
programa, y representa el programa en un preprocessed forma. El proceso de recopilación resultante está
mostrado en Figura 16.1.

Program
Declarations a Declaracio
nes

Fin de
frente

Mesa de Árbol de
símbolo sintaxis

Fin
posterior

Códi
go

Figura 16.1. El compilador que consta de fin de frente y fin posterior


Señalamos fuera uno ventaja significativa de esta estructura en Capítulo 1: el partitioning de un
compilador en un objetivo-fin de frente independiente y un objetivo-fin posterior dependiente. En el
158
siguiente, centramos en la interfaz entre las dos partes, concretamente la estructura del árbol de sintaxis.
Además, muestramos cómo el árbol está generado.

159
Exactamente tan en un programa de fuente donde las declaraciones refieren a declarations, tan hace el
árbol de sintaxis refiere a entradas en la mesa de símbolo. Esto da aumento al understandable deseo de
declarar los elementos de la mesa de símbolo (objetos) en tal moda que es posible de referir a ellos del
símbolo someten él así como del árbol de sintaxis. Tipo tan básico introducimos el tipo Objeta cuáles
pueden suponer formas diferentes como apropiados de representar constantes, variables, tipos, y
procedimientos. Sólo el tipo de atributo es común a todo. Aquí y posteriormente hacemos uso de Oberon
característica extensión de tipo llamado (Reiser y Wirth, 1992).
Objeto = PUNTERO A ObjDesc;
ObjDesc = Tipo RÉCORD: FIN de Tipo ;
ConstDesc = REGISTRO (ObjDesc) valor: LONGINT FIN ;
VarDesc = REGISTRO (ObjDesc) adr, nivel: LONGINT FIN
;
La mesa de símbolo consta de listas de elementos, uno para cada alcance (ve Sección 8.2). Los elementos
constan de el nombre (identificador) y una referencia al objeto identificado.
Ident = PUNTERO A IdentDesc;.
IdentDesc = Nombre RÉCORD: VARIEDAD 32 DE CHAR;.
obj: Objeto; luego:
Ident FIN ;
PUNTERO = de alcance A ScopeDesc;.
ScopeDesc = GRABA primero: Ident; dsc: FIN de Alcance ;
El árbol de sintaxis es más concebido como árbol binario. Llamamos sus Nodos de elementos. Si un
sintáctico construye tiene la forma de una lista, está representado como árbol degenerado en qué el
último elemento tiene una rama vacía.
PUNTERO = de nodo A
NodeDesc; NodeDesc = REGISTRO
(Objeto)
op: ENTERO;
A la izquierda,
bien: FIN de Objeto
Dejado nos considerar el siguiente breve excerpt de un texto de programa
como un ejemplo: VAR x, y, z: ENTERO;
EMPIEZA z := x + y - 5; ...
El fin de frente parses el texto de fuente y construye la mesa de símbolo y el árbol de sintaxis cuando mostrado en
Figura
16.2. Las representaciones de tipos de datos están omitidas.

Raíz

:=

topScope + 5
Variable

Alcance Ident
“z” “x” “y”

Figura 16.2. Mesa de símbolo (abajo) y árbol de sintaxis (encima).

160
Representaciones de llamadas de procedimiento, el SI y MIENTRAS las declaraciones y la secuencia de
declaración están mostradas en Figuras 16.3 - 16.5.

161
Llam
ada
P par par par

Un b c

Figura 16.3. Llamada de procedimiento.

SI MIENTR
AS

b0 S0 SI b S

b1 S1 S2

Figura 16.4. SI y MIENTRAS declaraciones.

; ; ;

S0 S1 Sn

Figura 16.5. Secuencia de declaración.

Para concluir, los ejemplos siguientes demuestran cómo las estructuras de dato descritas están generadas.
El lector tendría que comparar este compilador excerpts con los procedimientos correspondientes del
Oberon-0 compilador listado en Apéndice C. Todo uso de marca de algoritmos subsiguiente del
procedimiento auxiliar Nuevo, el cual genera un nodo nuevo.
El PROCEDIMIENTO Nuevo(op: ENTERO; x, y:
Objeto): Elemento; VAR z: Elemento;
EMPIEZA Nuevo(z); z.op := op; z.A la izquierda := x;
z.Correcto := y; REGRESO z ACABA Nuevo;
Factor de
PROCEDIMIENTO():
Objeto; VAR x: Objeto; c:
Constante;
EMPIEZA
SI sym = ident ENTONCES x := Este(nombre); Consigue(sym); x := selector(x)
ELSIF sym = Numera ENTONCES NUEVO(c); c.Valor := número;
Consigue(sym); x := c ELSIF sym = lparen ENTONCES Conseguir(sym);
x := expresión();
SI sym = rparen ENTONCES Conseguir(sym) MÁS
Mark(22) FIN ELSIF sym = no ENTONCES Conseguir(sym); x
:= Nuevo(no, CERO, factor()) MÁS ...
FIN ;
REGRESO
x
Factor de FIN;
162
Plazo de PROCEDIMIENTO():
Objeto; VAR op: ENTERO; x:
Objeto;
EMPIEZA x := factor();
MIENTRAS (sym >= tiempo) & (sym <= y)
op := sym; Consigue(sym); x := Nuevo(op, x,
factor()) FIN ;
REGRESO x
Plazo de FIN;
Declaración de
PROCEDIMIENTO(): Objeto;
VAR x: Objeto;
EMPIEZA
SI sym = ident ENTONCES
x := Este(nombre); Consigue(sym); x := selector(x);
SI sym = deviene ENTONCES Conseguir(sym); x := Nuevo(deviene, x,
expresión()) ELSIF ...
FIN
ELSIF sym = Mientras
ENTONCES
Conseguir(sym); x :=
expresión();
SI sym = ENTONCES Consigue(sym) MÁS
Mark(25) FIN ; x := Nuevo(mientras, x, statseq());
SI sym = el fin ENTONCES Consigue(sym) MÁS
Mark(20) FIN ELSIF ...
FIN ;
REGRESO
x
Declaración de FIN
Estos excerpts claramente muestra que la estructura del fin de frente está predeterminada por el parser. El
programa ha incluso devenir ligeramente más sencillo. Pero tenga que ser mantenido en importar aquel
tipo que comprueba ha sido omitido en el por encima de procedimientos por el bien de brevity. Aun así,
como objetivo-tarea independiente, el tipo que comprueba claramente pertenece delante fin.

16.6. Ejercicios
16.1. Mejora generación de código del Oberon-0 compilador tal que valores y direcciones, una vez
cargados a un registro, posiblemente puede ser reused sin reloading. Para el ejemplo
z := (x - y) * (x + y); y := x
El compilador presentado genera la secuencia de instrucción
LDW 1, 0, x
LDW 2, 0, y
SUB 1, 1, 2
LDW 2, 0, x
LDW 3, 0, y
AÑAD 2, 2, 3
E
MUL 1, 1, 2
STW 1, 0, z
LDW 1, 0, x
STW 1, 0, y
La versión mejorada es para
generar LDW 1, 0, x
LDW 2, 0, y
163
SUB 3, 1, 2
AÑADE 4, 1, 2
MUL 5, 3, 4

164
STW 5, 0, z
STW 1, 0, y
Medida el beneficio a mano de un número razonablemente grande de casos de prueba.
16.2. Qué instrucciones adicionales del RISC arquitectura de Capítulo 9 sería deseable de facilitar las
implementaciones de los ejercicios de preceder, y para generar código más corto y más eficaz?
16.3. Optimizar el Oberon-0 compilador de tal manera que las variables escalaras están destinadas en
registros en vez de memoria si es posible. Medida el beneficio conseguido y comparar él con el
obtenido en Ejercicio 16.1. Cómo es las variables trataron tan VAR parámetros?
16.4. Construir un módulo OSGx cuál reemplaza OSG (ve listar en Apéndice C) y genera código para un
CISC arquitectura x. La interfaz dada de OSG tendría que ser retenido lo más lejos posible para que
módulos OSS y OSP quedar sin cambios.

165
Apéndice Un
Sintaxis
A.1 Oberon-0
ident = Dígito {de letra | de la
letra}. Dígito = de dígito {del
entero}.
Selector = {"." ident | "[" Expresión "]"}.
Factor = ident entero | de selector | "(" expresión ")" | "~"
factor. Factor = de plazo {("*" | "DIV" | "MOD" | "&") factor}.
SimpleExpression = ["+"|"-"] Plazo {("+"|"-" | "O )"plazo}.
Expresión = SimpleExpression
[("=" | "#" | "<" | "<=" | ">" | ">=") SimpleExpression].
Asignación = ident selector ":=" expresión.
ActualParameters = "(" [Expresión {"," expresión}] ")" .
ProcedureCall = ident [ActualParameters].
IfStatement = "SI" expresión "ENTONCES" StatementSequence
{"ELSIF" Expresión "ENTONCES"
StatementSequence} ["MÁS" StatementSequence]
"FIN".
WhileStatement = "MIENTRAS" expresión "" StatementSequence "FIN".
Declaración = [asignación | ProcedureCall | IfStatement | WhileStatement].
StatementSequence = Declaración {";" declaración}.
IdentList = ident {"," ident}.
ArrayType = "Expresión" de VARIEDAD "DE
tipo." FieldList = [IdentList ":" Tipo].
RecordType = "RÉCORD" FieldList {";" FieldList} "FIN".
Tipo = ident | ArrayType | RecordType.
FPSection = ["VAR"] IdentList ":" Tipo.
FormalParameters = "(" [FPSection {";" FPSection}] ")".
ProcedureHeading = "PROCEDIMIENTO" ident [FormalParameters].
ProcedureBody = declarations ["EMPIEZA" StatementSequence] "FIN".
ProcedureDeclaration = ProcedureHeading ";" ProcedureBody ident.
declarations = ["CONST" {ident "=" Expresión ";"}]
["TIPO" {ident "=" tipo ";"}]
["VAR" {IdentList ":" Tipo ";"}]
{ProcedureDeclaration ";"}.
MÓDULO = "de módulo" ident ";" declarations
["EMPIEZA" StatementSequence] "FIN" ident
"." .

A.2 Oberon
ident = Dígito {de letra | de la
letra}. Entero = de número |
real.
Dígito = de dígito {de dígito} | de entero
{hexDigit} "H". Dígito = de dígito {real} "."
{Dígito} [ScaleFactor]. ScaleFactor = ("E" |
"D") ["+" | "-"] dígito {de dígito}.
hexDigit = Dígito | "Un" | "B" | "C" | "D" | "E" | "F".
Dígito = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" |
"9". CharConstant = '"' Carácter '"' | dígito {hexDigit}
"X". Cuerda = '"' {carácter} '"' .
identdef = ident ["*"].
qualident = [ident "."] ident.
166
ConstantDeclaration = identdef "=" ConstExpression.
ConstExpression = Expresión.

167
TypeDeclaration = identdef "=" Tipo.
Tipo = qualident | ArrayType | RecordType | PointerType | ProcedureType.
ArrayType = Longitud de VARIEDAD {"," longitud} DE tipo.
Longitud = ConstExpression.
RecordType = REGISTRO ["(" BaseType ")"] FieldListSequence FIN.
BaseType = qualident.
FieldListSequence = FieldList {";" FieldList}.
FieldList = [IdentList ":" Tipo].
IdentList = identdef {"," identdef}.
PointerType = PUNTERO para
escribir.
ProcedureType = PROCEDIMIENTO
[FormalParameters]. VariableDeclaration = IdentList
":" Tipo.
designator = qualident {"." ident | "[" ExpList "]" | "(" qualident ")" | "^" }.
ExpList = Expresión {"," expresión}.
Expresión = SimpleExpression [relación SimpleExpression].
Relación = "=" | "#" | "<" | "<=" | ">" | ">=" | EN | ES.
SimpleExpression = ["+"|"-"] Denomina {AddOperator plazo}.
AddOperator = "+" | "-" | O .
Factor = de plazo {MulOperator factor}.
MulOperator = "*" | "/" | DIV | MOD | "&" .
Número = de factor | CharConstant | conjunto | de CERO | de la cuerda |
designator [ActualParameters] | "(" Expresión ")" | "~" factor.
Conjunto = "{" [elemento {"," elemento}] "}".
Expresión = de elemento [".." Expresión].
ActualParameters = "(" [ExpList] ")" .
Declaración = [asignación | ProcedureCall |
IfStatement | CaseStatement | WhileStatement | RepeatStatement |
LoopStatement | ForStatement | WithStatement | REGRESO | de SALIDA
[expresión] ].
Asignación = designator ":=" expresión. ProcedureCall
= designator [ActualParameters]. StatementSequence =
Declaración {";" declaración}. IfStatement = SI
expresión ENTONCES StatementSequence
{ELSIF Expresión ENTONCES
StatementSequence} [MÁS
StatementSequence] FIN.
CaseStatement = Expresión de CASO DE caso {"|" de
caso} [MÁS StatementSequence] FIN.
Caso = [CaseLabelList ":" StatementSequence].
CaseLabelList = CaseLabels {"," CaseLabels}.
CaseLabels = ConstExpression [".." ConstExpression].
WhileStatement = MIENTRAS expresión StatementSequence FIN.
RepeatStatement = REPITE StatementSequence HASTA QUE
expresión. LoopStatement = BUCLE StatementSequence FIN.
ForStatement = PARA ident ":=" expresión A expresión [POR ConstExpression]
StatementSequence FIN .
WithStatement = CON qualident ":" qualident StatementSequence FIN .
ProcedureDeclaration = ProcedureHeading ";" ProcedureBody ident.
ProcedureHeading = PROCEDIMIENTO ["*"] identdef [FormalParameters].
ProcedureBody = DeclarationSequence [EMPIEZA StatementSequence] FIN.
ForwardDeclaration = PROCEDIMIENTO "^" ident ["*"] [FormalParameters].
DeclarationSequence = {CONST {ConstantDeclaration ";"} |
TIPO {TypeDeclaration ";"} | VAR {VariableDeclaration ";"}}
{ProcedureDeclaration ";" | ForwardDeclaration ";"}.
FormalParameters = "(" [FPSection {";" FPSection}] ")" [":" qualident].

168
FPSection = [VAR] ident {"," ident} ":" FormalType.
FormalType = {VARIEDAD DE} (qualident | ProcedureType).

169
ImportList = Importación de IMPORTACIÓN {","
importación} ";" . Importación = ident [":=" ident].
MÓDULO = de módulo ident ";" [ImportList]
DeclarationSequence [EMPIEZA StatementSequence] FIN
ident "." .

A.3 Archivos de símbolo


SymFile = EMPIEZA llave {de nombre clave} importó módulos
[ CONST {Valor de nombre del tipo} ] [ VAR {nombre de tipo} ]
constantes y variables [ PROC {nombre de tipo {[VAR] FIN de nombre}
del tipo} ] procedimientos, parámetros [ nombre {de tipo del ALIAS} ] [
NEWTYP {tipo} ] FIN . Rebautizó procedimientos
Tipo = basicType | [Módulo] OldType | NewType.
basicType = BOOL | CHAR | El ENTERO | REAL | ...
NewType = Nombre de tipo de la VARIEDAD intval | DYNARRAY tipo de PUNTERO | de nombre de tipo
nombre
| Tipo de nombre de tipo {RÉCORD FIN} de nombre campos y tipos récord
| PROCTYP Nombre de tipo {[VAR] FIN de nombre del tipo . Tipos de procedimiento y
parámetros.
Las palabras que constan de las letras mayúsculas denotan símbolos terminales. En el archivo de símbolo,
están codificados como enteros. OldType Y el módulo denota tipo y números de módulo, aquello es, son
referencias a anteriormente definió objetos.

Apéndice B
El ASCII el carácter Puso
0 1 2 3 4 5 6 7
0 nul dle 0 @ P ` p
1 soh dc1 ! 1 Un Q Un q
2 stx dc2 " 2 B R b r
3 etx dc3 # 3 C S c s
4 eot dc4 $ 4 D T d t
5 enq nak % 5 E U e u
6 ack syn & 6 F V f v
7 Beli etb ' 7 G W G w
o
8 bs Pue ( 8 H X h x
de
9 ht em ) 9 Yo Y i y
Un lf sub * : J Z j z
B vt esc + ; K [ k {
C Ss fs , < L \ l |
D cr gs - = M ] m }
E Así rs . > N ^ n ~
que
F si nos / ? O _ o del

Apéndice C
El Oberon-0 Compilador
El compilador está partido a tres módulos, concretamente el escáner OSS, el parser OSP, y el generador
de código OSG. Único OSG referencias la arquitectura de objetivo (ve Capítulo 9) por módulo
importador RISC, el cual es un intérprete . Con la ayuda de este intérprete, el código compilado puede ser
ejecutado directamente después de que recopilación.

170
En Oberon, global, parameterless los procedimientos están dichos para ser órdenes, y pueden ser
activados por el usuario vía el Oberon sistema operativo. La orden Compila en el módulo principal OSP
inicia el parsing proceso por una llamada de Módulo de procedimiento qué corresponde al símbolo de
inicio de la sintaxis. El texto para ser compilado está dado tan parámetro. Según las convenciones del
Oberon sistema, pueda ser especificado en varias maneras:

171
OSP.Compila nombre el texto de fuente es el nombre de archivo
OSP.Compilar * el texto de fuente es el texto en el espectador
marcado OSP.Compilar @ los inicios de texto de la fuente con la
selección más tardía
La recopilación exitosa es inmediatamente seguida por el cargando del código compilado y ejecución del
cuerpo del módulo compilado. Aquí, cargando está emulado por copiar las instrucciones de código de
variedad en OSG a variedad M en RISC. Las direcciones relativas de variables globales están hechas
absolutas por añadir un valor de base cuando es consuetudinario en programa loaders. El compilador
entrega el dato requerido en la forma de la mesa ref conteniendo las direcciones de todas instrucciones
para ser fijadas arriba.
Las órdenes están registradas por el compilador en mesas comname y comadr. Procedimiento OSP.Exec
El nombre busca el nombre en la mesa comname, y suministra la dirección correspondiente de someter
comadr al intérprete como el punto de partida para ejecución.
Si un procedimiento compilado contiene llamadas de Leídos, los números textually siguiendo las
palabras OSP.Exec El nombre está leído y aparecer tan entradas al intérprete. Por ejemplo, el
procedimiento
El PROCEDIMIENTO Añade;
VAR x, y, z: ENTERO;
EMPIEZA Leído(x); Leído(y); Leído(z); Escribe(x+y+z);
WriteLn el FIN Añade
Activado por el Oberon orden OSP.Exec Añade 3 5 7 cosechas la producción 15.

C.1. El escáner
MÓDULO OSS; (* NW 19.9.93 / 17.11.94*)
IMPORTACIÓN Oberon, Textos;
CONST IdLen* = 16; KW = 34;
(*Símbolos*) null = 0;
Tiempo* = 1; div* = 3; mod* = 4; y* = 5; plus* = 6; minus* = 7; o* = 8;
eql* = 9; neq* = 10; lss* = 11; geq* = 12; leq* = 13; gtr* = 14;
Periodo* = 18; coma* = 19; colon* = 20; rparen* = 22; rbrak* = 23;
De* = 25; entonces* = 26; hacer* = 27;
lparen* = 29; lbrak* = 30; no* = 32; deviene* = 33; número* = 34; ident* = 37;
Punto y coma* = 38; fin* = 40; más* = 41; elsif* = 42;
Si* = 44; mientras* = 46;
Variedad* = 54; récord* = 55;
const* = 57; tipo* = 58; var* = 59; procedimiento* = 60; empieza* = 61; módulo* = 63;
eof = 64; TIPO Ident* = VARIEDAD IdLen DE CHAR;
VAR val*: LONGINT;
id*: Ident;
Error*:
BOOLEANO; ch:
CHAR;
nkw: ENTERO;
errpos: LONGINT;
R: Textos.Lector;
W:
Textos.Escritor;
keyTab : VARIEDAD KW DE
RÉCORD sym: ENTERO; id: VARIEDAD 12 DE CHAR FIN;
PROCEDIMIENTO Mark*(msg: VARIEDAD
DE CHAR); VAR p: LONGINT;
EMPIEZA p :=

172
Textos.Pos(R) - 1; SI > p
errpos ENTONCES
Textos.WriteString(W, " pos "); Textos.WriteInt(W, p,
1); Textos.Escribe(W, " "); Textos.WriteString(W, msg);

173
Textos.WriteLn(W); Textos.Anexa(Oberon.Registro,
W.buf) FIN ;
errpos := p; Error := FIN
CIERTO Mark;
El PROCEDIMIENTO Consigue*(VAR
sym: ENTERO); PROCEDIMIENTO
Ident;
VAR i, k: ENTERO;
EMPIEZA i := 0;
REPITE
SI i < IdLen ENTONCES id[i] := ch; INC(i)
FIN ; Textos.Leído(R, ch)
HASTA QUE (ch < "0") O (ch > "9") & (GORRA(ch) < "Un") O
(GORRA(ch) > "Z"); id[i] := 0X; k := 0;
MIENTRAS (k < nkw) & (id # keyTab[k].id) INC(k) FIN ;
SI k < nkw ENTONCES sym := keyTab[k].sym MÁS sym := ident
FIN de FIN Ident;
Número de PROCEDIMIENTO;
EMPIEZA val := 0; sym :=
número; REPITE
SI val <= (MAX(LONGINT) - ORD(ch) + ORD("0")) DIV 10
ENTONCES val := 10 * val + (ORD(ch) - ORD("0"))
MÁS Mark("número demasiado
grande"); val := 0 FIN ;
Textos.Leído(R, ch)
HASTA QUE (ch < "0") O (ch > "9")
Número de FIN;
Comentario de
PROCEDIMIENTO;
EMPIEZA
Textos.Leído(R, ch);
BUCLE
BUCLE
MIENTRAS ch = "(" HACER
Textos.Leído(R, ch); SI ch = "*"
ENTONCES FIN de comentario
FIN ;
SI ch = "*" ENTONCES Textos.Leído(R, ch); FIN
de SALIDA ; SI R.eot ENTONCES FIN de
SALIDA ;
Textos.Leído(R, ch)
FIN ;
SI ch = ")" ENTONCES Textos.Leído(R, ch); FIN de SALIDA ;
SI R.eot ENTONCES Mark("comenta no rescindido"); FIN de
FIN de la SALIDA
Comentario de FIN;
EMPIEZA
MIENTRAS ~R.eot & (ch <= " ") HACER
Textos.Leído(R, ch) FIN; SI R.eot ENTONCES sym :=
eof
MÁS
CASO ch DE
"&": Textos.Leído(R, ch); sym := y
| "*": Textos.Leído(R, ch); sym := tiempo
| "+": Textos.Leído(R, ch); sym := plus
174
| "-": Textos.Leído(R, ch); sym := minus
| "=": Textos.Leído(R, ch); sym := eql
| "#": Textos.Leído(R, ch); sym := neq
| "<": Textos.Leído(R, ch);

175
SI ch = "=" ENTONCES Textos.Leído(R, ch); sym := leq MÁS sym := lss FIN
| ">": Textos.Leído(R, ch);
SI ch = "=" ENTONCES Textos.Leído(R, ch); sym := geq MÁS sym := gtr FIN
| ";": Textos.Leído(R, ch); sym := punto y coma
| ",": Textos.Leído(R, ch); sym := coma
| ":": Textos.Leído(R, ch);
SI ch = "=" ENTONCES Textos.Leído(R, ch); sym := deviene MÁS sym := FIN de colon
| ".": Textos.Leído(R, ch); sym := periodo
| "(": Textos.Leído(R, ch);
SI ch = "*" ENTONCES comentario; Consigue(sym) MÁS sym := lparen FIN
| ")": Textos.Leído(R, ch); sym := rparen
| "[": Textos.Leído(R, ch); sym := lbrak
| "]": Textos.Leído(R, ch); sym := rbrak
| "0".."9": Número;
| "Un" .. "Z", "un".."z": Ident
| "~": Textos.Leído(R, ch); sym := no
MÁS Textos.Leído(R, ch); sym :=
null FIN
FIN de
FIN
Consigue;
PROCEDIMIENTO Init*(T: Textos.Texto; pos: LONGINT);
EMPIEZA error := FALSO; errpos := pos; Textos.OpenReader(R, T, pos);
Textos.Leído(R, ch) FIN Init;
PROCEDIMIENTO EnterKW(sym: ENTERO; nombre: VARIEDAD DE CHAR);
EMPIEZA keyTab[nkw].sym := sym; COPIA(nombre, keyTab[nkw].id);
INC(nkw) FIN EnterKW;
EMPIEZA Textos.OpenWriter(W); Error := CIERTO;
nkw := 0; EnterKW(null, "POR");
EnterKW(Hacer, "");
EnterKW(Si, "SI");
EnterKW(null, "EN");
EnterKW(null, "ES");
EnterKW(De, "DE );."
EnterKW(O, "O );."
EnterKW(null, "A );."
EnterKW(Fin, "FIN");
EnterKW(null, "PARA");
EnterKW(mod, "MOD");
EnterKW(null, "CERO");
EnterKW(var, "VAR");
EnterKW(null, "CASO");
EnterKW(más, "MÁS");
EnterKW(null, "SALIDA");
EnterKW(entonces,
"ENTONCES");
EnterKW(tipo, "TIPO");
EnterKW(null, "CON
);"EnterKW(variedad,
"VARIEDAD");
EnterKW(empieza,
"EMPEZAR");
EnterKW(const, "CONST");
EnterKW(elsif, "ELSIF");
EnterKW(null,
"IMPORTACIÓN");

176
EnterKW(null, "HASTA
QUE"); EnterKW(mientras,
"RATO"); EnterKW(registro,
"REGISTRO");

177
EnterKW(null, "REPETIR");
EnterKW(null, "REGRESO");
EnterKW(null, "PUNTERO");
EnterKW(procedimiento,
"PROCEDIMIENTO"); EnterKW(div,
"DIV");
EnterKW(null, "BUCLE");
EnterKW(módulo,
"MÓDULO");
FIN OSS.

C.2. El parser
MÓDULO OSP; (* NW 23.9.93 / 9.2.95*)
Espectadores de IMPORTACIÓN, Textos, Oberon, MenuViewers, TextFrames, OSS, OSG;

CONST WordSize = 4;
VAR sym: ENTERO; cargado: BOOLEANO;
topScope, universo: OSG.Objeto; (* enlazado lista, fin con
guardia *) guardia: OSG.Objeto;
W: Textos.Escritor;

PROCEDIMIENTO NewObj(VAR obj: OSG.Objeto; clase:


ENTERO); VAR nuevo, x: OSG.Objeto;
EMPIEZA x := topScope; guard.name := Oss.id;
MIENTRAS x.next.name # Oss.id HACER x :=
x.FIN próximo ; SI x.Guardia = próximo
ENTONCES
NUEVO(nuevo); new.name := Oss.id; nuevo.Clase := clase;
nuevo.Luego := guardia; x.Luego := nuevo; obj := nuevo
MÁS obj := x.Luego; OSS.Mark("mult
def") FIN
FIN NewObj;

El PROCEDIMIENTO encuentra(VAR obj:


OSG.Objeto); VAR s, x: OSG.Objeto;
EMPIEZA s := topScope; guard.name :=
Oss.id; BUCLE x := s.Luego;
MIENTRAS x.name # Oss.id HACER x :=
x.FIN próximo ; SI x # guardia ENTONCES obj
:= x; FIN de SALIDA ;
SI s = universo ENTONCES obj := x; OSS.Mark("undef"); FIN
de SALIDA ; s := s.dsc
FIN
El FIN
encuentra;

PROCEDIMIENTO FindField(VAR obj: OSG.Objeto; lista:


OSG.Objeto); EMPIEZA guard.name := Oss.id;
MIENTRAS list.name # Oss.id HACER lista :=
lista.FIN próximo ; obj := lista
FIN FindField;

PROCEDIMIENTO IsParam(obj: OSG.Objeto): BOOLEANO;


EMPIEZA REGRESO (obj.Clase = OSG.Par) O (obj.Clase = OSG.Var) & (obj.val
> 0) FIN IsParam;

PROCEDIMIENTO
178
OpenScope;
VAR s: OSG.Objeto;
EMPIEZA NUEVO(s); s.Clase := OSG.Cabeza; s.dsc := topScope; s.Luego := guardia;
topScope := s FIN OpenScope;

179
PROCEDIMIENTO CloseScope;
EMPIEZA topScope :=
topScope.dsc FIN CloseScope;

(* -------------------- Parser ---------------------*)

Expresión^ de PROCEDIMIENTO(VAR x:

OSG.Elemento); selector de

PROCEDIMIENTO(VAR x: OSG.Elemento);
VAR y: OSG.Elemento; obj:
OSG.Objeto; EMPIEZA
MIENTRAS (sym = OSS.lbrak) O (sym = OSS.Periodo)
SI sym = OSS.lbrak ENTONCES
OSS.Consigue(sym); expresión(y);
SI x.Tipo.Forma = OSG.Variedad ENTONCES OSG.Índice(x, y) MÁS OSS.Mark("no una
variedad") FIN ; SI sym = OSS.rbrak ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark("]?") FIN
MÁS OSS.Consigue(sym);
SI sym = OSS.ident ENTONCES
SI x.Tipo.Forma = OSG.Récord ENTONCES
FindField(obj, x.Tipo.Campos);
OSS.Consigue(sym);
SI obj # guardia ENTONCES OSG.Campo(x, obj) MÁS
OSS.Mark("undef") ACABA MÁS OSS.Mark("no un récord")
FIN
MÁS OSS.Mark("ident?")
FIN
FIN
de FIN
Selector de FIN;

Factor de PROCEDIMIENTO(VAR x:
OSG.Elemento); VAR obj:
OSG.Objeto;
EMPIEZA (*sync*)
SI sym < OSS.lparen ENTONCES
OSS.Mark("ident?"); REPITE OSS.Consigue(sym)
HASTA sym >= OSS.lparen
FIN ;
SI sym = OSS.ident ENTONCES encontrar(obj); OSS.Consigue(sym); OSG.MakeItem(x, obj); selector(x)
ELSIF sym = OSS.Número ENTONCES OSG.MakeConstItem(x, OSG.intType, OSS.val);
OSS.Consigue(sym) ELSIF sym = OSS.lparen ENTONCES
OSS.Consigue(sym); expresión(x);
SI sym = OSS.rparen ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(")?") FIN ELSIF sym = OSS.No ENTONCES
OSS.Consigue(sym); factor(x); Osg.op1(OSS.No, x) MÁS
OSS.Mark("factor?"); OSG.MakeItem(x, guardia)
FIN
Factor de FIN;

Plazo de PROCEDIMIENTO(VAR x:
OSG.Elemento); VAR y:
OSG.Elemento; op: ENTERO;
EMPIEZA factor(x);
MIENTRAS (sym >= OSS.Tiempo) & (sym <=
180
OSS.Y) op := sym; OSS.Consigue(sym);
SI op = OSS.Y ENTONCES Osg.op1(op, x) FIN ;
Factor(y); Osg.op2(op, x, y)
FIN
Plazo de FIN;

181
PROCEDIMIENTO SimpleExpression(VAR x:
OSG.Elemento); VAR y: OSG.Elemento; op:
ENTERO;
EMPIEZA
SI sym = OSS.Plus ENTONCES OSS.Consigue(sym); plazo(x)
ELSIF sym = OSS.minus ENTONCES OSS.Consigue(sym); plazo(x);
Osg.op1(OSS.minus, x) MÁS plazo(x)
FIN;
MIENTRAS (sym >= OSS.Plus) & (sym <= Oss.or)
op := sym; OSS.Consigue(sym);
SI op = Oss.or ENTONCES Osg.op1(op, x) FIN ;
Plazo(y); Osg.op2(op, x, y)
FIN
FIN SimpleExpression;

Expresión de PROCEDIMIENTO(VAR x:
OSG.Elemento); VAR y: OSG.Elemento;
op: ENTERO;
EMPIEZA SimpleExpression(x);
SI (sym >= OSS.eql) & (sym <= OSS.gtr) ENTONCES
op := sym; OSS.Consigue(sym); SimpleExpression(y);
OSG.Relación(op, x, y) FIN
Expresión de FIN;

Parámetro de PROCEDIMIENTO(VAR fp:


OSG.Objeto); VAR x: OSG.Elemento;
EMPIEZA expresión(x);
SI IsParam(fp) ENTONCES OSG.Parámetro(x, fp.Tipo, fp.Clase); fp :=
fp.Luego MÁS OSS.Mark("demasiados parámetros")
FIN
Parámetro de FIN;

PROCEDIMIENTO StatSequence;
VAR par, obj: OSG.Objeto; x, y: OSG.Elemento; L: LONGINT;

PROCEDIMIENTO param(VAR x:
OSG.Elemento); EMPIEZA
SI sym = OSS.lparen ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(")?") FIN ; expresión(x);
SI sym = OSS.rparen ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(")?") FIN de FIN param;

EMPIEZA (* StatSequence *)
BUCLE (*sync*) obj :=
guardia;
SI sym < OSS.ident ENTONCES
OSS.Mark("declaración?"); REPITE
OSS.Consigue(sym) HASTA sym >= OSS.ident
FIN ;
SI sym = OSS.ident ENTONCES
Encuentra(obj); OSS.Consigue(sym); OSG.MakeItem(x, obj); selector(x);
SI sym = OSS.Deviene ENTONCES OSS.Consigue(sym); expresión(y);
OSG.Tienda(x, y) ELSIF sym = OSS.eql ENTONCES OSS.Mark(":= ?");
OSS.Consigue(sym); expresión(y)
ELSIF x.Modo = OSG.Proc
ENTONCES par := obj.dsc;
SI sym = OSS.lparen ENTONCES
OSS.Consigue(sym); SI sym = OSS.rparen
182
ENTONCES OSS.Consigue(sym) MÁS
Parámetro de BUCLE(par);
SI sym = OSS.Coma ENTONCES OSS.Consigue(sym)

183
ELSIF sym = OSS.rparen ENTONCES
OSS.Consigue(sym); SALIDA ELSIF sym >=
OSS.Punto y coma ENTONCES SALIDA
MÁS OSS.Mark(") o , ?")
FIN
FIN
de FIN
FIN ;
SI obj.val < 0 ENTONCES
OSS.Mark("llamada de delantero") ELSIF
~IsParam(par) ENTONCES OSG.Llamada(x)
MÁS OSS.Mark("también pocos parámetros")
FIN
ELSIF x.Modo = OSG.SProc ENTONCES
SI obj.val <= 3 ENTONCES param(y)
FIN ; OSG.IOCall(x, y)
ELSIF obj.Clase = OSG.Typ ENTONCES OSS.Mark("asignación
ilegal?") MÁS OSS.Mark("declaración?")
FIN
ELSIF sym = Oss.if ENTONCES
OSS.Consigue(sym); expresión(x); OSG.CJump(x);
SI sym = OSS.Entonces ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("ENTONCES?") FIN ;
StatSequence; L := 0;
MIENTRAS sym =
OSS.elsif HACER
OSS.Consigue(sym); OSG.FJump(L); OSG.FixLink(x.Un); expresión(x);
OSG.CJump(x); SI sym = OSS.Entonces ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark("ENTONCES?") FIN ;
StatSequence
FIN ;
SI sym = OSS.Más ENTONCES
OSS.Consigue(sym); OSG.FJump(L); OSG.FixLink(x.Un);
StatSequence MÁS OSG.FixLink(x.Un)
FIN ;
OSG.FixLink(L);
SI sym = OSS.Fin ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("FIN?") FIN
ELSIF sym = OSS.Mientras ENTONCES
OSS.Consigue(sym); L := Osg.pc; expresión(x); OSG.CJump(x);
SI sym = Oss.do ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("HACER?") FIN ;
StatSequence; OSG.BJump(L); OSG.FixLink(x.Un);
SI sym = OSS.Fin ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark("FIN?") FIN de FIN ;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym)
ELSIF (sym >= OSS.Punto y coma) & (sym < Oss.if) O (sym >= OSS.Variedad)
ENTONCES SALE MÁS OSS.Mark("; ?")
FIN
de FIN
FIN StatSequence;

PROCEDIMIENTO IdentList(clase: ENTERO; VAR primero:


OSG.Objeto); VAR obj: OSG.Objeto;
EMPIEZA
SI sym = OSS.ident ENTONCES
NewObj(primero, clase);
OSS.Consigue(sym); MIENTRAS
sym = OSS.La coma HACE
OSS.Consigue(sym);
SI sym = OSS.ident ENTONCES NewObj(obj, clase);
184
OSS.Consigue(sym) MÁS OSS.Mark("ident?")
FIN
de
FIN;

185
SI sym = OSS.Colon ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(":?") FIN de FIN
FIN IdentList;

Tipo de PROCEDIMIENTO(VAR tipo: OSG.Tipo);


VAR obj, primero: OSG.Objeto; x: OSG.Elemento; tp:
OSG.Tipo; EMPIEZA tipo := OSG.intType; (*sync*)
SI (sym # OSS.ident) & (sym < OSS.Variedad) ENTONCES
OSS.Mark("tipo?"); REPITE OSS.Consigue(sym) HASTA QUE (sym =
OSS.ident) O (sym >= OSS.Variedad)
FIN ;
SI sym = OSS.ident
ENTONCES
encontrar(obj);
OSS.Consigue(sym);
SI obj.Clase = OSG.Typ ENTONCES tipo := obj.Tipo MÁS
OSS.Mark("tipo?") FIN ELSIF sym = OSS.Variedad ENTONCES
OSS.Consigue(sym); expresión(x);
SI (x.Modo # OSG.Const) O (x.Un < 0) ENTONCES OSS.Mark("índice
malo") FIN ; SI sym = Oss.of ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark("DE?") FIN ;
Tipo(tp); NUEVO(tipo); tipo.Forma := OSG.Variedad;
tipo.Base := tp; tipo.len := CORTO(x.Un); tipo.Medida :=
tipo.len * tp.Medida
ELSIF sym = OSS.Récord ENTONCES
OSS.Consigue(sym); NUEVO(tipo); tipo.Forma := OSG.Récord; tipo.Medida := 0;
OpenScope; BUCLE
SI sym = OSS.ident ENTONCES
IdentList(OSG.Fld, primero); Tipo(tp); obj := primero;
MIENTRAS obj # el guardia HACE
obj.Tipo := tp; obj.val := Tipo.Medida; INC(tipo.Medida, obj.Tipo.Medida);
obj := obj.FIN próximo
FIN ;
SI sym = OSS.Punto y coma ENTONCES
OSS.Consigue(sym) ELSIF sym = OSS.ident
ENTONCES OSS.Mark("; ?") MÁS SALIDA
FIN
de FIN
;
Tipo.Campos := topScope.Luego; CloseScope;
SI sym = OSS.Fin ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("FIN?") FIN
MÁS OSS.Mark("ident?")
FIN
Tipo de FIN;

PROCEDIMIENTO declarations(VAR varsize:


LONGINT); VAR obj, primero: OSG.Objeto;
x: OSG.Elemento; tp: OSG.Tipo; L: LONGINT;
EMPIEZA (*sync*)
SI (sym < OSS.const) & (sym # OSS.Fin) ENTONCES
OSS.Mark("declaration?"); REPITE OSS.Consigue(sym) HASTA QUE
(sym >= OSS.const) O (sym = OSS.Fin)
FIN ;
BUCL
E
SI sym = OSS.const
ENTONCES
OSS.Consigue(sym);
186
MIENTRAS sym = OSS.ident HACER
NewObj(obj, OSG.Const); OSS.Consigue(sym);
SI sym = OSS.eql ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("=?") FIN;
Expresión(x);
SI x.Modo = OSG.Const ENTONCES obj.val := x.Un; obj.Tipo
:= x.Tipo MÁS OSS.Mark("la expresión no constante")
FIN;

187
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN de FIN
FIN ;
SI sym = OSS.Tipo
ENTONCES
OSS.Consigue(sym);
MIENTRAS sym = OSS.ident
NewObj(obj, OSG.Typ);
OSS.Consigue(sym);
SI sym = OSS.eql ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("=?") FIN ;
Tipo(obj.Tipo);
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN de FIN
FIN ;
SI sym = OSS.var
ENTONCES
OSS.Consigue(sym);
MIENTRAS sym = OSS.ident
IdentList(OSG.Var, primero); Tipo(tp); obj :=
primero;
MIENTRAS obj # el guardia HACE
obj.Tipo := tp; obj.lev := OSG.curlev;
varsize := varsize + obj.Tipo.Medida; obj.val := -varsize; obj :=
obj.FIN próximo ;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark("; ?") FIN de FIN
FIN ;
SI (sym >= OSS.const) & (sym <= OSS.var) ENTONCES OSS.Mark("declaration?") MÁS FIN
de FIN de la SALIDA
FIN declarations;

PROCEDIMIENTO
ProcedureDecl; CONST
marksize = 8;
VAR proc, obj: OSG.Objeto;
procid: OSS.Ident;
locblksize, parblksize: LONGINT;

PROCEDIMIENTO FPSection;
VAR obj, primero: OSG.Objeto; tp: OSG.Tipo; parsize:
LONGINT; EMPIEZA
SI sym = OSS.var ENTONCES OSS.Consigue(sym);
IdentList(OSG.Par, primero) MÁS IdentList(OSG.Var, primero)
FIN ;
SI sym = OSS.ident
ENTONCES
encontrar(obj);
OSS.Consigue(sym);
SI obj.Clase = OSG.Typ ENTONCES tp :=
obj.Tipo MÁS OSS.Mark("ident?"); tp :=
OSG.intType FIN
MÁS OSS.Mark("ident?"); tp := OSG.intType
FIN ;
SI primero.Clase = OSG.Var
ENTONCES parsize :=
tp.Medida;
SI tp.Forma >= OSG.Variedad ENTONCES OSS.Mark("ningún struct
params") FIN ; MÁS parsize := WordSize
188
FIN ;
obj := Primero;
MIENTRAS obj # guardia obj.Tipo := tp; INC(parblksize, parsize); obj := obj.FIN
de FIN próximo FPSection;

EMPIEZA (* ProcedureDecl *)

189
OSS.Consigue(sym);
SI sym = OSS.ident
ENTONCES procid :=
Oss.id;
NewObj(proc, OSG.Proc); OSS.Consigue(sym); parblksize :=
marksize; OSG.IncLevel(1); OpenScope; proc.val := -1;
SI sym = OSS.lparen
ENTONCES
OSS.Consigue(sym);
SI sym = OSS.rparen ENTONCES
OSS.Consigue(sym) MÁS FPSection;
MIENTRAS sym = OSS.Punto y coma OSS.Consigue(sym); FPSection FIN ;
SI sym = OSS.rparen ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(")?") FIN de FIN
ELSIF OSG.curlev = 1 ENTONCES
OSG.EnterCmd(procid) FIN ;
obj := topScope.Luego; locblksize := parblksize;
MIENTRAS obj #
guardia obj.lev :=
OSG.curlev;
SI obj.Clase = OSG.Par ENTONCES DEC(locblksize,
WordSize) MÁS obj.val := locblksize; obj := obj.Luego
FIN
de FIN
;
proc.dsc := topScope.Luego;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN; locblksize := 0; declarations(locblksize);
MIENTRAS sym =
OSS.Procedimiento
ProcedureDecl;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN de FIN ;
proc.val := Osg.pc; OSG.Introduce(locblksize);
SI sym = OSS.Empieza ENTONCES OSS.Consigue(sym); StatSequence FIN ;
SI sym = OSS.Fin ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("FIN?") FIN ;
SI sym = OSS.ident ENTONCES
SI procid # Oss.id ENTONCES OSS.Mark("ningún
partido") FIN ; OSS.Consigue(sym)
FIN ;
OSG.Regreso(parblksize - marksize); CloseScope; OSG.IncLevel(-
1) FIN
FIN ProcedureDecl;

Módulo de PROCEDIMIENTO(VAR S:
Textos.Escáner); VAR modid: OSS.Ident;
varsize: LONGINT;
EMPIEZA Textos.WriteString(W, "
compilando "); SI sym = OSS.Módulo
ENTONCES
OSS.Consigue(sym); OSG.Abierto; OpenScope; varsize := 0;
SI sym = OSS.ident ENTONCES
modid := Oss.id; OSS.Consigue(sym);
Textos.WriteString(W, modid); Textos.WriteLn(W); Textos.Anexa(Oberon.Registro,
W.buf) MÁS OSS.Mark("ident?")
FIN ;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN; declarations(varsize);
190
MIENTRAS sym =
OSS.Procedimiento
ProcedureDecl;
SI sym = OSS.Punto y coma ENTONCES OSS.Consigue(sym) MÁS
OSS.Mark(";?") FIN de FIN ;
OSG.Encabezamiento(varsize);

191
SI sym = OSS.Empieza ENTONCES OSS.Consigue(sym); StatSequence FIN ;
SI sym = OSS.Fin ENTONCES OSS.Consigue(sym) MÁS OSS.Mark("FIN?") FIN ;
SI sym = OSS.ident ENTONCES
SI modid # Oss.id ENTONCES OSS.Mark("ningún
partido") FIN ; OSS.Consigue(sym)
MÁS OSS.Mark("ident?")
FIN ;
SI sym # OSS.Periodo ENTONCES OSS.Mark(".
?") FIN ; CloseScope;
SI ~OSS.Error ENTONCES
COPIA(modid, S.s); OSG.Cercano(S, varsize); Textos.WriteString(W, "el código
generado"); Textos.WriteInt(W, Osg.pc, 6); Textos.WriteLn(W);
Textos.Anexa(Oberon.Registro, W.buf)
FIN
MÁS OSS.Mark("MÓDULO?")
FIN
Módulo de FIN;

El PROCEDIMIENTO Compila*;
VAR Suplica, fin, tiempo: LONGINT;
S: Textos.Escáner; T: Textos.Texto; v:
Espectadores.Espectador; EMPIEZA cargado :=
FALSO;
Textos.OpenScanner(S, Oberon.Par.Texto, Oberon.Par.pos);
Textos.Escáner(S); SI S.Textos = de clase.Char ENTONCES
SI S.c = "*" ENTONCES
v := Oberon.MarkedViewer();
SI (v.dsc # CERO) & (v.dsc.Luego ES TextFrames.Marco) ENTONCES
OSS.Init(v.dsc.Luego(TextFrames.Marco).Texto, 0); OSS.Consigue(sym);
Módulo(S) FIN
ELSIF S.c = "@" ENTONCES
Oberon.GetSelection(T, suplica, fin, tiempo);
SI tiempo >= 0 ENTONCES OSS.Init(T, suplicar);
OSS.Consigue(sym); Módulo(S) FIN de FIN
ELSIF S.Clase = Texts.name ENTONCES
NUEVO(T); Textos.Abierto(T, S.s); OSS.Init(T, 0);
OSS.Consigue(sym); Módulo(S) FIN
El FIN Compila;

El PROCEDIMIENTO Descodifica*;
VAR V: MenuViewers.Espectador; T:
Textos.Texto; X, Y: ENTERO;
EMPIEZA T := TextFrames.Texto(""); Oberon.AllocateSystemViewer(Oberon.Par.Marco.X,
X, Y); V := MenuViewers.Nuevo(
TextFrames.NewMenu("Registro.Texto", "Sistema.Sistema cercano.Sistema de copia.Crece
Edita.La búsqueda Edita.Tienda"),
TextFrames.NewText(T, 0), TextFrames.menuH, X, Y);
OSG.Descodifica(T)
El FIN Descodifica;

Carga de PROCEDIMIENTO*;
VAR S:
Textos.Escáner;
EMPIEZA
SI ~OSS.El error & ~cargado ENTONCES
Textos.OpenScanner(S, Oberon.Par.Texto, Oberon.Par.pos); OSG.Carga(S); cargado :=
FIN CIERTO
Carga de FIN;
192
PROCEDIMIENTO
Exec*;

193
VAR S:
Textos.Escáner;
EMPIEZA
SI cargado ENTONCES
Textos.OpenScanner(S, Oberon.Par.Texto, Oberon.Par.pos);
Textos.Escáner(S); SI S.Clase = Texts.name ENTONCES OSG.Exec(S)
FIN
FIN
FIN Exec;

El PROCEDIMIENTO introduce(cl: ENTERO; n: LONGINT; nombre: OSS.Ident;


Tipo: OSG.Tipo); VAR obj: OSG.Objeto;
EMPIEZA NUEVO(obj);
obj.Clase := cl; obj.val := n; obj.name := Nombre; obj.Tipo := tipo; obj.dsc :=
CERO; obj.Luego := topScope.Luego; topScope.Luego := obj
El FIN introduce;

EMPIEZA Textos.OpenWriter(W); Textos.WriteString(W, "Oberon0 Compilador 9.2.95");


Textos.WriteLn(W); Textos.Anexa(Oberon.Registro, W.buf);
NUEVO(guardia); guardia.Clase := OSG.Var; Guardia.Tipo := OSG.intType;
Guardia.val := 0; topScope := CERO; OpenScope;
Introduce(OSG.Typ, 1, "BOOLEANO",
OSG.boolType); Introduce(OSG.Typ, 2,
"ENTERO", OSG.intType); Introduce(OSG.Const,
1, "CIERTO", OSG.boolType);
Introduce(OSG.Const, 0, "FALSO",
OSG.boolType); Introduce(OSG.SProc, 1,
"Leído", CERO); introduce(OSG.SProc, 2,
"Escribe", CERO); introduce(OSG.SProc, 3,
"WriteHex", CERO); introduce(OSG.SProc, 4,
"WriteLn", CERO);
Universo :=
topScope FIN OSP.

C.3. El generador de código


MÓDULO OSG; (* NW 18.12.94 / 10.2.95 / 24.3.96 / 25.11.05*)
IMPORTACIÓN Oberon, Textos, OSS, RISC;

CONST maxCode = 1000; maxRel = 200; NofCom = 16;


(* modo / de clase*) Cabeza* = 0;
Var* = 1; Par* = 2; Const* = 3; Fld* = 4; Typ* = 5; Proc* = 6; SProc* = 7;
Reg = 10; Cond = 11;
(* Forma *) Booleano* = 0; Entero* = 1; Variedad* = 2; Récord* = 3;

MOV = 0; MVN = 1; AÑADE = 2; SUB = 3; MUL = 4; Div = 5; Mod = 6; CMP = 7;


MOVI = 16; MVNI = 17; ADDI = 18; SUBI = 19; MULI = 20; DIVI = 21; MODI = 22; CMPI = 23;
CHKI = 24;
LDW = 32; LDB = 33; POP = 34; STW = 36; STB = 37; PSH = 38;
RD = 40; WRD= 41; WRH = 42; WRL = 43;
BEQ = 48; BNE = 49; BLT = 50; BGE = 51; BLE = 52; BGT = 53; BR = 56; BSR = 57; RET = 58;

FP = 12; SP = 13; LNK = 14; PC = 15; (*reservado registra*)

PUNTERO de Objeto* = del TIPO A ObjDesc;


PUNTERO* = de tipo A TypeDesc;

194
REGISTRO* = de elemento
Modo*, lev*: ENTERO;

195
Tipo*: Tipo;
Un*, b, c, r:
LONGINT;
FIN ;

ObjDesc*= Clase
RÉCORD*, lev*:
ENTERO; luego*, dsc*:
Objeto; tipo*: Tipo;
Nombre*:
OSS.Ident; val*:
LONGINT
FIN ;

TypeDesc* = Forma
RÉCORD*:
ENTERO; campos*:
Objeto; base*: Tipo;
Medida*, len*: FIN de
ENTERO ;

VAR boolType*, intType*: Tipo;


curlev*, pc*: ENTERO;
cno: ENTERO;
Entrada, fixlist: LONGINT;
regs: SET; (* utilizado
registra *) W: Textos.Escritor;
Código: VARIEDAD maxCode DE LONGINT;
comname: VARIEDAD NofCom DE OSS.Ident; (*Órdenes*)
comadr: VARIEDAD NofCom DE LONGINT;
mnemo: VARIEDAD 64, 5 DE CHAR; (*para descodificador*)

PROCEDIMIENTO GetReg(VAR r:
LONGINT); VAR i: ENTERO;
EMPIEZA i := 0;
MIENTRAS (i < FP) & (i EN regs) INC(i) FIN ;
INCL(regs, i); r := i
FIN GetReg;

El PROCEDIMIENTO Puesto(op, un, b, c: LONGINT);


EMPIEZA (*emit instrucción*)
SI op >= 32 ENTONCES DEC(op, 64) FIN ;
Código[pc] := CENIZA(CENIZA(CENIZA(op, 4) + un, 4) + b, 18) + (c
MOD 40000H); INC(pc)
El FIN Puso;

PROCEDIMIENTO PutBR(op, disp: LONGINT);


EMPIEZA (*emit instrucción de rama*)
Código[pc] := CENIZA(op-40H, 26) + (disp MOD 4000000H);
INC(pc) FIN PutBR;

PROCEDIMIENTO TestRange(x: LONGINT);


EMPIEZA (*18-mordió entidad*)
SI (x >= 20000H) O (x < -20000H) ENTONCES OSS.Mark("valor demasiado
grande") FIN de FIN TestRange;

Carga de
PROCEDIMIENTO(VAR x:
196
Elemento); VAR r: LONGINT;

197
EMPIEZA (*x.Modo #
Reg*) SI x.Modo = Var
ENTONCES
SI x.lev = 0 ENTONCES x.Un := x.Un - pc*4 FIN ;
GetReg(r); Puesto(LDW, r, x.r, x.Un); EXCL(regs, x.r);
x.r := r ELSIF x.Modo = Const ENTONCES
TestRange(x.Un); GetReg(x.r); Puesto(MOVI, x.r,
0, x.Un) FIN ;
x.Modo :=
Reg carga de
FIN;

PROCEDIMIENTO loadBool(VAR x:
Elemento); EMPIEZA
SI x.Tipo.La forma # Booleana ENTONCES
OSS.Mark("Booleano?") FIN ; carga(x); x.Modo := Cond; x.Un
:= 0; x.b := 0; x.c := 1
FIN loadBool;

PROCEDIMIENTO PutOp(cd: LONGINT; VAR x, y:


Elemento); EMPIEZA
SI x.Modo # Reg ENTONCES carga(x) FIN ;
SI y.Modo = Const ENTONCES TestRange(y.Un); Puesto(cd+16,
x.r, x.r, y.Un) MÁS
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(cd, x.r, x.r, y.r);
EXCL(regs, y.r)
FIN
FIN PutOp;

PROCEDIMIENTO negated(cond: LONGINT):


LONGINT; EMPIEZA
SI EXTRAÑO(cond) ENTONCES REGRESO cond-1 MÁS REGRESO cond+1 FIN
FIN negated;

El PROCEDIMIENTO fusionado(L0, L1: LONGINT):


LONGINT; VAR L2, L3: LONGINT;
EMPIEZA
SI L0 # 0
ENTONCES
L2 := L0;
BUCLE L3 := código[L2] MOD
40000H; SI L3 = 0 ENTONCES
FIN de SALIDA ;
L2 := L3
FIN ;
Código[L2] := código[L2] - L3 + L1;
REGRESO L0 MÁS REGRESO L1
FIN
El FIN fusionó;

El PROCEDIMIENTO fija(en, con: LONGINT);


EMPIEZA código[en] := código[en] DIV 400000H * 400000H + (con MOD 400000H)
El FIN fija;

PROCEDIMIENTO FixWith(L0, L1:


LONGINT); VAR L2: LONGINT;
EMPIEZA
198
MIENTRAS L0 # 0 L2 := código[L0] MOD 40000H; fija(L0, L1-L0); L0 := L2 FIN
FIN FixWith;

PROCEDIMIENTO FixLink*(L: LONGINT);

199
VAR L1: LONGINT;
EMPIEZA
MIENTRAS L # 0 L1 := código[L] MOD 40000H; fija(L, pc-L); L := L1 FIN
FIN FixLink;

(*-----------------------------------------------*)

PROCEDIMIENTO IncLevel*(n:

ENTERO);
EMPIEZA INC(curlev, n)
FIN IncLevel;

PROCEDIMIENTO MakeConstItem*(VAR x: Elemento; typ: Tipo; val:


LONGINT); EMPIEZA x.Modo := Const; x.Tipo := typ; x.Un := val
FIN MakeConstItem;

PROCEDIMIENTO MakeItem*(VAR x: Elemento;


y: Objeto); VAR r: LONGINT;
EMPIEZA x.Modo := y.Clase; x.Tipo := y.Tipo; x.lev := y.lev; x.Un := y.val;
x.b := 0; SI y.lev = 0 ENTONCES x.r := PC
ELSIF y.lev = curlev ENTONCES
x.r := FP MÁS OSS.Mark("nivel!");
x.r := 0 FIN ;
SI y.Clase = Par ENTONCES GetReg(r); Puesto(LDW, r, x.r, x.Un); x.Modo := Var; x.r := r;
x.Un := 0 FIN de FIN MakeItem;

Campo de PROCEDIMIENTO*(VAR x: Elemento; y:


Objeto); (* x := x.y *) EMPIEZA INC(x.Un, y.val); x.Tipo :=
y.Tipo
Campo de FIN;

Índice de PROCEDIMIENTO*(VAR x, y: Elemento); (* x :=


x[y] *) EMPIEZA
SI y.Tipo # intType ENTONCES OSS.Mark("índice no entero")
FIN ; SI y.Modo = Const ENTONCES
SI (y.Un < 0) O (y.Un >= x.Tipo.len) ENTONCES OSS.Mark("índice
malo") FIN ; INC(x.Un, y.Un * x.Tipo.Base.Medida)
MÁS
SI y.Modo # Reg ENTONCES
carga(y) FIN ; Puesto(CHKI, y.r, 0,
x.Tipo.len);
Puesto(MULI, y.r, y.r, x.Tipo.Base.Medida);
Puesto(AÑADE, y.r, x.r, y.r); EXCL(regs, x.r);
x.r := y.r FIN;
x.Tipo :=
x.Tipo.Índice de FIN
de la base;

PROCEDIMIENTO Op1*(op: ENTERO; VAR x: Elemento); (* x


:= op x *) VAR t: LONGINT;
EMPIEZA
SI op = OSS.minus ENTONCES
SI x.Tipo.Entero # de forma ENTONCES
OSS.Mark("tipo malo") ELSIF x.Modo = Const
ENTONCES x.Un := -x.Un
MÁS
SI x.Modo = Var ENTONCES
200
carga(x) FIN ; Puesto(MVN, x.r, 0,
x.r)
FIN
ELSIF op = OSS.No ENTONCES
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;

201
x.c := negated(x.c); t := x.Un; x.Un := x.b; x.b
:= t ELSIF op = OSS.Y ENTONCES.
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
PutBR(BEQ + negated(x.c), x.Un); EXCL(regs, x.r); x.Un := pc-1; FixLink(x.b); x.b
:= 0 ELSIF op = Oss.or ENTONCES
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
PutBR(BEQ + x.c, x.b); EXCL(regs, x.r); x.b := pc-1; FixLink(x.Un); x.Un
:= 0 FIN
FIN Op1;

PROCEDIMIENTO Op2*(op: ENTERO; VAR x, y: Elemento); (* x := x op


y *) EMPIEZA
SI (x.Tipo.Entero = de forma) & (y.Tipo.Entero = de
forma) ENTONCES SI (x.Modo = Const) & (y.Modo =
Const) ENTONCES
(*El desbordamiento comprueba desaparecido*)
SI op = OSS.Plus ENTONCES INC(x.Un,
y.Un) ELSIF op = OSS.minus ENTONCES
DEC(x.Un, y.Un) ELSIF op = OSS.Tiempo
ENTONCES x.Un := x.Un * y.Un
ELSIF op = OSS.div ENTONCES x.Un := x.Un
DIV y.Un ELSIF op = OSS.mod ENTONCES
x.Un := x.Un MOD y.Un MÁS OSS.Mark("tipo
malo")
FIN
MÁS
SI op = OSS.Plus ENTONCES PutOp(AÑADE,
x, y) ELSIF op = OSS.minus ENTONCES
PutOp(SUB, x, y) ELSIF op = OSS.Tiempo
ENTONCES PutOp(MUL, x, y) ELSIF op =
OSS.div ENTONCES PutOp(Div, x, y) ELSIF
op = OSS.mod ENTONCES PutOp(Mod, x, y)
MÁS OSS.Mark("tipo malo")
FIN
de FIN
ELSIF (x.Tipo.La forma = Booleana) & (y.Tipo.La forma =
Booleana) ENTONCES SI y.Modo # Cond ENTONCES
loadBool(y) FIN ;
SI op = Oss.or ENTONCES x.Un := y.Un; x.b := Fusionado(y.b, x.b); x.c
:= y.c ELSIF op = OSS.Y ENTONCES x.Un := fusionado(y.Un, x.Un);
x.b := y.b; x.c := y.c FIN
MÁS OSS.Mark("tipo
malo") FIN ;
FIN Op2;

Relación de PROCEDIMIENTO*(op: ENTERO; VAR x, y: Elemento); (* x


:= x ? y *) EMPIEZA
SI (x.Tipo.Entero # de forma) O (y.Tipo.Entero # de forma) ENTONCES
OSS.Mark("tipo malo") MÁS PutOp(CMP, x, y); x.c := op - OSS.eql; EXCL(regs,
y.r)
FIN ;
x.Modo := Cond; x.Tipo := boolType; x.Un := 0; x.b
:= 0 Relación de FIN;

Tienda de PROCEDIMIENTO*(VAR x, y:
Elemento); (* x := y *) VAR r: LONGINT;
EMPIEZA
SI (x.Tipo.Forma EN {Booleano, Entero}) & (x.Tipo.Forma =
202
y.Tipo.Forma) ENTONCES SI y.Modo = Cond ENTONCES
Puesto(BEQ + negated(y.c), y.r, 0, y.Un); EXCL(regs, y.r); y.Un := pc-1;
FixLink(y.b); GetReg(y.r); Puesto(MOVI, y.r, 0, 1); PutBR(BR, 2);
FixLink(y.Un); Puesto(MOVI, y.r, 0, 0)

203
ELSIF y.Modo # Reg ENTONCES
carga(y) FIN ;
SI x.Modo = Var ENTONCES
SI x.lev = 0 ENTONCES x.Un := x.Un -
pc*4 FIN ; Puesto(STW, y.r, x.r, x.Un)
MÁS OSS.Mark("asignación ilegal")
FIN ;
EXCL(regs, x.r); EXCL(regs, y.r)
MÁS OSS.Mark("asignación incompatible")
FIN
Tienda de FIN;

Parámetro de PROCEDIMIENTO*(VAR x: Elemento; ftyp: Tipo;


clase: ENTERO); VAR r: LONGINT;
EMPIEZA
SI x.Tipo = ftyp ENTONCES
SI clase = Par ENTONCES (*Var
param*) SI x.Modo = Var
ENTONCES
SI x.Un # 0 ENTONCES GetReg(r);
Puesto(ADDI, r, x.r, x.Un) MÁS r := x.r
FIN
MÁS OSS.Mark("modo de parámetro
ilegal") FIN ;
Puesto(PSH, r, SP, 4); EXCL(regs, r)
MÁS (*valor param*)
SI x.Modo # Reg ENTONCES
carga(x) FIN ; Puesto(PSH, x.r, SP, 4);
EXCL(regs, x.r)
FIN
MÁS OSS.Mark("tipo de parámetro
malo") FIN
Parámetro de FIN;

(*---------------------------------*)

PROCEDIMIENTO CJump*(VAR x:
Elemento); EMPIEZA
SI x.Tipo.La forma = Booleana ENTONCES
SI x.Modo # Cond ENTONCES loadBool(x) FIN ;
PutBR(BEQ + negated(x.c), x.Un); EXCL(regs, x.r); FixLink(x.b); x.Un :=
pc-1 MÁS OSS.Mark("Booleano?"); x.Un := pc
FIN
FIN CJump;

PROCEDIMIENTO BJump*(L:
LONGINT); EMPIEZA PutBR(BR, L-
pc)
FIN BJump;

PROCEDIMIENTO FJump*(VAR L:
LONGINT); EMPIEZA PutBR(BR, L); L :=
pc-1
FIN FJump;

Llamada de
PROCEDIMIENTO*(VAR x:
Elemento); EMPIEZA PutBR(BSR,
204
x.Un - pc)
Llamada de FIN;

PROCEDIMIENTO IOCall*(VAR x, y: Elemento);

205
VAR z:
Elemento;
EMPIEZA
SI x.Un < 4 ENTONCES
SI y.Tipo.Entero # de forma ENTONCES
OSS.Mark("Entero?") FIN de FIN ;
SI x.Un = 1 ENTONCES
GetReg(z.r); z.Modo := Reg; z.Tipo := intType; Puesto(RD, z.r, 0, 0);
Tienda(y, z) ELSIF x.Un = 2 ENTONCES carga(y); Puesto(WRD, 0, 0, y.r);
EXCL(regs, y.r)
ELSIF x.Un = 3 ENTONCES carga(y); Puesto(WRH, 0, 0, y.r);
EXCL(regs, y.r) MÁS Puesto(WRL, 0, 0, 0)
FIN
FIN IOCall;

Encabezamiento de PROCEDIMIENTO*(medida: LONGINT);


EMPIEZA entrada := pc; Puesto(MOVI, SP, 0, RISC.MemSize - Medida);
(*init SP*) Puesto(PSH, LNK, SP, 4)
Encabezamiento de FIN;

El PROCEDIMIENTO
Introduce*(medida: LONGINT);
EMPIEZA
Puesto(PSH, LNK, SP, 4);
Puesto(PSH, FP, SP, 4);
Puesto(MOV, FP, 0,
SP);
Puesto(SUBI, SP, SP,
medida)
El FIN Introduce;

Regreso de PROCEDIMIENTO*(medida:
LONGINT); EMPIEZA
Puesto(MOV, SP, 0, FP);
Puesto(POP, FP, SP, 4);
Puesto(POP, LNK, SP,
medida+4); PutBR(RET,
LNK)
Regreso de FIN;

El PROCEDIMIENTO Abierto*;
EMPIEZA curlev := 0; pc := 0; cno := 0; regs :=
{} el FIN Abierto;

El PROCEDIMIENTO Cercano*(VAR S: Textos.Escáner; globals:


LONGINT); EMPIEZA Puesto(POP, LNK, SP, 4); PutBR(RET,
LNK);
El FIN Cercano;

PROCEDIMIENTO EnterCmd*(VAR nombre: VARIEDAD DE CHAR);


EMPIEZA COPIA(nombre, comname[cno]); comadr[cno] := pc*4;
INC(cno) FIN EnterCmd;

(*-------------------------------------------*)

Carga de PROCEDIMIENTO*(VAR S:
Textos.Escáner); EMPIEZA
RISC.Carga(código, pc);
206
Textos.WriteString(W, " el código cargado"); Textos.WriteLn(W); Textos.Anexa(Oberon.Registro, W.buf);
RISC.Ejecuta(entrada*4, S, Oberon.Registro)
Carga de FIN;

PROCEDIMIENTO Exec*(VAR S:
Textos.Escáner); VAR i: ENTERO;

207
EMPIEZA i := 0;
MIENTRAS (i < cno) & (S.s # comname[i]) INC(i) FIN ;
SI i < cno ENTONCES RISC.Ejecuta(comadr[i], S,
Oberon.Registro) FIN de FIN Exec;

El PROCEDIMIENTO Descodifica*(T:
Textos.Texto); VAR i, w, op, un:
LONGINT;
EMPIEZA Textos.WriteString(W, "entrada"); Textos.WriteInt(W, entrada*4, 6);
Textos.WriteLn(W); i := 0;
MIENTRAS i < pc HACER
w := Código[i]; op := w DIV 4000000H MOD 40H;
Textos.WriteInt(W, 4*i, 4); Textos.Escribe(W, 9X); Textos.WriteString(W, mnemo[op]);
SI op < BEQ ENTONCES
Un := w MOD 40000H;
SI un >= 20000H ENTONCES DEC(un, 40000H) (*extensión de
señal*) FIN ; Textos.Escribe(W, 9X); Textos.WriteInt(W, w DIV
400000H MOD 10H, 4); Textos.Escribe(W, ","); Textos.WriteInt(W, w
DIV 40000H MOD 10H, 4); Textos.Escribe(W, ",")
MÁS un := w MOD 4000000H;
SI un >= 2000000H ENTONCES DEC(un, 4000000H) (*extensión de
señal*) FIN de FIN ;
Textos.WriteInt(W, un, 6); Textos.WriteLn(W);
INC(i) FIN ;
Textos.WriteLn(W); Textos.Anexa(T,
W.buf) El FIN Descodifica;

EMPIEZA Textos.OpenWriter(W);
NUEVO(boolType); boolType.Forma := Booleano;
boolType.Medida := 4; NUEVO(intType); intType.Forma :=
Entero; intType.Medida := 4; mnemo[MOV] := "MOV ";
mnemo[MVN] := "MVN ";
mnemo[AÑADE] := "AÑADE ";
mnemo[SUB] := "SUB ";
mnemo[MUL] := "MUL ";
mnemo[Div] := "DIV ";
mnemo[Mod] := "MOD ";
mnemo[CMP] := "CMP ";
mnemo[MOVI] := "MOVI";
mnemo[MVNI] := "MVNI";
mnemo[ADDI] := "ADDI";
mnemo[SUBI] := "SUBI";
mnemo[MULI] := "MULI";
mnemo[DIVI] := "DIVI";
mnemo[MODI] := "MODI";
mnemo[CMPI] := "CMPI";
mnemo[CHKI] := "CHKI";
mnemo[LDW] := "LDW ";
mnemo[LDB] := "LDB ";
mnemo[POP] := "POP ";
mnemo[STW] := "STW ";
mnemo[STB] := "STB ";
mnemo[PSH] := "PSH ";
mnemo[BEQ] := "BEQ ";
mnemo[BNE] := "BNE ";
mnemo[BLT] := "BLT ";
mnemo[BGE] := "BGE ";

208
mnemo[BLE] := "BLE ";
mnemo[BGT] := "BGT ";
mnemo[BR] := "BR ";
mnemo[BSR] := "BSR ";
mnemo[RET] := "RET ";
mnemo[RD] := "LEÍDO";
mnemo[WRD] := "WRD ";
mnemo[WRH] := "WRH ";
mnemo[WRL] := "WRL ";
FIN OSG.

209
Referencias
Un.V. Aho, J.D. Ullman. Principios de Diseño de Compilador. Lectura MA: Addison-Wesley, 1985.
F. L. DeRemer. Sencillo LR(k) gramáticas. Comm. ACM, 14, 7 (julio 1971), 453-460.
M. Franz. El caso para archivos de símbolo universal. Programación estructurada 14 (1993), 136-147.
S. L. Graham, S. P. Rhodes. Recuperación de error de sintaxis práctica. Comm. ACM, 18, 11, (Nov. 1975), 639-
650.
J. L. Hennessy, D. Un. Patterson. Arquitectura de ordenador. Una Aproximación
Cuantitativa. Morgan Kaufmann, 1990.
C.Un.R. Hoare. Notas en los datos que estructuran.
En Programación Estructurada. O.-J. Dahl, E.W. Dijkstra, C.Un.R. Hoare, Acad. Prensa, 1972.
U. Kasten. Uebersetzerbau. Oldenbourg, 1990
D. E. Knuth. En la traducción de lenguas de izquierdos a
correctos.
Información y Control, 8, 6 (Dic. 1965), 607-639.
D.E. Knuth. Superior-abajo análisis de sintaxis. Acta Informatica 1 (1971), 79-110.
W. R. LaLonde, et al. Un LALR(k) parser generador.
Proc. IFIP Congreso 71, Al norte-Holanda, 153-157.
J.G.Mitchell, W. Maybury, R. Dulce. Mesa Manual de lengua.
Xerox Palo Centro de Búsqueda del alto, Informe Técnico CSL-78-3.
P. Naur (Ed). Informe en la lengua algorítmica Algol 60.
Comm. ACM, 3 (1960), 299-314, y Comm. ACM, 6, 1 (1963), 1-17.
P. Rechenberg, H. Mössenböck. Ein Compilador-Generador für Mikrocomputer. C. Hanser, 1985.
M. Reiser, N. Wirth. Programación en Oberon. Wokingham: Addison-Wesley, 1992.
N. Wirth. El Pascal de lenguaje de programación. Acta Informatica 1 (1971)
N. Wirth. Modula - Un lenguaje de programación para modular multiprogramming.
Software - Práctica y Experiencia, 7, (1977), 3-35.
N. Wirth. Qué puede hacemos sobre la diversidad innecesaria de
notación para definiciones sintácticas? Comm. ACM, 20, (1977),
11, 822-823.
N. Wirth. Programación en Modula-2. Heidelberg: Salmer-Verlag, 1982.
N. Wirth Y J. Gutknecht. Proyecto Oberon. Wokingham: Addison-Wesley, 1992.

210

Vous aimerez peut-être aussi