컴퓨터

난해한 프로그래밍 언어 ABF(ASCII BrainF**k)

DS1TPT 2021. 2. 27. 17:06

 ABF는 브레인퍽을 기반으로 한 난해한 프로그래밍 언어입니다. ABF는 BF의 확장 방언으로 취급할 수 있습니다.

ABF의 특징과 기능은 아래와 같습니다.

  • 브레인퍽의 명령어 8개를 포함
  • 다양한 데이터 타입 지원(schar, int, uchar, uint, double)
  • 논리연산, 비교연산, 사칙연산, 나머지연산, 비트단위 연산, 비트-쉬프트 연산 지원
  • 인터프리터로의 에러 코드 핸들링 지원(허접합니다)
  • BASIC 스타일의 줄 번호 및 jump 지원
  • Jump를 통한 서브루틴 구현 지원
  • 메모리 제로필 명령 내장
  • 훨씬 복잡한(?) 문법
  • Jump를 통한 코드 스파게티화 지원
  • 라이브러리 지원(인터프리터에서 지원함)
  • 무작위 숫자 생성 지원
  • ASCII 코드 타이핑 없이 문자열 저장 지원

 

 현재 ABF 인터프리터 버전은 1.12입니다. 윈도우, 대부분의 리눅스 및 유닉스 기반 시스템에서 실행할 수 있습니다. 인터프리터는 AMD64(x86_64) 환경에서 Debian 리눅스, Fedora 리눅스, 윈도우 10에서 구동됨을 확인했으며 라즈베리파이(ARM) 리눅스에서 구동됨을 확인했습니다(사용한 컴파일러: GCC, Clang). 윈도우의 경우 실행파일(Release에 있음)을 받아서 구동하시면 되며, 32비트 프로그램으로 빌드 되었습니다. 그 이외에 환경에서는 직접 소스코드를 빌드하셔야 합니다.

 

인터프리터 실행파일(WIN32)과 소스 코드, 예제 프로그램은 아래에 링크에서 다운로드 받으실 수 있습니다.

github.com/DS1TPT/ABF_Lang

 

DS1TPT/ABF_Lang

ABF(ASCII BrainFuck), an esolang inspired by Brainfuck, with much more commands & features - DS1TPT/ABF_Lang

github.com


목차

  1. ABF 명령어 목록
  2. 인터프리터 전용 명령어 목록
  3. 문법
  4. 인터프리터 간단 소개
  5. ABF 프로그램 코드 예제

ABF 명령어 목록

 

명령어 리스트(영문)

 브레인퍽의 명령어 중 4개(+, -, ., ,)는 각각 i, d, p, g로 변경됐습니다. i와 d는 ABF에서 32비트 정수와 64비트 부동소수점 데이터의 값도 증감할 수 있습니다. 이는 현재 포인터 모드에 따릅니다.

예시로 Hello, world 프로그램을 번역하면 다음과 같습니다.

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++. (브레인퍽)

$iiiiiiii[>iiii[>ii>iii>iii>i<<<<d]>i>i>d>>i[<]<d]>>p>dddpiiiiiiippiiip>>p<dp<piiipddddddpddddddddp>>ip>iip ;(ABF로 번역, 간소화 없음. $는 포인터 모드를 char 타입으로 지정하는 명령어입니다)

$l8[>l4[>ii>l3>l3>iq-4d]>i>i>d>>i[<]<d]>>p>l-3pl7ppl3p>>p<dp<pl3pl-6pl-8p>>ip>iip ;(간소화된 번역)

 


인터프리터 전용 명령어 목록

 

--help: 도움말 출력

--license: 라이선스 정보 출력

--load name: 프로그램 파일 name을 로드

--run: 프로그램 실행

--print-code: 프로그램 파일의 소스 코드 출력

--close: 프로그램 파일을 닫음

--init: 파일 포인터와 명령어 버퍼를 제외하고 인터프리터를 초기화

--compile-direct code: 브레인퍽 코드 code를 ABF로 변환. 간소화 사용 안함

