Durante vários anos, tentei desenvolver minha linguagem de programação. Eu queria criar na minha opinião a linguagem mais simples, totalmente funcional e conveniente.
Neste artigo, quero destacar as principais etapas do meu trabalho e começar descrevendo o conceito criado da linguagem e sua primeira implementação na qual estou trabalhando atualmente.
Eu digo com antecedência que escrevi o projeto inteiro no Free Pascal, porque Os programas nele podem ser montados para um grande número de plataformas, e o próprio compilador produz binários muito otimizados (eu coleciono todos os componentes do projeto com o sinalizador O2).
Tempo de execução do idioma
A primeira coisa a se falar é sobre uma máquina virtual que precisei escrever para executar aplicativos futuros no meu idioma. Decidi implementar uma arquitetura de pilha, talvez, porque era a maneira mais fácil. Não encontrei um único artigo normal sobre como fazer isso em russo; portanto, depois de ler o material em inglês, sentei-me para projetar e escrever minha bicicleta. Além disso, darei minhas idéias e desenvolvimentos "avançados" neste assunto.
Implementação de pilha
Obviamente, a VM está no topo da pilha. Na minha implementação, ele funciona em blocos. Em essência, essa é uma matriz simples de ponteiros e uma variável para armazenar o índice da parte superior da pilha.
Quando é inicializado, uma matriz de 256 elementos é criada. Se mais ponteiros forem lançados na pilha, seu tamanho será aumentado pelos próximos 256 elementos. Assim, ao remover itens da pilha, seu tamanho é ajustável.
Uma VM usa várias pilhas:
- A pilha principal.
- Pilha para armazenar pontos de retorno.
- Pilha de coletor de lixo.
- O manipulador de pilha dos blocos try / catch / finalmente.
Constantes e variáveis
Com isso, tudo é simples. As constantes são processadas em um pequeno pedaço de código separado e estão disponíveis em aplicativos no futuro em endereços estáticos. As variáveis são uma matriz de ponteiros de um determinado tamanho; o acesso às células é realizado pelo índice - ou seja, endereço estático. As variáveis podem ser colocadas no topo da pilha ou lidas a partir daí. Na verdade, porque nossas variáveis essencialmente armazenam ponteiros para valores na memória da VM e, em seguida, trabalham com ponteiros implícitos predominando no idioma.
Coletor de lixo
Na minha VM é semi-automático. I.e. o desenvolvedor decide quando chamar o coletor de lixo. Ele não funciona de acordo com o contador comum de ponteiros, como no mesmo Python, Perl, Ruby, Lua, etc. É implementado através de um sistema de marcadores. I.e. quando se entende que uma variável recebe um valor temporário, um ponteiro para esse valor é adicionado à pilha do coletor de lixo. No futuro, o coletor percorre rapidamente uma lista já preparada de indicadores.
Manipulando blocos try / catch / finalmente
Como em qualquer linguagem moderna, o tratamento de exceções é um componente importante. O kernel da VM é agrupado em um bloco try..catch, que pode retornar à execução do código após capturar a exceção, colocando um pouco de informação sobre ele na pilha. No código do aplicativo, você pode especificar blocos de código try / catch / finalmente, indicando os pontos de entrada a serem capturados (manipulador de exceções) e finalmente / fim (fim do bloco).
Multithreading
É suportado no nível da VM. É simples e conveniente de usar. Como funciona sem um sistema de interrupção, o código deve ser executado em vários threads várias vezes mais rápido, respectivamente.
Bibliotecas externas para VM
Não há como ficar sem isso. A VM suporta importações, assim como é implementada em outros idiomas. Você pode escrever parte do código no Mash e parte do código em idiomas nativos e vinculá-los.
Tradutor do idioma de alto nível Mash para bytecode for VM
Linguagem intermediária
Para escrever rapidamente um tradutor de um idioma complexo para o código da VM, primeiro desenvolvi um idioma intermediário. Acabou sendo uma visão assustadora, como uma montadora, que não há nenhum sentido especial a considerar aqui. Só posso dizer que, nesse nível, o tradutor processa a maioria das constantes, variáveis, calcula seus endereços estáticos e endereços de pontos de entrada.
Arquitetura do tradutor
Eu não escolhi a melhor arquitetura para implementação. O tradutor não cria uma árvore de códigos, como convém a outros tradutores. Ele olha para o início da construção. I.e. se o trecho de código analisado se parecer com "while <condition>:", é óbvio que essa é uma construção while do loop e precisa ser processada como a construção do loop while. Algo como um caso de troca complicado.
Graças a essa solução arquitetônica, o tradutor não foi muito rápido. No entanto, a simplicidade de seu refinamento aumentou significativamente. Adicionei os desenhos necessários mais rapidamente do que meu café poderia esfriar. O suporte total ao POO foi implementado em menos de uma semana.
Otimização de código
Aqui, é claro, poderia ser realizado melhor (e será realizado, mas mais tarde, quando as mãos chegarem). Até o momento, o otimizador sabe apenas como cortar código, constantes e importações não usadas do assembly. Além disso, várias constantes com o mesmo valor são substituídas por uma. Isso é tudo.
Idioma mash
O conceito básico de linguagem
A idéia principal era desenvolver a linguagem mais funcional e simples. Eu acredito que o desenvolvimento lida com sua tarefa com um estrondo.
Blocos de código, procedimentos e funções
Todas as construções no idioma são abertas com dois pontos
: e fechadas com o operador
final .
Procedimentos e funções são declarados como proc e func, respectivamente. Os argumentos estão listados entre parênteses. Assim como a maioria dos outros idiomas.
A
instrução return pode retornar um valor de uma função, a instrução
break permite que você saia de um procedimento / função (se estiver fora dos loops).
Exemplo de código:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
- : for..end, while..end, until..end
- : if..[else..]end, switch..[case..end..][else..]end
- : proc <>():… end, func <>():… end
- Label & goto: <>:, jump <>
- Enum .
, var .
:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
.
. Mash - . .. , , ( .. ), ().
, .
:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
proc main():
x ?= new MyClass(10, 20)
println(x->Summ())
x->Free()
end
: 30.
:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyNewClass(10, 20)
println(x->Summ())
x->Free()
end
: 60.
? !:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyClass(10, 20)
x->Summ ?= MyNewClass::Summ
println(x->Summ())
x->Free()
end
: 60.
:
uses <bf>
uses <crt>
class MyClass:
var a, b
end
proc main():
x ?= new MyClass
println(BoolToStr(x->type == MyClass))
x->rem()
println(BoolToStr(typeof(3.14) == typeReal))
end
: true, true.
?= .
= .
. .
@<> — .
?<> — .
@= — .
:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
: - , 10, 11.
Try..[catch..][finally..]end
:
uses <bf>
uses <crt>
proc main():
println("Start")
try:
println("Trying to do something...")
a ?= 10 / 0
catch:
println(getError())
finally:
println("Finally")
end
println("End")
inputln()
end
GraalVM & Truffle. JIT , . , JIT GraalVM LLVM.
.
GitHub, , .