#+title: Linguagem Brisa #+subtitle: Especificações #+author: Blau Araujo #+email: blau@debxp.org * Visão geral Um código em Brisa tem esta aparência: #+begin_example @doc Bloco de comentários... @end ; Declaração de uma função com retorno 'int' (inteiro 32 bits com sinal)... int:add42 (int:a) return(a + 42) ; Declaração de uma função com retorno 'nil' (símbolo sem associação de valor)... nil:salve(str:name) print("Salve, ", name, "!\n") end ; Função 'main' (ponto de entrada)... int:main () int:x = 23 ; declaração de variável inicializada com expressão constante int:y = x + 58 ; declaração de variável inicializada com avaliação da expressão int:z ; declaração de variável não inicializada (valor 'nil') @doc: outras sintaxes válidas... int:a, b, c ; declaração de múltiplas variáveis de mesmo tipo não inicializadas int:a = 1, b = 2, c = 3 ; declaração de múltiplas variáveis de mesmo tipo inicializadas int:a = b = c ; variáveis 'a' e 'b' inicializadas com a avaliação de 'c' (que deve estar inicializada) @end salve("simpatia") ; chamada da função 'salve' z = x + 58 ; operação de atribuição print("z + 42 = ", add42(z), "\n") ; chamada da função 'add42' como argumento da função builtin 'print' exit(0) ; função builtin 'exit' (opcional) end #+end_example ** Características principais do exemplo *** Terminação de instruções - Todas as instruções simples (uma linha) são delimitadas por quebras de linha; - Linhas longas podem ser quebradas com contrabarras (~\~); - Todas as instruções compostas (blocos) são delimitadas por um cabeçalho de bloco e pela palavra reservada ~end~; - Se houver apenas uma instrução no bloco, o terminador ~end~ pode ser dispensado, desde que a instrução seja escrita na mesma linha do cabeçalho do bloco. Os cabeçalhos de blocos podem ser: - Assinaturas de funções; - Estruturas de repetição ~for~, ~while~ e ~until~; - Estruturas de decisão ~if~, ~elif~, ~else~; - Estruturas de seleção ~select~, ~case~. #+begin_quote Nas estruturas ~if~ e ~select~, o terminador ~end~ só é utilizado após o bloco encabeçado pela última condição avaliada. #+end_quote *** Blocos de comentários #+begin_example @doc ... @end #+end_example - O cabeçalho ~@doc~ tem que iniciar a linha; - Espaços e tabulações antes de ~@doc~ ou ~@end~ são ignorados; - Tudo após ~@doc~ é ignorado, inclusive ocorrências de ~@doc~ antes do terminador ~@end~, ou de ~@end~, desde que não seja a única palavra na linha; - A palavra ~@end~ tem que estar sozinha na linha para ser interpretado como um terminador de bloco de comentários; - O cabeçalho ~@doc~ pode ser seguido que qualquer caractere, por exemplo: #+begin_example @doc: o que estamos comentando ... @end #+end_example *** Comentários inline #+begin_example [...]; ... #+end_example Tudo após ~;~ é ignorado. *** Declaração de identificadores Todo identificador (nome de função ou de variável) deve ser declarado como: #+begin_example tipo[espaços]:[espaços]lista_de_nomes ; os espaços são ignorados na compilação #+end_example - Nomes seguidos de ~([lista de parâmetros])~ declaram /funções/; - Nomes seguidos de ~= expressão~ identificam /variáveis inicializadas/; - Nomes seguidos do fim da linha ou de uma vírgula (~,~) declaram /variáveis não inicializadas/; - Espaços entre ~tipo~ e ~:~, ou entre ~:~ e o primeiro nome, são ignorados; - A ~lista_de_nomes~ consiste de um ou mais nomes de /variáveis/ (inicializadas ou não) separadas por vírgulas (~,~); - Todas as variáveis da lista são declaradas com o mesmo tipo; - Variáveis não inicializadas avaliam ~nil~ (símbolo sem valor associado); - Variáveis do tipo ~ptr~ identificam endereços e, se não inicializadas, avaliam ~NULL~ (ponteiro nulo). #+begin_quote Em Brisa, /variáveis/ são nomes que identificam dados em endereços na memória e /ponteiros/ são nomes que identificam endereços de dados na memória. #+end_quote *Importante!* Para ser um nome válido, a palavra deve conter apenas a combinação de um ou mais caracteres que casem com a expressão regular ~[A-Za-z_][0-9A-Za-z_]*~, ou seja, palavras iniciadas com caracteres alfabéticos ou sublinhado (~_~) seguidos de zero ou mais caracteres alfanuméricos ou sublinhado. ** Expressões constantes São palavras que representam valores literais associados a tipos de dados: - *Inteiros decimais*: ~[+-]?(0|[1-9][0-9]*)~ (~0~, ~42~, ~-123~, etc) - *Inteiros hexadecimais:* ~0[Xx][0-9A-Fa-f]+~ (~0x0a~, ~0xffcb~, etc) - *Inteiros octais:* ~[+-]?0[Oo][1-7]+~ (~0o1~, ~0o123~, ~-0o42~, etc) - *Inteiros binários:* ~0[Bb][0-1]~ (~0b0110~, ~0b10001010~, etc) - *Ponto flutuante:* ~1.23~, ~3.4e23~... - *Lista de caracteres:* ~'lista de caracteres imprimíveis ou escapados'~ (sem terminação nula) - *Strings:* ~"lista de caracteres imprimíveis ou escapados"~ (inclui terminação nula) - *Boolianos:* ~true~ (byte ~0x01~) e ~false~ (byte ~0x00~) *Importante!* O sinal de representações hexadecimais e binárias depende do tipo associado à variável. No entanto, por padrão, essas representações serão tratadas como ~unsigned int~ quando utilizadas em expressões: #+begin_example int:a = 0xff ; avalia -1 (em 32 bits) u32:b = 0xff ; avalia 255 (em 32 bits) int:c c = 5 + 0xff ; 5 + 255 = 300 c += a ; 300 + (-1) = 299 c += b ; 299 + 255 = 554 #+end_example #+begin_quote Os tipos ~int~ e ~i32~ são equivalentes, ou seja, são inteiros de 32 bits com sinal. #+end_quote ** Especificadores de tipos *** Inteiros - ~int~ ou ~i32~: inteiros de 32 bits com sinal; - ~u32~: inteiros de 32 bits sem sinal; - ~i64~: inteiros de 64 bits com sinal; - ~u64~: inteiros de 64 bits sem sinal; - ~byte~ ou ~i8~: inteiro de 8 bits com sinal; - ~u8~: inteiro de 8 bits sem sinal; - ~word~ ou ~i16~: inteiro de 16 bits com sinal; - ~u16~: inteiro de 16 bits sem sinal; *** Endereços - ~ptr~: define a variável como um ponteiro (endereços de 64 bits); - ~str~: mesmo que um ponteiro para um vetor de bytes na memória; - ~arr~: mesmo que um ponteiro para uma tabela (vetor associativo de dados de tipos diferentes); - ~vec~: mesmo que um ponteiro para um vetor (vetor indexado de dados de mesmo tipo); *Nota sobre ponteiros não inicializados:* Todos os tipos relacionados a endereços avaliam ~NULL~ quando não inicializados. *Nota sobre tabelas e vetores:* Em Brisa, /arrays/ (tabelas) são coleções dinâmicas e associativas de elementos de tipos diferentes, enquanto /vetores/ são listas dinâmicas de elementos de um mesmo tipo, o que é determinado pelo tipo do primeiro elemento. #+begin_example arr:tabela = { "banana", "qtd" 10, "unidade" "quilo" } @doc: o acesso pode ser por chave ou índice... tabela["0"] = "banana" -> tabela[0] = 0 tabela["qtd"] = 10 -> tabela[1] = "preço" tabela["unidade"] = "quilo" -> tabela[2] = "unidades" @end vec:lista = { 123, 42, 23 } @doc: acesso apenas pelos índices... lista[0] = 123 lista[1] = 42 lista[2] = 23 @end #+end_example *** O tipo especial 'nil' A palavra reservada ~nil~ representa um endereço fixo e único na memória sem um dado associado. Sua avaliação depende do contexto: - Expressões aritméticas: avalia ~0~; - Expressões boolianas: avalia ~false~; - Contexto de strings: representa uma string vazia (byte ~0x00~); - Declarações de variáveis: expressa que a variável não foi inicializada; - Retornos de funções: expressa que a função não tem retorno. ** Funções builtin A biblioteca padrão da linguagem Brisa (~librisa~) oferece vários construtores de linguagem na forma de funções /builtin/, como: - ~echo(str:string, ...)~: imprime, na saída padrão, todos os argumentos interpolados por um espaço e com uma quebra de linha ao final (os terminadores nulos são removidos). - ~print(str:string, ...)~: imprime, na saída padrão, o resultado da concatenação de todos os argumentos (não inclui a quebra de linha final). - ~strlen(str:string)~: retorna a quantidade de caracteres de uma string (desconta o terminador nulo) - ~exit(byte:status)~: termina o programa com o estado de término ~status~. - ~return(expressão)~: utilizada para retornar um valor do tipo especificado na declaração da função. #+begin_quote Em todas as funções /builtin/ que trabalham com strings, é possível passar expressões como argumentos, inclusive valores numéricos retornados por funções, porque a conversão para string será feita em tempo de execução. #+end_quote ** Declaração de funções Não existe uma separação entre a declaração e a definição de funções: funções são declaradas no mesmo momento em que são definidas, o que pode ser feito de duas formas: #+begin_example ; Declaração em bloco... tipo:nome (lista_de_parâmetros) instruções... [return(expressão)] end ; Declaração em linha... tipo:nome (lista_de_parâmetros) uma_instrução #+end_example - Na declaração em bloco, a primeira instrução até pode ser escrita na mesma linha do cabeçalho da função, mas isso pode tornar o código confuso. - A função /builtin/ ~return~ é obrigatória em funções com retorno diferente de ~nil~. *** Delimitadores de argumentos Os parêntesis da lista de parâmetros servem como delimitadores de blocos e, portanto, possibilitam a escrita de argumentos em várias linhas nas chamadas de funções: #+begin_example ; Isso é válido... print( "Maria ", "tinha ", "um carneirinho." ) #+end_example *Importante!* Isso é válido apenas no contexto de chamadas de funções -- em expressões, os parêntesis são operadores de precedência. *** Funções anônimas (closures) É possível criar funções anônimas com os nomes reservados ~function~, ~func~ ou ~fun~ (só pela diversão): #+begin_example ; função anônima em bloco... fun[c[tion]] (parâmetros) instruções... [return(expressão)] end ; função anônima em linha... tipo:fun[c[tion]] (parâmetros) uma_instrução #+end_example Entretanto, elas só podem ser criadas em atribuições de variáveis ou como argumentos de outras funções: #+begin_example int:x x = fun(int:num) return(num + 42) ; o tipo do retorno é o mesmo da variável 'x' echo(x(42)) ; imprime '84' echo(x(81)) ; imprime '123' #+end_example