--compile code: 브레인퍽 코드 code를 ABF로 변환. 간소화 사용

--exit: 인터프리터 종료

--clrscr: 화면 지움

--import name: 라이브러리 파일 name을 불러옴

--close-lib: 라이브러리 파일을 닫음

++name: 라이브러리 전용. 서브루틴 name의 시작지점 지시자

--srend: 라이브러리 전용. 서브루틴 종료지점 지시자

--list-begin: 라이브러리 전용, 서브루틴 리스트 시점 지시자

--list-end: 라이브러리 전용, 서브루틴 리스트 종점 지시자

--record-start: 명령줄 입력 저장 기능 시작

--record-stop: 명령줄 입력 저장 기능 중지

--record-del: 명령줄 입력 저장 파일 삭제

--record-print: 명령줄 입력 저장 파일의 내용을 출력

--call name: 라이브러리에 정의된 서브루틴 name을 호출

--disp-internal-vars: 인터프리터 내부 변수 값 출력

--disp-subroutine-list: 라이브러리 서브루틴 리스트 출력

 

※ 위 명령에 name, code는 문자열 지시 명령어 '`'를 사용해서는 안됩니다.

※ 변환된 브레인퍽 코드는 명령줄 입력 저장 기능이 시작된 경우 파일에 저장됩니다.

 


문법

 

***주의*** 설명에서 “mem“은 메모리를 부호 없는 문자(1 Byte) 배열로 설명한 것입니다. 부동소수점은 IEEE 754 배정밀도 부동소수점 타입을 말합니다.

 

[인수 전달]

  • 모든 연산의 피연산자는 주소 X 및 Y의 값입니다.
  • 'w' 명령을 제외한 모든 명령에 전달되는 인수의 타입은 정수입니다. 부호 여부는 현재 포인터 모드에 따라 결정됩니다.
  • 'w' 명령의 인수는 문자, 정수 포인터의 경우 정수이며, 부동소수점 포인터의 경우 부동소수점(정수 표현 허용)으로 전달하여야 합니다. 부호 여부는 현재 포인터 모드에 따라 결정됩니다.
  • 인수는 연산자 명령 바로 뒤에 입력되어야 합니다. 공백은 허용하지 않습니다.
  • 인수의 크기는 문자, 정수 포인터의 경우 인터프리터를 구동하는 컴퓨터의 정수 사이즈에 따르며(일반적으로 4바이트), 부동소수점 포인터의 경우 부동소수점 데이터 크기(일반적으로 8바이트)에 따릅니다.
  • 인수가 여러개인 경우 ','로 구분되어야 합니다. 예시: a0,5 ; *mem && *(mem + 5)
  • 마지막이 아닌 인수에 인수값이 전달되지 않은 경우, 인수는 현재 포인터가 가르키는 주소로 설정됩니다. 예시: $+,1000 ; *(mem + addr) + *(mem + 1000)
  • 마지막 인수가 ','인 경우, 마지막 인수는 현재 포인터가 가르키는 주소로 설정됩니다. 예시: $m1000,, ; *(mem + addr) = *(mem + 1000)
  • 'h', 'l', 'q' 명령의 경우 인수의 타입은 포인터 모드에 관계 없이 부호 있는 데이터로 강제됩니다.

 

