Uma alternativa infinitamente mais portátil à linguagem de programação C.

Para aqueles que se lembram de "grátis", o Oak é essencialmente uma versão mais robusta e de alto nível desse projeto. O objetivo do carvalho é ser o mais alto possível no frontend, mas o mais pequeno e baixo possível no back -end.
Sou graduado e calouro na faculdade em faculdade procurando trabalho. Se você gosta dos meus projetos, considere me apoiar comprando um café para mim!
A chave para a portabilidade insana de Oak é sua implementação incrivelmente compacta de back -end. O código para o back -end de Oak pode ser expresso em menos de 100 linhas de C. Uma implementação tão pequena só é possível devido ao pequeno conjunto de instruções da representação intermediária. O IR de Oak é composto apenas por 17 instruções diferentes . Isso está a par com o Brainfuck!
O back -end do carvalho funciona de maneira muito simples. Cada instrução opera em uma fita de memória . Esta fita é essencialmente uma matriz estática de flutuadores de precisão dupla.
let x : num = 5.25 ; ... let p : & num = & x ; `beginning of heap`
| | |
v v v
[ 0 , 0 , 0 , 5.25 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , ... ]
^
|
`current location of the stack pointer`Quando uma variável é definida em uma função, ela recebe uma posição estática em relação ao ponteiro base atual da máquina virtual. Portanto, quando uma função é chamada, o espaço para as variáveis da função é alocado na pilha e o ponteiro base é incrementado para usar esse novo espaço. Em seguida, o compilador apenas substitui a variável por seu endereço adicionado ao deslocamento do ponteiro base no restante do código!
Além disso, a fita de memória funciona como uma pilha e uma pilha . Após o espaço para todas as variáveis do programa, a memória usada para a pilha começa. A pilha cresce e diminui com dados ao longo do programa: quando dois números são resumidos, por exemplo, eles são retirados da pilha e substituídos pelo resultado. Da mesma forma, a pilha cresce e diminui ao longo do programa. A pilha, no entanto, é usada para dados alocados dinamicamente : informações com uma pegada de memória desconhecida no tempo de compilação .
Agora que você entende como o back -end de Oak opera fundamentalmente, aqui está o conjunto completo de instruções!
| Instrução | Efeito colateral |
|---|---|
push(n: f64); | Empurre um número na pilha. |
add(); | Coloque dois números da pilha e empurre sua soma. |
subtract(); | Coloque dois números da pilha. Subtraia o primeiro do segundo e pressione o resultado. |
multiply(); | Coloque dois números da pilha e empurre seu produto. |
divide(); | Coloque dois números da pilha. Divida o segundo pelo primeiro e empurre o resultado. |
sign(); | Coloque um número da pilha. Se for maior ou igual a zero, empurre 1 , pressione -1 . |
allocate(); | Coloque um número da pilha e retorne um ponteiro para esse número de células livres na pilha. |
free(); | Coloque um número da pilha e vá para onde esse número aponta na memória. Coloque outro número da pilha e livre que muitas células neste local na memória. |
store(size: i32); | Coloque um número da pilha e vá para onde esse número aponta na memória. Em seguida, os números size do pop fora da pilha. Armazene esses números em ordem inversa neste local na memória. |
load(size: i32); | Coloque um número da pilha e vá para onde esse número aponta na memória. Em seguida, pressione o número size de células de memória consecutivas na pilha. |
call(fn: i32); | Ligue para uma função definida pelo usuário pelo ID atribuído pelo compilador. |
call_foreign_fn(name: String); | Chame uma função estrangeira por seu nome na fonte. |
begin_while(); | Comece um loop de tempo. Para cada iteração, coloque um número da pilha. Se o número não for zero, continue o loop. |
end_while(); | Marque o fim de um loop de um tempo. |
load_base_ptr(); | Carregue o ponteiro base da estrutura de pilha estabelecida, que é sempre menor ou igual ao ponteiro da pilha. As variáveis são armazenadas em relação ao ponteiro base para cada função. Portanto, uma função que define x: num e y: num , x pode ser armazenada em base_ptr + 1 e y pode ser armazenada em base_ptr + 2 . Isso permite que as funções armazenem variáveis na memória dinamicamente e conforme necessário, em vez de usar locais estáticos de memória. |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | Retire o número de células arg_size fora da pilha e guarde -as. Em seguida, ligue para load_base_ptr para retomar o quadro da pilha pai quando essa função terminar. Empurre local_scope_size Número de zeros na pilha para abrir espaço para as variáveis da função. Finalmente, empurre as células de argumento armazenadas de volta à pilha, pois foram originalmente ordenadas. |
end_stack_frame(return_size: i32, local_scope_size: i32); | return_size número de células fora da pilha e guarde -as. Em seguida, pop local_scope_size número de células fora da pilha para descartar a memória do quadro da pilha. Coloque um valor da pilha e armazene -o no ponteiro base para retomar o quadro da pilha pai. Finalmente, empurre as células de valor de retorno armazenadas de volta à pilha, pois foram originalmente ordenadas. |
Usando apenas essas instruções, o Oak é capaz de implementar abstrações de nível ainda mais alto do que C pode oferecer !!! Isso pode não parecer muito, mas é muito poderoso para um idioma tão pequeno.
A sintaxe do carvalho é fortemente inspirada na linguagem de programação da ferrugem.
As funções são declaradas com a palavra -chave fn e são sintaticamente idênticas às funções de ferrugem, com exceção da semântica return . Além disso, os tipos e constantes definidos pelo usuário são declarados com as palavras -chave type e const respectivamente.
Semelhante aos atributos externos de Rust, Oak apresenta muitas bandeiras de tempo de compilação. Alguns deles são demonstrados abaixo, juntamente com outros recursos de carvalho.

