Issuu on Google+

UNIVERSIDAD CAPITAN GENERAL GERARDO BARRIOS SAN MIGUEL. FACULTAD: CIENCIAS Y TECNOLOGIA.

Asignatura:

Compiladores e Interpretes.

Catedrático:

Lic. Carlos Alberto Sibrian Galicia.

Proyecto:

“Proceso para Diseñar un Lenguaje de Programación”.

Integrantes del Equipo: Wendy Patricia Bonilla Vásquez.

SMIS036610

Laura Xiomara Fuentes Medrano

SMIS021703

Josué Eli Benítez.

SMIS101809

San Miguel, Mayo de 2012.


Contenido INTRODUCCION ............................................................................................................................... i OBJETIVOS ....................................................................................................................................... 2 DESARROLLO. ................................................................................................................................. 3 Lenguaje de Alto Nivel ....................................................................................................................... 3 Lenguaje de Bajo Nivel. ...................................................................................................................... 4 1. Proceso de creación de un programa .............................................................................................. 7 1.1

Sinopsis ............................................................................................................................... 7

2. Presentación del problema .............................................................................................................. 7 3. Comprender el problema. ................................................................................................................ 8 4. Diseñar los ficheros y módulos ...................................................................................................... 9 5. Escribir el código ......................................................................................................................... 10 6. Análisis Léxico............................................................................................................................. 14 7. Análisis sintáctico ........................................................................................................................ 15 8. Análisis semántico ....................................................................................................................... 16 9. Generador de código .................................................................................................................... 17 10. Enlazado ..................................................................................................................................... 18 10.1 Versión de depuración .......................................................................................................... 19 10.2 Versión de publicación ......................................................................................................... 19 10.3 Librería ................................................................................................................................. 20 11. Errores ........................................................................................................................................ 20 11.1 Tipos de errores ..................................................................................................................... 20 11.2 Gravedad de los errores ........................................................................................................ 21 11.3 Generalidades sobre los errores de compilación .................................................................. 22 11.4 Errores de ejecución ............................................................................................................. 24 Pseudocódigo .................................................................................................................................... 26 Aplicación ......................................................................................................................................... 26 Sintaxis .............................................................................................................................................. 27 Definición de datos del pseudocódigo .............................................................................................. 28 Compiladores modernos.................................................................................................................... 28 BIBLIOGRAFIA…………………………………………………………........................................................................55 CONCLUSION……………………………………………………………......................................................................56


Proceso para Diseñar un Lenguaje de Programación

INTRODUCCION

Los lenguajes de programación se han convertido en una parte fundamental en la actualidad, cabe mencionar que existen una derivación de lenguajes, ejemplo de ello: Java, Visual Basic, C++, Php, entre otros. Además de ser los lenguajes de programación más sobresalientes, estos suelen ser software gratuito pero también pagado. Todo lenguaje contiene su teoría, al igual que una secuencia de pasos de programación. En el siguiente documento, se hace mención de los lenguajes de alto y bajo nivel, el concepto de cada uno de ellos y las características que estos lenguajes poseen. Además se trata el tema sobre el proceso de creación de un lenguaje de programación, la diferencia entre un lenguaje compilado y un lenguaje interpretado. Resaltando un sintaxis de código fuente como ejemplo, los tipos de errores que se generan en la depuración de códigos, su nivel de gravedad y sus consecuencias. Dentro del documento se hace énfasis en los tres análisis en el proceso de compilación como lo son: el análisis Léxico, Sintáctico y el Semántico, cuáles son las funciones de cada uno y las características que poseen. Posteriormente a los análisis se habla sobre el generador de código que es prácticamente una de las fases de finalización del proceso de compilación. También se hace referencia al concepto y función del Pseudocódigo, la sintaxis y su aplicación. Otro tema que es de mucha importancia debido al gran auge que han adquirido los compiladores es el de los compiladores modernos, del cuál se hace énfasis en este documento y con el cual se pretende dar a conocer los más recientes y más utilizados lenguajes de programación.

i


Proceso para Diseñar un Lenguaje de Programación OBJETIVOS

General: o

Investigar el proceso para diseñar un lenguaje de programación.

Específicos: o

Conocer los diferentes tipos de lenguajes de programación y la diferencia entre un lenguaje compilado y un lenguaje interpretado.

o

Conocer el proceso de diseño de un lenguaje de programación.

o

Investigar algunos ejemplos sobre como diseñar un lenguaje de programación.

o

Conocer los compiladores modernos.

o

Analizar los algoritmos de un lenguaje de programación.

o

Conocer los lenguajes de alto nivel y bajo nivel.

2


Proceso para Diseñar un Lenguaje de Programación DESARROLLO. Lenguaje de Alto Nivel

¿Qué es un lenguaje de alto nivel? Un lenguaje de alto nivel permite al programador escribir las instrucciones de un programa utilizando palabras o expresiones sintácticas muy similares al inglés. Por ejemplo, en C se pueden usar palabras tales como: case, if, for, while, etc. para construir con ellas instrucciones como:

if( numero > 0 ) printf( "El número es positivo" )

Que traducido al castellano viene a decir que: si numero es mayor que cero, entonces, escribir por pantalla el mensaje: "El número es positivo".

Ésta es la razón por la que a estos lenguajes se les considera de alto nivel, porque se pueden utilizar palabras de muy fácil comprensión para el programador. En contraposición, los lenguajes de bajo nivel son aquellos que están más cerca del "entendimiento" de la máquina. Otros lenguajes de alto nivel son: Ada, BASIC, COBOL, FORTRAN, Pascal, etc.

Otra característica importante de los lenguajes de alto nivel es que, para la mayoría de las instrucciones de estos lenguajes, se necesitarían varias instrucciones en un lenguaje ensamblador para indicar lo mismo. De igual forma que, la mayoría de las instrucciones de un lenguaje ensamblador, también agrupa a varias instrucciones de un lenguaje máquina.

3


Proceso para Diseñar un Lenguaje de Programación Figura - Relación entre las instrucciones de alto nivel, ensamblador y máquina.

Lenguaje de Bajo Nivel. Un lenguaje de programación de bajo nivel de abstracción es el que proporciona un conjunto de instrucciones aritmeticológicas sin la capacidad de encapsular dichas instrucciones en funciones que no estén ya contempladas en la arquitectura del hardware. Dicho lenguaje es muy simple o nada complicado, pero estructurar programas a ese nivel es muy difícil. Dado que este lenguaje viene dado por las especificaciones técnicas del hardware, no permite una abstracción fuera de lo estipulado para el microprocesador de un ordenador. Consecuentemente, es fácilmente trasladado a lenguaje de máquina. La estructura de los lenguajes es como sigue:  Lenguaje Máquina - Las invocaciones a memoria, como los procesos aritmético lógicos son posiciones literales de conmutadores físicos del hardware en su representación booleana. Estos lenguajes son literales de tareas.  Lenguajes de bajo nivel - Son instrucciones que ensamblan los grupos de conmutadores necesarios para expresar una mínima lógica aritmética. Están íntimamente vinculados al hardware. Por norma general están disponibles a nivel firmware, cmos o chip set. Estos lenguajes están orientados a procesos. Los procesos se componen de tareas. Contienen tantas instrucciones como la arquitectura del hardware así haya sido diseñada. 

Por ejemplo: La arquitectura CISC contiene muchas más instrucciones a este nivel, que la RISC.

Son denominados como ensambladores de un hardware concreto.

 Lenguajes de medio nivel - Son aquellos que, basándose en los juegos de instrucciones disponibles (chip set), permiten el uso de funciones a nivel aritmético, pero a nivel lógico dependen de literales en ensamblador. Estos lenguajes están orientados a procedimientos. Los procedimientos se componen de procesos. 

Ejemplos: C, Basic.

4


Proceso para Diseñar un Lenguaje de Programación  Lenguajes de alto nivel - Son aquellos que permiten una máxima flexibilidad al programador a la hora de abstraerse o de ser literal. Permiten un camino bidireccional entre el lenguaje máquina y una expresión casi oral entre la escritura del programa y su posterior compilación. Estos lenguajes están orientados a objetos. Los objetos se componen de propiedades cuya naturaleza emerge de procedimientos. 

Ejemplos: C++, Fortran, Cobol, Lisp.

 Lenguajes de aplicaciones - Son aquellos que no permiten una bidireccionalidad conceptual entre el lenguaje máquina y los lenguajes de alto nivel, ni tampoco la literalidad a la hora de invocar conceptos lógicos. Se basan en librerías creadas en lenguajes de alto nivel. Pueden permitir la creación de nuevas librerías, pero son propietarias y dependientes de las suministradas por la aplicación. Estos lenguajes están orientados a eventos. Los eventos acontecen cuando las propiedades de un objeto interactúan con otro. 

Ejemplos: Visual Basic para aplicaciones.

 Lenguajes de redes - Son aquellos que se basan en un convenio de instrucciones totalmente independientes de la máquina, y completamente dependientes de la red a la que están orientadas. Se dividen en descriptivos (HTML, XML, VML), de cliente-Servidor (Java, PHP) y de script. 

La palabra bajo no implica que el lenguaje sea inferior a un lenguaje de alto nivel; se refiere a la reducida abstracción entre el lenguaje y el hardware. Por ejemplo, se utiliza este tipo de lenguajes para programar controladores de dispositivos.

 Interacción Máquina Vs Humano En este tipo de lenguajes se trabaja a nivel de instrucciones, es decir, su programación es al más fino detalle, además, está completamente orientado a la máquina. 

Adaptación - Máxima entre programación y aprovechamiento del recurso de la máquina.

Velocidad - Máxima al contar con un acceso directo a los recursos, sin capas intermedias.

Portabilidad - Mínima por estar restringido a las especificaciones del fabricante.

Abstracción - Mínima por depender completamente de la técnica del hardware.

5


Proceso para Diseñar un Lenguaje de Programación 

Uso - Requiere de la máxima atención y de una organización estructurada en base a los planos del hardware y del objetivo del software.

6


Proceso para Diseñar un Lenguaje de Programación 1. Proceso de creación de un programa 1.1 Sinopsis Programming is the art of expressing solutions to problems so that a computer can execute those solutions. (La programación es el arte de expresar las soluciones a los problemas de manera que una computadora puede ejecutar esas soluciones.) BjarneStroustrup: "Programming: Principles and PracticeUsing C++". ("Programación: Principios y Práctica usando C + +"). Escribir un programa es establecer el comportamiento de una máquina mediante una serie de algoritmos que definirán su funcionamiento. En el estado actual de la ciencia, este algoritmo se plasma por escrito utilizando un lenguaje artificial comprensible por el humano-programador. Generalmente estas instrucciones, que aquí se denominan código fuente, vienen acompañadas de algunos datos en forma de texto o imágenes, contenidas en uno o varios ficheros denominados ficheros de recursos ("resources"). Sin embargo, las instrucciones y recursos solo pueden ser utilizadas por la máquina después de un proceso de traducción que es realizado por la propia máquina (puede ser distinta de la que ejecuta el programa). El proceso exige que el código fuente sea transformado en una nueva secuencia de instrucciones según un nuevo sistema de codificación (el lenguaje máquina), y que los recursos adopten una disposición particular. Este conjunto de instrucciones y datos, que constituyen el denominado ejecutable, corresponden a acciones concretas y datos, que pueden ser entendidas, ejecutadas y utilizados por la máquina.