[데이터와 포인터 처리]

  • 문자 타입의 크기는 1바이트입니다. 정수와 부동소수점의 크기는 시스템이 사용하는 크기에 따릅니다. 일반적으로 정수형의 크기는 4바이트, 부동소수점의 크기는 8바이트입니다.
  • 부호 여부는 특정한 명령어의 입력이 없는 경우 유지됩니다.
  • 비트-쉬프트 연산을 제외한 모든 연산의 값은 버퍼에 저장됩니다. 버퍼의 값을 메모리에 저장하려면 '=' 명령을 사용해야 합니다. 버퍼의 값은 '=' 명령 사용 후에도 지워지지 않고 유지됩니다.
  • 주소는 포인터 타입에 관계 없이 바이트 단위로 설정됩니다.
  • 모든 데이터 읽기와 쓰기는 현재 포인터가 가르키는 주소부터 시작합니다. 예시: #f5w255: 주소 5에서 8까지 정수값 255 저장
  • 메모리에 저장된 데이터 타입의 검사는 행해지지 않습니다. 예시: '#f0w2147483647$f2w0의 결과: 0xffff007f(리틀-엔디언 시스템)
  • '\' (백슬래쉬) 명령은 현재 포인터 타입의 크기만큼 데이터를 정수형으로 출력합니다. 부동소수점 포인터를 사용하는 경우, 8바이트 정수형으로 출력합니다.
  • '_' 명령은 항상 부동소수점 크기 만큼의 데이터를 부동소수점으로 출력합니다.
  • '/' 명령은 부동소수점 포인터를 사용하지 않는 경우 나눗셈의 몫을 계산하여 저장합니다.
  • ':' 명령은 항상 부호있는 문자 타입으로 버퍼에 값을 저장합니다.
  • 'x' 명령은 버퍼를 사용합니다. 주소 X의 값이 버퍼에 남을 수 있습니다.
  • if문 실행을 위해 조건식을 체크하는 경우, 인터프리터는 이전에 'c' 명령으로 정의한 조건식을 사용합니다. 이때 포인터 타입은 if문 조건식 체크가 시작된 시점의 타입을 따릅니다.
  • 부동소수점 포인터가 활성화된 경우 비트-쉬프트를 제외한 모든 비트 단위 연산과 나머지 계산을 할 수 없습니다.
  • '=' 명령은 버퍼의 값을 현재 포인터 모드에 맞게 불러와 메모리에 저장합니다. 잘못된 포인터 타입으로 버퍼의 값을 읽는 경우 정의되지 않은 동작이 일어날 수 있습니다.

 

[명령어 용례와 문법]

  • if문 이전에 반드시 'c' 명령으로 조건식이 정의되어야 합니다.
  • 'c'명령은 인수가 3개 필요합니다. 인수 순서는 X, Y, 비교/논리연산자 입니다.
  • 'c'명령으로 인수를 전달하는 경우 연산자로 'n' 또는 '~'를 사용하는 경우, 인수 Y는 필요하지 않습니다. 하지만 인수는 반드시 3개가 전달되어야 하므로, 다음의 2가지 방법으로 인수를 전달할 수 있습니다: 방법1: c1,1,n(...) 방법2: c1,,n(...)
  • 'b'명령은 현재 루프문의 실행을 중단하고 현재 루프문의 ']' 다음 명령어부터 명령 실행을 시작합니다.
  • 'i'와 'd'는 데이터 값을 증감합니다. 포인터 타입의 영향을 받습니다.
  • 'e'와 '!' 명령은 비교 연산자입니다. 연산 결과가 참인 경우 1을, 거짓인 경우 0을 반환합니다.
  • 'h' 명령은 오류 코드를 인터프리터에 전달하고 프로그램 실행을 중단합니다. 오류 코드는 항상 부호 있는 정수 타입입니다. 이 명령은 파일 내에서만 사용할 수 있습니다.
  • 'j' 명령은 라이브러리가 아닌 파일 내에서만 사용할 수 있습니다. 이 명령을 사용하기 위해서는 BASIC 형식의 줄번호를 지정해야 합니다.
  • 줄번호는 필수 사항이 아닙니다. 'j' 명령을 사용하더라도 줄번호가 필요하지 않는 경우 생략할 수 있습니다.
  • 줄번호, ABF 명령어, 그리고 주석문은 인터프리터 전용 명령어와 함께 적혀서는 안됩니다.
  • 'l' 명령은 현재 주소의 데이터 값을 X만큼 증감합니다. 포인터 타입의 영향을 받으며, X가 음수인 경우 값을 감소시킵니다.
  • 'q'명령은 포인터가 가르키는 주소를 X만큼 증감합니다. 이 명령은 포인터 타입의 영향을 받지 않습니다.
  • 'r' 명령은 return 기능으로, 파일 내 서브루틴 종료 시 사용합니다(라이브러리 사용 금지). 이전에 'y' 명령으로 저장된 줄번호의 다음 줄로 돌아가 명령을 실행합니다.
  • 's' 명령은 '`'로 둘러싸인 문자열을 현재 주소부터 저장합니다. 문자열의 끝에는 널(null)문자가 저장됩니다. 예시: f0s`Hello, world!`
  • 't' 명령은 오류 없이 파일 실행을 정상 종료할 때 사용합니다.
  • 't' 명령 뒤에 오는 모든 문자는 주석문으로 처리됩니다.
  • 'u' 명령은 X초 동안 프로그램 실행을 멈춥니다.
  • 'x' 명령은 포인터 타입의 영향을 받습니다.
  • 'y' 명령은 BASIC 형식의 줄번호를 줄번호 버퍼에 저장합니다.
  • 중첩 if문과 중첩 루프문을 사용할 수 있습니다.
  • '/' 명령은 X의 값을 분자로, Y의 값을 분모로 합니다.
  • ';' 명령과 같은 줄에 있으며 해당 명령 뒤에 있는 모든 문자는 주석문으로 처리됩니다.
  • ':' 명령은 X의 값이 Y의 값보다 작은 경우 -1(0xff)를, 같은 경우 0을, 큰 경우 1(0x01)을 버퍼에 저장합니다.
  • 비트-쉬프트 연산자는 포인터 타입에 영향을 받습니다. 이 연산자는 다른 비트 연산자와는 달리 부동소수점 포인터가 활성화된 경우에도 사용할 수 있습니다.
  • 짝을 이루는 소괄호와 대괄호(각각 if문, 루프문)는 같은 줄에 있어야 합니다.
  • 인터프리터는 라이브러리 내부에서 jump 기능('j', 'r', 'y')을 사용하는 것을 막지 않으나, jump 기능은 절대로 라이브러리 내에서 사용해서는 안됩니다. 또한, 라이브러리 내에서 'z' 명령을 사용하는 것은 지양해야 합니다.
  • 대문자와 공백문자는 명령어가 아닙니다. 명령어 중간에 주석문을 써야할 필요가 있는 경우 대문자와 공백문자를 사용할 수 있습니다.

