Olá Habr! Vá direto ao ponto. No momento, estou lendo "The Dragon Book" e desenvolvendo um compilador para a minha linguagem de programação chamada Lolo (em homenagem ao pinguim do desenho animado soviético-japonês). Pretendo terminar dentro de um ano se nada doer. Em paralelo, publicarei trechos interessantes da experiência de tradução, criação de código intermediário, otimização etc. etc. Bem, hoje vou apresentar o idioma a você. Sente-se e vá.
A linguagem é compilada, imperativa, não orientada a objetos; a semântica foi descaradamente descartada do C e complementada com muitos recursos úteis. Vamos começar com eles.
Modificações semânticas
Indicadores seguros
Você pode ter pensado em indicadores inteligentes da Rust agora, mas eles não são. No meu idioma, a segurança do acesso à memória é fornecida por duas expressões idiomáticas. Primeiro: a falta de uma operação de desreferenciamento de ponteiros. Em vez disso, ao acessar o ponteiro declarado, o próprio objeto é referido. Ou seja, você pode e deve escrever assim:
int # pointer ~~ new int(5) int variable ~ pointer + 7
A variável variável agora contém o número 12. Agora você vê uma sintaxe desconhecida e, talvez, esteja um pouco perplexo, mas vou explicar tudo no decorrer do artigo. Segundo idioma: falta de operações nos ponteiros. Novamente: todas as operações ao acessar ponteiros, incluindo atribuição, incremento e decremento, são executadas nos objetos. A única operação que se relaciona diretamente ao ponteiro é a atribuição por endereço ou, como eu chamo, identificação. No exemplo de código acima, na primeira linha, é precisamente identificação. Qualquer ponteiro pode ser definido como o endereço apenas da área de memória já alocada, que é a nova operação retornada. Você também pode colocar um ponteiro para o endereço de outra variável alocada, mesmo na pilha, mesmo na pilha. Aqui está um exemplo:
int variable ~ 5 int # pointer ~~ variable
Aqui "~" é a operação de atribuição usual. Você também pode identificar ponteiros com um ponteiro nulo especial. Ele atua como um ponteiro que se refere a um endereço nulo. Após identificar as operações de comparação e comparação na identidade (endereços idênticos) com nulo, elas produzirão true:
int # pointer ~~ null if (pointer = null) nop ;; true if (pointer == nul) nop ;; true
Aqui "=" é uma comparação de valores, "==" é uma comparação por endereços, "nop" é uma operação vazia e depois de ";;" - comente. E sim, null é a única operação de ponteiro com a qual é possível sem verificar a compatibilidade do tipo.
Assim, os ponteiros só podem ser atribuídos à memória alocada ou às áreas nulas e não podem ser movidos para nenhum lugar. No entanto, essas medidas não protegem totalmente contra erros de falha de segmentação. Para obtê-lo, basta seguir estas etapas:
int # pointer1 ~~ new int(5) int # pointer2 ~~ pointer1 delete pointer1 int variable ~ pointer2 ;; segmentation fault!
Eu acho que tudo está claro aqui. Mas cometer esse erro só pode ser feito de propósito e, depois, ter trabalhado duro. Afinal, a operação de exclusão faz o mesmo que o coletor de lixo, apenas com menos segurança. Falando dele ...
Coletor de lixo
Coletor de lixo - ele também é colecionador em Lolo. Provavelmente não há necessidade de explicar o que é. Só posso dizer que ele pode ser desativado por uma opção especial durante a compilação. Testamos o programa com o coletor, tudo funciona como deveria - você pode inserir a opção e tentar otimizar o programa usando o gerenciamento manual de memória.
Matrizes incorporadas
Embora eu tenha dito que a semântica da linguagem é eliminada de C, as diferenças são bastante significativas. Aqui matrizes são ponteiros. As matrizes têm sua própria sintaxe e endereçamento seguro. Não, não com uma verificação de alcance. Com eles, em princípio, é difícil obter um erro de tempo de execução. Isso ocorre porque cada matriz armazena o comprimento no tamanho da variável, como em Java, e com cada indexação do índice ... existe o restante da divisão por esse tamanho! Uma decisão estúpida, à primeira vista, até olharmos para índices negativos. Se você encontrar o restante da divisão -1 pelo comprimento da matriz, obtém um número igual ao tamanho-1, ou seja, o elemento mais finito. Com essa manobra, podemos acessar índices não apenas desde o início, mas também no final da matriz. Outro truque é converter qualquer tipo primitivo na matriz de bytes []. Mas como você obtém um erro de execução, você pergunta? Vou deixar essa pergunta para você como um enigma fácil.
Referências
Não sei ao certo se o padrão C atual inclui links, mas eles definitivamente estarão no Lolo. Talvez a falta de referências nas versões anteriores do C seja uma das principais razões para ponteiros para ponteiros. Eles são necessários para passar argumentos para o endereço, para retornar valores de funções sem copiar. Ponteiros e matrizes também podem ser passados por referência (uma vez que ao passar por valor, as matrizes serão completamente copiadas e os ponteiros configurados para um novo local pela operação ~~ não o salvarão).
Multithreading
Tudo é mais bonito e mais bonito. Eu já estou apaixonado pelo meu idioma. Seu próximo hobby é multithreading. Honestamente, ainda não decidi completamente quais ferramentas serão fornecidas. Provavelmente, a palavra-chave sincronizada com todas as propriedades de ala-Java e, possivelmente, a palavra-chave simultânea na frente de funções não embutidas, o que significa "executar essas funções em encadeamentos paralelos".
Sequências em linha
São strings, não literais de strings, como em C ++. Cada linha terá seu próprio comprimento, a indexação ocorrerá com a localização do restante. Em geral, as strings no Lolo são muito semelhantes às matrizes de caracteres, exceto que as matrizes não têm concatenação via "+", animação através de "*" e comparações através de "<" e ">". E já que estamos falando de falas, devemos mencionar os personagens. Símbolos em Lolo não são números, como em C ++. E eles contêm não um byte, mas 4 para caracteres DKOTI e 6 para caracteres UTF. Vou falar sobre o DKOTI da próxima vez, mas por enquanto, apenas saiba que o Lolo suporta caracteres e seqüências de caracteres em duas codificações. E sim, a propriedade length pode até ser obtida de constantes:
int len ~ "Hello, world!".length ;; len = 13
Tipo booleano com três valores
A grande maioria das linguagens de programação que possuem um tipo de dados lógicos usa lógica binária. Mas em Lolo será ternário, ou melhor, ternário confuso. Três valores: verdadeiro - verdadeiro, falso - falso e nenhum - nada. Até agora, não encontrei na linguagem das operações que não retorne nenhuma, mas lembro-me de muitos exemplos da prática em que bandeiras com três valores seriam muito úteis. Tinha que usar enumerações ou um tipo inteiro. Não precisa mais. Esse é apenas o nome desse tipo que não posso escolher. O mais comum é "lógico", mas muito longo. Outras opções são "luk" em homenagem a Jan Lukasevich, "brus" em homenagem a N. P. Brusnetsov e "trit", mas estritamente falando, esse tipo não é um trit. Em geral, a pesquisa está no final do artigo.
Listas para inicializar estruturas e listas
Se, depois de declarar uma variável estrutural, colocar o sinal ~ e abrir os colchetes, você poderá definir os valores de seus campos alternadamente ou na forma de um dicionário. Se você executar esse procedimento com uma matriz, poderá definir os valores de suas células, apenas sem um dicionário. Não há nada especial para contar, basta olhar para o código:
struct { int i; real r; str s; } variable ~ [ i: 5, r: 3.14, s: "Hello!" ] int[5] arr ~ [ 1, 2, 3, 4, 5 ]
Retornar vários valores de funções
Assim como no Go! Você pode escrever vários nomes de variáveis separados por vírgulas e atribuir a eles todos os valores retornados da função de uma vez:
int, real function() { return 5, 3.14 } byte § { int i; real r i, r ~ function }
Módulos em vez de cabeçalhos
Tudo está claro aqui. Em vez de cabeçalhos C-timidos - módulos de Java.
para (item automático: matriz)
Novamente Java nativo. Como temos matrizes com comprimento, é um pecado não usar a expressão para cada uma.
O operador de seleção não é apenas para int
Não conheço você, mas em C e C ++ estou muito enfurecido com a falta de capacidade de usar a operação de caso de comutação para variáveis não inteiras. E a sintaxe também enfurece. Aqui em Pascal é outra questão. E agora em Lolo:
case variable { "hello", "HELLO": nop "world": { nop; nop } "WORLD": nop }
Operadores de energia e divisão
E isso é do Python.
real r ~ 3.14 ** 2 int i ~ r // 3
Tuplas de parâmetros de função
Lembra que todas as operações com ponteiros são proibidas no Lolo, exceto a identificação? Agora vamos lembrar como acessar parâmetros de função a partir de listas de parâmetros de comprimento variável. Você precisa declarar um ponteiro para o primeiro elemento e depois incrementar até que a verificação da verdade retorne verdadeira. Você não pode incrementar no Lolo. Mas tudo bem. Afinal, a lista de parâmetros aqui é apresentada na forma de uma tupla de comprimento fixo (dependente de chamada), com índice seguro, como em matrizes. O nome dele é "?" A verificação de tipo é realizada apenas para os parâmetros definidos na definição da função. Os demais parâmetros (“multiponto”) são reduzidos a qualquer tipo e, com um movimento constrangedor, seu comportamento não é definido. Mas, ainda assim, essa tupla é muito mais segura e conveniente do que as macros em C.
void function(...) { if (?.size > 1) { int i ~ ?[0] real r ~ ?[1] } }
Intervalos numéricos
E outro personagem - uma família de tipos de intervalo (intervalo, intervalo, intervalo, etc.). Eles são dados por dois números inteiros através de dois pontos (..) e podem cortar uma matriz de uma matriz, uma string de uma string, em geral, uma coisa útil, eu acho.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] int[3] subarr = arr[1..3] ;; [ 2, 3, 4 ]
Operador
De Pascal. Trabalha com strings, matrizes, tuplas? e intervalos.
int[5] arr ~ [ 1, 2, 3, 4, 5 ] if (4 in arr) nop
Dicionário de parâmetros de função
Honestamente, eu já estou confuso como essa coisa é chamada corretamente, com isso você pode especificar diretamente os argumentos de funções não puras:
int pos = str_find(string, npos: -1)
Opções padrão
De C ++. Aqui, nem mesmo um exemplo é necessário, e tudo fica claro.
Exceções
Bem, e onde sem eles?
try { raise SEGMENTATION_FAULT_EXCEPTION } except (Exception e) { print(e.rus) }
Nenhum salto incondicional
Porque em 2019, o uso do operador GOTO da morte é semelhante.
Sintaxe
Bem, um pouco de conversa sobre a sintaxe. Como você notou, o ponto e vírgula é raso. As linguagens de programação modernas se saem muito bem sem essa fonte de erro. Exemplos são Python, Kotlin. O operador de seta (->) é combinado com o operador de ponto. Ao chamar funções sem argumentos, os colchetes são opcionais. As strings são dadas em números e vice-versa. Operadores lógicos e bit a bit são combinados. Existem modificadores de função para tabulação. Funções aninhadas type_of. E o mais importante - multilinguismo. Sim, vou duplicar palavras-chave, propriedades de strings e matrizes e todos os identificadores da biblioteca padrão em todos os idiomas da comunicação internacional, a saber: inglês, russo, japonês, chinês, espanhol, português, árabe, francês, alemão e latim.
De fato, todas as opções acima não incluem metade das capacidades do Lolo. Só não consigo me lembrar de todos os recursos. Vou adicionar quando o compilador estiver pronto.