A Linguagem µScheme António Menezes Leitão 3 de Maio de 2010 1 Introdução É cada vez mais usual as grandes aplicações incorporarem uma linguagem de scripting que permita fazer pequenos (e grandes) programas que usam as APIs dessas aplicações. Por exemplo, AutoCAD disponibiliza AutoLisp e VB, Blender disponibiliza Python, e Gimp disponibiliza Script-Fu e Python. Uma vez que, em geral, a capacidade de fazer scripts eficientes nestas aplicações não é um requisito fundamental, opta-se antes por disponibilizar linguagens que sejam simples, fáceis de aprender, e fáceis de implementar. 2 Objectivos Pretende-se que implemente, em Java, uma linguagem de scripting para Java. Essa linguagem denomina-se µScheme1 e pretende ser usada como linguagem de extensão para aplicações Java. A linguagem µScheme é sintática e semanticamente semelhante à linguagem Scheme na sua versão R4RS (o standard desta linguagem encontra-se disponı́vel em http://people.csail.mit.edu/jaffer/r4rs_toc.html) e, por este motivo, este enunciado não explica o que poderá ser encontrado em qualquer manual de Scheme (ver, por exemplo, http://mitpress.mit. edu/sicp/) mas sim as diferenças fundamentais entre µScheme e Scheme. Pretende-se que crie a classe ist.leic.pa.MScheme e que nesta classe exista um método estático denominado repl, que não recebe quaisquer argumentos e que, uma vez invocado, lê uma expressão do standard input, escreve o resultado da avaliação dessa expressão no standard output e envia o possı́vel erro para o standard error, repetindo esta sequência até atingir o fim de ficheiro ou até que um erro seja gerado. As próximas secções detalham um pouco mais a linguagem a implementar. 1 O nome µScheme pronuncia-se “micro-scheme.” 1 2.1 Tipos de Dados Como versão simplificada de Scheme que é, a linguagem µScheme reduz ao mı́nimo o conjunto de tipos e operações disponı́veis sobre esses tipos. Em µScheme existem os seguintes tipos de dados pré-definidos: number O tipo number é usado em todas as operações aritméticas e é o tipo de objecto que é produzido quando se lê a representação externa de qualquer número. Este tipo é implementado directamente com o tipo Java BigDecimal. Isso permite garantir precisão total em todas as operações matemáticas realizadas, bem como evitar qualquer limite arbitrário quanto à dimensão dos números. Como se verá adiante, isto não é limitação para o uso das APIs de Java pois existem funções que convertem do tipo BigDecimal para os outros tipos numéricos usuais que também são manipuláveis em Scheme (embora não possam ser lidos directamente). function As funções são considerado um tipo de dados primitivo na linguagem que é criado como resultado da avaliação de expressões lambda. pair µScheme, à semelhança da linguagem Scheme, disponibiliza algumas operações elementares para a construção de pares de objectos. No entanto, o conjunto de operações é (intencionalmente) muito mais limitado, existindo apenas cons, car, cdr, e null?. Outras funções podem ser adicionadas através da normal definição de funções em µScheme. boolean Existe um tipo booleano que se caracteriza por ter dois valores representados pelos sı́mbolos #t e #f. Qualquer operação relacional produz um destes valores. Estes sı́mbolos avaliam para eles próprios. string O tipo string corresponde directamente ao tipo string de Java, sendo possı́vel ler directamente valores deste tipo. Tipos “Estrangeiros” Qualquer objecto Java (seja de que tipo for) pode ser usado em µScheme. À semelhança dos números, estes objectos avaliam para eles próprios. Para simplificar o desenvolvimento do seu avaliador de µScheme, a cadeira fornece uma implementação em Java (que os alunos podem modificar livremente ou reimplementar por completo) das operações read, display, e newline capazes de ler e escrever os tipos number, pair, boolean e string. 2.2 Limitações da Linguagem µScheme µScheme simplifica substancialmente a linguagem Scheme em diversas áreas: 2 Recursão µScheme usa recursão como estrutura de controle mais genérica mas, contráriamente à linguagem Scheme, µScheme não é recursiva de cauda, i.e., qualquer invocação (recursiva ou não recursiva e em posição de cauda ou não) consome espaço. Isto implica que ciclos com um número indeterminado de repetições podem fazer abortar o programa. Iteração µScheme não providencia quaisquer estruturas de controle de iteração. Em particular, não existem operações de repetição do tipo do, ou named lets. Avaliação Diferida µScheme não providencia as formas para avaliação diferida delay ou force. Continuações µScheme não permite qualquer reificação de continuações e, consequentemente, também não implementa retorno de múltiplos valores. Macros µScheme não providencia macros higiénicas. 2.3 Funções A linguagem µScheme providencia as seguintes funções pré-definidas: • +, *, -, /. Aceitam qualquer número de argumentos numéricos e produzem um resultado numérico. • =, >, >=, <, <=. Aceitam dois argumentos numéricos e produzem um resultado booleano. • eq?. Aceita dois argumentos e testa se são o mesmo objecto. Note-se que este procedimento não é apropriado para testar números, mesmo que pequenos. • cons, car, cdr, null?. Operações básicas para manipular pares e listas. • jtype: Dada uma string representando o nome de um tipo em Java, produz um objecto representando esse tipo. • jconstructor: Dada a representação de um tipo e qualquer número de representações de outros tipos, produz um objecto representando o construtor do primeiro tipo cuja assinatura coincide com os restantes tipos. • jnew: Dada a representação de um construtor e dados os argumentos apropriados para esse construtor, invoca o construtor e produz uma representação do objecto construı́do. 3 • jmethod: Dada a representação de um tipo, dada uma string e qualquer número de representações de outros tipos, produz um objecto representando o método definido no primeiro tipo cujo nome é igual à string e cuja assinatura coincide com os restantes tipos. • jcall: Dada a representação de um método, dada a representação de um objecto e dados os argumentos apropriados para esse método, invoca o método e produz uma representação do objecto resultante construı́do. Se o método em questão for estático, o segundo argumento é ignorado. • jbyte: recebe um number e produz o byte correspondente em Java. • jshort: recebe um number e produz o short correspondente em Java. • jint: recebe um number e produz o int correspondente em Java. • jlong: recebe um number e produz o long correspondente em Java. • jfloat: recebe um number e produz o float correspondente em Java. • jdouble: recebe um number e produz o double correspondente em Java. • jboolean: recebe um boolean e produz o boolean correspondente em Java. • jchar: recebe uma string de um só elem e produz o char correspondente em Java. • jstring: recebe uma string e produz a instância de java.lang.String correspondente em Java. Dada a coincidência entre o tipo µScheme string e o tipo Java java.lang.String, esta função deverá ser trivial de implementar. • jnull: não recebe argumentos e produz o null em Java. 2.4 µScheme como Linguagem de Scripting de Java Uma das grandes vantagens da linguagem µScheme é a sua capacidade para interagir com as bibliotecas da linguagem Java. A tı́tulo de exemplo, considere a seguinte expressão µScheme que calcula a raiz quadrada do número 2.0: (let ((math-class (jtype "java.lang.Math"))) (let ((sqrt-method (jmethod math-class "sqrt" (jtype "double")))) (jcall sqrt-method math-class (jdouble 2.0)))) 4 Naturalmente, deverá ser possı́vel definir funções µScheme que “escondam” as invocações e as conversões de valores. Por exemplo, considere a seguinte interacção (onde intercalamos o input com o output): (let ((math-class (jtype "java.lang.Math"))) (let ((sqrt-method (jmethod math-class "sqrt" (jtype "double")))) (define (math-sqrt arg) (jcall sqrt-method math-class (jdouble arg))))) ... (math-sqrt 2.0) 1.4142135 2.5 Extensões Se pretender, pode fazer as extensões que achar apropriadas que possam valorizar ainda mais o seu trabalho. Tenha em conta que essa valorização será, no máximo, de dois valores a somar à nota que obtiver pela implementação do que foi pedido nas outras secções. Algumas das extensões que poderão ser interessantes são: • Aproximar o conjunto de funções e tipos do µScheme dos existentes em Scheme • Implementação de novos operadores (e.g., o defun do Common Lisp) • Implementação de macros • Implementação de lazy evaluation • Implementação de futures • Tratamento de arrays • Tratamento de excepções • Operações para obtenção dos valores dos fields dos objectos Java 3 Código A sua implementação de µScheme deverá funcionar em Java 6. O código desenvolvido deverá estar escrito no melhor estilo que for possı́vel, permitindo a sua fácil leitura e dispensando excessivos comentários. É sempre preferı́vel ter código mais claro com poucos comentários do que ter código obscuro com muitos comentários. O código deverá ser modular, divido em funcionalidades com responsabilidades especı́ficas e reduzidas. Cada módulo deverá ter um curto comentário a descrever o seu objectivo. 5 4 Apresentação Não se pretende que faça um relatório do seu trabalho mas sim uma apresentação pública. Esta deverá ser em Inglês, preparada para ter 15 minutos de duração (aproximadamente 8 slides), deverá centrar-se nas opções arquitecturais tomadas e poderá incluir os detalhes que considere relevantes. Pretende-se que consiga “vender” a sua solução ao seus colegas e ao corpo docente. 5 Formato da entrega O projecto é entregue por via electrónica através do Portal Fénix. Cada grupo deverá entregar um único ficheiro comprimido em formato ZIP, com o nome mscheme.zip. Na raı́z do ficheiro ZIP deverão estar obrigatoriamente: • o directório src com o código fonte desenvolvido (quer em Java, quer, eventualmente, em Scheme); • o directório lib com quaisquer bibliotecas necessárias à compilação e/ou execução do projecto; • o ficheiro p2.pdf com os slides da apresentação do trabalho; • o ficheiro build.xml para compilar o código Java e gerar o mscheme.jar. O formato aceite para os slides de apresentação do trabalho é o PDF. O ficheiro com a apresentação tem de estar na raı́z do ficheiro ZIP e tem de ter o nome p2.pdf. O ficheiro build.xml é um ficheiro de Ant (http://ant.apache.org) e deve produzir na mesma localização o ficheiro mscheme.jar com todo o código Java necessário para executar o avaliador de µScheme. O alvo de omissão do build.xml deve efectuar o trabalho descrito, ou seja, simplesmente executar numa shell $ ant deve produzir o resultado esperado (mscheme.jar). 6 Avaliação Os critérios de avaliação incluem: • A qualidade das soluções desenvolvidas. • A clareza dos programas desenvolvidos. 6 • A qualidade da apresentação pública. Em caso de dúvidas, o corpo docente poderá pedir explicações sobre o funcionamento do projecto desenvolvido, incluı́ndo eventuais demonstrações. 7 Plágio Considera-se plágio o uso de quaisquer fragmentos de programas que não tenham sido fornecidos pelos docentes da disciplina. Não se considera plágio o uso de ideias cedidas por terceiros desde que seja feita a devida atribuição. Esta disciplina segue normas muito rı́gidas relativamente ao plágio. Quaisquer projectos que sejam considerados plagiados serão anulados, independentemente de quem plagiou e de quem tiver sido plagiado, independentemente de o plágio ter sido autorizado, ou não, pela parte plagiada. Isto não deverá ser impedimento para a troca salutar de ideias e para a normal camaradagem e entreajuda que deve existir entre colegas. Contudo, sugere-se que nunca cedam fragmentos de programas sob pena de quem os recebe não os entender e se limitar a plagiá-los com maior ou menos esforço de “camuflagem.” 8 Notas Finais Não se esqueça da Lei de Murphy. 9 Prazos O código e os slides da apresentação deverão ser entregues via fénix, até às 20:00 do dia 4 de Junho. As apresentações irão decorrer nas aulas práticas seguintes à entrega. Apenas um elemento do grupo irá fazer a apresentação que não poderá exceder os 15 minutos. Esse elemento será escolhido pelo docente no momento da apresentação. 7