인터프리터 간단 소개

 

***ABF 인터프리터는 Apache License Version 2.0 라이센스 하에 배포됩니다***

 

[사양 정보]

  • 기본 프로그램 메모리 크기: 65535 바이트(64kB)
  • 명령 버퍼: 널문자 포함 4096 바이트(4kB)
  • 파일 길이 제한: 널문자 포함 1024 바이트(1kB)
  • 정수형 크기: 4 바이트(시스템에 따라 다를 수 있음)
  • 부동소수점 크기: 8 바이트(시스템에 따라 다를 수 있음)
  • 지원 데이터 형식: 부호 있는 문자, 부호 없는 문자(바이트), 정수, 부호 없는 정수, 배정밀도 부동소수점

 

WIN32 인터프리터는 x86 또는 AMD64 윈도우에서 실행될 수 있습니다. 만약 x86 또는 AMD64 환경이 아니거나, 유닉스, 리눅스 등의 운영체제를 사용하는 경우 직접 소스 코드를 빌드하셔야 합니다.

 

 인터프리터는 1개의 인수를 필요로 합니다. 인수는 실행할 프로그램 파일의 경로 및 이름입니다. 인수가 주어진 경우 인터프리터는 프로그램을 즉시 실행하며, 인터프리터 정보는 출력하지 않습니다. 만약 인수 없이 인터프리터를 실행하는 경우 프롬프트 모드로 시작됩니다. 프롬프트 모드에서는 명령어를 실시간으로 입력하여 프로그램의 작동을 확인할 수 있으며, 프로그램과 라이브러리 파일을 불러와 실행할 수도 있습니다.

윈도우의 경우 명령줄에 abf_lang_intrpr.exe c:\abf\pi.abf 를 입력하면 c:\abf\ 경로의 pi.abf 파일이 실행됩니다.