En general este comportamiento pretende modelar o mimetizar el comportamiento de una entidad del mundo real, o de una abstracción que hemos imaginado; y es de tipo genérico. Se pretende que la máquina se comporte como una función que acepta un conjunto de condiciones de entrada y devuelve como salida un comportamiento concreto y predecible para cada combinación de las condiciones de entrada.

2. Presentación del problema Hay bastante literatura sobre programación en general; a los académicos les gusta hablar de "Teoría de la Programación", y mucha gente se ha dedicado a especular sobre el tema. Incluso hay modas al respecto. Es posible confeccionar una lista de las características que "debe" y "no debe" tener un buen programa (incluyendo la del Jefe, que solo tiene dos puntos: "Que esté para ayer; que salga barato"). El propio Stroustrup (TC++PL) compara las condiciones para escribir un buen programa 7


Proceso para Diseñar un Lenguaje de Programación con las de escribir buena prosa. Según él, existen dos respuestas:” Saber que se quiere decir" y "Práctica. Imitar buenos escritores". Más adelante nos recuerda que aprender a manejar bien un lenguaje puede constar tanto tiempo y esfuerzo como aprender a expresarse en un lenguaje natural o tocar un instrumento. Por supuesto sería un atrevimiento por mi parte contradecir tan docta opinión, pero puestos a filosofar me gustaría puntualizar que el verdadero problema está en el segundo punto de la segunda respuesta; la primera, aunque ciertamente importante, me parece la verdad de Perogrullo. Siempre me ha parecido que programar (programar bien) tiene mucho de arte. Me parece que debe ocurrir como con la música; seguramente muchos pueden decir que debe tener una buena ejecución de violín, pero imitar a Paganini debe ser harina de otro costal. Seguramente los profesores de armonía saben que debe tener y no tener una buena sinfonía, pero otra cosa debe ser imitar a Mozart. Bajando a la tierra; tampoco se trata aquí de hacer "Paganinis de la programación C++" (ya me gustaría para mí); el mensaje que quisiera transmitir es doble: El contenido en un viejo Refrán Español:” La Universidad no presta lo que la naturaleza no da". Como suena un poco duro, añadiré un consuelo para los que somos menos dotados; un proverbio que leí hace tiempo, en línea con la respuesta de Stroustrup: "Por el dinero del trabajo los Dioses lo venden todo". A continuación se comentan brevemente los pasos imprescindibles en la creación de un programa C++. Vaya por delante, que las anotaciones de los puntos 3, 4 y 5 son opinión del que suscribe basados en la propia experiencia, por tanto totalmente subjetivos y opinables.

3. Comprender el problema. Esta es la típica obviedad que a veces se pasa por alto. Hemos dicho que escribir un programa es establecer el comportamiento de una máquina; parece lo más natural del mundo enterarse primero de cual es ese comportamiento. Tener una imagen mental lo más clara posible de las características de lo que pretendemos modelar. Esta cuestión es lo que los teóricos denominan el "espacio" del problema, "'What' domain" en la literatura inglesa. A esta fase se la suele denominar análisis, y mi consejo particular es que después de una primera toma de contacto, el segundo paso sea definir de la forma más detallada posible el principio y el final del problema. Es decir: cual es la información de partida (incluyendo su formato y en que 8


Proceso para Diseñar un Lenguaje de Programación soporte se recibe) y cual es la información final y en que soporte se proporcionará; no es lo mismo mostrar una imagen que componer una factura o disparar un proceso si un sensor analógico-digital nos suministra una determinada señal (por citar algún ejemplo). Normalmente en ambas cuestiones tiene mucho que decir el cliente, es lo que se llama especificación; el resto (lo que hay entre los datos de entrada y la salida), debe rellenarlo el programador. Generalmente si se tienen bien definidos ambos extremos, se tiene resuelta la mitad del problema; cuando se tengan diseñados los ficheros se tendrán dos terceras partes -ver a continuación-. Este sistema tiene además la ventaja de poner inmediatamente de manifiesto las indefiniciones de partida; a veces los clientes no saben exactamente qué desean y hay que ayudarles a centrar el problema. Dentro de esta fase tiene especialísima importancia el tema de los límites; esto se refiere al orden de magnitudes que se manejarán. ¿De que rango serán las magnitudes numéricas? ¿Podrán adoptar valores negativos? ¿Hay información alfanumérica? ¿Como son de largas estas cadenas? Especialmente si el programa implica diseño de archivos (como es casi seguro), ¿Cual podrá llegar a ser su tamaño dentro de la vida del programa? Si se manejan ficheros u objetos binarios, ¿Como son de grandes? ¿Que concepto tiene el cliente de los que sería "rápido" o "lento"? (¿milisegundos, minutos, horas?). En esta fase sea especialmente precavido y no se crea a pié juntillas todo lo que le digan (intente hacer de abogado del diablo). Como postre, diseñe las líneas maestras de una estrategia de recuperación de errores de ejecución, incluyendo que hará con los no recuperables (errores fatales). Piense por ejemplo que si algún día lo llaman para ver "que ha pasado", quizás le interese disponer de un volcado de texto ASCII en el disco con una descripción del estatus del programa como parte de las funciones de salida. Hoy día, cuando se empieza a hablar de frigoríficos que avisarán de que faltan provisiones o de lavadoras que avisarán al técnico si se estropean, no estaría de más que sus programas estuviesen a la altura de las circunstancias.

4. Diseñar los ficheros y módulos Si el programa debe utilizar ficheros que no vengan impuestos (ya existentes), y suponiendo que todo lo anterior esté suficientemente claro, este es el momento de hacerlo. Ponga por escrito la

9


Proceso para Diseñar un Lenguaje de Programación especificación de tales ficheros, incluyendo el nombre que dará a las variables y, en su caso, el que tendrán en el disco o almacenamiento externo. Esto puede concretarse quizás a la definición de algunas estructuras. En esta fase es posible que tenga que repreguntar alguna cosa que se pasó por alto. Teniendo ya una imagen más o menos clara de lo que hará su programa, si éste es mediano o grande, es posible que todavía tenga que realizar una labor previa antes de ponerse a escribir el código: diseñar a grandes rasgos cuales serán los módulos del programa; módulos que se corresponderán aproximadamente con la distribución del código en ficheros fuente independientes. Quizás tenga que decidir también si algunas partes aparecerán como librerías. Recuerde lo indicado al respecto al tratar de los Sub espacios de Nombres. Esta fase es especialmente importante en el caso de programas muy grandes, cuyo desarrollo se reparte entre varios programadores que se encargan de uno o varios de estos módulos. En estos casos, el análisis, la especificación, la subdivisión en partes (con sus especificaciones particulares), y la asignación de estas como tareas a los programadores, lo habrá realizado el jefe de programación y desarrollo.

5. Escribir el código Suponiendo cumplimentados los pasos anteriores, el programador está en condiciones de construir una imagen mental clara de como será esa conexión entre la información de entrada y la salida, es lo que se denomina "espacio" de la solución ("'How' domain"); su forma concreta es justamente el fuente del programa que se pretende. La codificación consiste justamente trasportar a papel (en el lenguaje de programación elegido) la imagen mental de esa conexión. Para escribir el código fuente de un programa C++ solo se puede utilizar un subconjunto de 96 caracteres del juego total de caracteres US-ASCII Son los siguientes

Juego de caracteres imprimibles:

a b c d e f g h i j k l m n o p q r s t u v w x y z

10


Proceso para Diseñar un Lenguaje de Programación

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0

1

2

3

4

5

6

7

8

9

_ { } [ ] # ( ) <> % : ; . ? * + / ^ & | ~ ! = , \ " ’

Caracteres no-imprimibles denominados separadores

Espacio horizontal; Tabulación horizontal (TAB); Tabulación vertical (VT); Salto de forma (FF); Nueva línea (NL).

Nota: Para escribir el código solo hace falta un editor de texto plano, aunque las modernas "suites" de programación incluyen editores específicos que están conectados con el depurador, el compilador el enlazador (ver más adelante) e incluso el sistema de ayudas, de forma que, por ejemplo, pueden mostrarnos automáticamente la sentencia en la que se ha producido un error de compilación, o la página correspondiente del manual si pulsamos en una palabra reservada y pedimos ayuda (F1 generalmente). También muestran en diversos colores las palabras clave, los comentarios, Etc. Los más avanzados disponen incluso de opciones que podríamos calificar de "inteligentes", en el sentido que pueden prever cual será nuestro próximo movimiento en función de la sentencia que estamos escribiendo (por ejemplo, ofrecernos una lista de las propiedades y métodos de una clase si nos estamos refiriendo a ella). Durante la fase de escritura no desdeñe dos puntos: 

Incluir la mayor cantidad de comentarios y aclaraciones posibles. Cuando se está muy "metido" en el programa todo parece evidente, pero piense que tiene que retocarlo dentro de unos años, quizás entonces le parezca "Chino" y agradecerá haber dejado la mayor cantidad de documentación y aclaraciones al respecto. Incluso si es seguro que no volverá a verlo, piense en el sufrido programador que le seguirá si tiene que habérselas con su código. En este sentido C++ no es precisamente COBOL, aunque afortunadamente permite incluir en la fuente comentarios todo lo extensos que se desee. No caiga tampoco en el error de pensar que esa información ya está en la documentación escrita que le han obligado a entregar junto con las fuentes; posiblemente dentro de unos años Usted mismo no encuentre esos documentos.

11


Proceso para Diseñar un Lenguaje de Programación 

Incluir la mayor cantidad posible de rutinas y condiciones de comprobación de errores. Piense que el operador es un "manazas" o que los datos pueden venir con alguna corrupción, error de transmisión, etc. Verifique constantemente que las condiciones son las esperadas.

Una vez que se tiene el código fuente (en uno o varios módulos), el proceso de traducirlo a instrucciones comprensibles por el procesador (lenguaje máquina) puede hacerse siguiendo dos modelos: los intérpretes y los compiladores En el caso de lenguajes compilados como C++, el fichero de texto plano que contiene la fuente del programador (con la terminación .C ó .CPP), es sometido a un proceso de varias fases que terminan en la obtención del ejecutable. De forma genérica, todo este proceso se denomina "compilación", aunque es una generalización un tanto incorrecta, ya que la compilación propiamente dicha es solo una de las etapas intermedias. Sería más correcto decir "construcción" del ejecutable, aunque por la extensión y generalización de su

uso

seguiremos

el

término

utilizando genérico

"compilación"

para referirnos

a

procesos

él.

Los

de

construcción

del ejecutable

se

esquematizan

en

la figura que

comentamos a

continuación:

12


Proceso para Dise帽ar un Lenguaje de Programaci贸n

13


Proceso para Diseñar un Lenguaje de Programación 6. Análisis Léxico

