Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

Lisp stares at PHP

2015-09-02 22:11 -0300. Tags: comp, prog, pldesign, lisp, php, lows, in-english

[This post is also available in English.]

No último post, eu falei sobre o lows, um Lisp que compila para PHP 5.2 que eu comecei a desenvolver, e o que eu fiz até agora nele. Neste post, eu pretendo discutir algumas questões de design da linguagem, mais ou menos na mesma idéia da série Blueprints for a shell (só que bem menor (or so I hope)). Como já mencionei, não sei quando vou mexer no lows de novo (afinal, eu tenho certos afazeres mundanos, tais como um mestrado para terminar), mas deixo aqui documentadas algumas idéias. Comentários são sempre bem-vindos.

Tipos de dados

Os tipos de dados do PHP não casam muito bem (leia-se: não casam praticamente nada) com os tipos convencionais do mundo Lisp. O plano é tentar projetar a linguagem para permitir um estilo de programação suficientemente Lisp-like com os tipos existentes, ao invés de tentar recriar os tipos líspicos tradicionais, até porque isso não seria muito viável em termos de performance em PHP.

Arrays

Basicamente o único tipo de coleção que o PHP possui é o array, que faz tanto o papel de lista quanto de dicionário (e mesmo como dicionário, ele preserva a ordem das inserções). Eu não vejo nenhuma maneira eficiente de implementar um cons em PHP (adiciona um elemento a uma lista, criando uma lista nova, em espaço/tempo O(1)). Daria para criar uma classe Cons, mas o overhead seria muito grande, e além do mais o objetivo da linguagem é interagir facilmente com código PHP. So, arrays.

As operações map, filter, reduce e afins são independentes da representação da coleção e funcionam igualmente bem com arrays. O idioma de criar listas usando cons e recursão, por outro lado, cai por terra, mas ao mesmo tempo ele já não seria uma boa escolha em PHP porque chamadas de função são mais custosas, e não há tail call optimization. Na verdade tail call optimization não nos ajudaria nesse caso anyway, porque a recursão não fica in tail position; nessa situação, quando a performance é relevante, a galera costuma acumular os elementos da lista como um argumento da chamada recursiva e chamar reverse! no final do loop, mas se é para fazer isso, podemos passar um array como argumento e adicionar elementos com push. Anyway.

No futuro, map e afins podem ser inlined, o que torna essas construções basicamente equivalentes a loops. Até mesmo a closure passada como argumento para o map não precisa ser construída, e ao invés disso o corpo do lambda pode ser inserido diretamente no loop.

Há que se pensar na sintaxe equivalente ao Array(...) do PHP. Para listas simples, (array 1 2 3) é suficiente. O problema é o equivalente da forma Array("foo"=>1, "bar"=>2). Quando a chave é uma string literal simples, uma possibilidade seria usar keywords do Chicken: (array foo: 1 bar: 2). Quando a chave não é literal, algo como (array (=> key1 val1) (=> key2 val2)) poderia ser usado, mas isso é meio verboso. I don't know.

Uma bizarrice de arrays do PHP é que elas são passadas por cópia. Ao que parece, simplesmente não existe uma maneira de passar um array "by sharing", como os valores costumam ser passados em Lisp ou em Python. É possível criar uma referência (que na verdade é um alias) para uma variável que contém um array, mas a referência é à variável, não ao array; se outro valor é atribuído à variável, a referência/alias reflete a modificação. Acho que teremos que conviver com isso.

Símbolos

Não existe nada equivalente a símbolos em PHP. A principal vantagem de símbolos sobre strings é o fato de que eles podem ser comparados em tempo constante, mas o PHP não possui nada realmente equivalente ao operador eq dos Lisps, então acho que não teria muito propósito implementar símbolos em lows. 'foo poderia ser usado como abreviação para "foo", talvez.

Pharen tem um "operador" #foo, que gera a string correspondente ao nome da função foo. A razão desse operador é que Pharen converte hífens em nomes de função para underlines (o que é uma boa idéia em um Lisp→PHP), e o operador # produz uma string com o nome convertido. Talvez ' pudesse ser usado com o mesmo propósito em lows.

By the way, lows não tem um operador quote de verdade, pois o código de um programa lows não é representado por uma estrutura de dados de lows (i.e., do PHP). Yeah, lows não é uma linguagem homoicônica, e talvez nem merecesse o título de Lisp por conta disso. Macros em lows não seriam escritas em lows, mas sim em (Chicken) Scheme, mas veremos isso mais adiante.

(Qual seria então o significado de '(+ 1 2) em lows? Erro de sintaxe? Uma abreviação de (array '+ '1 '2)?)

Miscelânea

Não há muito o que dizer sobre os demais tipos (números, strings, booleanos, e NULL). PHP converte loucamente entre tipos, o que é meio desagradável, mas não há muito o que fazer, a menos que queiramos inserir checks antes do uso de qualquer operação. (Isso poderia ser uma opção de compilação, útil para debugging, talvez.)

Operadores

Operações aritméticas e afins em Lisp costumam ser funções, e não operadores especiais. Porém, não queremos ter que chamar uma função em lows para realizar operações aritméticas, pois o custo de uma chamada de função em PHP é alto. (Mesmo Lisps costumam otimizar essas chamadas quando é possível determinar os tipos dos argumentos em tempo de compilação.) Por outro lado, seria bom podermos passar + e afins como argumento para outras funções. Uma possível solução seria expandir '+ para (lambda (x y) (+ x y)), mas isso fixa o número de argumentos do operador, enquanto o ideal é que (+ 1 2 3) seja possível e seja traduzido para 1+2+3. Outra possibilidade seria ter tanto uma função, definida na biblioteca padrão, quanto um operador especial, e o compilador decidiria qual usar dependendo do caso. Mas não tem por que incluir uma função para cada operador na biblioteca padrão, se o próprio compilador pode gerar o lambda com o código apropriado automaticamente quando necessário.

A maioria dos operadores do lows seriam equivalentes diretos, e teriam o mesmo nome, dos operadores do PHP. As exceções seriam:

Os operadores binários têm um detalhe extra: x || y em PHP sempre retorna um booleano, enquanto em Lisp (or x y) costuma retornar x se ele é verdadeiro, e y caso contrário. Daria para traduzir (or x y) para x? x : y (com o detalhe de que x não pode ser avaliado duas vezes), mas isso meio que estraga a idéia de tradução direta (os condicionais de ifs teriam uma cara bizarra no código resultante). Talvez o compilador pudesse detectar quando o and/or está sendo usado como condicional de um if e traduzi-lo para &&/|| nesses casos.

De qualquer forma, o que o PHP acha que é verdadeiro é diferente do que um Lisp normalmente acha que é verdadeiro: em Lisp, normalmente o valor booleano falso (ou a lista vazia) é considerada como falso, e tudo o mais é considerado verdadeiro (inclusive 0, a string vazia, etc.). Esse comportamento inclusive faria mais sentido em PHP, que possui funções como strpos que devolvem um índice numa string (que pode ser 0) ou false. Mas não adiantaria mudar só o and, teria que mudar o comportamento do if também. Tem que ver isso aí...

Funções

Funções em PHP não verificam se o número de argumentos passados corresponde ao número de parâmetros declarados. Poderia haver uma opção de compilação para inserir um check no começo de cada função.

While we are at it, poderíamos permitir declarar os tipos dos argumentos e adicionar checks no começo da função, ou verificar e emitir warnings em tempo de compilação quando possível. Mas isso ficaria para o futuro distante.

Muito me alegraria ter keyword arguments em lows. Eles poderiam ser passados como um array, i.e., (f x y a: 1 b: 2) seria equivalente a (f x y (array a: 1 b: 2)), e no corpo da função referências a a seriam traduzidas para referências à chave no array.

Variáveis globais e constantes

O plano original era que um acesso a uma variável não declarada localmente seria interpretado como um acesso à variável global de nome correspondente. Porém, estive pensando se não seria melhor usar um prefixo ou outra convenção para nomear variáveis globais (e.g., *foo*, a la Common Lisp, ou $foo, a la Ruby). A vantagem seria poder detectar erros de digitação em tempo de compilação, ao invés de assumir que o nome não declarado se trata de uma variável global.

Outro problema são as constantes globais. O Pharen aparentemente decide se um nome é uma constante ou uma variável exigindo que constantes sejam escritas em maiúsculas. ($_SERVER e afins também são escritos em maiúsculas, mas o Pharen usa um operador especial $ para acessar essas variáveis.) Nada no PHP exige que o nome de uma constante seja totalmente em maiúsculas, entretanto. Uma possibilidade seria usar +nome+, uma convenção usada por alguns programadores Common Lisp. I don't know. (error-reporting +E-ALL+)? Talvez (error-reporting #E_ALL)? (error-reporting <E_ALL>)?

Namespaces

Um dialeto de Lisp é classificado como Lisp-1 ou Lisp-2 dependendo de se nomes de funções e de variáveis vivem em um mesmo namespace (Lisp-1), ou se dois namespaces separados são usados (Lisp-2). PHP é mais próximo de um Lisp-2 do que de um Lisp-1, mas a distinção não é bem a mesma, pois em PHP variáveis possuem um prefixo que as identifica ($), enquanto em Lisp a posição do nome em uma chamada determina o namespace a ser utilizado (se o nome aparece como primeiro elemento em uma lista entre parênteses, ele é um nome de função; caso contrário, é uma variável).

Para o lows ser um Lisp-1, seria necessário determinar se (f x y) deveria ser traduzido para f($x, $y) ou $f($x, $y). No geral, isso não é possível (o compilador lows não tem conhecimento dos nomes definidos fora do arquivo que está compilando). Assim, um Lisp-2 é uma escolha mais natural: (f x y) sempre traduz para f($x, $y). Para obter o equivalente de $f($x, $y), i.e., para usar uma variável como a função a ser chamada, deve-se usar um operador especial, normalmente chamado funcall nos Lisps: (funcall f x y).

Em PHP 5.2, $f() só funciona se $f é uma string contendo o nome da função a ser chamada; não é possível chamar closures criadas com (lambda ...) dessa maneira. Por outro lado, call_user_func($f, ...) funciona tanto quando $f é uma string quanto com outros valores chamáveis. Como queremos que funcall funcione em ambos os casos, funcall deve ser traduzido para call_user_func.

A situação contrária ao funcall, i.e., quando se quer usar uma função nomeada como argumento para outra, é coberta pelo nosso operador 'foo, que produz uma string com o nome (convertido) da função foo. Como PHP não possui funções de primeira classe nem declarações locais de função, a string com o nome da função é suficiente para identificar a que função está se referindo.

Classes, métodos e bagulheiras

Definições de classes e métodos são relativamente straightforward. (defclass Foo atributos/métodos...) não tem por que ser muito diferente da construção equivalente em PHP. Podemos adicionar algumas abreviações, e.g., permitir definir um construtor padrão ao invés do costumeiro $this->x = $x; $this->y = $y; … do PHP.

A bagunça começa na hora de escolher uma sintaxe para chamada de métodos e acesso a atributos. Pharen usa algo como (-> objeto (método args...)), mas isso é meio verboso. Uma solução seria permitir (objeto->método args...) como uma abreviação, no caso mais comum em que objeto é uma variável, mas eu prefiro . para isso ao invés de ->. By the way, e se eu quiser um método cujo nome está em uma variável, i.e., $objeto->$método(args...)? Suponho que em Pharen seja possível escrever (-> objeto ($método args...)), que é como eles resolvem o problema do funcall, mas meh, não é assim que lows funciona – e se eu quiser uma expressão ao invés de uma variável para computar o nome do método? Que tal (-> objeto (funcall método args...))? Mas agora eu vejo que toda essa sintaxe conflita com a sintaxe para acesso de atributos. Em Pharen, (-> objeto nome) é equivalente a $objeto->nome. Novamente, para usar um nome vindo de uma variável, pode-se usar $nome, mas, novamente, isso não permite usar uma expressão para calcular o nome do atributo; (-> objeto (computa-nome)) seria interpretado como $objeto->computa_nome().

So. Estou com a sensação de que o adequado em lows seria que $obj->nome fosse algo como (-> obj 'nome), com o nome quoted. Sem o quote, nome seria avaliado como uma expressão que produz um nome. Quanto a chamada de métodos, talvez o jeito seja usar um operador distinto do usado para atributos. Meh. Independentemente dos operadores escolhidos, obj.nome seria considerado uma abreviação para (-> obj 'nome), já que acredito que acesso com um nome fixo a um objeto em uma variável pré-determinada seja o caso mais comum. E (obj.método args...) seria uma abreviação para $obj->método(args...). Isso tudo requer mais pensação.