리눅스의 경우 ./abf_lang_Intrpr /abf/pi.abf 와 같이 입력합니다.

 

 인터프리터가 프롬프트 모드로 진입한 경우 현재 시스템 속성을 표시합니다. 그리고 명령줄에 READY(0,00000)>> 가 표시됩니다. 앞의 숫자는 포인터 모드(0: char, 1: int, 2: double, 3: uchar, 4: uint)이며, 뒤의 숫자는 현재 포인터가 가르키는 메모리의 10진수 주소입니다.

 인터프리터가 명령 실행 도중 구문 오류나 기타 오류를 발견한 경우 ?[오류 메시지] 형식으로 오류 메시지를 출력하고 실행을 중단합니다. 구문 오류의 경우 "cmd at 0, line 0"가 출력될 수 있습니다. cmd at 부분이 0인 경우 처음 명령어를 의미하며, line이 0인 경우 명령줄에 프로그램을 입력하여 실행한 것입니다. 프로그램 파일을 불러와 실행하면 line은 0으로 표시되지 않습니다.

 정수형 또는 부동소수점 포인터가 활성화된 상태에서 메모리에 값을 입력하는 경우, 인터프리터는 메모리 범위를 넘지 않는지 검사합니다. 또한, 'w' 명령으로 값을 입력하는 경우 포인터가 정수형 또는 부동소수점 타입이면 입력하려는 값의 범위를 검사하지 않습니다.

 인터프리터는 버퍼의 값을 '='와 같은 명령으로 사용했다 하더라도 값을 지우지 않도록 되어있습니다. 따라서 한번 연산한 값을 메모리의 여러 주소에 저장할 수 있습니다.

 ABF의 명령어 중 'g', 'k'의 구현을 위해 MSVC의 비표준 함수인 _getche()와 _getch()가 사용되었습니다. POSIX-호환 시스템의 경우 Termios를 이용해 g와 k를 구현했습니다. 'u' 또한 시스템 콜을 사용하기 때문에 비표준 함수를 사용하여 구현했습니다(sleep() 또는 _Sleep()). 만약 POSIX 호환이 되지 않으며 MS의 윈도우를 사용하지 않는 경우, 해당 명령어들을 위해 함수를 직접 구현하셔야 합니다.


ABF 프로그램 코드 예제

 

Press any key to continue 프로그램, s 명령어 미사용, 메모리 주소 0 사용

$f0w10pw13pw32pw80pw114pw101pw115ppw32pw97pw110pw121pw32pw107pw101pw121pw32pw116pw111pw32pw99pw111pw110pw116pw105pw110pw117pw101pw46pppw32pgw8pw32pw0r

 

바이트 값을 16진수로 출력하는 프로그램, 인수 주소 0, 임시 메모리 주소 1 - 6 사용

"$f1w16>%0,1=>>/0,1=<%4,1=f4w48>w48>w58f2[df5ic5,6,e(w65)f2]f3[df4ic4,6,e(w65)f3]f4p>p>w0<w0<w0<w0<w0<w0<r

 

바이트 값을 2진수로 출력하는 프로그램, 인수 주소 0, 임시 메모리 주소 1 - 5 사용

"$f4w0>w1<\\\\\\\f4w8f3w2</0,3=<%0,3=\f4ppf2[<%2,3=\>/2,3=f4ppf2c2,5,e(b)]\ f1w0>w0>w0>w0>w0r

 

jump 기능을 사용하는 원주율 계산 프로그램(파일 실행만 가능, Nilakantha 급수 사용)

1 zf0@w2.0f8w3.0f16w4.0f24w4.0f32w0.0f40w3.0$f48w13>@w8000.0

10 f32*0,8=*16,32=/24,32=f40+40,32=$f48p@f40_

20 f0iif8iif16ii

25 c16,49,e(j100)

30 f32*0,8=*16,32=/24,32=f40-40,32=$f48p@f40_

40 f0iif8iif16ii

45 c16,49,e(j100)

50 j10

100 '$zf0t

h1