La especificación de un lenguaje de programación a menudo incluye un conjunto de reglas que definen el léxico. Estas reglas consisten comúnmente en expresiones regulares que indican el conjunto de posibles secuencias de caracteres que definen un token o lexema. En algunos lenguajes de programación es necesario establecer patrones para caracteres especiales (como el espacio en blanco) que la gramática pueda reconocer sin que constituya un token en sí. Esta etapa está basada usualmente en una máquina de estados finitos. Esta máquina contiene la información de las posibles secuencias de caracteres que puede conformar cualquier token que sea parte del lenguaje (las instancias individuales de estas secuencias de caracteres son denominados lexemas). Por ejemplo, un token de naturaleza entero puede contener cualquier secuencia de caracteres numéricos. Funciones del analizador léxico. Analizador léxico (scanner): lee la secuencia de caracteres del programa fuente, caracter a caracter, y los agrupa para formar unidades con significado propio, los componentes léxicos (tokens en ingles). Estos componentes léxicos representan: Palabras reservadas: if, while, do, . . . Identificadores: asociados a variables, nombres de funciones, Tipos definidos por el usuario, etiquetas,... Por ejemplo: posición, Velocidad, tiempo, . . . Operadores: = * + - / == > < & ! = . . . Símbolos especiales: ; ( ) [ ] f g ... Constantes numéricas: literales que representan valores en- teros, en coma flotante,

14


Proceso para Diseñar un Lenguaje de Programación

7. Análisis sintáctico Puesto que el fichero fuente está escrito en un "lenguaje" (C++ en este caso) que tiene sus propias reglas de sintaxis (como los lenguajes naturales), el compilador debe comprobar que estas reglas se han respetado. Este análisis ("Parsing") es realizado por el analizador sintáctico En esta fase se realizan comprobaciones como que los paréntesis están cerrados, que no aparecen expresiones incompletas, etc. Para realizar esta labor, el "parser" debe identificar los tokens, de forma que la fuente es tokenizado, esto es, reducido a tokens y separadores. El fuente es escaneado, el analizador sintáctico (parser) extrae los tokens, seleccionando el que coincida con la secuencia de caracteres más larga posible dentro de la secuencia analizada. Por ejemplo, la palabra clave externa es reconocida como un solo token (identificador de clase de almacenamiento) en vez de seleccionar externa (una palabra reservada) y al (que sería un identificador). Los separadores (whitespaces) es el nombre genérico dado a los espacios (32), tabulaciones verticales VT (11), horizontales TAB (9) nueva línea NL (10) y retorno de carro CR (13). Los separadores sirven para indicar donde empiezan y terminan las palabras, pero después de esto cualquier separador redundante es descartado. Por ejemplo, las dos secuencias: inti; float f; int i; float f; Son léxicamente equivalentes y el resultado del análisis son las seis palabras siguientes: int i 15


Proceso para Diseñar un Lenguaje de Programación ; float f ; El carácter ASCII espacio puede formar parte de cadenas literales (alfanuméricas), en cuyo caso es protegido del proceso de análisis, permaneciendo como parte de la cadena. Por ejemplo: char name[] = "Playa Victoria"; Es reducido a siete tokens, incluyendo una cadena literal "Playa Victoria" char name [ ] = "Playa Victoria" ;

8. Análisis semántico En lenguajes como el Ensamblador la comprobación se limita al análisis anteriormente señalado; con esto se garantiza que la fuente original es correcta (sintácticamente), es decir, es un escrito correcto desde el punto de vista del lenguaje, otra cosa es que tenga un sentido computacional correcto, o diga tonterías, incongruencias o sinsentidos. Por supuesto la meta del compilador es conseguir descubrir con anticipación (al runtime) el máximo de errores posibles. En los lenguajes de alto nivel, esto se consigue con una cierta comprobación del "sentido" o "significado" del escrito, es el denominado análisis semántico (análisis del significado). La mejor baza de que dispone C++ para esta segunda comprobación es la comprobación estática de tipos. Es decir, que las variables y las operaciones entre ellas se usan correctamente; esto supone verificar que las llamadas a funciones, los valores devueltos por estas y los operando de las expresiones corresponden con el tipo que se les supone en cada caso. Por ejemplo:

16


Proceso para Diseñar un Lenguaje de Programación int x; charfunc(); .... x = func(); En este caso, la primera línea declara que la variable x es tipo int (entero); la segunda declara que la función devuelve un carácter (char); si una líneas más adelante se pretende igualar la variable x con el valor devuelto por la función, el analizador semántico estaría en condiciones de asegurar que existe una incongruencia en las pretensiones del programador, generando el correspondiente mensaje de advertencia o error.

9. Generador de código Todos estos tokens identificados por el analizador sintáctico, son organizados en una estructura como las hojas de un árbol. A continuación, el generador de código recorre este árbol traduciendo sus hojas directamente en código de la máquina para la que se compila. Si se solicita, el compilador también puede en esta fase generar un fichero en lenguaje macro ensamblador para su posible inspección por el programador (este código es también dependiente de la máquina para la que se compila y fácilmente entendible por el humano; puede contener incluso comentarios para facilitar su lectura). Nota: Los compiladores modernos suelen incluir opciones que permiten generar código optimizado para el tipo de procesador que se utilizará. Por ejemplo, el compilador Borland C++ dispone de opciones que permiten generar código optimizado para procesadores Intel de modelos específicos. Como cabría esperar, el compilador GNU c++ es el que ofrece más posibilidades en este sentido, que incluyen el tipo de procesador dentro de una larga lista de fabricantes, y dentro de estos diferentes modelos. En concreto, para la familias Intel i386 y x86-64, permite elegir entre 20 posibilidades diferentes.

A veces, después del generador de código puede ejecutarse un optimizador (peepholeoptmizer). Este generador de código sería propiamente el compilador, es decir, el encargado de traducir algo entendible por el humano en código máquina. 17


Proceso para Diseñar un Lenguaje de Programación En cualquier caso, el resultado es un fichero "objeto", generalmente con la terminación .obj o .o. También puede ordenarse al compilador que incluya en el "objeto", determinada información adicional que será utilizada más tarde por el depurador, por ejemplo los números de línea de las sentencias. Cuando se hace así, se habla de una compilación "provisional" o de "depuración"; distinta de la que se realiza para la versión definitiva (de campo) del programa en la que no se incluyen este tipo de información que ya no es necesaria.

10. Enlazado El último paso en construir un ejecutable es el enlazado. Recibe este nombre el proceso de aglutinar todos los recursos en un solo fichero ejecutable. Estos recursos son desde luego los ficheros-objeto obtenidos en la compilación de los diversos módulos (ficheros .c) que componen el programa. Además, si se han utilizado funciones o clases de librería (como es casi seguro), el enlazador ("Linker") es el programa encargado de incluir los módulos adecuados en el fichero ejecutable final. Así pues, la función primordial del enlazador es resolver todas las referencias que puedan existir en el programa, es decir: que cada invocación a un valor o a una función corresponda una dirección donde se encuentra el recurso correspondiente, y que estén todos contenidos en un solo fichero que pueda ser cargado y ejecutado por el Sistema Operativo. Eventualmente algunos recursos pueden estar en otros ficheros distintos del ejecutable, librerías de enlazado dinámico (en Windows se denominan DLLs). En cuyo caso el enlazador también incluirá las direcciones y convenciones de llamada adecuadas para que puedan ser traídos a ejecución desde el programa principal. Por último, el enlazador se encarga de insertar en el ejecutable un trozo de código especial: el módulo inicial, que es el encargado de iniciar la ejecución. Hay que tener en cuenta que generalmente el enlazador puede producir diversos tipos de resultados:

18


Proceso para Diseñar un Lenguaje de Programación 10.1 Versión de depuración Se trata de una versión en la que dentro del propio ejecutable, se incluye información adicional no estrictamente necesaria para la ejecución sino para la depuración (por ejemplo los números de línea del código fuente que corresponde a cada sentencia). Estos ejecutables permiten la ejecución en un modo especial, en la que por ejemplo, pueden ejecutarse las sentencias paso a paso, o que el programa se detenga al llegar a diversos puntos establecidos de antemano; ver el contenido de las variables, el estado de la pila y otros aspectos internos muy útiles cuando se intentan depurar errores de tiempo de ejecución. Esto se consigue porque el programa corre bajo control de otro programa que actúa de controlador de la ejecución, es el depurador ("Debugger").

10.2 Versión de publicación Es la versión definitiva que saldrá al público (se entregará al usuario). Se distingue de las versiones internas en que no incluye información para depuración. Es buena práctica incluir en cada versión publicada información sobre el número de versión del programa y de compilación (esto suele venir indicado en la siguiente forma: Versión xx.yy.zz build nnn). Generalmente los programas sufren muchas modificaciones a lo largo de su vida (corrección de errores, perfeccionamientos, versiones para diversas plataformas, etc.), y es preciso identificarlos. Es costumbre hablar de "versiones", que se identifican por grupos de números separados por puntos. Por ejemplo: Versión xx.yy.zz. Cada fabricante de software, grupo de trabajo o programador, utiliza una convención, estableciéndose que tipo de cambios dan lugar a diferencias de versión en el grupo de cifras xx; en el yy o en el zz. Generalmente se acepta que los cambios de mayor nivel (xx) representan versiones totalmente nuevas del programa; que requieren incluso rescribir los manuales de uso. Los cambios menores corresponden a modificaciones en el grupo yy (por ejemplo utilizar otras versiones de las librerías o distinto compilador); finalmente los cambios de detalle representan modificaciones en el grupo zz. Viene a representar cambios mínimos, que no merecen una alteración del último grupo de cifras, pero cambios al fin y al cabo (cuando se recompila es porque algo ha cambiado, aunque sea un comentario en el fuente). Es también costumbre incluir un último identificador: El número de compilación o construcción ("build" en la literatura inglesa); es un número progresivamente creciente para cada compilación

19


Proceso para Diseñar un Lenguaje de Programación distinta. A título de ejemplo, en la página adjunta se muestra la clasificación utilizada para las sucesiones versiones de los productos de un conocido fabricante.

10.3 Librería En las páginas siguientes veremos que como resultado de la "compilación", no siempre se desea conseguir un ejecutable; al menos no en el sentido tradicional del término, sino una librería (de las que existen varios tipos), o un fichero objeto. En lo que respecta al lenguaje C++, existen dos tipos fundamentales: Estáticas y Dinámicas. Las primeras son colecciones de ficheros pre compilados, cuyo código puede ser añadido a un ejecutable en el proceso de enlazado (los ficheros de la Librería Estándar 5 que acompañan a los compiladores C++ son de este tipo). Las segundas son auténticos ejecutables externos que son invocados desde otro ejecutable y devuelven el control a este cuando terminan su ejecución.

11. Errores La verdadera prueba de fuego del programador se presenta cuando lanza la orden de compilar y enlazar su programa. Todos los módulos involucrados en los pasos anteriores, compilador, analizador sintáctico y enlazador pueden detectar errores en nuestro código y mostrar los mensajes correspondientes.

11.1 Tipos de errores En cuanto al momento en que se producen, son básicamente de tres tipos: 

De tiempo de compilación. Se engloban aquí los errores detectados por preprocesador, el analizador sintáctico y el propio compilador. Los hay meramente sintácticos, por ejemplo un paréntesis no cerrado; también de tipo lógico, por ejemplo la referencia a una variable no declarada previamente, etc. etc.

