C 프로그래밍 언어에 대한 무한히 휴대용 대안.

"무료"를 기억하는 사람들에게 오크는 본질적으로 그 프로젝트의보다 강력하고 높은 수준의 버전입니다. 참나무의 목표는 프론트 엔드에서 가능한 한 높은 수준이지만 백엔드에서 가능한 한 작고 낮은 수준이어야합니다.
나는 일을 찾고있는 대학에서 신선한 고등학교 졸업생이자 신입생입니다. 당신이 내 프로젝트를 즐기고 있다면, 커피를 사서 나를 지원하는 것을 고려하십시오!
오크의 미친 휴대 성의 핵심은 엄청나게 컴팩트 한 백엔드 구현입니다. Oak의 백엔드에 대한 코드는 100 줄의 C 미만으로 표현 될 수 있습니다. 이러한 작은 구현은 중간 표현의 작은 명령 세트로 인해 가능합니다. Oak 's IR은 17 가지 지침 으로만 구성됩니다. 그것은 Brainfuck과 동등합니다!
오크의 백엔드는 매우 간단하게 기능합니다. 모든 명령어는 메모리 테이프 에서 작동합니다. 이 테이프는 본질적으로 정적 배열의 이중 정제 플로트입니다.
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`변수가 함수에 정의되면 가상 머신의 현재 기본 포인터에 비해 정적 위치가 제공됩니다. 따라서 함수가 호출되면 기능 변수의 공간이 스택에 할당되고 기본 포인터 가이 새로운 공간을 사용하도록 증가합니다. 그런 다음 컴파일러는 변수를 코드의 나머지 부분에서 기본 포인터 오프셋에 추가하여 변수를 대체합니다!
또한 메모리 테이프는 스택 및 힙 으로 기능합니다. 모든 프로그램 변수에 대한 공간이 지정된 후 스택에 사용되는 메모리가 시작됩니다. 스택은 프로그램 전체에서 데이터로 성장 하고 줄어 듭니다 . 예를 들어 두 숫자가 합산되면 스택에서 튀어 나와 결과로 대체됩니다. 마찬가지로, 힙은 프로그램 전체에 걸쳐 성장하고 줄어 듭니다. 그러나 힙은 동적으로 할당 된 데이터에 사용됩니다. 컴파일 시간에 알려지지 않은 메모리 풋 프린트가있는 정보.
이제 오크 백엔드가 근본적으로 운영되는 방식을 이해 했으므로 여기에 완전한 지침 세트가 있습니다!
| 지침 | 부작용 |
|---|---|
push(n: f64); | 스택에 숫자를 밀어냅니다. |
add(); | 스택에서 두 개의 숫자를 튀기고 합계를 누릅니다. |
subtract(); | 스택에서 두 개의 숫자를 튀어 나옵니다. 첫 번째를 두 번째로 빼고 결과를 누르십시오. |
multiply(); | 스택에서 두 개의 숫자를 튀기고 제품을 밀어 넣으십시오. |
divide(); | 스택에서 두 개의 숫자를 튀어 나옵니다. 두 번째를 첫 번째로 나누고 결과를 누릅니다. |
sign(); | 스택에서 숫자를 꺼냅니다. 0 인 경우 0이면 1 누르면 -1 누르십시오. |
allocate(); | 스택에서 숫자를 꺼내고 힙의 자유 셀 수에 대한 포인터를 반환하십시오. |
free(); | 스택에서 숫자를 꺼내고이 숫자가 메모리의 위치로 이동하십시오. 스택에서 다른 번호를 꺼내고이 위치에있는 많은 셀을 메모리에서 제거하십시오. |
store(size: i32); | 스택에서 숫자를 꺼내고이 숫자가 메모리의 위치로 이동하십시오. 그런 다음 스택에서 팝 size 숫자가 떨어집니다. 이 숫자를 메모리 에서이 위치에 역 순서로 저장하십시오. |
load(size: i32); | 스택에서 숫자를 꺼내고이 숫자가 메모리의 위치로 이동하십시오. 그런 다음 연속 메모리 셀의 size 수를 스택에 푸시하십시오. |
call(fn: i32); | 컴파일러가 할당 된 ID로 사용자 정의 된 기능을 호출하십시오. |
call_foreign_fn(name: String); | 소스에서 이름으로 외국 기능을 호출하십시오. |
begin_while(); | While Loop을 시작하십시오. 각 반복마다 스택에서 숫자를 꺼냅니다. 숫자가 0이 아닌 경우 루프를 계속하십시오. |
end_while(); | while 루프의 끝을 표시하십시오. |
load_base_ptr(); | 확립 된 스택 프레임의베이스 포인터를로드합니다. 변수는 각 함수의 기본 포인터에 대해 저장됩니다. 따라서 x: num 및 y: num , x 정의하는 함수는 base_ptr + 1 에 저장 될 수 있고 y base_ptr + 2 에 저장 될 수 있습니다. 이를 통해 함수는 정적 메모리 위치를 사용하지 않고 메모리에 동적으로 그리고 필요에 따라 변수를 동적으로 저장할 수 있습니다. |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | 스택에서 arg_size 수의 셀을 팝업하여 저장하십시오. 그런 다음 load_base_ptr 호출 하여이 함수가 종료되면 상위 스택 프레임을 재개하십시오. local_scope_size 수의 0을 스택에 눌러 함수 변수를위한 공간을 만듭니다. 마지막으로, 저장된 인수 셀을 원래 주문한대로 스택에 다시 밀어 넣으십시오. |
end_stack_frame(return_size: i32, local_scope_size: i32); | return_size 스택에서 셀 수를 꺼내어 저장하십시오. 그런 다음 스택 프레임의 메모리를 폐기하기 위해 스택에서 셀 수를 꺼내 local_scope_size . 스택에서 값을 꺼내어베이스 포인터에 보관하여 부모 스택 프레임을 재개하십시오. 마지막으로 저장된 리턴 값 셀을 원래 주문한대로 스택에 다시 밀어 넣으십시오. |
이러한 지침 만 사용하여 OAK는 C가 제공 할 수있는 것보다 훨씬 높은 수준의 추상화를 구현할 수 있습니다 !!! 그것은별로 들리지 않을 수도 있지만,이 작은 언어는 매우 강력합니다.
오크의 구문은 Rust 프로그래밍 언어에서 크게 영감을 받았습니다.
함수는 fn 키워드로 선언되며 return 의미론을 제외하고는 녹 기능과 구문 적으로 동일합니다. 또한 사용자 정의 유형 및 상수는 각각 type 및 const 키워드로 선언됩니다.
Rust의 외부 속성과 유사하게 Oak는 많은 컴파일 타임 플래그를 소개합니다. 이 중 일부는 아래에 다른 오크 기능과 함께 아래에 있습니다.

그렇다면 오크 컴파일러는 정확히 어떻게 작동합니까?
구조를 기능으로 평평하게하십시오
putnumln(*bday.day) 은 putnumln(*Date::day(&bday)) 가됩니다. 이것은 매우 간단한 과정입니다.모든 작업 유형의 크기를 계산하십시오
// `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 ) ;
}프로그램의 메모리 풋 프린트를 정적으로 계산합니다
참나무 표현과 명세서를 동등한 IR 지침으로 변환하십시오
메소드 호출이 유효한 여러 환경이 있습니다. 방법은 항상 인수로 구조에 대한 포인터를 취합니다 . 그러나 메소드를 호출하는 객체는 포인터 일 필요가 없습니다 . 예를 들어, 다음 코드는 유효합니다. let bday: Date = Date::new(); bday.print(); . 변수 bday 포인터가 아니지만 .print() 를 여전히 사용할 수 있습니다. 이유는 다음과 같습니다.
컴파일러가 평평한 메소드 호출을 볼 때 "인스턴스 표현식"을 포인터로 변환하는 방법을 찾아야합니다. 변수의 경우, 이것은 쉽습니다. 참조를 추가하십시오! 예를 들어 이미 포인터 인 표현은 훨씬 쉽습니다. 아무것도하지 마십시오! 그러나 다른 종류의 표현에는 조금 더 장점입니다. 컴파일러는 숨겨진 변수로 몰래 표현식을 저장 한 다음 변수를 인스턴스 표현식으로 사용하여 메소드 호출을 다시 컴파일합니다. 꽤 멋져요?
대상에 대한 IR 지침을 조립하십시오
Target 이라는 특성이 있습니다. Target 특성을 사용하여 언어에 대한 IR 지침을 각각 구현하면 Oak는 새로운 프로그래밍 또는 어셈블리 언어로 자동 컴파일 할 수 있습니다! 예, 소리만큼 쉽습니다! 사용자가 인터넷에 액세스하지 않고 라이브러리 및 파일의 문서를 읽을 수 있도록 OAK는 doc 하위 통과를 제공합니다. 이를 통해 저자는 코드에 문서 속성을 추가하여 다른 사용자가 소스를 살펴보고 댓글을 읽지 않고도 코드 또는 API를 이해하도록 돕습니다.
다음은 몇 가지 예제 코드입니다.
# [ 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 ( ) ;
} 다음은 doc 서브 명령의 예제 사용이 서식 된 문서를 터미널에 인쇄합니다.

현재 개발 빌드를 얻으려면 저장소를 복제하고 설치하십시오.
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . 현재 릴리스 빌드를 얻으려면 Crates.io에서 설치하십시오.
# Also works for updating oakc
cargo install -f oakc그런 다음 오크 바이너리와 함께 오크 파일을 컴파일 할 수 있습니다.
oak c examples/hello_world.ok -c
main.exeC 백엔드 - C99를 지원하는 모든 GCC 컴파일러
백엔드 - Golang 1.14 컴파일러로 이동하십시오
TypeScript 백엔드 - TypeScript 3.9 컴파일러