Então, como exatamente o compilador de carvalho funciona?
Achatar estruturas em suas funções
putnumln(*bday.day) torna -se putnumln(*Date::day(&bday)) . Este é um processo bastante simples.Calcule o tamanho do tipo de todas as operações
// `3` is the size of the structure on the stack
fn Date :: new ( month : 1 , day : 1 , year : 1 ) -> 3 {
month ; day ; year
}
// self is a pointer to an item of size `3`
fn Date :: day ( self : & 3 ) -> & 1 { self + 1 }
fn main ( ) -> 0 {
let bday : 3 = Date :: new ( 5 , 14 , 2002 ) ;
}Calcule estaticamente a pegada de memória do programa
Converter expressões e declarações de carvalho em instruções de IR equivalentes
Existem muitas circunstâncias diferentes em que uma chamada de método é válida. Os métodos sempre levam um ponteiro para a estrutura como um argumento . No entanto, um objeto que chama um método não precisa ser um ponteiro . Por exemplo, o código a seguir é válido: let bday: Date = Date::new(); bday.print(); . A variável bday não é um ponteiro, mas o método .print() ainda pode ser usado. Aqui está o porquê.
Quando o compilador vê uma chamada de método achatada, ele precisa encontrar uma maneira de transformar a "expressão da instância" em um ponteiro. Para variáveis, isso é fácil: basta adicionar uma referência! Por exemplo, expressões que já são dicas, é ainda mais fácil: não faça nada! Para qualquer outro tipo de expressão, porém, é um pouco mais detalhado. O compilador se infiltra em uma variável oculta para armazenar a expressão e, em seguida, compila a chamada do método novamente usando a variável como expressão da instância. Muito legal, certo?
Montar as instruções de IR para um alvo
Target . Se você implementar cada uma das instruções do IR para o seu idioma usando a característica Target , o OAK poderá compilar automaticamente até o seu novo idioma de programação ou montagem! Sim, é tão fácil quanto parece! Para permitir que os usuários leiam a documentação de bibliotecas e arquivos sem acesso à Internet, o OAK fornece o subcomando doc . Isso permite que os autores adicionem atributos de documentação ao seu código para ajudar outros usuários a entender seu código ou API sem precisar examinar a fonte e ler comentários.
Aqui está algum código de exemplo.
# [ std ]
# [ header ( "This file tests Oak's doc subcommand." ) ]
# [ doc ( "This constant is a constant." ) ]
const CONSTANT = 3 ;
// No doc attribute
const TEST = CONSTANT + 5 ;
# [ doc ( "This structure represents a given date in time.
A Date object has three members:
|Member|Value|
|-|-|
|`month: num` | The month component of the date |
|`day: num` | The day component of the date |
|`year: num` | The year component of the date |" ) ]
struct Date {
let month : num , day : num , year : num ;
# [ doc ( "The constructor used to create a date." ) ]
fn new ( month : num , day : num , year : num ) -> Date {
return [ month , day , year ] ;
}
# [ doc ( "Print the date object to STDOUT" ) ]
fn print ( self : & Date ) {
putnum ( self ->month ) ; putchar ( '/' ) ;
putnum ( self ->day ) ; putchar ( '/' ) ;
putnumln ( self ->year ) ;
}
}
# [ doc ( "This function takes a number `n` and returns `n * n`, or `n` squared." ) ]
fn square ( n : num ) -> num {
return n * n
}
fn main ( ) {
let d = Date :: new ( 5 , 14 , 2002 ) ;
d . print ( ) ;
} E aqui está o exemplo de uso do subcomando doc para imprimir a documentação formatada no terminal.

Para obter a construção do desenvolvimento atual, clone o repositório e instale -o.
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . Para obter a compilação de liberação atual, instale em Crates.io.
# Also works for updating oakc
cargo install -f oakcEm seguida, os arquivos de carvalho podem ser compilados com o binário OAKC.
oak c examples/hello_world.ok -c
main.exeC back -end - qualquer compilador GCC que suporta C99
Vá back -end - Compilador Golang 1.14
Back -end de texto datilografado - Compilador TypeScript 3.9