De tiempo de enlazado. Son detectados por el enlazador. Por ejemplo una llamada a función cuya definición no aparece por ninguna parte (el enlazador no es capaz de encontrarla en los directorios que tiene asignados como "normales" para buscar); también la

20


Proceso para Diseñar un Lenguaje de Programación inversa: dos funciones del mismo nombre situadas en dos módulos (fuentes) distintos (la referencia aparece duplicada). 

De tiempo de ejecución (runtime). Existe finalmente una última clase de errores: los que se producen cuando se ejecuta el programa; son precisamente los más difíciles de diagnosticar y verificar, sobre todo en aplicaciones grandes (los relativos a "pérdidas misteriosas" de memoria y punteros descontrolados son especialmente temibles).

11.2 Gravedad de los errores Los errores producidos durante la compilación son de dos tipos, según su gravedad: 

Errores fatales ("Errors"): Son errores graves, el proceso no puede continuar y es detenido después de mostrar la información pertinente.

Advertencias ("Warnings"): No son errores graves pero si circunstancias sospechosas o inusuales de las que el compilador entiende que merecen una advertencia por si es algo que se nos ha escapado inadvertidamente (por ejemplo: Una variable declarada que no se utiliza para nada más). En estos casos, el proceso continúa y si no hay errores graves se construye un ejecutable.

En todos los casos el aviso incluye indicación del fichero ("fuente" .C/.CPP), el número de línea, y el nombre de la función donde se produce el error, así como una explicación más o menos clara de su motivo. En principio pueden ser cuestiones banales, como haber olvidado poner un punto y coma; al final de una sentencia (muy corriente en los que estamos acostumbrados a programar en otros lenguajes). En otros casos los mensajes son especialmente crípticos, sobre todo para el profano, pero poco a poco los entendemos mejor y podemos aprender mucho de ellos si prestamos la debida atención y entendemos su "porqué". Recordar que todos los compiladores disponen de opciones para modificar el número y tipo de los errores y advertencias ("Kartings") que aparecen. Respecto a los primeros, puede instruirse al compilador para que suspenda la actividad al aparecer el primer error, o que continúe hasta que aparezca un número determinado de ellos. Respecto a los avisos, puede ordenarse que no muestre ninguno, o que sea más o menos benevolente en determinados aspectos. Por ejemplo, puede indicarse que la comprobación siga estrictamente el estándar C++ y que avise de cualquier desviación al respecto (los compiladores suelen permitir ciertas "peculiaridades" que no son estándar). 21


Proceso para Diseñar un Lenguaje de Programación

11.3 Generalidades sobre los errores de compilación Respecto a los errores de compilación, es importante hacer una advertencia al neófito: Con frecuencia el compilador nos informa de error en una línea más abajo de donde está verdaderamente. Por ejemplo, olvidar un punto y coma de final de sentencia puede dar lugar a que el compilador nos informe de un error incomprensible dos o tres línea más abajo. Cuando se realizan modificaciones en fuentes grandes y no se tiene mucha práctica, es preferible realizar cambios pequeños y compilar sistemáticamente después de cada uno. Así sabremos que el error corresponde a lo último que hemos tocado. Hay veces en que quitar una simple coma en una sentencia produce un listado de 15 o 20 errores en líneas siguientes. En las asignaciones del tipo: Lvalue = Rvalue; En las que intentamos asignar un valor Rvalue a un Lvalue, son muy frecuentes los errores en que el compilador produce un mensaje del siguiente aspecto: Error.... Cannot convert 'xxxxx' to 'yyyyy' in function.... Lo importante a reseñar aquí, es que las expresiones xxxxx e yyyyy informan sobre el tipo de objeto que hay en cada lado de la expresión de asignación. Nota: En el capítulo dedicado a los tipos de datos se describe detalladamente como el compilador clasifica los objetos según su tipo. En las asignaciones, el Lvalue debe recibir un valor de su mismo tipo. Si el tipo del Rvalue no concuerda con él, el compilador puede intentar adecuarlo, pero si esto no es posible, se produce un error como el señalado. En él se nos indica que el tipo xxxxx, que corresponde al Rvalue (el resultado de la expresión a la derecha del operador = ), no puede ser convertido al tipo yyyyy del Lvalue. Hay que advertir que las expresiones xxxxx e yyyyy están codificadas. Cada compilador utiliza un algoritmo interno para designar cada uno de los innumerables tipos que pueden existir en C++. En 22


Proceso para Diseñar un Lenguaje de Programación concreto, la designación utilizada en estos mensajes es la misma que utiliza en el operador typeid. En situaciones difíciles, es mucha la información que puede obtenerse de estas expresiones si se observan detenidamente.

Aunque la comprobación estática de tipos, y del cumplimiento de las reglas sintácticas realizada por el compilador, resultan muy eficientes en lo que respecta a la detección de errores, en realidad, el trabajo dista de ser completo y suficiente. Existen multitud de circunstancias potencialmente erróneas que son pasadas por alto. En especial las relacionadas con pérdidas de memoria; existencia de punteros descolgados; bucles infinitos; objetos declarados pero no utilizados, y un largo etcétera. Algunos de estos errores pueden permanecer agazapados en el código y solo aparecer en circunstancias muy especiales, incluso después de que la aplicación haya sido rodada largo tiempo sin contratiempos. Muchas de estas circunstancias pueden evitarse, o al menos mitigarse, siguiendo ciertas pautas y recomendaciones "de buena práctica", muchas de las cuales están contenidas en la obra TC++PL de Stroustrup; la mayoría en forma de advertencias sobre cosas que "no" debe hacerse. Sin embargo, el problema persiste, máxime en un lenguaje como C++ plagados de peligros potenciales que acechan en el arcén, y con el que es posible "volarse la pierna completa". Para reforzar la calidad del código y prevenir errores posteriores (de run-time), se han creado multitud de herramientas. Entre las más conocidas se encuentran las siguientes: 

Lint, denominadas así en atención a que tienen su origen en una utilidad de este nombre (lint) desarrollada inicialmente en el entorno Unix. Estas utilidades se ejecutan sobre el fuente sin compilar (no confundirlas con los depuradores "debugger" -de run-time-, aunque también sirven para "depurar" el código); comprueban la sintaxis y errores en los tipos de datos de forma más concienzuda y profunda que los compiladores C/C++, y avisan de gran cantidad de peligros potenciales; incorrecciones; desviaciones sobre las reglas universalmente aceptadas como de "buena práctica", etc. Actualmente han llegado a un elevado nivel de sofisticación, de forma que un buen Lint puede evitarnos muchas horas de depuración. En realidad es una herramienta que no debería faltar en el taller del programador profesional.

cb. Esta utilidad, originaria del SO AIX, reformatea el código fuente contenido en un fichero y lo vuelca sobre el dispositivo estándar de salida utilizando un formateo basado en sangrados y espaciados, que ayudan a interpretar la estructura del código.

23


Proceso para Diseñar un Lenguaje de Programación 

cflow. Esta utilidad, originaria del SO AIX, analiza el contenido de un fichero objeto C/C++ y proporciona en la salida estándar (stdout) un gráfico de sus referencias externas.

cxref. Esta utilidad, análoga a las anteriores, analiza los fuentes C/C++ y genera una tabla con todos los símbolos encontrados en cada fichero, incluyendo los nombres de los parámetros formales de las funciones (contenidos en la definición de la función). La tabla es mostrada en el dispositivo estándar de salida (stdout), e incluye el sitio en que cada referencia se ha resuelto (suponiendo que la definición esté en el código analizado).

Si está interesado en las características y posibilidades de estos productos, la mayoría comerciales y algunos muy costosos, aparte de la correspondiente búsqueda en Google, existe un interesante artículo de Scott Meyers (uno de los "Gurus" del C++) y Martin Klaus titulado "A First Look at C++ Program Analyzers", en el que se repasan las cualidades y características de distintos paquetes, incluyendo una evaluación de su comportamiento frente a lo que el autor considera deseable. Aparecido en el número de Febrero de 1997 del Dr. Dobb's Journal, existe una versión de prepublicación accesible on-line, que es incluso más completa que el artículo publicado (probablemente debido a las exigencias de maquetación de la revista): www.aristeia.com. Uno de los productos punteros y más antiguos, es el de Gimpel Software www.gimpel.com; esta empresa dispone de dos versiones denominadas PC-Lint y FlexeLint. La primera para Windows, la segunda, más general, para cualquier entorno que soporte un compilador C, incluyendo Unix y Linux. Si tiene interés en comprobar más de cerca el tipo de información que proporcionan estas utilidades, en el sitio de este fabricante existe una sección denominada Bug del mes ("Bug of the month") en la que se exponen ejemplos de código, junto con el resultado del análisis (después de pulsar un botón). Además de sumamente instructivos, los casos propuestos pueden servirle para auto-evaluar sus conocimientos de C++ al respecto. A mi entender también pueden constituir una magnífica fuente de inspiración para los enseñantes que busquen material para ejemplo o evaluación (sed piadosa en los exámenes porque algunos son realmente para niveles avanzados No se puede pretender que después de un semestre de estudio, el alumno esté en condiciones de adivinar correctamente el "bug C/C++ del mes"

11.4 Errores de ejecución Para los errores de tiempo de ejecución se requieren estrategias especiales. En principio, durante la fase de comprobación inicial, se tienen las ofrecidas por el depurador 24

. Prácticamente todos los


Proceso para Diseñar un Lenguaje de Programación entornos de desarrollo disponen de un depurador más o menos potente y sofisticado. Puede afirmarse que el depurador es otra herramienta que no debe faltar en el arsenal de cualquier programador profesional, en especial porque hay errores que son prácticamente imposibles de diagnosticar y corregir sin su ayuda. Como se ha indicado, el depurador incluye en el ejecutable un código especial que realiza las funciones de depuración deseadas, pero aparte de los que podríamos denominar estándar (cuyos módulos son incluidos en durante la fase de enlazado del ejecutable), existen herramientas específicas que analizan el ejecutable y son capaces de detectar determinados errores e inconsistencias. Estas herramientas realizan su trabajo durante la ejecución, para lo que modifican el código a analizar incluyendo determinados módulos que les permiten controlar el desarrollo de la ejecución (se dice que "instrumentan" el código). La forma de realizar esta "instrumentación" depende de la herramienta: puede realizarse durante la compilación ("compile-time"), añadiendo código que no aparece en el fuente; durante el enlazado ("link-time"); durante la carga ("loadtime"), cuando el ejecutable es acomodado en memoria, o antes de la carga, sobre cualquier ejecutable listo para ser usado. Generalmente estas herramientas controlan la ejecución, toman nota de las incidencias, y finalmente proporcionan un informe de las mismas cuando la ejecución finaliza. Nota: no existe una denominación unificada para este tipo de productos. Quizás el más conocido es esBoundsChecker, de Numega www.numega.com (actualmente aparece como Compuware).

También puede intentar una búsqueda en Google bajo el epígrafe

"Memorydebugger".

