brisa/README.org

10 KiB
Raw Blame History

Linguagem Brisa

Visão geral

Um código em Brisa tem esta aparência:

@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

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.

Nas estruturas if e select, o terminador end só é utilizado após o bloco encabeçado pela última condição avaliada.

Blocos de comentários

@doc
...
@end
  • 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:
@doc: o que estamos comentando
...
@end

Comentários inline

[...]; ...

Tudo após ; é ignorado.

Declaração de identificadores

Todo identificador (nome de função ou de variável) deve ser declarado como:

tipo[espaços]:[espaços]lista_de_nomes    ; os espaços são ignorados na compilação
  • 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).

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.

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:

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

Os tipos int e i32 são equivalentes, ou seja, são inteiros de 32 bits com sinal.

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.

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

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.

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.

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:

; 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
  • 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:

; Isso é válido...
print(
   "Maria ",
   "tinha ",
   "um carneirinho."
)

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):

; 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

Entretanto, elas só podem ser criadas em atribuições de variáveis ou como argumentos de outras funções:

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'