Analogamente, o argumento do operador new também deveria ser quoted: (new 'Foo), não (new Foo). Nesse sentido, as operações do lows seriam mais parecidas com o make-instance e slot-value do CLOS do que com o Pharen. Now, o CLOS usa (método objeto args...) para chamadas de métodos (que na verdade são multi-métodos; o objeto é um argumento como os demais, que tambem podem ser especializados para diferentes classes). O problema de adotar essa sintaxe em lows é que é necessário saber que método é um método e não uma função para decidir que código PHP gerar. Uma possiblidade seria usar (.método objeto args...). Tem que ver isso aí. Outra situação a ser considerada é quando queremos passar um método como argumento para uma função (i.e., o equivalente do Array($obj, "método") do PHP). Hmmrgh...

Falando de abreviações, acho que seria uma boa adotar @nome como uma abreviação de $this->nome, a la Ruby. (@método args...) poderia ser abreviação de $this->método(args...).

[Um problema de usar (. objeto 'nome) é que . é sintaxe especial em Scheme, e atualmente eu uso a função read do Scheme para ler o código-fonte lows de um arquivo. Mais adiante, é de se pensar escrever um reader próprio para o lows. Isso também teria a vantagem de permitir manter informação de linha e coluna nas formas lidas.]

Macros

Como o compilador é escrito em Chicken Scheme, e não PHP (e nem por sonho eu pensaria em escrevê-lo em PHP (embora talvez reescrevê-lo em lows no futuro não seja algo de se descartar)), e não possui um interpretador próprio, ele não é capaz de rodar código lows em tempo de compilação. Isso exclui a possibilidade de macros procedurais nativas em lows. O meu plano, por ora, é permitir que macros em Scheme possam ser escritas, pois essas podem ser executadas pelo compilador. Por um lado é bizarro (em um Lisp) escrever macros em uma linguagem diferente da linguagem de programação, mas por outro isso tem a vantagem de permitir usar todas as funções do Chicken na geração de código. Uma outra possibilidade (não-exclusiva) é ter um sistema de macros baseado em casamento de padrões, como o syntax-rules do Scheme, que não requer a execução de código lows em tempo de compilação.

Além das macros que recebem código lows e produzem código lows, também seria interessante ter uma construção para emitir código PHP diretamente, similar ao asm do GCC e afins. No caso mais simples, a construção seria usada com uma string constante, mas nada impediria que expressões arbitrárias em Scheme pudessem ser usadas para gerar o código PHP.

Enough talk

Acho que era isso por enquanto. Sugestões, opiniões, etc., são bem-vindos.


[Version in English follows.]

In the last post, I talked about lows, a Lisp which compiles to PHP 5.2 which I started to develop, and what I got done so far. In this post, I intend to discuss some design questions of the language, more or less in the same spirit of the Blueprints for a shell series (in Portuguese) (except much shorter (or so I hope)). As I mentioned, I don't know when I'm going to work on lows again (after all, I've got some mundane tasks to do, such as a Master's to finish), but I'll leave some ideas documented here. Comments are always welcome.

Data types

The PHP data types don't match very well (read: mostly don't match) the conventional types from the Lisp world. The plan is to try to design the language to enable a sufficiently Lisp-like programming style with the existing types, rather than trying to recreate the traditional Lispy types in PHP, even more because that would not be very feasible in terms of performance in PHP.

Arrays

Basically the only type of collection PHP has is the array, which fills the roles of both lists and dictionaries (and even as a dictionary, it preserves insertion order). I don't see any efficient way to implement cons in PHP (adds an element to a list, creating a new list, in space/time O(1)). It would be possible to create a Cons class, but the overhead would be too large, and moreover the goal of the language is to interact easily with PHP code. So, arrays.

The map, filter, reduce and similar operations are independent of the representation of the collection and work equally well with arrays. The idiom of building lists using cons and recursion, on the other hand, is right out, but at the same time it wouldn't be a good choice in PHP anyway because function calls are more costly, and there is no tail call optimization. Actually, tail call optimization wouldn't help in this case anyway, because the recursion is not in tail position; in this situation, when performance matters, people usually accumulate the elements of the list in an argument to the recursive call, and call reverse! at the end of the loop, but if we're going to do that, we can equally well pass an array as the argument and accumulate the elements with push. Anyway.

In the future, map and company can be inlined, which makes these constructions basically equivalent to loops. Even the closure passed as an argument to map does not need to be created, and instead the body of lambda can be inserted directly in the loop.

We have to think about the syntax equivalent to PHP's Array(...). For simple lists, (array 1 2 3) works. The problem is the equivalent of the form Array("foo"=>1, "bar"=>2). When the key is a simple literal string, one possibility would be to use Chicken keywords: (array foo: 1 bar: 2). When the key is not a literal, something like (array (=> key1 val1) (=> key2 val2)) could be used, but that is somewhat verbose. I don't know.

One oddity of PHP arrays is that they are passed by copy. Apparently, there simply is no way to pass an array "by sharing", as values are usually passed in Lisp or Python. It is possible to create a reference (actually an alias) to a variable containing an array, but the reference is to the variable, not to the array; if another value is assigned to the variable, the reference/alias will reflect the modification. I guess we'll have to live with that.

Symbols

There is nothing equivalent to symbols in PHP. The main advantage of symbols over strings is that they can be compared in constant time, but PHP doesn't really have anything equivalent to the Lisp eq operator, so I think there wouldn't be much to gain in implementing symbols in lows. 'foo could be used as an abbreviation for "foo", perhaps.

Pharen has an "operator" #foo, which yields the string corresponding to the name of the function foo. The reason for that operator is that Pharen converts hyphens in function names to underscores (which is a good idea in a Lisp→PHP), and the # operator yields a string with the converted name. Perhaps ' could be used to the same purpose in lows.

By the way, lows does not have a true quote operator, as the code of a lows program is not represented by a lows (i.e., PHP) data structure. Yeah, lows is not a homoiconic language, and perhaps doesn't even deserve the name "Lisp" because of that. Macros in lows wouldn't be written in lows, but rather in (Chicken) Scheme, as we'll see later.

(What would be the meaning of '(+ 1 2) in lows? Syntax error? Shorthand for (array '+ '1 '2)?)

Miscellaneous

There isn't much to say about the remaining types (numbers, strings, booleans, and NULL). PHP converts nilly-willy between types, which is somewhat annoying, but there is not much we can do, unless we want to insert checks before every operation. (This could be a compilation option, useful for debugging, perhaps).

Operators

Arithmetic operations and the like in Lisp are usually functions, not special operators. However, we don't want to have to call a function in lows to perform arithmetic operations, because the cost of a function call in PHP is high. (Even Lisps usually optimize these calls away when it is possible to determine the types of the operands at compile time.) On the other hand, it would be nice to be able to pass + and the like as arguments to other functions. One possibility would be to expand '+ to (lambda (x y) (+ x y)), but that would fix the number of arguments of the operator, whereas ideally (+ 1 2 3) should work and translate to 1+2+3. Another possibility would be to have both a function, defined in the standard library, and the special operator, and the compiler would decide which to use depending on the situation. But there is no reason to include a function for each operator in the standard library, when the compiler can automatically generate the lambda with the appropriate code as needed.

Most lows operators would be directly equivalent, and would have the same name, as the PHP operators. The exceptions would be:

The binary operators have an extra detail: x || y in PHP always returns a boolean, whereas in Lisp (or x y) usually returns x if it is true-ish, and y otherwise. It would be possible to translate (or x y) to x? x : y (taking extra care not to evaluate x twice), but that kinda messes with the idea of straightforward translation (the conditions of if blocks would look weird in the compiled code). Maybe the compiler could detect when and/or are being used as the condition of an if and translate them to &&/|| in those cases.

In any case, what PHP thinks is true is at odds with what Lisps usually think is true: in Lisp, usually the false boolean value (or the empty list) is considered false, and everything else is considered true (including 0, the empty string, etc.). This behaviour would actually make more sense in PHP, which has functions such as strpos which return an index into a string (which may be 0) or false. But then it wouldn't be enough to change the behavior of and, we'd have to change the behavior of if too. Gotta think about it.

Functions

PHP functions don't check if the number of arguments in a call match the number of declared parameters. There could be a compilation option to insert a check at the beginning of each function.

While we are at it, we could allow declaring the types of the parameters and add checks at the beginning of the function, or verify and emit warnings at compile time if possible. But that would be left for the distant future.

It would much gladden me to have keyword arguments in lows. They could be passed in an array, i.e., (f x y a: 1 b: 2) would be equivalent to (f x y (array a: 1 b: 2)), and in the function body references to a would be translated to array dereferences.

Global variables and constants

The original plan was that an access to a variable not declared locally would be interpreted as an access to the global variable with that name. However, I have been thinking if it wouldn't be better to use a prefix or some other convention to name global variables (e.g., *foo* as in Common Lisp, or $foo as in Ruby). The advantage would be to be able to detect mistyped variable names at compile time, rather than assuming that the undeclared name refers to a global.

Another problem is global constants. Pharen seems to decide if a name is a constant or a variable by requiring all constants to have all-uppercase names. ($_SERVER et al. are all-uppercase too, but Pharen uses a special operator $ to access those.) Nothing in PHP requires that the name of a constant be all-uppercase, though. A possibility would be to use +name+, a convention used by some Common Lisp programmers. I don't know. (error-reporting +E-ALL+)? Maybe (error-reporting #E_ALL)? (error-reporting <E_ALL>)?

Namespaces

Lisp dialects are usually classified as Lisp-1 or Lisp-2 depending on whether function and variable names live in a single namespace (Lisp-1), or if there are two separate namespaces for them (Lisp-2). PHP is closer to a Lisp-2 than a Lisp-1, but the distinction is not quite the same, because in PHP variable names have a prefix identifying them ($), whereas in Lisp the position of the name in a call determines which namespace is to be used (if the name appears as the first element of a parenthesized list, it is a function name; otherwise, it's a variable name).

For lows to be a Lisp-1, it would be necessary to determine if (f x y) should be translated to f($x, $y) or $f($x, $y). In general, this is not possible (the lows compiler has no knowledge of the global names defined outside the file being compiled). Therefore, a Lisp-2 is a more natural choice: (f x y) always translates to f($x, $y). To get the equivalent of $f($x, $y), i.e., to use a variable as a function to be called, a special operator must be used, usually named funcall in Lisps: (funcall f x y).

In PHP 5.2, $f() only works if $f is a string containing the name of the function to be called; it is not possible to call closures created with (lambda ...) in this way. On the other hand, call_user_func($f, ...) works equally well when $f is a string and with other callable values. Since we want funcall to work in both cases, funcall must be translated to call_user_func.

The opposite situation to funcall, i.e., when we want to pass a named function as an argument to another, is covered by our ' operator, which yields a string with the (converted) name of the function foo. Since PHP does not have first-class functions or local function declarations, a string with the function name is enough to identify which function is being referenced.

Classes, methods, and stuff

Class and method definitions are relatively straightforward. (defclass Foo attributes/methods...) doesn't have to be very different from the equivalent construction in PHP. We can add some shorthands, e.g., allow a default constructor instead of the usual $this->x = $x; $this->y = $y; … in PHP.

The mess begins when choosing a syntax for method calls and attribute access. Pharen uses something like (-> object (method args...)), but that is somewhat verbose. A solution would be to allow (object->method args...) as a shorthand for the most common case when object is a variable, but I prefer . for this instead of ->. By the way, what if I want to use a method whose name is in a variable, i.e., $object->$method(args...)? I suppose Pharen allows (-> object ($method args...)), which is how they solve the funcall problem, but meh, that's not how lows works – what if I want an expression instead of a variable to compute the method name? What about (-> object (funcall method args...))? But now I see that all this syntax conflicts with the syntax for attribute access. In Pharen, (-> object name) is equivalent to $object->name. Again, to use a name from a variable, it allows $name, but, again, this does not allow using an expression to compute the name of the attribute; (-> object (compute-name)) would be interpreted as $object->compute_name().

So. I have the feeling that the appropriate thing in lows would be that $obj->name should be something like (-> obj 'name), with the name quoted. Without the quote, name would be evaluated as an expression yielding a name. As for method calls, perhaps the solution is to use a distinct operator from that used for attribute access. Meh. Regardless of the operators chosen, obj.name would be considered shorthand for (-> obj 'name), as I think access with a fixed name to an object in a known variable is the most common case. And (obj.method args...) would be shorthand for $obj->method(args...). All of this requires more thinking.

Analogously, the argument for the new operator should also be quoted: (new 'Foo), not (new Foo). In this sense, lows's operations would be more similar to make-instance and slot-value from CLOS than to Pharen. Now, CLOS uses (method object args...) for method calls (which are actually multi-methods; object is an argument just like the others, which can also be specialized for different classes). The problem of adopting this syntax in lows is that it requires knowing that method is a method and not a function to decide which PHP code to emit. One possibility would be using (.method object args...). Gotta think about that. Another situation to be considered is when we want to pass a method as an argument to a function (i.e., the equivalent of PHP's Array($obj, "method")). Hmmrgh...

Speaking of shorthand, I think it would be a good idea to adopt @name as shorthand for $this->name, a la Ruby. (@method args...) could be shorthand for $this->method(args...).

[One problem of using (. object 'name) is that . is special syntax in Scheme, and currently I use Scheme's read function to read lows source code from a file. Later on, one might think about writing a proper reader for lows. That would also have the advantage of allowing the compiler to keep track of line and column information.]

Macros

Because the compiler is written in Chicken Scheme, and not PHP (and I wouldn't even dream of writing it in PHP (though perhaps rewriting it in lows someday is not entirely unthinkable)), and has no lows intepreter, it is not capable of running lows code at compile time. This excludes the possibility of native procedural macros in lows. My plan, for now, is to allow writing macros in Scheme, as those could be run by the compiler. On the one hand it is somewhat weird (for a Lisp) to write macros in a different language, but on the other hand this has the advantage of allowing one to use all Chicken functions in code generation. Another (non-mutually-exclusive) possibility is to have a macro system based on pattern matching, like Scheme's syntax-rules, which does not require running lows code at compile time.

Beside macros which take lows code and yield lows code, it would also be interesting to have a construction to emit PHP code directly, similar to asm in GCC and the like. In the simplest case, the construction would be used with a constant string, but nothing would exclude using arbitrary Scheme expressions to generate PHP code.

Enough talk

I think that's it for now. Suggestions, opinions, etc., are welcome.

2 comentários

Lisp meets PHP

2015-09-02 04:25 -0300. Tags: comp, prog, pldesign, php, lisp, lows, in-english

[This post is also available in English.]

Como eu andei comentando por aí, eu comecei a implementar uma linguagem Lisp-like que compila para PHP 5.2, chamada lows. Não sei quando vou mexer nesse projeto de novo, mas deixo aqui algumas notas para o meu eu futuro e para quem tiver interesse.

Prelúdio

Tudo começou no domingo retrasado, quando eu resolvi dar uma mexida no blog system, for a change. Mexer no blog sempre é uma experiência ambivalente, pois por um lado tem uma porção de idéias que eu gostaria de implementar nele, mas por outro lado eu tenho que fazer isso em PHP, porque é a única coisa que roda no inf.ufrgs.br (e eu não pretendo pagar por hospedagem any time soon). Eu comentei com uma pessoa que se eu tivesse a opção, eu já teria reescrito o blog em Scheme há muito tempo. Ela me perguntou por que eu não fazia algo para rodar Scheme em PHP, e eu comentei que já tinha pensado em fazer um compilador de Lisp para PHP, mas que achava que era muita mão só para poder escrever o blog em Lisp. Assim, eu segui meu domingo fuçando no blog em PHP.

Depois de uma porção de gambiarras e mais uma porção de concessões às bizarrices do PHP (e.g., existem os métodos mágicos __call, __callStatic e __get, mas não existe um __getStatic, sabe-se lá por quê), eu consegui reescrever a parte do blog responsável por mensagens multilíngües de uma maneira que me agradasse, e até já não estava mais achando tão horrível escrever o código em PHP.

No final do dia, depois de ter testado o código no meu servidor local, eu resolvi fazer upload da nova versão para o inf.ufrgs.br. Para minha surpresa, o PHP começou a reportar uma porção de erros no código. Turns out que a versão do PHP que roda no inf.ufrgs.br é a "5.2.6-1+lenny16". Para quem não sabe, lenny é o codinome do Debian 5. O Debian 5 foi lançado em 2009 e não recebe mais atualizações desde 2012. Três releases estáveis do Debian saíram desde então (as releases estáveis do Debian saem a cada mais ou menos dois anos). Meanwhile, eu estava rodando PHP 5.6.12 em um Debian testing em casa, e praticamente todo o código que eu tinha escrito usava features introduzidas no PHP 5.3.

Depois de tentar sem muito sucesso mudar um pouco o código para ver se conseguia fazê-lo rodar no PHP 5.2, eu resolvi largar de mão e deixar para mexer no código outro dia. Porém: (1) eu não estava a fim de enfeiar o código só para fazê-lo rodar em um PHP velho; (2) more generally, eu não estava a fim de mexer em PHP de novo; e (3) eu não estava conseguindo dormir aquele dia. Conclusão: comecei a escrever um tradutor Lisp→PHP, primariamente for the lol. Mais uma noite mal-dormida, e eis que eu tinha um tradutor (ou compilador, como preferir) Lisp→PHP que fazia pouca coisa, mas o suficiente para me convencer de que a idéia era pelo menos viável. Nasceu assim o lows, ou Lisp for Old Web Servers.

Idéia

A idéia do projeto é criar uma linguagem Lisp-like que satisfaça os seguintes objetivos:

Compilar para PHP 5.2. A idéia é eu poder rodar o código resultante no inf.ufrgs.br (e idealmente escrever a próxima versão do blog em lows), então eu preciso "targetar" especificamente PHP 5.2. Eu também podia tentar convencer a galera da admrede a atualizar o servidor, mas (1) acho pouco provável que isso aconteça any time soon, e eu não estava a fim de esperar; (2) a essa altura eu já tinha tomado a limitação a PHP 5.2 como um desafio (lembre-se de que eu comecei o projeto para matar tempo enquanto o sono não vinha); (3) já existe um projeto similar, chamado Pharen, que targeta PHP 5.5, e eu queria um diferencial (a.k.a. desculpa) para justificar o meu projeto.

Gerar código PHP relativamente straightforward. Tanto quanto possível, o código PHP resultante da compilação deve ser uma tradução mais ou menos direta do código lows original. A idéia é facilitar a depuração (e em particular a tarefa de encontrar o código lows correspondente a um erro reportado no código PHP), e também a esperança de que quanto mais direto for o código resultante, menor o impacto na performance de escrever o código em lows ao invés de diretamente em PHP.

Integrar facilmente com PHP. Deve ser possível usar funções, classes, etc. do PHP a partir de código lows e vice-versa, sem necessidade de conversões, anotações e afins.

Manter uma essência Lisp-like. A idéia não é simplesmente criar um redressing de PHP em S-expressions, mas sim uma linguagem que permita programar em um estilo semi-funcional e "Lispy" e evite as bizarrices do PHP na medida do possível (ao mesmo tempo em que introduz outras bizarrices).

Esse conjunto de objetivos influencia tanto a implementação (que deve gerar um PHP relativamente limpo/direto) quanto o design da linguagem (que não deve fugir muito do PHP para permitir a tradução relativamente direta e a compatibilidade com código PHP).

Transformando Lisp em PHP

Expressões e statements

Um desafio que eu encontrei logo no começo é o fato de que o PHP faz uma distinção entre expressões e statements, que (mostly) não existe em Lisp. Em particular, coisas como if, let (declaração de variáveis locais) e progn (executa uma seqüência de expressões e retorna o valor da última, mais ou menos análogo a um bloco entre chaves em PHP, mas que produz um valor) são expressões em lows. O if em princípio até poderia ser traduzido para o operador ternário (test? then : else), e o let poderia ser mais-ou-menos contornado já que atribuição é uma expressão em PHP. O problema é que PHP não tem um operador vírgula como em C. Coisas como:

(+ 1 (let ((x 23)
           (y 42))
       (* x y)))

não possuem uma tradução direta para PHP, pois não é possível escrever 1 + ($x=23, $y=42, $x*$y). Uma solução gambiarrenta seria gerar:

1 + ((($x=23) || TRUE) ? ((($y=42) || TRUE) ? ($x*$y)
                                            : whatever)
                       : whatever)

o que simula o operador vírgula usando só um branch do operador ternário, mas: (1) isso não funciona no caso geral (em particular, se uma das expressões é um progn contendo um echo ou alguma outra coisa statementosa); (2) isso vai totalmente contra a idéia de gerar código straightforward. A solução é mover as atribuições para antes da soma, mas, no caso geral, só mover qualquer coisa que não seja uma expressão em PHP para antes da expressão não é suficiente: se os branches de um condicional contêm expressões com efeitos colaterais, não é possível movê-las para fora do condicional. Por exemplo, em algo como:

(defun print-and-return (val)
  (echo "O valor é " val "\n")
  val)

(+ 1 (if (> x y)
         (let ((a (print-and-return (- x y))))
           (* a a))
       0))

não é possível traduzir a soma para:

$a = print_and_return($x-$y);
1 + (($x>$y)? ($a*$a) : 0)

pois print_and_return não pode ser chamada antes que o teste $x>$y seja realizado.

[A essa altura talvez lhe ocorra (como me ocorreu) o pensamento: "Ok, e por que a gente simplesmente não proíbe expressões complexas desse tipo aninhadas em outras expressões? Quando é que eu vou usar isso anyway?" Mas esse é justamente o tipo de limitação tosca de que nós estamos tentando fugir criando uma nova linguagem ao invés de programar em PHP! "Do not tell me “that’s what you get for doing weird things”. If two features exist, someday, someone will find a reason to use them together."]

A solução que eu encontrei foi traduzir o (if ...) para um bloco if em PHP, armazenar o valor do if em uma variável temporária, e usar a variável temporária na soma. O exemplo anterior fica algo como:

if ($x>$y) {
    $a = print_and_return($x-$y);
    $tmp = $a*$a;
} else {
    $tmp = 0;
}

1 + $tmp

Isso significa que para traduzir uma expressão como (+ ...), pode ser necessário emitir blocos de código antes da tradução da soma propriamente dita. Conseqüentemente, a função de tradução não pode ser simplesmente algo como:

translate[(+ lhs rhs)] = translate[lhs] + translate[rhs]

pois tanto a tradução de lhs quanto de rhs podem requerer a inserção de blocos de código antes da soma (e a soma, por sua vez, pode estar aninhada em outra expressão).

A solução que eu encontrei para esse problema foi fazer as funções de tradução retornarem dois valores: a expressão equivalente em PHP, e uma lista de "efeitos", que são basicamente (mas não necessariamente) instruções para emitir código nas redondezas da tradução. Por exemplo, a função de tradução aplicada ao if do exemplo gera a expressão $tmp (que pode ser inserida no meio de outra expressão que usa o valor do if), e o efeito (EmitBefore bloco-if-em-PHP), que indica que o bloco-if-em-PHP deve ser inserido antes da expressão que contém o if na geração do código PHP. Como a inserção só pode ser realizada fora de uma expressão, o efeito é propagado pelas funções de tradução de expressões, até que ele chega em uma função que emite statements (e.g., o corpo de um bloco if do PHP, ou o corpo de uma função) e pode então ser emitido. Pseudocodiciosamente (oops, hmm):

translate[(+ lhs rhs)] =
   let
       lhs-trans; lhs-effects = translate[lhs]
       rhs-trans; rhs-effects = translate[rhs]
   in
       lhs + rhs; lhs-effects ++ rhs-effects


translate-statement[item] =
   let
       item-trans; item-effects = translate[item]
   in
       (código correspondente aos EmitBefore em item-effects) ++ item-trans ;
       (efeitos em item-effects excluindo os EmitBefore já processados)

O mesmo mecanismo pode ser usado para emitir código em outras situações (e.g., no caso do lambda, como veremos adiante), ou para coletar e propagar informações durante a tradução. Por exemplo, quando uma variável x que não possui declaração visível é usada, é emitido um efeito (Global x). A função que traduz o corpo de uma função coleta esses efeitos para gerar declarações do tipo global $x; no começo da função.

lambda

O próximo desafio foi traduzir o lambda para PHP. PHP >=5.3 possui closures (meio toscas – é necessário declarar explicitamente que variáveis são capturadas pela closure – mas elas existem), mas PHP 5.2 não. A próxima coisa que eu pensei foi usar uma classe "callable" com um método mágico __invoke, mas turns out que classes chamáveis só foram introduzidas em PHP 5.3 também. Porém, as funções que aceitam coisas chamáveis em PHP, como call_user_func e usort, aceitam arrays da forma Array(objeto, nome-de-método) como chamáveis. Pois, aí está algo que o lambda pode retornar.

Capturar as variáveis em uma closure mostrou-se bem mais fácil do que eu antecipava, graças às referências do PHP. Uma closure em lows é representada por uma classe com um membro/slot/propriedade/atributo/whatever para cada variável capturada. Quando a classe é instanciada, as variáveis são passadas por referência para o construtor. Dentro do corpo do lambda, referências a variáveis capturadas x são traduzidas para $this->x; como $this->x foi inicializado com uma referência ao $x capturado, o corpo do lambda vê a mesma variável $x através do atributo, inclusive refletindo modificações à mesma.

Como exemplo, algo como:

(defun adder (x)
  (lambda (n)
    (+ x n)))

(defun main ()
  (let ((f (adder 10)))
    (call_user_func f 5)))

vira algo como:

class Closure1 {
    function __construct(&$x) {
        // Captura de variáveis.
        $this->x = &$x;
    }

    function __invoke($n) {
        // Corpo do lambda.
        return $this->x + $n;
    }
}

function adder($x) {
    // Cria a closure, passando a variável a ser capturada para o seu construtor,
    // e retorna um valor que, quando chamado, chama o método "__invoke" da closure.
    return Array(new Closure1($x), "__invoke");
}

function main() {
    $f = adder(10);
    return call_user_func($f, 5);
}

E assim, o PHP e suas referências nos surpreendem positivamente (o que é uma surpresa in itself).

By the way, o mecanismo de efeitos aqui é usado para duas coisas: (1) a geração da classe antes da função que contém o lambda é feita propagando um efeito (EmitBeforeToplevel definição-da-classe); (2) cada referência a uma variável externa ao lambda gera um efeito (CapturedVar x); esses efeitos são coletados pela função que traduz o lambda para saber que atributos devem ser inicializados na classe e que argumentos devem ser passados ao construtor. Quando eu criei a treta dos efeitos eu não tinha pensado em todas essas aplicações, então mui me alegrou descobrir que eu podia reusar o mecanismo para essas coisas.

Name clashes

Em PHP, variáveis locais têm como escopo a função inteira onde se encontram, não apenas o bloco onde foram declaradas. Conseqüentemente, em código como:

(let ((x 23))
  (echo "x aqui vale 23: " x "\n")
  (let ((x 42))
    (echo "x aqui dentro vale 42: " x "\n"))
  (echo "x aqui fora ainda vale 23: " x "\n"))

não se pode usar o mesmo nome para as duas variáveis x na tradução, pois a definição mais interna de x sobrescreveria a mais externa. A solução e renomear uma das (ou ambas as) variáveis. O ideal seria fazer o mínimo de renomeações possível, para facilitar a leitura e depuração do código resultante. Porém, a implementação atual simplesmente renomeia todas as variáveis (adicionando um prefixo _número_), já que testar quando uma variável deve ser renomeada não é muito simples. Essa decisão não é local: mesmo não havendo nenhuma variável x visivelmente declarada no ponto onde ocorre um (let ((x 23)) ...), ainda assim é necessário renomear o x se em um ponto posterior da função uma variável global x for referenciada.

O algoritmo de renomeação / geração de nomes temporários assume que nomes iniciados por _número_ são reservados para o compilador. Acredito que isso não seja um problema na prática. (Para o caso de variáveis locais, uma variável _42_ vai ser renomeada para algo como _1__42_ de qualquer forma.) Um problema mais sério dessa abordagem é no escopo global, em particular nos nomes gerados para as classes que implementam closures (e.g., _1_Closure), pois esses nomes podem conflitar com closures criadas em outros arquivos (e.g., quando os resultados da tradução de múltiplos arquivos são incluídos com include em um programa PHP). Talvez uma solução seja incluir o nome do arquivo no nome da classe, ou gerar um hash a partir do código da closure (mas isso ainda gera conflito se um lambda idêntico aparece em outro arquivo), or something. I don't know. Também seria bom se o nome da classe fosse informativo o suficiente para indicar de onde saiu a definição no código original (e.g., _1_Closure_arquivo_função_linha). [Side remark: namespaces não existem em PHP 5.2.]

Um conflito de variável mais sutil é quando um let é executado múltiplas vezes e um lambda captura uma variável definida pelo let. Por exemplo, supondo a existência de uma construção while:

(let ((array-of-lambdas (array))
      (i 0))
  (while (< i 5)
    (let ((n 0))
      (array_push array-of-lambdas
                  (lambda ()
                    (set! n (+ n 1))
                    (echo n))))
    (set! i (+ i 1))))

Isso seria traduzido para algo como:

Class _1_Closure {
    function __construct(&$n) {
        $this->n = &$n;
    }

    function __invoke() {
        $this->n = $this->n + 1;
        echo $this->n;
    }
}

$array_of_lambdas = Array();
$i = 0;
while ($i < 5) {
    $n = 0;
    array_push($array_of_lambdas,
               Array(new _1_Closure($n), "__invoke"));
    $i = $i + 1;
}

O problema é que todas as iterações do loop usam a mesma variável $n, que é passada por referência ao construtor da closure; o correto seria cada iteração capturar um $n diferente. A solução é emitir uma chamada a unset($n) no final do while, de maneira que cada iteração crie uma variável nova, mas eu ainda não implementei isso.

PHP formatado

Um dos objetivos do projeto é gerar PHP legível, e isso involve gerar código com indentação adequada. Depois de alguns false starts (na versão inicial, as funções de tradução geravam strings de código PHP diretamente, e a minha idéia original era usar caracteres especiais do ASCII como indicadores de "increase indentation" e "decrease indentation" quando o código fosse impresso, mas eu me dei conta de que não dava para escolher caracteres para isso porque qualquer caractere pode aparecer em uma string; além disso, misturar geração de código e questões de formatação estava ficando um bocado desagradável), eu resolvi fazer as funções de tradução gerarem estruturas representando árvores de sintaxe abstrata (ASTs) de PHP. Depois da tradução, as árvores são passadas a uma função print-php que trata dos detalhes sórdidos de imprimir o código com quebras de linha, indentações, espaços e parênteses nos lugares apropriados. Separation of concerns FTW.

O futuro

Como o post ficou grande, e eu deveria ir dormir, ficaremos por aqui. Em um post futuro, pretendo falar de algumas features que falta implementar, tais como classes, chamadas de métodos e demais firulas orientadas a objetos, bem como as decisões de design mais tricky (que eram o objetivo inicial do post, mas enfim). Quem tiver interesse, pode dar uma olhada no código no GitHub.


[English version follows.]

As I have been talking about, I started implementing a Lisp-like language which compiles to PHP 5.2, called lows. I don't know when I'm going to work on this project again, but I'll leave here some notes for my future self and whoever might be interested.

Prelude

It all began last last Sunday, when I decided to play with my blog system, for a change. Working on the blog system is always an ambivalent experience, because on the one hand there is a bunch of ideas I would like to implement in it, but on the other hand I have to do it in PHP, as that is the only thing that runs at inf.ufrgs.br (and I don't plan to pay for hosting any time soon). I commented to a person that if I had the choice, I would have rewritten the blog in Scheme long ago. She asked my why I didn't make something to run Scheme in PHP, and I said I had already though of writing a compiler from Lisp to PHP, but that I thought it was too much work just to be able to write the blog in Lisp. So, I went on with my Sunday messing with the blog in PHP.

After a number of kludges and another number of concessions to the oddities of PHP (e.g., there are the __call, __callStatic and __get magic methods, but no __getStatic, who knows why), I succeeded in rewriting the part of the blog responsible for multilingual messages in a way that pleased me, and I was even not finding it so horrible to write the code in PHP.

At the end of the day, after having tested the code in my local server, I decided to upload the new version to inf.ufrgs.br. To my surprise, PHP started reporting lots of errors in the code. It turns out that the version of PHP running at inf.ufrgs.br is "5.2.6-1+lenny16". For those who don't know, lenny is the codename of Debian 5. Debian 5 was launched in 2009 and does not get updates since 2012. Three stable Debian releases have been out since then (the stable releases of Debian are launched more or less every two years). Meanwhile, I was running PHP 5.6.12 in a Debian testing at home, and practically all the code I had written used features introduced in PHP 5.3.

After trying without much success to change the code a bit to see if I got it to run on PHP 5.2, I decided to leave it alone and work on the code another day. However: (1) I was not willing to uglify my code just to make it run on an old PHP; (2) more generally, I wasn't willing to work with PHP again; and (3) I was having difficulties to sleep that day. Conclusion: I stared writing a Lisp→PHP translator, primarily for the lol. One more badly-slept night later, and so it was that I had a Lisp→PHP translator (or compiler, if you prefer) that did little, but enough to convince me that the idea was at least feasible. Thus lows, or Lisp for Old Web Servers, was born.

Idea

The idea of the project is to create a Lisp-like language which satisfies the following criteria:

Compile to PHP 5.2. The idea is for me to be able to run the resulting code at inf.ufrgs.br (and ideally write the next version of the blog in lows), so I need to target specifically PHP 5.2. I could also try to convince the admins at INF to upgrade the server, but (1) I don't think that's going to happen any time soon, and I was not willing to wait; (2) at this point I had already taken the limitation to PHP 5.2 as a challenge (remember that I started the project to kill time while I couldn't sleep); (3) there is already a similar project, called Pharen, which targets PHP 5.5, and I wanted a distinctive feature (a.k.a. excuse) to justify my project.

Emit relatively straightforward PHP code. As much as possible, the PHP code resulting from compilation should be a more or less direct translation of the lows source. The idea is to ease debugging (and in particular the task of finding the lows code corresponding to a PHP error message), and also the hope that the more direct the resulting code, the smaller the impact on performance of writing the code in lows rather than directly in PHP.

Integrate easily with PHP. It must be possible to use PHP functions, classes, etc. from lows code and vice-versa, without requiring conversions, annotations and the like.

Keep a Lisp-like essence. The idea is not simply to make a redressing of PHP in S-expressions, but rather a language which enables programming in a semi-functional and "Lispy" style and avoids the oddities of PHP as much as possible (while introducing new oddities of its own).

This set of goals influences both the implementation (which must emit relatively clean/direct PHP code) and the design of the language (which must not stray away too much from PHP to allow a relatively direct translation and compatibiltity with PHP code).

Transforming Lisp into PHP

Expressions and statements

A challenge I found right at the beginning is the fact that PHP makes a distinction between expressions and statements, which (mostly) does not exist in Lisp. In particular, things like if, let (local variable declaration) and progn (runs a sequence of expressions and returns the value of the last one, more or less like a block in braces in PHP, but yielding a value) are expressions in lows. if in principle could be translated to the ternary operator (test? then : else), and let could be more-or-less worked around because assignment is an expression in PHP. The problem is that PHP does not have a comma operator like that of C. Things like:

(+ 1 (let ((x 23)
           (y 42))
       (* x y)))

don't have a direct translation to PHP, because it is not possible to write 1 + ($x=23, $y=42, $x*$y). A kludgy solution would be to emit:

1 + ((($x=23) || TRUE) ? ((($y=42) || TRUE) ? ($x*$y)
                                            : whatever)
                       : whatever)

which emulates the comma operator by using only one branch of the ternary operator, but (1) that doesn't work in the general case (in particular, if one of the expressions is a progn containing an echo or some other statement-y thing); (2) that goes totally against the idea of emitting straightforward code. The solution is to move the assignments to before the addition, but, in general, just moving anything that is not an expression to before the expression is not enough: if the branches of a conditional contain expressions with side effects, they cannot be moved out of the conditional. For instance, in something like:

(defun print-and-return (val)
  (echo "The value is " val "\n")
  val)

(+ 1 (if (> x y)
         (let ((a (print-and-return (- x y))))
           (* a a))
       0))

it is not possible to translate the addition to:

$a = print_and_return($x-$y);
1 + (($x>$y)? ($a*$a) : 0)

because print_and_return cannot be called before the test $x>$y is performed.

[At this point, perhaps it ocurred to you (as ocurred to me) the thought: "Okay, why don't we just forbid complex expressions like these nested in other expressions? When will I use that anyway?" But that is exactly the kind of weird limitation which we are trying to escape from by creating a new language instead of programming in PHP! "Do not tell me “that’s what you get for doing weird things”. If two features exist, someday, someone will find a reason to use them together."]

The solution I found was to translate (if ...) to a PHP if block, store the value of the if expression into a temporary variable, and use the temporary in the addition. The previous example becomes something like:

if ($x>$y) {
    $a = print_and_return($x-$y);
    $tmp = $a*$a;
} else {
    $tmp = 0;
}

1 + $tmp

This means that to translate an expression like (+ ...), it may be necessary to emit blocks of code before the translation of the addition itself. As a consequence, the translation function cannot be just something like:

translate[(+ lhs rhs)] = translate[lhs] + translate[rhs]

because both lhs and rhs may require inserting blocks of code before the addition (and the addition itself may be nested in another expression).

The solution I found for this problem was to make the translation functions return two values: the equivalent expression in PHP, and a list of "effects", which are basically (but not necessarily) instructions to emit code in the surroundings of the translation. For example, the translation function, when applied to the example if, yields the expression $tmp (which can be inserted in the middle of another expression which uses the value of the if, and the effect (EmitBefore PHP-if-block), which indicates that PHP-if-block must be inserted before the expression containing the if when emitting the PHP code. Since the insertion can only be performed outside of an expression, the effect is propagated by the functions responsible for translating expressions, until it arrives at a function which emits statements (e.g., the body of a PHP if block, or a function body), where it can then be emitted. Pseudocodefully:

translate[(+ lhs rhs)] =
   let
       lhs-trans; lhs-effects = translate[lhs]
       rhs-trans; rhs-effects = translate[rhs]
   in
       lhs + rhs; lhs-effects ++ rhs-effects


translate-statement[item] =
   let
       item-trans; item-effects = translate[item]
   in
       (code corresponding to the EmitBefores in item-effects) ++ item-trans ;
       (effects in item-effects excluding those EmitBefores already processed)

The same mechanism can be used to emit code in other situations (e.g., in the case of lambda, as we'll see later), or to collect and propagate information during translation. For example, when a variable x which has no visible declaration is used, a (Global x) effect is generated. The function responsible for translating functions collects those effects to generate global $x; declarations at the beginning of the function.

lambda

The next challenge was to translate lambda to PHP. PHP >=5.3 has closures (somewhat crappy ones – one must declare explicitly which variables are to be captured by the closure – but they exist), but PHP 5.2 doesn't. The next thing I thought was to use a "callable" class with an __invoke magic method, but it turns out that callable classes were introduced only in PHP 5.3 too. However, the functions which accept callable things in PHP, such as call_user_func and usort, accept arrays of the form Array(object, method-name) as callables. So, this is something that lambda can return.

Capturing variables in a closure proved much easier than I anticipated, thanks to PHP references. A closure in lows is represented as a class with a member/slot/property/attribute/whatever for each captured variable. When the class is instantiated, the variables are passed by reference to the constructor. Inside the body of lambda, references to captured variables x are translated to $this->x; because $this->x was initialized with a reference to the captured $x, the lambda body sees the same variable $x through the attribute, even reflecting modifications to it.

As an example, something like:

(defun adder (x)
  (lambda (n)
    (+ x n)))

(defun main ()
  (let ((f (adder 10)))
    (call_user_func f 5)))

turns into something like:

class Closure1 {
    function __construct(&$x) {
        // Variable capture.
        $this->x = &$x;
    }

    function __invoke($n) {
        // lambda body.
        return $this->x + $n;
    }
}

function adder($x) {
    // Create the closure, passing the variables to be captured to the constructor,
    // and returns a value that, when called, calls the closures' "__invoke" method.
    return Array(new Closure1($x), "__invoke");
}

function main() {
    $f = adder(10);
    return call_user_func($f, 5);
}

And so, PHP and its references surprise us positively (which is a surprise in itself).

By the way, the effects mechanism is used here for two things: (1) emitting the class before the function containing the lambda is done by propagating an (EmitBeforeToplevel class-definition) effect; (2) each reference to a variable external to the lambda generates a (CapturedVar x) effect; these effects are collected by the function responsible for translating lambda to find out which attributes must be initialized in the class and which arguments must be passed to the constructor. When I came up with the effects idea I hadn't thought about all those applications, so it much gladdened me to find out I could use the mechanism for those things too.

Name clashes

In PHP, local variables have the scope of the entire function where they are created, not just the block where they were declared. As a consequence, in code like:

(let ((x 23))
  (echo "x here is 23: " x "\n")
  (let ((x 42))
    (echo "x here inside is 42: " x "\n"))
  (echo "x out here still is 23: " x "\n"))

we cannot use the same name for both x variables in the translation, because the innermost definition of x would overwrite the outermost one. The solution is to rename one of the (or both) variables. Ideally we should perform the minimum number of renames possible, to make it easier to read and debug the resulting code. However, the current implementation simply renames all variables (adding a _number_ prefix), since testing when a variable must be renamed is not very simple. This decision is non-local: even if there is no visible declaration of a variable x at the point where a (let ((x 23)) ...) occurs, it is still necessary to rename x if at some later point in the function a global variable x is referenced.

The renaming / temporary name generation algorithm assumes that names beginning with _number_ are reserved to the compiler. I think this is not a problem in practice. (In the case of local variables, a variable _42_ would be renamed to something like _1__42_ anyway.) A more serious problem of this approach is at the global scope, in particular in the names of generated classes which implemente closures (e.g., _1_Closure), because those names may conflict with closures created in other files (e.g., when the translation results of multiple files are included into a single PHP program). Perhaps a solution is to include the file name in the name of the class, or to compute a hash from the closure code (but this would still cause conflicts if an identical lambda appears in another file), or something. I don't know. It would also be nice if the class name were descriptive enough to indicate where the definition came from in the source code (e.g., _1_Closure_file_function_line). [Side remark: namespaces don't exist in PHP 5.2.]

A more subtle variable conflict occurs when a let is executed multiple times and a lambda captures a variable defined by the let. For example, supposing the existence of a while construction:

(let ((array-of-lambdas (array))
      (i 0))
  (while (< i 5)
    (let ((n 0))
      (array_push array-of-lambdas
                  (lambda ()
                    (set! n (+ n 1))
                    (echo n))))
    (set! i (+ i 1))))

This would be translated to:

Class _1_Closure {
    function __construct(&$n) {
        $this->n = &$n;
    }

    function __invoke() {
        $this->n = $this->n + 1;
        echo $this->n;
    }
}

$array_of_lambdas = Array();
$i = 0;
while ($i < 5) {
    $n = 0;
    array_push($array_of_lambdas,
               Array(new _1_Closure($n), "__invoke"));
    $i = $i + 1;
}

The problem is that all iterations of the loop use the same $n variable, which is passed by reference to the closure constructor; the correct would be for each iteration to capture a different $n. The solution is to emit an unset($n) at the end of the while body, so that each iteration would create a new variable, but I haven't implemented this yet.

Pretty-printed PHP

One of the goals of the project is to emit readable PHP code, and this involves emitting properly indented code. After some false starts (in the initial version, the translation functions emitted PHP code strings directly, and my original idea was to use some special ASCII characters to indicate "increase indentation" and "decrease indentation" when printing, but I realized that I could not choose any characters for that because any character can appear in a string; moreover, mixing code generation and formatting questions was becoming rather ugly), I decided to make the translation function emit structures representing PHP abastract syntax trees (ASTs). After translation, the trees are passed to a print-php function, which takes care of the gory details of printing the code with line breaks, indentation, spaces and parentheses at the proper places. Separation of concerns FTW.

The future

As this post turned quite long, and I should get some sleep, we'll finish here. In a future post, I intend to talk about some features that are still missing, such as classes, method calls and other stuff, as well as trickier design decisions (which were the initial goal of this post, but anyway). If you are interested, you can look at the code on GitHub.

1 comentário

Compiling an LLVM pass against Debian's pre-packaged LLVM

2015-08-21 11:46 -0300. Tags: comp, prog, llvm, in-english

So, I'm writing an LLVM pass for my Master's. Until now, I had been compiling LLVM myself and compiling the pass within LLVM's source tree, following more-or-less the standard instructions. This works fine in my main machine, which has plenty of memory for compiling LLVM, but not in my netbook, which has only 1GB of RAM, which is not nearly enough to compile and link Clang/LLVM.

Fortunately, it is possible (and easy) to compile a pass against the precompiled Debian LLVM packages. For that you'll need the package llvm-dev, which depends on llvm-3.5-dev. (Debian is still using Clang/LLVM 3.5; llvm.org/apt has more recent packages, if you happen to need them.)

Makefile

With the packages installed, all you have to do is adjust your project's Makefile to use the installed headers. (Alternatively, you could probably use CMake to compile your module, just like you can do with an LLVM you compiled yourself, but I got it working with a Makefile and right now I'm more interested in finishing my Master's than figuring out build systems.)

Some additional -D (macro definition) flags also seem to be required for the LLVM headers to work:

CXX := c++ -fPIC -Wall -W -std=c++11 -g \
           -D_DEBUG -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \
           -I/usr/include/llvm-3.5/ -I/usr/include/llvm-c-3.5/

Then, you have to write the rules to build your pass, which should be compiled to a shared library. My project's rules look like this:

LLVMTyr.so: Tyr.cpp.o
	$(CXX) -shared -o LLVMTyr.so Tyr.cpp.o

Tyr.cpp.o: Tyr.cpp
	$(CXX) Tyr.cpp -c -o Tyr.cpp.o

If compilation succeeds, you can run your pass by running LLVM's opt with the option -load ./YourLibrary.so. For instance:

# Generate an LLVM IR file with Clang.
clang -S -emit-llvm test.c -o test.ll

# Use it as input to your pass.
opt -load ./LLVMTyr.so (your pass' options) test.ll

3 comentários

Criando apresentações de slides em LaTeX/Beamer usando o Org mode

2015-08-19 00:27 -0300. Tags: comp, editor, emacs, latex

Já pensou em poder escrever seus slides assim:

* Recovering memory safety

- How can we recover memory safety in C programs?
- Traditional solution: add metadata to allow checking
- This has a number of drawbacks:
  - It *changes memory representation* of objects
    - requires recompilation of everything (external libraries, OS syscalls)
  - C pointers can point to any part of an object
    - No simple/cheap way to find metadata from an arbitrary pointer
    - Pointers themselves must carry bounds, \\
      or separate data structure must be looked up
    - Changes representation and/or is expensive
- But there is another way...

E eles ficarem assim?

[Slide produzido pelo backend de exportação para Beamer do Org mode]

Pois isso é possível usando o backend de exportação do Org mode do Emacs para Beamer, um pacote LaTeX para criação de slides.

Ingredientes

Você vai precisar de:

Org mode

Explicar o que é o Org mode é um tanto quanto complicado, porque ele faz uma porção de coisas. Segundo o site do projeto, "Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system". Se você nunca usou o Emacs, um modo basicamente define um conjunto de funções, keybindings e comportamentos para edição de um certo tipo de arquivo. Por exemplo, existe um c-mode, html-mode, etc. O Org mode é bem mais mágico que isso, mas para o objetivo deste post, o Org mode é basicamente um modo para editar arquivos em um formato de plain-text estruturado (a la Markdown). O Org mode permite exportar esses documentos para diversos formatos usando uma variedade de backends de exportação, entre eles o beamer.

O backend beamer não é carregado por padrão. Você pode configurar quais backends são carregados por padrão executando M-x customize-variable RET org-export-backends RET (i.e., tecle Meta+X (i.e., Alt+X), digite customize-variable (usando TAB para completar, se desejar), dê Enter, digite org-export-backends, e dê Enter novamente). Selecione a checkbox beamer e clique em "Apply and save". Tecle q para sair da janela de Customize.

Para criar um documento, crie um arquivo com a extensão .org (e.g., tecle C-x C-f exemplo.org RET (i.e., tecle Ctrl+X, Ctrl+F, digite o nome do arquivo e dê Enter)). O Emacs deverá entrar no modo Org automaticamente.

Para ter uma idéia do markup utilizado, dê uma olhada no arquivo de exemplo linkado no começo do post. Basicamente:

Exportando o arquivo

Para exportar, tecle C-c C-e. Isso abrirá o "Org Export Dispatcher", um painel com dúzias de opções de exportação. Tecle l ("Export to LaTeX"), e depois P ("As a PDF file (Beamer)"). Alternativamente, você pode selecionar B ao invés de P, o que coloca a saída em LaTeX em um novo buffer, ao invés de gerar o PDF diretamente. Se ocorrer algum erro durante a exportação, você pode ver a saída do pdflatex no buffer *Org PDF LaTeX Output* (você pode ir para o buffer usando o menu "Buffers" na interface gráfics, ou teclando C-x b e digitando o nome do buffer (usando TAB para completar se desejado)).

Resumindo: C-c C-e l P (i.e., Ctrl+C, Ctrl+E, letra L, Shift+P).

Miscelânea

Por padrão, quando um arquivo Org é aberto, ele é mostrado com as seções colapsadas. Use Shift+TAB para expandir (ou re-colapsar) todos os títulos, ou TAB sobre um título para expandir aquele título específico.

Mais informações

Eu sou novo tanto no uso do Org mode quanto do Beamer, então não tenho muito mais o que dizer no momento. Mais informações podem ser encontradas no manual do Org mode dentro do próprio Emacs (C-h i, procure por Org mode) e no site do Org mode.

Customizações na aparência da apresentação são realizadas primariamente incluindo os comandos LaTeX apropriados usando linhas #+LATEX_HEADER. Para mais informações, você pode procurar diretamente por informação sobre o Beamer, ao invés de especificamente sobre o Org Mode.

Se você nunca usou o Emacs, pode querer dar uma olhada no How to Learn Emacs: A Hand-drawn One-pager for Beginners.

3 comentários

Mail, mail, mail

2015-08-10 23:13 -0300. Tags: comp, rant, life

Como é o causo de quando em quando por aqui (e provavelmente mais quando do que se me apercebe), este post tem o objetivo único e exclusivo de reclamar da vida (com uma e outra informação útil solta, talvez, e meramente incidental). Com este anteaviso, prossigo o relato.

Até agora, o meu cliente de e-mail favorito era o Claws Mail. Agora parando para pensar, faz pelo menos uns 10 anos que eu uso o Claws Mail (quando eu comecei a usá-lo, ele ainda se chamava Sylpheed-Claws), e durante a maior parte desse tempo, eu fui um cliente satisfeito.

Eu sempre preferi manter cópia dos e-mails localmente, então eu baixava os e-mails via POP. (Além disso, no 3G era simplesmente inviável usar IMAP.) Em algum momento no ano passado, o INF resolveu defasar o POP em favor do IMAP. Durante um certo período, o POP simplesmente ficou fora do ar (desconfio que isso não tenha sido intencional). Além disso, o sistema anti-spam, que anteriormente marcava as mensagens como spam adicionando [***SPAM***] ao subject, passou a mover as mensagens para uma pasta Junk, que não vem pelo POP. Como é da natureza dos anti-spams marcar coisas como e-mails de confirmação de inscrição como spam, ficar sem ver essa pasta não era factível. Conclusão: tive que migrar para o IMAP.

Turned out, entretanto, que o suporte ao Claws a manter as pastas de IMAP sempre sincronizadas para uso offline era menos que excelente (e acho que nessa época eu ainda usava 3G), então usar o IMAP da INF diretamente não era viável. Além disso, nesse meio tempo eu comecei a ler e-mail pelo celular, e ter as contas não-sincronizadas no celular e no PC estava meio desagradável. A solução que eu encontrei então foi usar um programinha chamado OfflineIMAP, que permite manter uma mailbox local sincronizada com um servidor IMAP. Diferentemente dos fetchmails da vida, a sincronização do OfflineIMAP é bidirecional: mudanças em qualquer uma das pontas são propagadas para a outra. O problema é que o OfflineIMAP só suporta mailboxes no formato Maildir, e o Claws só suporta mailboxes no formato MH. A solução comumente adotada nessa situação é instalar um servidor IMAP local (eu usei o Dovecot), fazer o OfflineIMAP sincronizar o servidor IMAP remoto com o servidor IMAP local (ao invés de uma pasta Maildir), e apontar o cliente de e-mail para o IMAP local. Deu um trabalhinho, mas consegui.

Um inconveniente disso é que se você quiser atualizar os e-mails manualmente ao invés de esperar o próximo momento de atualização, é necessário mandar um sinal para o processo (pkill -USR1 offlineimap). Outro inconveniente é que o OfflineIMAP não consegue identificar que uma mensagem foi movida para outra pasta; na sincronização, ele deleta a mensagem original na outra ponta e faz upload da mensagem para a pasta nova. A primeira conseqüência disso é que não é possível mover coisas para a pasta Junk localmente, porque o IMAP da INF não aceita fazer uploads de mensagens novas para a pasta Junk, apenas que mensagens de outras pastas sejam movidas para ela. A outra conseqüência é que e-mails grandes movidos localmente precisam ser uploadeados novamente, o que é lento e sujeito ao famoso trancaço da INF, uma doença que afeta todos os serviços da INF (IMAP, POP, HTTP, SSH, you name it) desde absolutamente sempre, em que o servidor da INF esquece da conexão e o upload/download simplesmente trava. Baixar os e-mails por POP via 3G era algo que quase sempre exigia múltiplas tentativas (e como POP não suporta "resumar" o download de uma mensagem, os e-mails de 5MB da lista da graduação com PDFs e fotos de produtos à venda eram algo especialmente lamentável, em particular porque normalmente eu não tinha o menor interesse nos e-mails de 5MB em questão). Bons tempos. Mas o problema ainda persiste, em escala menor, com os uploads desnecessários de mensagens de 5MB do OfflineIMAP. Mas ok, no geral, tudo funciona mais ou menos bem.

O problema é que agora o Claws Mail não estava mais notificando a chegada de mensagens novas. As it happens, a idéia do Claws de o que é uma mensagem nova em uma conta IMAP depende da flag "Recent" mantida pelo servidor; essa flag diz se uma mensagem já foi de alguma forma vista por um cliente. Se uma mensagem não possui essa flag, o Claws não a considera como uma mensagem nova, mesmo que ela esteja não-lida. De certa forma faz sentido, porque se você já abriu a mailbox em um outro aparelho e viu que a mensagem estava lá (mesmo sem tê-la aberto), você já sabe que ela existe e não precisa ser notificado. O problema é que a flag Recent é de inteiro controle do servidor IMAP – ela é removida automaticamente quando uam mensagem é "vista" e um cliente IMAP simplesmente não tem como readicionar a flag –, e o OfflineIMAP, no processo de baixar as mensagens para o servidor local, "vê" a mensagem. Well, fuck.

Na verdade o problema no Claws é pior, porque o Claws não aplica filtros automaticamente a mensagens que não sejam novas. Ok, dá para fazer a filtragem diretamente no servidor, configurando os filtros pelo webmail do serviço. O problema é quando o filtro deveria mover a mensagem para fora da mailbox. Por exemplo, eu tinha uma regra que movia todas as mensagens da CONLANG Mailing List para uma mailbox local (onde eu mantenho todo o archive da lista). Além de não aplicar os filtros a mensagens vistas, o Claws também não aplica filtros a mensagens novas que não estejam na Inbox da mailbox. Isso quer dizer que se eu adiciono um filtro no webmail para mover as mensagens da CONLANG para uma pasta "Conlang", e um filtro local para mover as mensagens novas em "Conlang" para o archive local, o filtro local não funciona.

Eu fiquei convivendo com essa situação assim mesmo por um bom tempo. Há uns meses atrás, eu me irritei com o Claws e fui ver se achava um outro cliente de e-mail que me agradasse. O primeiro que eu tentei foi o Thunderbird, que na ocasião eu não gostei não lembro mais por que razões específicas. No processo de procurar um cliente de e-mail novo eu acabei lembrando do Gnus, que foi o motivo original pelo qual eu acabei começando a usar o Emacs. No fim, eu resolvi parcialmente o meu problema com o Claws comentando fora o teste que limitava os filtros apenas à Inbox, larguei de mão o Gnus, e tudo ficou bem (com o efeito colateral de que eu comecei a usar o Emacs, so: yay). It soon turned out, entretanto, que a versão do RSSyl (o plugin leitor de feeds do Claws) que veio na versão que eu recompilei do Claws estava falhando em atualizar alguns feeds (na verdade ele parecia só estar perdendo um feed específico, o Little Strange World, mas eu não sabia quantos mais ele podia estar perdendo sem eu saber), então eu acabei voltando a usar o Claws pré-compilado do Debian e convivendo com seus defeitos. Era desagradável não ter barulhinho de notificação e ter que mover as mensagens da CONLANG para o archive local manualmente (eu acabei parando de mover), mas estava dando para engolir.

A última gota foi na última sexta-feira. Eu recebi um e-mail no começo da tarde com o subject "Matricula 2015/2 - URGENTE", me informando que eu não havia feito a matrícula nesse semestre e me pedindo para "nos informar uma posição a respeito". Isso me deixou bastante alarmado; eu achei que não era necessário me reinscrever na Dissertação de Mestrado, pois no Trabalho de Graduação na INF o camarada só se inscreve no semestre inicial e não é necessário fazer nada no semestre seguinte para continuar matriculado. Respondi explicando a situação e perguntando o que deveria fazer. Algum tempo depois, responderam-me da secretaria informando que eu deveria realizar a matrícula. A essa altura, entretanto, o período de matrícula já tinha encerrado há uma semana, então não era possível fazer isso pelo Portal do Aluno. Às 17h08 mandei um e-mail perguntando como eu deveria fazer a matrícula, e fui trabalhar no bendito código da minha dissertação. Turns out que às 17h09 tinha vindo uma resposta, perguntando em que disciplinas eu gostaria de me matricular, mas eu só vi isso por volta das 17h45, pois o Claws não notificou. A secretaria fecha às 17h30, o que significa que um problema que eu poderia ter resolvido no mesmo dia só seria resolvido na segunda (at best) por conta da tosquice do meu cliente de e-mail. Just kill me.

Eu passei o resto da sexta-feira tentando resolver o problema dos e-mails novos, e foi então que eu descobri o causo da flag Recent e o fato de que, como ela é controlada pelo servidor, não tem muito o que fazer (talvez haja alguma gambiarra, mas não encontrei). Resolvi então tentar usar o Claws direto com o IMAP da INF e evitar o OfflineIMAP, já que agora eu não uso mais o 3G. O que aconteceu foi algo ainda mais bizarro: algumas mensagens estavam sendo remarcadas como novas depois de já terem sido vistas (mas não lidas). Além disso, as mensagens que estavam sendo filtradas no servidor não estavam sendo marcadas como novas (não sei se porque o Claws considerou que mensagens novas fora da Inbox não eram dignas de serem chamadas de novas, ou se porque o sistema de filtragem no servidor acaba marcando as mensagens como "vistas" como efeito colateral, ou se por algum outro motivo). A essa altura eu enchi o saco do Claws e fui catar um cliente de e-mail novo.

(No processo de tentar figure out os problemas eu descobri uma porção de coisas legais, entretanto; a principal delas é que você pode usar o comando:

openssl s_client -crlf -connect servidor:porta

como uma espécie de netcat para abrir uma conexão SSL, e dar comandos de IMAP via terminal. Por sinal, eu achei bem mais conveniente fazer isso de dentro do Shell mode do Emacs do que por um emulador de terminal. But I digress.)

Thunderbird

Eu resolvi dar uma segunda chance para o Thunderbird/Icedove. And boy is it terrible. Para começar, o Thunderbird não faz a distinção entre mensagens novas e não-lidas que o Claws (quando funciona) faz; não existe um comando para saltar para a próxima mensagem nova, apenas para a próxima não-lida, o que é um problema porque eu tenho o costume de deixar mensagens que requerem atenção futura como não-lidas e não quero desmarcá-las sem querer enquanto leio mensagens novas. By the way, o atalho para ir para a próxima não-lida (N) não funciona quando você não está com uma pasta aberta (se você está na paginazinha da conta, por exemplo).

"Ah, mas você pode marcar as mensagens como 'starred' ao invés de marcar como não-lidas", right? A diferença é que uma pasta com uma mensagem não-lida aparece em negrito e com o número de mensagens não lidas ao lado do nome, o que me chama atenção para o fato de que há alguma pendência ali. Uma pasta com mensagens starred não mostra nenhuma indicação de que contém mensagens starred; o Claws pelo menos mostra um "✓" nos ícones das pastas que contêm mensagens marcadas, o que é bem pouco perceptível no meu tema com fundo preto, mas é melhor que nada. Por falar em tema, diversos elementos da interface do Thunderbird ignoram o meu tema e mostram elementos com cores incompatíveis, seja meramente destoantes do restante, seja com o texto da cor certa e o fundo da cor errada (e.g., branco no fundo cinza). Tudo bem que o meu tema é um arquivo gtkrc hacked together de qualquer jeito cinco anos atrás que eu basicamente nunca mais mexi, mas a maioria dos programas funciona relativamente bem com ele. Anyway.

O programa simplesmente não foi pensado para ser usado com o teclado. Coisas como alternar entre headers completos e simples não possuem um atalho de teclado; para adicionar um atalho, é necessário instalar um addon (!). O atalho do "quick filter" (o equivalente do search do Claws) é Ctrl+Shift+K; no Claws, é /. Aliás, o termo de pesquisa se perde quando se troca de pasta, e não tem histórico; no Claws o termo fica até a caixa ser fechada (com Esc, por exemplo), e ↑/↓ percorrem os últimos termos usados. Navegar pela lista de mensagens com as setas automaticamente seleciona a mensagem para visualização (o que a marca como lida, obviamente); no Claws, isso é configurável. O Claws permite habilitar o comportamento que era o default em aplicativos GTK 1, em que se pode parar sobre um comando de um menu qualquer e pressionar uma tecla para associá-la como atalho para aquele comando; assim, a maioria dos keybindings pode ser facilmente customizada. Alguns elementos da interface do Claws parecem ter sido projetados especificamente para funcionar bem com isso; por exemplo, o menu "Edit > Advanced" da janela de composição de mensagens contém entradas que parecem não fazer muito sentido em um menu, como "Move to beginning of line" e "Delete a word backward", até que você se dá conta de que eles estão ali para que você possa associar as teclas de atalho de sua preferência a esses comandos. Esse é o tipo de atenção a detalhes que torna o Claws totalmente excelente. Eu na verdade fiquei meio chocado com o fato de que um programa da idade do Thunderbird tenha tantos gaps de funcionalidade/usabilidade. Talvez o usuário no público-alvo do Thunderbird tenha expectativas diferentes da interface. I don't know.

On the bright side, o Thunderbird tem um search global por todas as pastas, que o Claws não tem. Além disso, a sincronização do IMAP para uso offline funciona direito. Finalmente, o Enigmail (add-on do Thunderbird para uso de PGP) parece ser fácil de usar. Acho que isso resume o que eu tenho de bom para dizer sobre o Thunderbird.

Ah, right, o problema das notificações: por algum motivo, o Thunderbird também não está mostrando notificações quando chegam mensagens novas (o que me dá alguma esperança de que o problema seja elsewhere e eu consiga corrigi-lo e voltar a usar o Claws), mas pelo menos eu achei um addon que permite adicionar um ícone à systray com o número de mensagens não-lidas em pastas selecionadas, o que resolve parcialmente meu problema. (O Claws também tem um plugin para adicionar um ícone à systray, mas ao invés de mostrar um número ele mostra um ícone não-customizável e horrível de perceber se está indicando a presença de mensagens não-lidas ou não.) A outra "vantagem" do Thunderbird é que eu achei ele tão ruim de usar que eu provavelmente vou continuar procurando outra solução ao invés de me acomodar com os problemas como eu fiz com o Claws por um ano e tanto.

Gnus

And then there is Gnus. Como dito anteriormente, o Gnus foi o que me levou a mexer no Emacs de novo, mas eu acabei largando ele de mão, em parte porque eu achei que tinha resolvido meu problema com o Claws, em parte porque configurar o Gnus é um tanto quanto não-trivial (e aprender a usá-lo também), especialmente para mim que não tinha praticamente nenhuma experiência com o Emacs então. Agora que eu já estou há mais de dois meses usando o Emacs (estou-vos devendo um post sobre isso, por sinal) e já estou relativamente habituado à criatura, acho que está na hora de dar uma segunda chance ao Gnus.

O Gnus é bastante bizarro, na verdade. Ele faz uma porção de coisas que fazem sentido em um news-reader, mas not so much em um mail-reader. Por exemplo, por padrão ele oculta "grupos" (pastas) que não têm mensagens não-lidas, e oculta as mensagens lidas em um grupo. Eu passei o último sábado quebrando a cabeça tentando configurar o dito cujo, e turns out que o problema que eu passei mais tempo tentando resolver era um bug no Gnus (que aparentemente eu vou ter que reportar uma hora, pois não achei nenhuma menção dele na Internet). Fazer ele funcionar com duas contas de e-mail, e escolher a conta apropriada para mandar mensagens dependendo do contexto, salvar a mensagem enviada na pasta apropriada, etc., é algo que eu tenho que olhar com calma como se faz. Pelo menos o conceito do Gnus de uma mensagem vista aparentemente é independente de o que o IMAP acha que foi visto, o que já é um bom começo. Apesar de ser complicado e cheio de bizarrices, eu tenho a sensação de que o Gnus é suficientemente hackable/customizável para eu conseguir fazê-lo fazer o que eu quero (de certa forma isso é meio verdade sobre o Emacs como um todo). Se eu for bem-sucedido, provavelmente vou postar um tutorial de configuração por aqui.

EOF

Well, esse post ficou bem maior do que eu esperava. Mas agora que eu já escrevi, vou publicar e eras isso. Já que você chegou até aqui, leve umas músicas como recompensa.

3 comentários

FISL 16

2015-07-11 23:27 -0300. Tags: comp, life, freedom

Nos últimos quatro dias ocorreu o 16º Fórum Internacional do Software Livre. Minha proposta de palestra sobre o lash não foi aceita (o que por um lado foi bom, porque o projeto anda meio dormente devido a obrigações mestrariosas e má administração temporal), mas eu assisti uma porção de palestras, a maioria das quais foram bem boas. Também encontrei o Marcus Aurelius por lá, que trabalhou como voluntário na tradução do site do FISL para o esperanto, e troquei umas palavras com seu Lucas Zawacki, que estava no espaço do Dumont Hackerspace (e que ganhou a Hackathon EBC/FISL 16 com o aplicativo Pautaí, por sinal; congrats!).

Eis um resumo das palestras que eu assisti por lá. O site do FISL contém a programação completa, com link para os vídeos da maioria das palestras.

Dia 1

Email criptografado: usando GPG e icedove para criptografar sua correspondência, por Felipe Cabral

A idéia era que fosse uma oficina, mas acabou sendo mais uma palestra sobre conceitos básicos de PGP. Também foi visto um programinha chamado Seahorse (pacote seahorse no Debian) para gerar e administrar chaves de PGP e afins. Eu fui na palestra achando que ia aprender alguma coisa que me ajudasse a fazer o PGP funcionar no Claws Mail. Turns out que no Thunderbird/Icedove, tudo o que é necessário para fazer o PGP funcionar é instalar o add-on Enigmail (pacote enigmail no Debian), seguir os passos de configuração que serão apresentados depois que o Enigmail é instalado, e ser feliz. Me deu até vontade de experimentar o Thunderbird de novo, mas tem que ver isso aí. Meanwhile, continuo unenlightened quanto ao Claws.

Half my life with Perl, por Randal Schwartz

O autor do Programming Perl (a.k.a. Camel Book), entre outros, e atual host do podcast FLOSS Weekly, conta sua vida e seu envolvimento com o Perl. Foi uma palestra bem interessante, mesmo eu não conhecendo grandes coisas do Perl e sua comunidade.

Negócios em Software Livre, isso existe?, por Vagner Fonseca

Uma palestra sobre como ganhar dinheiro vendendo serviços baseados em software livre para empresas, tais como suporte, customização de programas livres de acordo com as necessidades da empresa, soluções de monitoramento de rede e de uso de recursos, entre outros. Também foram discutidos tipos de contrato (por projeto, por hora, contrato de suporte) e quando cada tipo vale a pena (basicamente, cobrar por projeto só vale a pena para coisas bem simples e punctuais, caso contrário corre-se o risco de algo demorar muito mais do que o previsto e o cidadão acabar efetivamente pagando para trabalhar; em contrato de suporte se cobra menos do que em um contrato por hora, mas tem-se a estabilidade e se consome um tempo mais limitado por semana em uma empresa, então é possível atender mais de uma ao mesmo tempo), entre outras coisas.

"Enemy spotted - Applying infovis at security field", por Felipe Afonso Espósito

Uma palestra sobre visualização de dados e como isso pode ser usado com dados de segurança. Não tenho muito mais o que comentar.

Let's Encrypt: Uma Autoridade Certificadora Gratuita e Automatizada, por Seth Schoen

Seth Schoen, membro da EFF, falou (em português!) sobre o andamento do Let's Encrypt, uma iniciativa da EFF, Mozilla, University of Michigan e outros para criar uma autoridade certificadora e um mecanismo para obtenção e validação automática e gratuita de certificados digitais, e instalação automática em servidores web, eliminando uma barreira que existe atualmente para se usar HTTPS. A data prevista para o serviço ser disponibilizado para o público é 14 de setembro deste ano.

Eu pretendia assistir a APIs em Tempo Real Usando Websockets em PHP, mas a sala lotou e eu não cheguei a tempo. Ao invés disso, eu e o Marcus ficamos tentando entender as entranhas do cua-mode do Emacs, inter alia.

Dia 2

Encontro de Hackers GNU

Alexandre Oliva (FSF Latin America), Felipe Sanches, e Deborah Anne Nicholson (Open Invention Network, MediaGoblin) falaram sobre uma porção de projetos relacionados com liberdade digital. Entre eles, o Twister, uma plataforma peer-to-peer de microblogging, e MediaGoblin, uma plataforma descentralizada de compartilhamento de mídia. Também foram mencionadas algumas idéias de projetos que seriam interessantes de criar, como uma plataforma peer-to-peer para distribuição de código (eu mencionei que já existe um projeto nesse sentido).

Outra idéia interessante que o Alexandre Oliva mencionou é que os ambientes computacionais modernos são pouco programáveis (sounds weirdly familiar), e que seria interessante criar uma biblioteca que facilitasse para o usuário descobrir que funções o programa chama quando se clica em algum botão ou menu e tornasse esses programas programáveis. O conceito é parecido com o que eu vejo como ideal de ambiente computacional, mas eu nunca tinha pensando em pôr essa funcionalidade em uma biblioteca, ao invés de algo mais fundamental. É uma abordagem interessante de se pensar.

O Felipe Sanches também comentou a questão de firmware aberto, as dificuldades envolvidas em reverse-engineering de firmware e hardware, e a necessidade de incentivar o compartilhamento de informação sobre técnicas de engenharia reversa.

Programação Orientada a Objetos em C puro: o caso do htop, por Hisham Muhammad

O autor do htop falou sobre as técnicas de programação que usou no desenvolvimento desse programa, em particular o uso de orientação a objetos em C. Eu já sabia um pouco sobre o assunto, mas a palestra valeu a pena mesmo assim. Mais para o final, o Hisham falou sobre o uso de collections para gerenciar "ownership" de ponteiros (alocação e liberação de memória) em C, e como isso torna a gerência manual de memória do C menos horrível de se usar. Por fim, ele falou sobre o dit, um editor de texto que ele escreveu reusando componentes do htop.

Assinatura digital e o padrão ICP-Brasil, por Paulo Cesar Barbosa Fernandes

Uma palestra sobre o padrão brasileiro de assinaturas digitais, seus aspectos legais, e algumas informações gerais sobre o uso de assinaturas digitais. Talvez a coisa mais importante que eu aprendi na palestra é que uma assinatura digital obtida adequadamente seguindo o padrão ICP-Brasil tem o valor legal de uma assinatura de papel (embora ainda haja legislação a ser atualizada para levar isso em conta).

Desvendando o IPv6: Tecnologia indispensável para o Futuro da Internet, por Lucenildo Lins de Aquino Júnior

Nada muito aprofundado tecnicamente, mas foi uma boa palestra. O mais surprising aqui foi saber que uma boa parte do tráfego da Internet já é em IPv6, inclusive no Brasil (pelo menos em São Paulo), mas não lembro mais os números. Até me deu alguma esperança de que teremos o bendito IPv6 em alguns anos.

What's new in systemd in 2015, and what's coming in 2016, por Lennart Poettering

Exatamente o que o título diz. Foram mencionadas funcionalidades de configuração de rede, resolução de DNS, containers, e todas essas coisas que nós nos perguntamos se deviam mesmo fazer parte do systemd, mas isso fica para outra discussão.

Finding a Great Project to Work On, or Great People to Work on Your Project, por Deborah Anne Nicholson

Uma palestra muito boa sobre, entre outras coisas, como fazer com que um projeto seja "welcoming" a novos colaboradores, especialmente não-desenvolvedores (pessoas envolvidas com tradução, documentação, divulgação, arte, etc.). Alguns dos tópicos mencionados são coisas que deveriam ser senso comum, mas infelizmente não são, tais como ter uma descrição de o que é o projeto na página inicial, links para mailing lists, FAQs, informação de contato, etc. Outro ponto mencionado é que se você como desenvolvedor não tem grandes skills comunicativos, encontre uma pessoa para fazer esse papel no seu projeto. Also, incentive e recompense os esforços dos colaboradores. Provavelmente é melhor assistir o vídeo do que eu tentar explicar (talvez eu tenha skills comunicativos sub-ótimos).

Acho que esse foi o melhor dia do FISL para mim.

Dia 3

Ocorreu uma miniDebConf (conferência sobre o Debian) durante esse dia na sala 41D.

Debian: o que é, e como funciona, por Antonio Terceiro

Uma palestra introdutória sobre o Debian, que no entanto me ensinou sobre uma porção de recursos online do Debian que eu não conhecia, tais como o blog Bits from Debian, o Ultimate Debian Database (que é mais útil para desenvolvedores do Debian), um pastebin, a página com informações para se tornar um novo membro do Debian, páginas onde se pode navegar pelos fontes do Debian e fazer buscas textuais no código, e um security bug tracker.

Não sou programador, como posso ajudar o Projeto Debian?, por Luiz Guaraldo

Não lembro mais o que foi visto nessa.

Containers and systemd, por Lennart Poettering

Uma palestra sobre o suporte a containers do systemd. A palestra começou com "everyone knows what containers are", except I didn't, então embora as funcionalidades apresentadas tenham parecido interessantes, eu meio que fiquei boiando sobre como as coisas funcionam.

Empacotamento de software no Debian, por João Eriberto Mota Filho

Uma palestra muito boa sobre o processo de criar um pacote Debian, cobrindo as ferramentas utilizadas, o ambiente de empacotamento, os arquivos que se deve editar, etc. Se você pretende criar um .deb na vida, assista.

Dependências de pacotes de código fonte, por Thadeu Lima de Souza Cascardo

Essa palestra pode ser resumida a "pacotes de fonte possuem dependências". Foram vistos alguns conceitos como dependências de compilação vs. dependências de execução, mas nada do que eu esperava ver foi visto, tal como como baixar pacotes fonte e suas dependências e compilá-los usando as ferramentas do Debian. A palestra terminou com o palestrante debugando um script em Perl que ele escreveu para calcular as dependências recursivas de um pacote fonte, e eu fui embora fazer outras coisas pelo mundo.

Dia 4

Decidi almoçar em casa e só consegui chegar às 13h e pouco no FISL.

Javascript e as novidades nas funções em ES2015+, por Felipe Nascimento de Moura

Cheguei uns 10 minutos atrasado na palestra. Ficou bem pouco claro para mim o que eram coisas novas do JavaScript vs. coisas que já existem. Foram vistas features como generators (que o Firefox já suporta há mais de oito mil anos, mas agora eles vão entrar no EcmaScript oficial, com uma sintaxe levemente diferente), execução assíncrona, setInterval para executar ações periodicamente, "arrow functions" (uma sintaxe nova para funções anônimas, e que captura o valor de this), e outras coisas que ya no recuerdo más.

HTTP: passado, presente e futuro, por Luiz Fernando Rodrigues

Aprendi o suficiente para querer me informar melhor sobre o HTTP/2, but that's it.

Programming Efficiently, por Jon "Maddog" Hall

A palestra foi mais sobre a importância da eficiência em programação, e não sobre técnicas de programação eficiente. O Maddog também falou sobre o fato de que muita gente sai de uma escola/faculdade sem saber como funciona o hardware, que o Raspberry Pi foi criado para ser uma máquina "hackable" e boa para o aprendizado, e que ele usou alguns Banana Pi para montar um cluster pequeno, barato e com bom poder computacional que pode ser usado para ensinar high-performance computing e afins. A palestra foi bem boa, embora não fosse sobre o que eu pensei.

How much of your computer is non-free, and how worried should you be?, por Matthew Garrett

Palestra sobre o fato de que nossos dispositivos contêm bem mais processadores do que normalmente a gente pensa, normalmente rodando software proprietário que não temos nem como ver nem como modificar. Exemplos particularmente alarmantes são HDs (com uma menção de uma galera que conseguiu fazer um HD bootar um kernel Linux) e SD cards. Worse still, muitos desses dispositivos só aceitam firmwares assinados pelo fabricante, usualmente encriptados, o que nos impede de analisar e controlar o comportamento desses dispositivos. Recomendo assistir o vídeo.

Archlinux: Você no comando, por Israel Lopes dos Santos

Uma palestra introdutória sobre o Arch Linux, explicando a filosofia da distribuição, o gerenciador de pacotes, o fato de que criar um pacote para o Arch é relativamente fácil e que qualquer um pode submeter um pacote para o Arch User Repository (AUR). Na seção de perguntas, um cidadão resolveu criticar o uso de "Linux" ao invés de "GNU/Linux" de uma maneira indireta/irônica que o palestrante não entendeu. Foi bem desnecessário; ser indireto e irônico não ganha ninguém à causa, na minha humilde opinião.

That was it. 9/10 would go again.

3 comentários

Como usar o modelo LaTeX do INF/UFRGS para TCCs e afins

2015-06-26 01:16 -0300. Tags: comp, latex, academia

Salve! Como muita gente cai neste blog procurando pelos modelos LaTeX do INF/UFRGS, e eu parei de disponibilizar o meu "pack" em favor do repositório oficial, resolvi escrever um tutorial de como usar o modelo.

Instalando o LaTeX

GNU/Linux

Nas distribuições Debian, Ubuntu e afins, você pode instalar os pacotes do LaTeX com o comando:

sudo apt-get install texlive-latex-base texlive-lang-portuguese

Se você já possui o comando pdflatex, você já tem o LaTeX instalado e pode dispensar esse passo. Porém, é possível que você não tenha o pacote texlive-lang-portuguese, o que pode causar problemas como hifenação incorreta e strings na língua errada na capa do modelo e outras partes com texto pré-definido. Se isso acontecer, verifique se o pacote está instalado. Mesmo que o seu trabalho seja em inglês, o modelo pode não funcionar corretamente na ausência desse pacote.

Outros sistemas

É possível usar o LaTeX em outros sistemas operacionais, como o Windows, mas nunca fiz isso e não sei como é a experiência. Se alguém tiver alguma dica, deixe nos comentários.

ShareLaTeX

Uma outra opção é usar o ShareLaTeX.com, uma espécie de Google Docs para documentos LaTeX. A vantagem é que você não precisa instalar nada, e pode acessar de qualquer lugar. A desvantagem é que se o servidor do ShareLaTeX.com sair do ar você está ralado, então eu recomendo baixar regularmente o .zip com os arquivos para não correr esse risco.

Instalando o iiufrgs

O pacote iiufrgs, que contém os modelos do INF/UFRGS, fica disponível em um repositório do GitHub. Para baixá-lo, você pode clicar em Download ZIP na barra lateral da página, ou usar o comando git para clonar o repositório:

git clone https://github.com/schnorr/iiufrgs.git

A vantagem de usar o git é que você pode usar o comando git pull dentro do diretório do repositório para atualizá-lo.

Dentro do repositório, há um diretório inputs, que contém o pacote propriamente dito, e um diretório examples, que contém alguns documentos de exemplo.

Para usar o modelo, é necessário que os arquivos em inputs estejam no path do LaTeX. Há diversas maneiras de fazer isso:

That's it.

Preparando o documento

O diretório exemplos contém uma porção de (adivinhe só?) arquivos de exemplo para os diversos tipos de documentos:

Copie o arquivo apropriado para o diretório onde você vai colocar seu projeto LaTeX (que pode ser um diretório vazio qualquer que você criou, ou uma cópia do inputs caso tenha optado por não instalar o iiufrgs no local padrão, ou faça upload do arquivo de exemplo para o seu projeto no ShareLaTeX). Renomeie o arquivo se for do seu interesse.

Agora é necessário editar algumas partes do arquivo.

A linha \documentclass indica o tipo de documento a ser gerado. Ela tem a seguinte cara:

\documentclass[cic,tc]{iiufrgs}

Como você já copiou um documento de exemplo do tipo apropriado, a princípio você não tem que mexer nessa linha. Se seu documento for em inglês, adicione a opção english na linha:

\documentclass[cic,tc,english]{iiufrgs}

Há outras opções, que estão documentadas nos comentários do arquivo.

Como mencionado, não há um exemplo de Plano de Estudos e Pesquisa; para isso, use o arquivo ppgc-diss.tex e altere a opção diss para pep na linha \documentclass. Você provavelmente também vai querer apagar coisas como dedicatória e agradecimentos do documento.

As próximas coisas a alterar são a linha \title, que contém o título do trabalho, e \author, que contém o nome do autor. Essa linha tem o formato:

\author{último nome}{primeiro nome e nomes do meio}

A linha \advisor, que contém o nome do orientador, segue o mesmo formato, mas inclui também o título (Prof. Dr., por exemplo). Se houver um co-orientador, descomente a linha \coadvisor e preencha da mesma maneira.

Descomente a linha \location e preencha com a cidade e UF adequados (i.e.,

\location{Porto Alegre}{RS}

que por alguma razão não é o default nos arquivos de exemplo; go figure).

O documento possui uma série de linhas comentadas definindo comandos \nominataWhatever. Na segunda página do PDF gerado a partir do modelo, fica um quadrinho com os nomes dos responsáveis por diversos setores da universidade (reitor, bibliotecário-chefe, etc.). Confira no PDF se os nomes inclusos estão atualizados. Se algum não estiver correto, descomente a linha correspondente e substitua o nome. Note ainda que alguns títulos têm concordância de gênero (e.g., "Bibliotecário-chefe" vs. "Bibliotecária-chefe"), então lembre-se de ajustar a linha com o título do cargo também. Você pode descobrir quem é o bibliotecário/a-chefe atual no site da biblioteca do INF.

Mais adiante, há uma série de comandos \keyword; estas aparecem como palavras-chave do documento na página do resumo/abstract. Escolher as palavras-chave é uma arte esotérica e misteriosa; consulte seu orientador em caso de dúvida.

A próxima coisa a mudar é a "dedicatória", que costuma ser uma citação de sua preferência. Ela é opcional, mas é uma das partes mais divertidas de escrever a monografia anyway. Logo depois, vem a seção de agradecimentos, que também é opcional (or so they say).

Em seguida, vem o o bloco \begin{abstract}, onde vai o resumo na língua do documento. Não pode haver linhas em branco entre o fim do texto e o \end{abstract}; caso contrário, a compilação do documento dá um erro de "There's no line here to end". Go figure.

A seguir, vem o bloco \begin{englishabstract}{keyword1, keyword2, …}. Apesar do nome, o que vai nesse bloco é o resumo na outra língua: se a monografia for em português, aí vai o abstract em inglês; se a monografia for em inglês, aí vai o abstract em português. O segundo argumento do bloco é a lista de palavras-chave/keywords (as mesmas que você usou antes com os comandos \keyword, mas na outra língua). Também não pode haver linhas em branco antes do \end{englishabstract}.

A seguir, vem a lista de abreviaturas; confira as instruções nos comentários. Note, entretanto, que essa lista deveria estar em ordem alfabética, que eu saiba. Em seguida, há a lista de símbolos, que vem comentada por padrão, mas você pode descomentá-la e preenchê-la de maneira análoga se você usa algum símbolo matemático que requeira explicação no seu documento.

A seguir vêm os comandos \listoffigures, \listoftables e \tableofcontents, que geram automaticamente as respectivas listas. Você não precisa mexer nesses comandos.

Finalmente, vem o texto do documento propriamente dito. No documento de exemplo, há algumas instruções sobre citações, figuras e outros detalhes, que você pode (deve) ler, mas no final você deve apagar tudo desde o \chapter{Introdução} até logo antes do \bibliographystyle e substituir pelo conteúdo da sua monografia. Se você preferir criar arquivos separados para cada capítulo para se organizar, você pode usar o comando:

\input{arquivo.tex}

que é uma espécie de #include, que insere o conteúdo do arquivo especificado nesse ponto do documento. Você pode incluir arquivos em outros diretórios. Por exemplo, você pode criar um diretório capitulos e usar \input{capitulos/introducao.tex}.

A linha \bibliography{nome} diz o nome (sem a extensão) do arquivo onde estão suas referências bibliográficas (no formato BibTeX). Por exemplo, se a linha diz:

\bibliography{biblio}

então o LaTeX vai procurar as referências no arquivo biblio.bib.

Compilando o documento

Se você está usando o ShareLaTeX, tudo o que você tem que fazer é clicar no botão "faz". Há outras IDEs de edição de LaTeX com funcionalidades análogas. Se não é o seu caso, continue lendo.

O comando principal para compilação de documentos LaTeX é o pdflatex. A sintaxe básica é pdflatex arquivo.tex. Se a compilação for bem-sucedida, será gerado um arquivo.pdf com o resultado, bem como um arquivo.log com mensagens do LaTeX e outros arquivo.* usados internamente pelo LaTeX.

Se ocorrer um erro durante a compilação, por padrão o pdflatex cai em um prompt de depuração muito doido. Para sair do prompt, digite x e dê ENTER. Para evitar cair no prompt, use a opção -halt-on-error antes do nome do arquivo.

Outra opção útil é -file-line-error, que precede os erros com nome-do-arquivo:linha:. Alguns editores de texto, como Emacs e Vim, possuem funcionalidades para rodar um comando externo de compilação, coletar as mensagens de erro e saltar diretamente para a linha onde ocorreu o erro, desde que as mensagens de erro contenham a localização do erro em um formato reconhecido (que é o que essa opção faz).

O comando pdflatex realiza apenas uma passada pelo arquivo. Porém, coisas como gerar os números das páginas no índice e números de seção em referências requerem duas passadas pelo arquivo. Além disso, a geração de bibliografia requer a chamada do comando bibtex, e depois disso é necessário rodar o pdflatex novamente (mais duas vezes). O mais prático é escrever um Makefile para rodar todos esses comandos de uma vez. Crie um arquivo chamado Makefile com o seguinte conteúdo:

# As linhas 'export' só são necessárias se você optou por não instalar o
# iiufrgs em um local padrão e não colocar seu documento e os arquivos de
# 'inputs/' no mesmo diretório.
export TEXINPUTS = .:caminho-completo-do-diretório-inputs:
export BSTINPUTS = $(TEXINPUTS)
export BIBINPUTS = $(TEXINPUTS)

PDFLATEX = pdflatex -halt-on-error -file-line-error
FILENAME = nome-do-seu-documento-sem-a-extensão

all:
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex
	bibtex $(FILENAME)
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex

Note que as linhas indentadas devem ser precedidas de um caractere TAB, não de espaços. Note também que o bibtex recebe o nome do arquivo LaTeX (não o nome do arquivo de bibliografia), sem a extensão.

Feito isso, agora é só rodar make para compilar seu documento. No Vim, você pode usar o comando :make para compilar, e :copen para listar os erros encontrados. No Emacs, você pode usar M-x compile. (No latex-mode do Emacs também há um comando tex-compile, acessível via C-c C-c, que serve para compilar o documento, mas ele não vai usar o Makefile. Eu sou novo nestas terras do Emacs, então não sei bem como isso funciona. Também existe um modo mais avançado de edição de LaTeX chamado AUCTeX, mas não tenho experiência com ele.)

lastpage.sty

O iiufrgs usa um arquivo lastpage.sty para determinar o número da última página, que ele inclui no quadrinho CIP na segunda página do documento. Se você se deparou com o erro:

! LaTeX Error: File `lastpage.sty' not found.

você tem duas opções:

Bibliografia

A maneira normal de trabalhar com referências bibliográficas no LaTeX é através do BibTeX. O iiufrgs vem com um arquivo BibTeX de exemplo, biblio.bib, que aparentemente saiu da coleção pessoal de alguém e foi gerado pelo JabRef (pacote jabref no Debian/Ubuntu/etc), um gerenciador gráfico de bibliografias em BibTeX. Eu nunca usei o JabRef, mas se você gosta de ferramentas gráficas, pode querer experimentá-lo. Caso contrário, continue lendo.

O arquivo .bib é basicamente uma seqüência de entradas bibliográficas. Uma entrada tem mais ou menos essa cara:

@tipo-de-publicação{label-da-sua-escolha,
  title = {Título da Publicação},
  author = {Turing, Alan and Church, Alonzo and Gödel, Kurt},
  year = {2101},
  booktitle = {Proceedings of the 42nd Intergalactic Conference on Computability Theory},
  pages = {123--134},
  publisher = {ACM}
}

tipo-da-publicação é algo como article (artigo em journal), inproceedings (artigo de conferência), book (livro inteiro), incollection (capítulo de livro, quando o autor do capítulo é diferente do autor/editor do livro), electronic (publicação na Internet), ou uma infinidade de outras possibilidades. O tipo de publicação define os campos que podem aparecer dentro da entrada e o formato como ela aparece na bibliografia do PDF gerado.

label-da-sua-escolha é a label que você vai usar com os comandos \cite e afins.

BibTeX é um mundo à parte e eu não vou tentar explicar tudo o que há para explicar aqui; para mais informações, procure por "BibTeX" na Internet. Deixo, entretanto, algumas dicas:

Fim do conto

Acho que era isso. Em um post futuro, pode ser que eu escreva uma introduçãozinha aos comandos de formatação do LaTeX, mas com o que aparece no arquivo de exemplo já dá para se virar um pouco. Você pode dar uma olhada nos outros posts com a tag latex.

Dúvidas, sugestões e correções podem ser deixadas nos comentários.

2 comentários

Trocando de janela pelo nome

2015-06-23 02:50 -0300. Tags: comp, unix

Por uma série de acidentes enquanto experimentava window managers hoje,

  1. Eu me dei conta de que seria bem conveniente poder trocar de janela digitando uma parte do título ao invés de procurar na barra de tarefas ou dar vinte Alt+Tabs até encontrar a janela. (Eu me dei conta disso quando, depois de não conseguir achar a janela que eu queria no i3, a minha primeira reação foi querer dar C-x b (o comando switch-buffer do Emacs) e digitar o nome da janela.)
  2. Eu descobri ferramentas apropriadas para tornar isso possível.

[Screenshot do programa 'dmenu']

Você vai precisar de:

Com isso, podemos escrever um pequeno script para apresentar um menu e selecionar a janela escolhida.

O script

#!/bin/bash

option="$(
    wmctrl -xl |
        #         \1       \2       \3       \4       \5
        #         win-id   desktop  class    hostname title
        sed -r 's|([^ ]* +)([^ ]* +)([^ ]* +)([^ ]* +)(.*)|\1 \3 \5|' |
        sort -k 3 |
        dmenu -i -l 10
    )" || exit 1

wmctrl -ia "${option%% *}"

wmctrl -l lista as janelas existentes. A opção -x inclui a classe da janela na listagem. O sed não é estritamente necessário, mas deixa a lista menos poluída removendo campos desnecessários; você pode alterar essa linha para escolher os campos. O ID da janela é meio irrelevante para o usuário, mas precisamos dele para poder passá-lo ao wmctrl para ativar a janela.

sort -k 3 ordena o menu pelo título da janela. Você pode comentar essa linha fora se não quiser ordenar a lista, ou mudar os parâmetros para obter uma ordem diferente (e.g., sort -k 2 para ordenar pela classe).

Quanto ao dmenu, a opção -i faz com que ele ignore maiúsculas vs. minúsculas ao filtrar as opções pelo texto digitado pelo usuário. -l 10 indica que queremos uma opção por linha, e que no máximo 10 linhas devem ser mostradas de cada vez. Por padrão, o dmenu usa apenas uma linha e mostra as opções lado a lado. (Uma coisa meio ruim do dmenu é que ele não dá nenhuma indicação de que é possível scrollar o menu; ele só mostra as primeiras N opções e as demais ficam escondidas.)

wmctrl -a JANELA ativa a primeira janela cujo título contenha a string specificada. Como queremos que a seleção seja inambígua, utilizamos a opção -i, que permite especificar o ID da janela ao invés do título. Para extrair o ID da seleção, removemos tudo depois do primeiro espaço na string ("${option%% *}").

Instalação

Salve o script no local de sua preferência, dê permissão de execução a ele (chmod +x nome-do-script), e associe-o a alguma tecla de atalho no seu ambiente gráfico favorito. Por exemplo, no IceWM isso pode ser feito adicionando no ~/.icewm/keys uma linha como:

key "Super+Tab" /caminho/do/script

substituindo Super+Tab pelo atalho de sua preferência (Super é a tecla "janelinha").

Para mais informações e possibilidades, consulte a manpage dos programas.

Comentários

Ensinando inglês

2015-06-09 23:28 -0300. Tags: lang, comic, img, random

Quadrinho:

Quadro 1:

B: Como se diz "fazer" em inglês?

A: "Do".

B: Mas "do" não é pra fazer pergunta?

---

Quadro 2 (cena imaginada por A):

A: Ah, sim, o inglês usa "do" como um auxiliar dummy em perguntas, provavelmente como uma maneira de equilibrar a tendência histórica do inglês de deslocar o verbo para o começo em perguntas, como as demais línguas germânicas, com a tendência a  uma ordem SVO mais rígida, provavelmente motivada pela perda de morfologia que permita distinguir substantivos e verbos em inglês, o que pressiona a língua a desambiguar usando a sintaxe ...

---

Quadro 3:

A:

A: É, serve pra fazer pergunta também.

Baseado em fatos reais.

(E eu ia mudar o texto para "distinguir substantivos e verbos facilmente em inglês", mas já gastei meu estoque de paciência com o GIMP hoje.)

2 comentários

Emacs, again

2015-06-05 02:30 -0300. Tags: comp, editor, emacs

No último sábado eu resolvi experimentar usar o Emacs de novo. O plano era ficar usando durante um mês e depois escrever um post relatando a experiência. Porém, como ao longo dos três anos de blog eu observei que em geral quando eu deixo um post para escrever depois eu acabo não escrevendo, em parte porque eu acabo esquecendo o que eu ia escrever, resolvi escrever um post inicial agora. (Além disso, quando eu comecei a escrever esse post eu não estava conseguindo dormir e precisava passar o tempo.)

So far, estou curtindo horrores.

Da primeira vez que eu tentei usar o Emacs, eu larguei de mão em dois ou três dias. Acho que o principal fator de diferença foi que dessa vez eu resolvi usar a versão gráfica do Emacs. Assim, quando eu não consigo fazer alguma coisa pelos comandos de teclado eu posso recorrer ao mouse e get stuff done. Dito isso, como eu não sou exatamente um fã do mouse, na prática eu acabo me motivando a aprender os comandos anyway.

(Um vício bem mais difícil de eu me livrar é usar as setas, Home/End e afins ao invés dos comandos "tradicionais" do Emacs. Right now eu larguei um pano de prato por cima do lado não-alfabético do teclado para ver se me habituo a não usar essas teclas.)

Mas por quê?

Tudo começou porque eu estava meio de saco cheio do Claws Mail, experimentei o Thunderbird e não gostei muito (não lembro mais por quê), e aí lembrei do Gnus e resolvi experimentar. No fim das contas eu consegui resolver o problema que eu estava tendo com o Claws comentando fora um teste no código-fonte e não curti muito o Gnus, mas a essas alturas eu já tinha mexido um bocado no Emacs e ressurgiu o interesse, reforçado pelos seguintes acontecimentos:

E aqui estamos.

Coming from Vim...

Em alguns aspectos, o Emacs pode ser mais "difícil" de usar do que o Vim. Seguem algumas observações nesse sentido.

Enquanto usar o Vim sem nenhuma customização é relativamente ok, usar o Emacs sem nenhuma customização não é uma experiência tão agradável. Desde que eu comecei a usar o Emacs, quase todo dia eu adiciono alguma coisa no meu ~/.emacs, que provavelmente vai continuar crescendo na mesma taxa por um bom tempo. (De certa forma o ponto do editor é ser customizado/reprogramado to death, então eu desconfio que enquanto eu usar o Emacs eu não vou parar de mexer no ~/.emacs; porém, eu espero mexer nele com menos freqüência depois de algum tempo de uso.)

Além disso, embora o Emacs ganhe bonito em flexibilidade, certas configurações simples são mais fáceis de fazer no Vim do que no Emacs. Por exemplo, no Vim define-se um keybinding novo em termos das teclas que teriam que ser pressionadas para realizar o comando desejado. No Emacs, cada tecla é associada a uma função que é executada quando a tecla é pressionada, o que por um lado é bem mais limpo do que a maneira como o Vim faz as coisas (definir uma ação complexa em termos de keypresses pode ser um inferno), mas por outro lado exige que uma função em Emacs Lisp seja definida sempre que se quer associar uma seqüência de comandos a uma tecla.

Dito isso, Emacs Lisp é uma linguagem infinitamente mais agradável de usar do que Vimscript. Além disso, é fácil descobrir qual é o nome da função associada a cada tecla, usando os comandos Ctrl-h k (describe-key) e Ctrl-h c (describe-key-briefly), o que facilita na hora de definir funções novas em termos das funções das teclas existentes.

Além disso, embora eu tenha mudado as cores do editor para texto branco no fundo preto, o restante do esquema de cores se adaptou sozinho, e eu achei o esquema de cores tão bom que nem desativei o syntax highlighting (que normalmente é a primeira coisa que eu faço quando uso o Vim em outro computador). A única exceção foi o AUCTeX, que escolheu umas cores diferentes do resto do editor por alguma razão.

Comandos

Enquanto o Vim possui um modo específico para dar comandos (o Normal mode), o que permite que os comandos sejam teclas simples1, o Emacs possui apenas um "modo" (no sentido Vim da palavra2), e os comandos de edição normalmente são introduzidos por um keystroke envolvendo Control ou Alt (Meta no linguajar emacsístico). Muitos comandos exigem uma seqüência de keystrokes: o comando para abrir um arquivo é Ctrl-x Ctrl-f, por exemplo, e o comando para abrir um arquivo em uma nova janela é Ctrl-x 4 Ctrl-f. Da primeira vez que eu experimentei o Emacs eu achei esses comandos totalmente bizarros e meio que larguei ele de mão por causa disso. Porém, agora que eu resolvi usar o editor de novo com a mente em um modo (heh) mais propenso a aceitar coisas novas (e com a interface gráfica para me salvar nos momentos difíceis), eu tenho me adaptado aos comandos novos relativamente fácil.

Embora seja tentador logo no começo alterar o keymap para usar comandos mais intuitivos, eu resolvi fazer um esforço para aprender os comandos convencionais do editor, e só definir keybindings novas para comandos que eu mesmo criei ou que não têm uma keybinding padrão, for most part. Até agora tem sido relativamente tranqüilo. Se depois de mais tempo de uso eu vir que algum keybinding realmente não me agrada, eu mudo. O mais difícil tem sido lembrar de usar Ctrl-s no Emacs e Ctrl-f no Firefox para procurar texto, e usar Ctrl-f (forward) para avançar um caractere e Ctrl-b (back) para voltar um caractere, e analogamente Meta-f e Meta-b para avançar e voltar uma palavra. Esses aí eu sou capaz de acabar rebindando para Ctrl-. e Ctrl-, ou algo do tipo (ou me conformar em usar as setas mesmo), se não me habituar nas próximas semanas.

O Emacs usa uma notação especial para descrever keystrokes. Basicamente, C-x significa Ctrl-x, M-x significa Meta-x (Alt-x), C-M-x significa Ctrl-Meta-x, RET é o Enter, SPC é o espaço, DEL é o backspace, e seqüências de teclas são escritas separadas por espaço (C-x C-f significa "tecle Ctrl-x, e depois tecle Ctrl-f"). Eu vou usar essa notação no restante do post.

Uma coisa legal do Emacs é que todo comando possui um nome (o nome da função Lisp que o implementa). Mesmo quando não se sabe o atalho de teclado que ativa o comando, é possível dar M-x e digitar o nome do comando (que freqüentemente é relativamente fácil de adivinhar, pois a nomenclatura é mais ou menos consistente; além disso é possível usar TAB para completar os nomes). Se o comando possui um atalho associado, após executar o comando o Emacs mostra uma mensagem como You can run the command `mark-paragraph' with M-h, e assim você aprende a tecla do comando.

Outra coisa que ajuda é que o help do Emacs é muito bom de usar. Todos os comandos de ajuda começam com C-h; C-h ? mostra todas as opções de ajuda. C-h f mostra a documentação de uma função ou comando, C-h k mostra a documentação do comando associado a uma tecla, C-h c mostra o nome do comando associado à tecla, C-h m mostra a documentação do modo atual, C-h r abre o manual do Emacs, entre outros. Como já mencionado, a documentação de uma função vem com um link para o código-fonte, quando disponível (no Debian, é necessário instalar o pacote emacs24-el). O link não é parte do texto da documentação, mas sim uma funcionalidade do próprio Emacs: se você pedir o help de uma função que você mesmo definiu no seu ~/.emacs, o Emacs vai mostrar um link para o ponto da definição no arquivo. (Até as poucas funções definidas em C têm links para a definição no fonte, mas nesse caso o Emacs abre uma janela perguntando o diretório onde se encontra o fonte C do Emacs, que eu não tenho aqui.)

Uma coisa um pouco ruim é que quase todos os keystrokes já têm um comando associado, seja por padrão, seja pelo modo em uso, o que limita as escolhas para novos keybindings que não conflitem com os existentes. Por convenção, o Emacs reserva C-c seguido por uma letra para o usuário, mas C-c seguido por outras coisas são reservados para os modos. Porém, existe um refúgio: a tecla C-z por padrão vem associada ao comando suspend-frame, que suspende o Emacs em modo texto e minimiza a janela no modo gráfico. Eu basicamente não uso esse comando no modo gráfico anyway (e ele também fica acessível via C-x C-z), então o que eu fiz foi rebindar ele como uma "prefix key" e colocar os comandos novos que eu defino sob o C-z (por exemplo, C-z C-z para salvar e fechar o arquivo e C-z d para inserir a data atual). Como C-z é um comando padrão do Emacs, isso tem a vantagem de que basicamente nenhum modo usa essa tecla.

Emacs as a daemon

Enquanto o modo de uso normal do Vim (e editores de texto em geral) é abrir o editor, editar arquivos e fechar, o modo de uso normal do Emacs (e sistemas operacionais em geral) é deixar o editor aberto o tempo inteiro enquanto se está trabalhando, em parte porque o editor tende a acumular estado (arquivos e aplicativos abertos, modos em uso), em parte porque ele demora um pouco mais para carregar do que o Vim (especialmente a versão gráfica, e especialmente se o ~/.emacs faz mil coisas na inicialização). Porém, é possível rodar o Emacs como um daemon, e aí pode-se usar o comando emacsclient para abrir um novo "frame" (janela) do Emacs conectado à sessão já em execução, o que é mais rápido do que iniciar o editor do zero. emacsclient -a "" inicia um daemon automaticamente se já não houver um rodando, e conecta-se a um existente se houver. Além disso, é possível passar as opções -t para rodar no terminal e -c para criar uma nova janela gráfica (por padrão ele abre o arquivo passado na linha de comando em uma janela existente, se houver). É um comando bem útil para setar como o valor da variável de ambiente EDITOR, que programas como git, crontab, etc., usam para determinar o editor a ser usado.

Shell mode

O Emacs possui um "modo shell" (M-x shell), que executa o bash ou afim dentro de um buffer do Emacs, onde os comandos normais de edição do Emacs ficam disponíveis. O modo tem algumas bizarrices (o texto é um buffer normal, o que significa que é possível editar a saída dos comandos que já foram executados, apagar o prompt [update: isso é controlável pela variável comint-prompt-read-only; mais informações no help da variável (C-h v comint-prompt-read-only)], etc.), mas é interessante se você quer realizar o máximo de tarefas possível sem sair do Emacs.

O shell mode toma conta do completion de nomes de arquivos e comandos, mas ele usa a função de completion do Emacs, que por padrão exclui certos tipos de arquivos que normalmente não se tem interesse em abrir num editor de textos (e.g., arquivos .o), o que não faz muito sentido em um shell. A solução que eu encontrei foi adicionar um "hook" (função que roda quando um modo é iniciado) no meu ~/.emacs que anula a lista de extensões a ignorar no buffer do shell.

Outro "gotcha" é que como o buffer usa os comandos normais do Emacs, coisas como Ctrl-C, seta para cima, etc., não têm o comportamento normal do terminal. Os comandos Ctrl-* devem ser precedidos de C-c (e.g., C-c C-c para interromper o processo, C-c C-z para suspender, etc.). Manipulação de histórico é feita através de M-p e M-n (de "previous" e "next", análogo ao C-p e C-n para trocar de linha no buffer).

Como trata-se de um buffer simples, coisas que exigem poderes "gráficos" do terminal, como aplicativos de tela cheia, não funcionam no shell mode. Porém, é possível adicionar suporte a cores no buffer (para coisas como ls --color) rodando a função ansi-color-for-comint-mode-on (seja via M-x, seja no ~/.emacsrc (lembrando de pôr parênteses em volta do comando nesse caso)). Vale ainda notar que o shell mode seta a variável de ambiente TERM para dumb, o que faz com que ls, grep e companhia não usem cores por default. É possível contornar isso alterando o ~/.bashrc para mudar o valor de TERM (para vt100, por exemplo) se a variável INSIDE_EMACS estiver setada, mas aí os programas que usam TERM para decidir se vão operar em modo tela cheia ou não vão incorretamente assumir que podem rodar em tela cheia dentro do shell mode. Não sei se há algum valor de TERM que indique suporte a cores mas não às demais funções gráficas do terminal.

Também existe um "modo terminal" (M-x term), que é um emulador de terminal de verdade (não apenas um buffer com um shell) onde se pode rodar qualquer programa de terminal, mas aí perdem-se os comandos normais do Emacs. Além disso, pelo que eu testei o emulador é pra lá de lento (o alsamixer leva uns três ou quatro segundos para abrir com o processador a 800MHz3).

M-x finish-post RET

Por hoje ficamos por aqui. Eu provavelmente hei de postar mais coisas à medida em que for descobrindo funções novas no editor, e atualizar meu ~/.emacs no GitHub ocasionalmente.

_____

1 Na verdade, faz uns cinco anos que eu uso o Vim com keybindings especiais para não ter que trocar de modo para os comandos de edição mais freqüentes, então eu já usava um editor não-modal for most part, o que significa que de certa forma eu não aproveitava muito das vantagens de usar o Vim.

2 O que o Emacs chama de modos são coisas como o html-mode, latex-mode, c-mode, etc., chamados major modes, que alteram o comportamento do editor (como comandos e syntax highlighting) para facilitar a edição de um certo tipo de arquivo, ou aplicações como o Gnus. Também há os minor modes, que são usados em conjunto com os major modes e se comportam mais ou menos como algumas opções ativadas com :set no Vim.

3 Normalmente eu uso a máquina em modo de economia de energia e só seto o governor do cpufreq para performance quando vou fazer alguma coisa mais pesada, primariamente porque com a freqüência baixa o fan faz menos barulho.

9 comentários

Main menu

Posts recentes

Tags

comp (92) prog (37) life (36) unix (30) random (26) lang (23) mundane (21) about (19) mind (16) pldesign (14) img (13) web (13) rant (12) ramble (11) privacy (8) bash (7) lash (7) esperanto (7) lisp (6) home (6) shell (6) academia (6) misc (5) freedom (5) conlang (5) latex (4) copyright (4) worldly (4) book (4) mestrado (4) editor (4) php (4) music (4) kbd (3) in-english (3) film (3) wrong (3) politics (3) security (3) poem (2) physics (2) android (2) cook (2) network (2) c (2) emacs (2) comic (2) lows (2) treta (2) pointless (1) llvm (1) philosophy (1) perl (1) audio (1) kindle (1)

Elsewhere

Quod vide


Copyright © 2010-2015 Vítor Bujés Ubatuba De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.