Después de todas las medidas preventivas ya reseñadas, cuando finalmente, después de las pruebas de "laboratorio" damos por bueno el programa, este queda merced a si mismo; a la calidad de su propio mecanismo de defensa. Como errar es humano, los diseñadores del C++ pensaron que a pesar de la programación más cuidadosa, siempre pueden presentarse circunstancias excepcionales o imprevistas. Para poder hacerles frente, dotaron al lenguaje de opciones especiales con las que tratar este tipo de situaciones, de forma que pudiese seguir todo bajo control; estos recursos específicos se exponen con detalle en el capítulo dedicado al Tratamiento de Excepciones.

25


Proceso para Diseñar un Lenguaje de Programación Pseudocódigo En ciencias de la computación, y análisis numérico el pseudocódigo (o falso lenguaje) es una descripción de un algoritmo de programación informático de alto nivel compacto e informal que utiliza las convenciones estructurales de un lenguaje de programación verdadero, pero que está diseñado para la lectura humana en lugar de la lectura en máquina, y con independencia de cualquier otro lenguaje de programación. Normalmente, el pseudocódigo omite detalles que no son esenciales para la comprensión humana del algoritmo, tales como declaraciones de variables, código específico del sistema y algunas subrutinas. El lenguaje de programación se complementa, donde sea conveniente, con descripciones detalladas en lenguaje natural, o con notación matemática compacta. Se utiliza pseudocódigo pues este es más fácil de entender para las personas que el código de lenguaje de programación convencional, ya que es una descripción eficiente y con un entorno independiente de los principios fundamentales de un algoritmo. Se utiliza comúnmente en los libros de texto y publicaciones científicas que se documentan varios algoritmos, y también en la planificación del desarrollo de programas informáticos, para esbozar la estructura del programa antes de realizar la codificación efectivamente. No existe una sintaxis estándar para el pseudocódigo, aunque los dos programas que manejan pseudocódigo tengan su sintaxis propia. Aunque parecido, el pseudocódigo no debe confundirse con los programas esqueleto que incluyen código ficticio, que pueden ser compilados sin errores. Aunque los diagramas de flujo y UML sean más amplios en el papel, pueden ser considerados como una alternativa gráfica al pseudocódigo.

Aplicación Muchas veces, los libros de texto y publicaciones científicas relacionadas con la informática y la computación numérica, utilizan pseudocódigo en la descripción de algoritmos, de manera que todos los programadores puedan entenderlo, aunque no todos conozcan el mismo lenguaje de programación. Por lo general, en los libros de texto, hay una explicación que acompaña la introducción que explica las convenciones particulares en uso. El nivel de detalle del pseudocódigo puede, en algunos casos, acercarse a la de formalizar los idiomas de propósito general. Un programador que tiene que aplicar un algoritmo específico, sobre todo uno des familiarizado, generalmente comienza con una descripción en pseudocódigo, y luego "traduce" esa descripción en el lenguaje de programación meta y lo modifica para que interactúe correctamente con el resto del programa. Los programadores también pueden iniciar un proyecto describiendo la forma del código

26


Proceso para Diseñar un Lenguaje de Programación en pseudocódigo en el papel antes de escribirlo en su lenguaje de programación, como ocurre en la estructuración de un enfoque de Top-down y Bottom-up arriba hacia abajo.

Sintaxis En la actualidad, por lo general, el pseudocódigo, como su nombre lo indica, no obedece a las reglas de sintaxis de ningún idioma en particular, no es de forma estándar sistemática, a pesar de que cualquier escritor en particular vaya a pedir prestado las estructuras de control general, la sintaxis y el estilo, por ejemplo, de algún lenguaje de programación convencional. Pero en caso de que se quiera ejecutar, se debe llevar a forma tipo, para que no genere mensajes de error. Las fuentes populares incluyen la sintaxis de Pascal, BASIC, C, C++, Java, Lisp, y ALGOL. Por lo general, se omiten las declaraciones de variables. A veces, las llamadas a funciones, los bloques de código y el código contenido dentro de un loop se remplazan por una sentencia de una línea en lenguaje natural. Dependiendo del escritor, el pseudocódigo puede variar mucho en su estilo, yendo desde una imitación casi exacta de un lenguaje de programación real en un extremo, hasta al acercarse a una descripción en prosa de formato de pseudocódigo.

Características y partes Las principales características de este lenguaje son: 1. Se puede ejecutar en un ordenador (con un IDE como por ejemplo SLE o PSeInt) 2. Es una forma de representación sencilla de utilizar y de manipular. 3. Facilita el paso del programa al lenguaje de programación. 4. Es independiente del lenguaje de programación que se vaya a utilizar. 5. Es un método que facilita la programación y solución al algoritmo del programa. Todo documento en pseudocódigo debe permitir la descripción de: 1. Instrucciones primitivas. 2. Instrucciones de proceso.... 3. Instrucciones de control. 4. Instrucciones compuestas. 5. Instrucciones de descripción.

27


Proceso para Diseñar un Lenguaje de Programación Estructura a seguir en su realización: 1. Cabecera. 1. Programa. 2. Módulo. 3. Tipos de datos. 4. Constantes. 5. Variables. 2. Cuerpo. 1. Inicio. 2. Instrucciones. 3. Fin.

Definición de datos del pseudocódigo La definición de datos se da por supuesta, sobre todo en las variables sencillas, si se emplea formaciones: pilas, colas, vectores o registros, se pueden definir en la cabecera del algoritmo, y naturalmente cuando empleemos el pseudocódigo para definir estructuras de datos, esta parte la desarrollaremos adecuadamente.

Compiladores modernos Al desarrollarse las primeras computadoras electrónicas, se vio la necesidad de programarlas, es decir, de almacenar en memoria la información sobre la tarea que iban a ejecutar. Las primeras se usaban como calculadoras simples; se les indicaban los pasos de cálculo, uno por uno. John Von Neumann desarrolló el modelo que lleva su nombre, para describir este concepto de "programa almacenado". En este modelo, se tiene una abstracción de la memoria como un conjunto de celdas, que almacenan simplemente números. Estos números pueden representar dos cosas: los datos, sobre los que va a trabajar el programa; o bien, el programa en sí. ¿Cómo es que describimos un programa como números? Se tenía el problema de representar las acciones que iba a realizar la computadora, y que la memoria, al estar compuesta por switches correspondientes al concepto de bit, solamente nos permitía almacenar números binarios.

28


Proceso para Diseñar un Lenguaje de Programación La solución que se tomó fue la siguiente: a cada acción que sea capaz de realizar nuestra computadora, asociarle un número, que será su código de operación (opcode). Por ejemplo, una calculadora programable simple podría asignar los opcodes: 1 = SUMA, 2 = RESTA, 3 = MULTIPLICA, 4 = DIVIDE. Supongamos que queremos realizar la operación 5 * 3 + 2, en la calculadora descrita arriba. En memoria, podríamos "escribir" el programa de la siguiente forma: Localidad Opcode Significado Comentario 0 5 5 En esta localidad, tenemos el primer número de la fórmula 1 3 * En esta localidad, tenemos el opcode que representa la multiplicación. 2 3 3 En esta localidad, tenemos el segundo número de la fórmula 3 1 + En esta localidad, tenemos el opcode que representa la suma. 4 2 2 En esta localidad, tenemos el último número de la fórmula Podemos ver que con esta representación, es simple expresar las operaciones de las que es capaz el hardware (en este caso, nuestra calculadora imaginaria), en la memoria. La descripción y uso de los opcodes es lo que llamamos lenguaje de máquina. Es decir, la lista de códigos que la máquina va a interpretar como instrucciones, describe las capacidades de programación que tenemos de ella; es el lenguaje más primitivo, depende directamente del hardware, y requiere del programador que conozca el funcionamiento de la máquina al más bajo nivel. Los lenguajes más primitivos fueron los lenguajes de máquina. Esto, ya que el hardware se desarrolló antes del software, y además cualquier software finalmente tiene que expresarse en el lenguaje que maneja el hardware. La programación en esos momentos era sumamente tediosa, pues el programador tenía que "bajarse" al nivel de la máquina y decirle, paso a pasito, cada punto de la tarea que tenía que realizar. Además, debía expresarlo en forma numérica; y por supuesto, este proceso era propenso a errores, con lo que la productividad del programador era muy limitada. Sin embargo, hay que recordar que en estos momentos, simplemente aún no existía alternativa. El primer gran avance que se dio, como ya se comentó, fue la abstracción dada por el Lenguaje Ensamblador, y con él, el nacimiento de las primeras herramientas automáticas para generar el código máquina. Esto redujo los errores triviales, como podía ser el número que correspondía a una operación, que son sumamente engorrosos y difíciles de detectar, pero fáciles de cometer. Sin embargo, aún aquí es fácil para el programador perderse y cometer errores de lógica, pues debe bajar al nivel de la forma en que trabaja el CPU, y entender bien todo lo que sucede dentro de él.

29


Proceso para Diseñar un Lenguaje de Programación Con el desarrollo en los 50s y 60s de algoritmos de más elevado nivel, y el aumento de poder del hardware, empezaron a entrar al uso de computadoras científicos de otras ramas; ellos conocían mucho de Física, Química y otras ramas similares, pero no de Computación, y por supuesto, les era sumamente complicado trabajar con lenguaje Ensamblador en vez de fórmulas. Así, nació el concepto de Lenguaje de Alto Nivel, con el primer compilador de FORTRAN (FORmula TRANslation), que, como su nombre indica, inició como un "simple" esfuerzo de traducir un lenguaje de fórmulas, al lenguaje ensamblador y por consiguiente al lenguaje de máquina. A partir de FORTRAN, se han desarrollado innumerables lenguajes, que siguen el mismo concepto: buscar la mayor abstracción posible, y facilitar la vida al programador, aumentando la productividad, encargándose los compiladores o intérpretes de traducir el lenguaje de alto nivel, al lenguaje de computadora. Hay que notar la existencia de lenguajes que combinan características de los de alto nivel y los de bajo nivel (es decir, Ensamblador). Mi ejemplo favorito es C: contiene estructuras de programación de alto nivel, y la facilidad de usar librerías que también son características de alto nivel; sin embargo, fue diseñado con muy pocas instrucciones, las cuales son sumamente sencillas, fáciles de traducir al lenguaje de la máquina; y requiere de un entendimiento apropiado de cómo funciona la máquina, el uso de la memoria, etcétera. Por ello, muchas personas consideramos a lenguajes como C (que fue diseñado para hacer sistemas operativos), lenguajes de nivel medio.

Java El lenguaje de programación Java, fue diseñado por la compañía Sun Microsystems Inc, con el propósito de crear un lenguaje que pudiera funcionar enredes computacionales heterogéneas (redes de computadoras formadas por más de un tipo de computadora, ya sean PC, MAC's, estaciones de trabajo, etc.), y que fuera independiente de la plataforma en la que se vaya a ejecutar. Esto significa que un programa de Java puede ejecutarse en cualquier máquina o plataforma. El lenguaje fue diseñado con las siguientes características en mente: •

Simple. Elimina la complejidad de los lenguajes como "C" y da paso al contexto de los

lenguajes modernos orientados a objetos. Orientado a Objetos. La filosofía de programación orientada a objetos es diferente a la programación convencional. •

Familiar. Como la mayoría de los programadores están acostumbrados a programar en C o

en C++, la sintaxis de Java es muy similar al de estos.

30


Proceso para Diseñar un Lenguaje de Programación •

Robusto. El sistema de Java maneja la memoria de la computadora por ti. No te tienes que

preocupar por apuntadores, memoria que no se esté utilizando, etc. Java realiza todo esto sin necesidad de que uno se lo indique. •

Seguro. El sistema de Java tiene ciertas políticas que evitan se puedan codificar virus con

este lenguaje. Existen muchas restricciones, especialmente para los applets, que limitan lo que se puede y no puede hacer con los recursos críticos de una computadora. •

Portable. Como el código compilado de Java (conocido como byte code) es interpretado, un

programa compilado de Java puede ser utilizado por cualquier computadora que tenga implementado el interprete de Java. •

Independiente a la arquitectura. Al compilar un programa en Java, el código resultante un

tipo de código binario conocido como byte code. Este códido es interpretado por diferentes computadoras de igual manera, solamente hay que implementar un intérprete para cada plataforma. De esa manera Java logra ser un lenguaje que no depende de una arquitectura computacional definida. •

Multithreaded. Un lenguaje que soporta multiples threads es un lenguaje que puede ejecutar

diferentes líneas de código al mismo tiempo. •

Interpretado. Java corre en máquina virtual, por lo tanto es interpretado.

Dinámico. Java no requiere que compiles todas las clases de un programa para que este

funcione. Si realizas una modificación a una clase Java se encarga de realizar un Dynamic Bynding o un Dynamic Loading para encontrar las clases. Java puede funcionar como una aplicación sola o como un "applet", que es un pequeño programa hecho en Java. Los applets de Java se pueden "pegar" a una página de Web (HTML), y con esto puedes tener un programa que cualquier persona que tenga un browser compatible podrá usar. Nota: Diferencia entre Java y CGI La diferencia es esencialmente simple, un CGI se ejecuta en el servidor mientras que un programa en Java se ejecuta en la máquina del usuario. Java funciona de la siguiente manera: El compilador de Java deja el programa en un Pseudo-código (no es código maquinal) y luego el intérprete de Java ejecuta el programa (lo que se conoce como el "Java Virtual Machine"). Por eso Java es multiplataforma, existe un intérprete para cada máquina diferente. Nota: El código maquinal es el código binario que la computadora entiende y puede ejecutar.

31


Proceso para Diseñar un Lenguaje de Programación Para entender bien como funciona un applet de Java vean el siguiente ejemplo: 1.

Existe un código de Java en un servidor de Web. (Los códigos de Java se caracterizan por

tener la extensión *.class). 2.

Una persona en Internet, con un browser compatible con Java, realiza una conexión al

servidor. 3.

El servidor envía el documento HTML y el código en Java (*.class).

4.

En la computadora del usuario remoto llegan ambos, y la Máquina Virtual de Java, que está

en el browser, transforma el código Java en un código que entienda la máquina local y se ejecuta el programa dentro de la página de Web. 5.

Si el usuario realiza otra conexión a otro URL o se sale del browser, el programa se deja de

ejecutar y en la computadora no queda rastro de él.

Ejemplo de tutorial de Java: En Java hay tres tipos de comentarios: // Comentarios para una sola línea /* Comentarios de una o más líneas */ /** Comentario de documentación, de una o más líneas */ Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizan del mismo modo. Los comentarios de documentación, colocados inmediatamente antes de una declaración (de variable o función), indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta de Java, javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código. En este tipo de comentario para documentación, se permite la introducción de algunos tokens o palabras clave, que harán que la información que les sigue aparezca de forma diferente al resto en la documentación. Identificadores Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que el programador necesite identificar o usar. 32


Proceso para Diseñar un Lenguaje de Programación En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las minúsculas y no hay longitud máxima. Serían identificadores válidos: identificador nombre_usuario Nombre_Usuario _variable_del_sistema $transacción y su uso sería, por ejemplo: int contador_principal; char _lista_de_ficheros; float $cantidad_en_Ptas;

Unix Ejemplo de Unix: No todo el "árbol" de directorios está compuesto por directorios de usuario. Existen muchos de ellos que son de uso general o del propio sistema y con los que habrá que familiarizarse. Los más importantes son: / La raíz, del que "cuelgan" todos. /bin y /usr/bin Contienen comandos UNIX ejecutables. /etc Es quizá el directorio más importante. Contiene ficheros de datos y configuración del sistema, el fichero de password, configuración de terminales, red, etc (de ahí su nombre). /dev 33


Proceso para Diseñar un Lenguaje de Programación Ficheros de dispositivos E/S. /usr/man Manual /tmp Directorio para arreglos temporales. TODOS los usuarios pueden leer y escribir en él.

C C es un lenguaje de programación diseñado por Dennis Ritchie, de los Laboratorios Bell, y Se instaló en un PDP-11 en 1972; se diseñó para ser el lenguaje de los Sistemas Operativos UNIX1. A su vez, UNIX es un Sistema Operativo desarrollado por Ken Thompson, quién Utilizó el lenguaje ensamblador y un lenguaje llamado B para producir las versiones originales de UNIX, en 1970. C se inventó para superar las limitaciones de B. C es un lenguaje maduro de propósitos generales que se desarrolló a partir de estas raíces; Su definición aparece en 1978 en el apéndice ``C Reference Manual'' del libro The C Programming Language, de Brian W. Kernighan y Dennis M. Ritchie (Englewood Cliffs, Nueva Jersey, Prentice-Hall 1978), pero el estándar recomendable más reciente apareció en Junio de 1983, en el documento de los Laboratorios Bell titulado The C Programming Language-Reference Manual, escrito por Dennis M. Ritchie

Un programa en C Generalizando, un programa en C consta de tres secciones. La primera sección es donde van todos los ``headers''. Estos ``headers'' son comúnmente los ``#define'' y los ``#include''. Como segunda sección se tienen las ``funciones''. Al igual que Pascal, en C todas las funciones que se van a ocupar en el programa deben ir antes que la función principal (main()). Declarando las funciones a ocupar al principio del programa, se logra que la función principal esté antes que el resto de las funciones. Ahora, solo se habla de funciones ya que en C no existen los procedimientos. Y como última sección se tiene a la función principal, llamada main. Cuando se ejecuta el programa, lo primero que se ejecuta es esta función, y de ahí sigue el resto del programa. 34


Proceso para Diseñar un Lenguaje de Programación Los símbolos {y} indican ``begin'' y ``end'' respectivamente. Si en una función o en un ciclo while, por ejemplo, su contenido es de solamente una línea, no es necesario usar ``llaves'' ({ }), en caso contrario es obligación usarlos. Ejemplo de un programa en C /*Programa ejemplo que despliega el contenido de "ROL" en pantalla*/ #include <stdio.h> #define ROL "9274002-1" despliega_rol() { printf("Mi rol es : \%s\n", ROL); } void main() { despliega_rol(); } /* Fin programa */

Pascal Pascal es un lenguaje de programación de alto nivel de propósito general; esto es, se puede utilizar para escribir programas para fines científicos y comerciales. El lenguaje de programación Pascal fue desarrollado por el profesor Niklaus (Nicolás) Wirth en Zurich, Zuiza, al final de los años 1960s y principios de los 70s. Wirth diseñó este lenguaje para que fuese un buen primer lenguaje de programación para personas comenzando a aprender a programar. Pascal tiene un número relativamente pequeño de conceptos para aprender y dominar. Su diseño facilita escribir programas usando un estilo que está generalmente aceptado como práctica estándar de programación buena. Otra de las metas del diseño de Wirth era la implementación fácil. Él diseñó un lenguaje para el cual fuese fácil escribir un compilador para un nuevo tipo de computadora. program Sorting; {

35


Proceso para Diseñar un Lenguaje de Programación Este programa lee un natural y una secuencia de N caracteres de la entrada estándar; construye un índice para ordenarlos de menor a mayor e imprime en la salida la secuencia ordenada. } uses CRT; Const Max = 10; Espacio = ' '; Enter = chr (13); type Indice = 1..Max; Cantidad= 0..Max; SecOfChar = record elems : array [Indice] of char; ult : Cantidad; end; SecOfInd = record elems : array [Indice] of Indice; ult : Cantidad; end; Natural = 0..MaxInt; function PosMin (idx: SecOfInd; i: Indice; s: SecOfChar): Cantidad; { Devuelve la posicion en el indice idx del menor caracter en s, para las posiciones >= i. } var j: Indice; pm: Cantidad; begin if i > idx.ult then pm := 0

36


Proceso para Dise帽ar un Lenguaje de Programaci贸n else begin pm := i; for j := i+1 to idx.ult do if s.elems[idx.elems[j]] < s.elems[idx.elems[pm]] then pm := j; end; PosMin := pm; end; procedure Swap (var idx: SecOfInd; i,j: Indice); { Intercambia las posiciones i j en idx. } var tmp: Indice; begin if (i<=idx.ult) and (j<=idx.ult) then begin tmp := idx.elems[i]; idx.elems[i] := idx.elems[j]; idx.elems[j] := tmp; end; end; procedure InicInds (var idx: SecOfInd; cant: Indice); { Construye la secuencia de indices 1,2,3,...,n. Sera el indice inicial para el ordenamiento de una secuencia de caracteres c1,c2,...,cn. } var n: Natural; begin n := cant; idx.ult := n; 37


Proceso para Dise帽ar un Lenguaje de Programaci贸n while n > 0 do begin idx.elems [n] := n; n := n-1; end; end; procedure InicSecChar (var s: SecOfChar); { Devuelve la secuencia vacia. } begin s.ult := 0; end; function Llena (s: SecOfChar): Boolean; begin Llena := s.ult = Max; end; { PRE: not Llena(s) } procedure InsCar (var s: SecOfChar; c: char); { Inserta el caracter c en la secuencia s } begin s.ult := s.ult + 1; s.elems [s.ult] := c; end; procedure IndSelSort (s: SecOfChar; var ind: SecOfInd); { Construye el indice que ordena la secuencia s. Ordena el indice inicial 1,2, ..., n por el metodo de selection sort } var i: Indice; begin 38


Proceso para Diseñar un Lenguaje de Programación InicInds (ind, s.ult); for i := 1 to ind.ult-1 do begin Swap (ind, i, PosMin (ind, i, s)); end end; procedure WriteSorted (idx: SecOfInd; s: SecOfChar); { Imprime en la salida estándar la secuencia s ordenada según el indice idx } var i: Indice; begin write ('Ordenado: '); for i := 1 to idx.ult do write (s.elems[idx.elems[i]],' '); writeln; end; procedure LeerCar (var c: char; var ok: boolean; sep: Char); { Lee de la entrada estándar un caracter seguido del caracter sep } var c1, c2: char; begin c := ReadKey; write (c); c1 := ReadKey; write (c1); ok := c1 = sep; end; procedure LeerSecOfChar (var s: SecOfChar; cant: Natural; var ok: Boolean); { Construye una secuencia de cant caracteres provistos por el procedimeinto LeerCar. Si cant > Max trunca. } 39


Proceso para Diseñar un Lenguaje de Programación var bien: Boolean; i: Natural; ch, sep: Char; begin writeln ('Ingrese ',cant, ' caracteres separados por blancos. Enter para terminar '); write (' > '); InicSecChar (s); i := 1; ok := true; sep := Espacio; while ok and (i <= cant) and not Llena (s) do begin if i = cant then sep := Enter; LeerCar (ch, bien, sep); i := i+1; ok := ok and bien; if ok then InsCar (s, ch); end; end; procedure LeerCant (var n: Natural); { Lee de la entrada estándar un natural <= Max } begin repeat writeln ('Ingrese cantidad de caracteres (<=',Max,')'); write (' > '); readln (n); 40


Proceso para Dise帽ar un Lenguaje de Programaci贸n until n <= Max; end; procedure Continuar (var seguir: Boolean); var car: Char; begin writeln; writeln ('Otro ? (s/n)'); write (' > '); car := ReadKey; writeln (car); seguir := car in ['s','S']; end; var cant: Natural; cars: SecOfChar; inds: SecOfInd; seguir, ok: boolean; begin repeat ClrScr; LeerCant (cant); LeerSecOfChar (cars, cant, ok); if ok then begin IndSelSort (cars, inds); writeln; WriteSorted (inds, cars); end 41


Proceso para Diseñar un Lenguaje de Programación else begin writeln; writeln ('Error en los datos'); end; Continuar (seguir); until not seguir; end.

QBasic Qbasic es un lenguaje de alto nivel, el cual consiste en instrucciones que los humanos pueden relacionar y entender. El compilador de Qbasic se encarga de traducir el mismo a lenguaje de máquina. Un programa es una secuencia de instrucciones. El proceso de ejecutar esas instrucciones se llama correr el programa. Los programas contienen las funciones de entrada, procesamiento y salida. La persona que resuelve problemas mediante escribir programas en la computadora se conoce como programador. Después de analizar el problema y desarrollar un plan para solucionarlo, escribe y prueba el programa que instruye a la computadora como llevar a cabo el plan. El procedimiento que realiza el programador se define como "problem solving". Pero es necesario especificar que un programador y un usuario no son lo mismo. Un usuario es cualquier persona que use el programa. Ejemplo de qbasic, para hacer una calculadora DIM total AS DOUBLE DIM number AS DOUBLE DIM secondNumber AS DOUBLE DIM more AS STRING DIM moreNumbers AS STRING DIM operation AS STRING total = 0 more = "y" 42


Proceso para Dise帽ar un Lenguaje de Programaci贸n moreNumbers = "c" CLS WHILE more = "y" INPUT "Enter the first number"; number total = number WHILE moreNumbers = "c" COLOR 14 PRINT "The total is:"; total COLOR 7 PRINT "Select an operation" COLOR 2 PRINT "(+)" COLOR 5 PRINT "(-)" COLOR 1 PRINT "(x)" COLOR 4 INPUT "(/)"; operation COLOR 7 CLS IF operation = "+" THEN REM where we do additions PRINT "Enter the number to Add to"; total INPUT secondNumber total = secondNumber + total COLOR 14 43


Proceso para Dise帽ar un Lenguaje de Programaci贸n PRINT "The total is now:"; total COLOR 7 ELSE IF operation = "-" THEN REM subtraction PRINT "Enter the number to Subtract from"; total INPUT secondNumber total = total - secondNumber COLOR 14 PRINT "The total is now:"; total COLOR 7 ELSE IF operation = "x" THEN REM multiplication PRINT "Enter the number to Multiply"; total; "by" INPUT secondNumber total = secondNumber * total REM * is the multiplication sign in programs COLOR 14 PRINT "The total is now:"; total COLOR 7 ELSE IF operation = "/" THEN REM division PRINT "Enter the number to Divide"; total; "by" INPUT secondNumber 44


Proceso para Dise帽ar un Lenguaje de Programaci贸n IF secondNumber = 0 THEN COLOR 4 PRINT "You cannot divide by zero" COLOR 7 ELSE total = total / secondNumber REM / is the division sign in programs END IF COLOR 14 PRINT "The total is now:"; total COLOR 7 ELSE PRINT "you must select an operation" END IF END IF END IF END IF INPUT "Do you wish to continue (c) or start with new numbers (n)";moreNumbers CLS WEND COLOR 14 PRINT "The grand total is:"; total COLOR 7 INPUT "Do you wish to make more calculations (y - n)"; more moreNumbers = "c" 45


Proceso para Diseñar un Lenguaje de Programación REM if we don't put "moreNumbers" back to y, it will always REM come back to "Do you wish to make more calculations" and never REM ask for numbers again REM (try it) total = 0 REM if we don't reset the total to 0, it will just REM keep on adding to the total WEND END

Linux Linux es una implementación del sistema operativo UNIX (uno más de entre los numerosos clónicos del histórico Unix), pero con la originalidad de ser gratuito y a la vez muy potente, que sale muy bien parado (no pocas veces victorioso) al compararlo con las versiones comerciales para sistemas de mayor envergadura y por tanto teóricamente superiores. Comenzó como proyecto personal del –entonces estudiante- Linus Torvalds, quien tomó como punto de partida otro viejo conocido, el Minix de Andy. S. Tanenbaum (profesor de sistemas operativos que creó su propio sistema operativo Unix en PCs XT para usarlo en su docencia). Actualmente Linus lo sigue desarrollando, pero a estas alturas el principal autor es la red Internet, desde donde una gigantesca familia de programadores y usuarios aportan diariamente su tiempo aumentando sus prestaciones y dando información y soporte técnico mútuo. La versión original -y aun predominante- comenzó para PCs compatibles (Intel 386 y superiores), existiendo también en desarrollo versiones para prácticamente todo tipo de plataformas: PowerPC <http://www.cs.us.es/archive/linuxppc/>, Sparc <http://www.geog.ubc.ca/sparclinux.html>, Alpha <http://www.azstarnet.com/~axplinux>, Mips <http://www.fnet.fr/linux-mips/>, etc. De todas ellas la más reciente en este momento es la versión para PowerMac <http://www.mklinux.org> (el PowerPC de Apple) basada en el microkernel Mach 3.0 y de la que 46


Proceso para Diseñar un Lenguaje de Programación ya hay una distribución para desarrolladores avalada directamente por Apple y OSF pero conservando el espíritu (gratuito, de libre distribución, etc) de la versión original. Un servidor la acaba de probar hace unos días y se ha llevado una grata sorpresa (aún tendrá muchos fallos, pero para ser una primerísima versión y el poco tiempo que lleva en marcha, ha avanzado más de lo que me esperaba).

Ejemplo de Linux:

Compilar el Kernel Dado que un diskette sólo almacena 1.44 Megabytes (1440 Kilobytes) de datos, no puedes el mismo kernel que utilizas al diskette. Primero debes conseguir los fuentes del núcleo y descomprimirlos en /usr/src/Linux. Luego ejecuta la siguiente orden desde el directorio /usr/src/Linux: make config Configura solamente aquello que realmente necesites. Yo, personalmente, sólo configuro el soporte para "ext2", soporte para la disquetera (floppy disk), y soporte para "PPP". Tus elecciones pueden se diferentes en función de lo que decidas incluir. Ahora introduce el siguiente comando: make dep; make clean; make zImage ¡make zImage es muy importante! Comprime el kernel definitivo. Después de que termine la compilación, deberás buscar el nuevo núcleo en /usr/src/linux/arch/i386/boot bajo el nombre de zImage. El sistema de ficheros: No es solamente un conjunto de ficheros Ahora hemos de crear el sistema de ficheros (en inglés: filesystem, fs) para el diskette. En vez de copiar los ficheros tal cual directamente al diskette, los comprimiremos antes de copiarlos. Esto nos hará un poco más difícil la faena de modificar todo permanentemente. Primero tecleamos el siguiente Comando: dd if=/dev/zero of=[DEVICE] bs=1k count=3000

47


Proceso para Diseñar un Lenguaje de Programación Donde [DEVICE] es "lugar" en el disco duro donde vas a guardar el sistema de ficheros descomprimido. Luego, introduce el siguiente comando y pulsa ENTER, sustituyendo [DEVICE] por el directorio en tu disco duro donde estás guardando el sistema de ficheros descomprimido: mke2fs -m 0 [DEVICE] Si make2fs te pregunta si realmente quieres hacer esto (Do you really want to do this?), acepta tecleando "y" (yes). Después tenemos que montar este sistema de ficheros que hemos creado. Para ello, el núcleo que utilices tiene que permitir "montar ficheros", en otras palabras, ha de tener habilitada la posibilidad de "loopback devices". Para ello has de compilar el núcleo de tu máquina (no el núcleo que hemos creado, sino el de tu propia máquina) con la opción: Loopback device support (CONFIG_BLK_DEV_LOOP) [M/n/y/?] Bien como módulo (M) o en el mismo núcleo (Y). Si lo compilas como módulo (lo más recomendable) luego tienes que insertar el módulo modprobe loop !No olvides rearrancar la máquina si has tenido que recompilar el núcleo! mount -t ext2 DEVICE /mnt Si se queja la orden mount puedes intentar con la siguiente orden: mount -o loop -t ext2 DEVICE /mnt Ahora debes copiar todos los ficheros que necesites en el nuevo sistema de ficheros. Primero, ponte en el directorio /mnt, (cd /mnt), y crea los siguientes directorios: /dev /pro /etc /bin /lib /mnt /usr Ahora crearemos el directorio /dev tecleando lo siguiente: cp -dpR /dev /mnt/dev

48


Proceso para Diseñar un Lenguaje de Programación Si se te acaban los i-nodos del diskette, puedes ir a /mnt/dev y borrar los archivos de dispositivo que no necesites. Cuando acabes de copiar los ficheros necesarios para /dev, ves a /etc. Para estar seguro copia todos los ficheros de /etc a /mnt/etc: cp -dpR /etc /mnt/etc Luego copia todo del directorio /lib en /mnt: cp -dpR /lib /mnt/lib Para el directorio /bin, copia sólo aquello que creas que necesitas en /mnt/bin. Copiar todo a tu diskette Ahora hemos de copiar todo en el/los diskette/s. Para hacer esto, debemos comprimir ahora el sistema de ficheros tecleando las siguientes órdenes: cd / umount /mnt dd if=[DEVICE] bs=1k | gzip -9 > rootfs.gz Ahora es importante comprobar el tamaño del núcleo. Ponte en /usr/src/linux/arch/i386/boot y teclea "ls -l". Luego divide el tamaño del núcleo entre 1024. Por ejemplo, si el tamaño es de 250000 bytes, entonces son 245 KB. En adelante, reemplaza [ROOTBEGIN] en las ordenes que aparezca por el número total de kilobytes que has calculado. Ahora copia el kernel al diskette usando el siguiente comando: dd if=zImage of=/dev/fd0 Este comando grabará el kernel en el diskette. Luego introduce el siguiente comando para que el kernel pueda encontrar la raíz del sistema de ficheros en el diskette. rdev /dev/fd0 /dev/fd0 Ahora tendrás que hacer un pequeño cálculo en hexadecimal. Suma 4000 al equivalente en hexadecimal de [ROOTBEGIN] (que en nuestro ejemplo es F5). Convierte el resultado a decimal y teclea el siguiente comando, sustituyendo 16629 con el resultado que tú has obtenido: rdev -r /dev/fd0 16629 Finalmente, teclea lo siguiente para copiar el sistema de ficheros al diskette: dd if=/rootfs.gz of=/dev/fd0 bs=1k seek=[ROOTBEGIN]

49


Proceso para Diseñar un Lenguaje de Programación El sistema de ficheros raíz será copiado al diskette justo después del kernel. ¡Ya lo tienes! Para el segundo diskette, el proceso es más fácil. Copia los ficheros que quieras en el diskette. No obstante, para poder usar los ficheros que hay en el segundo disco, tendrás que entrar lo siguiente después de arrancar con el diskette: mount /dev/fd0 /usr

Ensamblador Cuando abstraemos los opcodes y los sustituimos por una palabra que sea una clave de su significado, a la cual comúnmente se le conoce como mnemónico, tenemos el concepto de Lenguaje Ensamblador. Así, podemos definir simplemente al Lenguaje Ensamblador de la siguiente forma: Lenguaje Ensamblador es la primera abstracción del Lenguaje de Máquina, consistente en asociar a los opcodes palabras clave que faciliten su uso por parte del programador Como se puede ver, el Lenguaje Ensamblador es directamente traducible al Lenguaje de Máquina, y viceversa; simplemente, es una abstracción que facilita su uso para los seres humanos. Por otro lado, la computadora no entiende directamente al Lenguaje Ensamblador; es necesario traducirle a Lenguaje de Máquina. Originalmente, este proceso se hacía a mano, usando para ello hojas donde se escribían tablas de programa similares al ejemplo de la calculadora que vimos arriba. Pero, al ser tan directa la traducción, pronto aparecieron los programas Ensambladores, que son traductores que convierten el código fuente (en Lenguaje Ensamblador) a código objeto (es decir, a Lenguaje de Máquina). Una característica que hay que resaltar, es que al depender estos lenguajes del hardware, hay un distinto Lenguaje de Máquina (y, por consiguiente, un distinto Lenguaje Ensamblador) para cada CPU. Por ejemplo, podemos mencionar tres lenguajes completamente diferentes, que sin embargo vienen de la aplicación de los conceptos anteriores: 1. Lenguaje Ensamblador de la familia Intel 80x86 2.Lenguaje Ensamblador de la familia Motorola 68000 3.Lenguaje Ensamblador del procesador POWER, usado en las IBM RS/6000. Tenemos 3 fabricantes distintos, compitiendo entre sí y cada uno aplicando conceptos distintos en la manufactura de sus procesadores, su arquitectura y programación; todos estos aspectos, influyen en que el lenguaje de máquina y ensamblador cambie bastante. Ventajas y desventajas del Lenguaje Ensamblador

50


Proceso para Diseñar un Lenguaje de Programación Una vez que hemos visto la evolución de los lenguajes, cabe preguntarse: ¿En estos tiempos "modernos", para qué quiero el Lenguaje Ensamblador? El proceso de evolución trajo consigo algunas desventajas, que ahora veremos como las ventajas de usar el Lenguaje Ensamblador, respecto a un lenguaje de alto nivel: 1. Velocidad 2. Eficiencia de tamaño 3. Flexibilidad

Por otro lado, al ser un lenguaje más primitivo, el Ensamblador tiene ciertas desventajas respecto a los lenguajes de alto nivel: 1. Tiempo de programación 2. Programas fuentes grandes 3. Peligro de afectar recursos inesperadamente 4. Falta de portabilidad

Velocidad El proceso de traducción que realizan los intérpretes, implica un proceso de cómputo adicional al que el programador quiere realizar. Por ello, nos encontraremos con que un intérprete es siempre más lento que realizar la misma acción en Lenguaje Ensamblador, simplemente porque tiene el costo adicional de estar traduciendo el programa, cada vez que lo ejecutamos. De ahí nacieron los compiladores, que son mucho más rápidos que los intérpretes, pues hacen la traducción una vez y dejan el código objeto, que ya es Lenguaje de Máquina, y se puede ejecutar muy rápidamente. Aunque el proceso de traducción es más complejo y costoso que el de ensamblar un programa, normalmente podemos despreciarlo, contra las ventajas de codificar el programa más rápidamente. Sin embargo, la mayor parte de las veces, el código generado por un compilador es menos eficiente que el código equivalente que un programador escribiría. La razón es que el compilador no tiene tanta inteligencia, y requiere ser capaz de crear código genérico, que sirva tanto para un programa como para otro; en cambio, un programador humano puede aprovechar las características 51


Proceso para Diseñar un Lenguaje de Programación específicas del problema, reduciendo la generalidad pero al mismo tiempo, no desperdicia ninguna instrucción, no hace ningún proceso que no sea necesario. Para darnos una idea, en una PC, y suponiendo que todos son buenos programadores, un programa para ordenar una lista tardará cerca de 20 veces más en Visual Basic (un intérprete), y 2 veces más en C (un compilador), que el equivalente en Ensamblador. Por ello, cuando es crítica la velocidad del programa, Ensamblador se vuelve un candidato lógico como lenguaje. Ahora bien, esto no es un absoluto; un programa bien hecho en C puede ser muchas veces más rápido que un programa mal hecho en Ensamblador; sigue siendo sumamente importante la elección apropiada de algoritmos y estructuras de datos. Por ello, se recomienda buscar optimizar primero estos aspectos, en el lenguaje que se desee, y solamente usar Ensamblador cuando se requiere más optimización y no se puede lograr por estos medios.

Tamaño Por las mismas razones que vimos en el aspecto de velocidad, los compiladores e intérpretes generan más código máquina del necesario; por ello, el programa ejecutable crece. Así, cuando es importante reducir el tamaño del ejecutable, mejorando el uso de la memoria y teniendo también beneficios en velocidad, puede convenir usar el lenguaje Ensamblador. Entre los programas que es crítico el uso mínimo de memoria, tenemos a los virus y manejadores de dispositivos (drivers). Muchos de ellos, por supuesto, están escritos en lenguaje Ensamblador.

Flexibilidad Las razones anteriores son cuestión de grado: podemos hacer las cosas en otro lenguaje, pero queremos hacerlas más eficientemente. Pero todos los lenguajes de alto nivel tienen limitantes en el control; al hacer abstracciones, limitan su propia capacidad. Es decir, existen tareas que la máquina puede hacer, pero que un lenguaje de alto nivel no permite. Por ejemplo, en Visual Basic no es posible cambiar la resolución del monitor a medio programa; es una limitante, impuesta por la abstracción del GUI Windows. En cambio, en ensamblador es sumamente sencillo, pues tenemos el acceso directo al hardware del monitor.

52


Proceso para Diseñar un Lenguaje de Programación Resumiendo, la flexibilidad consiste en reconocer el hecho de que Todo lo que puede hacerse con una máquina, puede hacerse en el lenguaje ensamblador de esta máquina; los lenguajes de alto nivel tienen en una u otra forma limitante para explotar al máximo los recursos de la máquina.

Tiempo de programación Al ser de bajo nivel, el Lenguaje Ensamblador requiere más instrucciones para realizar el mismo proceso, en comparación con un lenguaje de alto nivel. Por otro lado, requiere de más cuidado por parte del programador, pues es propenso a que los errores de lógica se reflejen más fuertemente en la ejecución. Por todo esto, es más lento el desarrollo de programas comparables en Lenguaje Ensamblador que en un lenguaje de alto nivel, pues el programador goza de una menor abstracción.

Programas fuentes grandes Por las mismas razones que aumenta el tiempo, crecen los programas fuentes; simplemente, requerimos más instrucciones primitivas para describir procesos equivalentes. Esto es una desventaja porque dificulta el mantenimiento de los programas, y nuevamente reduce la productividad de los programadores.

Peligro de afectar recursos inesperadamente Tenemos la ventaja de que todo lo que se puede hacer en la máquina, se puede hacer con el Lenguaje Ensamblador (flexibilidad). El problema es que todo error que podamos cometer, o todo riesgo que podamos tener, podemos tenerlo también en este Lenguaje. Dicho de otra forma, tener mucho poder es útil pero también es peligroso. En la vida práctica, afortunadamente no ocurre mucho; sin embargo, al programar en este lenguaje verán que es mucho más común que la máquina se "cuelgue", "bloquee" o "se le vaya el avión"; y que se reinicialice. ¿Por qué?, porque con este lenguaje es perfectamente posible (y sencillo) realizar secuencias de instrucciones inválidas, que normalmente no aparecen al usar un lenguaje de alto nivel.

53


Proceso para Diseñar un Lenguaje de Programación En ciertos casos extremos, puede llegarse a sobre escribir información del CMOS de la máquina (no he visto efectos más riesgosos); pero, si no la conservamos, esto puede causar que dejemos de "ver" el disco duro, junto con toda su información.

Falta de portabilidad Como ya se mencionó, existe un lenguaje ensamblador para cada máquina; por ello, evidentemente no es una selección apropiada de lenguaje cuando deseamos codificar en una máquina y luego llevar los programas a otros sistemas operativos o modelos de computadoras. Si bien esto es un problema general a todos los lenguajes, es mucho más notorio en ensamblador: yo puedo reutilizar un 90% o más del código que desarrollo en "C", en una PC, al llevarlo a una RS/6000 con UNIX, y lo mismo si después lo llevo a una Macintosh, siempre y cuando esté bien hecho y siga los estándares de "C", y los principios de la programación estructurada. En cambio, si escribimos el programa en Ensamblador de la PC, por bien que lo desarrollemos y muchos estándares que sigamos, tendremos prácticamente que rescribir el 100 % del código al llevarlo a UNIX, y otra vez lo mismo al llevarlo a Mac.

54


Proceso para Dise帽ar un Lenguaje de Programaci贸n

BIBLIOGRAFIA

http://es.wikipedia.org/wiki/Pseudoc%C3%B3digo

http://www.monografias.com/trabajos/lengprog/lengprog.shtml

http://www.desarrolloweb.com/articulos/2387.php

http://www.monografias.com/trabajos/lengprog/lengprog.shtml

55


Proceso para Diseñar un Lenguaje de Programación CONCLUSION

En el presente trabajo de investigación sobre el proceso de creación de un lenguaje de programación se concluye lo siguiente:

El proceso para diseñar un lenguaje de programación es muy complejo, a su vez de vital importancia, es por ello necesario interactuar con cada fase que esta proporciona.

Es importante hacer diferencia entre las ventajas y desventajas que cada lenguaje de programación posee, de esa manera la manipulación de la aplicación será más factible.

Independientemente del lenguaje de programación el proceso de compilación es el mismo y los pasos para su creación deben llevarse a cabo de una forma secuencial ya que de uno depende el otro.

Es importante antes de iniciar el proceso de creación de un lenguaje de programación plantearse el problema y llevar a cabo un análisis del mismo con el cual se puede tener un orden lógico de todos los pasos a seguir para poder realizar un buen compilador.

56


Revista digital