Forth

덤프버전 : r20240101


프로그래밍 사이트 선정 프로그래밍 언어 순위 목록

⠀[ IEEE Spectrum 2021 ]⠀
{{{#!wiki style="display: inline-block; margin: 0 0 -5px; min-width: 25%"
⠀[ Stack Overflow 2022 ]⠀
{{{#!wiki style="display: inline-block; margin: 0 0 -5px; min-width: 25%">
⠀[ TIOBE 2023 ]⠀

프로그래밍 언어 목록 · 분류 · 문법




." Hello, world!"


파일:forth.png

1. 개요
1.1. 명칭
2. 역사
3. 문법
3.1. 사칙연산
3.2. 단어
3.2.1. 지역변수
3.2.2. 반환 스택
3.3. 제어문
3.3.1. 조건문
3.3.1.1. if 문
3.3.1.2. case 문
3.3.2. 반복문
3.3.2.1. begin~ 문
3.3.2.2. 횟수 반복문
3.3.2.2.1. ?do~ loop 문
3.3.2.2.2. for~ next 문
3.4. 메모리
3.4.1. 전역 변수
3.4.2. create와 allot
3.4.3. 메모리 할당
4. 구현체
4.1. GForth
4.2. SwiftForth, SwiftX
4.3. pForth
4.4. 기타 구현체
5. 외부 링크


1. 개요[편집]


포스 표준 홈페이지

찰스 무어(Charles H. Moore)에 의해 개발된 명령형, 절차적, 스택 기반 프로그래밍 언어이다. 반영(Reflective), 연결(Concatenative) 프로그래밍 패러다임에도 속한다. 주로 임베디드 장치를 위해서 쓰여지지만 PC를 타겟으로 한 프로그램도 작성된다.

포스는 스택 기반 패러다임으로, 입력된 데이터가 스택에 쌓이며 이를 처리한다. 때문에 코드 상으로 보면 LISP와는 정 반대인 역폴란드 표기법으로 수식을 작성하게 된다.

확장자는 .fs .fth .4th .forth 등이 쓰인다. .fs는 F##의 파일 확장자로 쓰이지만 제일 많이 쓰이는 포스 확장자 역시 .fs으로, Forth Source 혹은 Forth Script라는 뜻이다.


1.1. 명칭[편집]


Forth라는 이름은 4세대(차세대) 프로그래밍을 뜻하는 FOURTH로 지어졌으나, 당시 사용하던 시스템이 파일 이름을 5자로 제한했기 때문에, FORTH로 쓰게 됐다. 옛날에는 포스의 이름을 전부 대문자로 적었기 때문에 현재도 FORTH로 쓰는 경우가 많다.

실제로는 3세대 프로그래밍 언어로 분류된다.


2. 역사[편집]


포스는 1970년대 이전부터 찰스 무어에 의해 개발되기 시작하였다.

널리 보급된 이후, 1979년과 1983년에 FORTH-79. FORTH-83으로 정리되었고, 이는 1994년 ANSI에 의해 표준화되었다.
포스는 가볍고 단순했기 때문에 1980년대 상당한 인기를 끌었다.

1987년 KAIST의 변종홍은 '늘품'이라는 한국어 포스를 개발했다. 포스의 확장성으로 단어를 한국어로 바꿔 만든 것이다. 이는 당시 마이크로소프트웨어, 과학동아 등지에도 소개됐다.

이후 포스의 스택 기반 프로그래밍이라는 개념은 Factor, Joy 언어, 그리고 포스트스크립트에도 영향을 주었다.


3. 문법[편집]


주로 쓰이는 구현체인 GForth의 매뉴얼에서 영어로 된 Forth 튜토리얼이 있어서 포스에 대해서 공부할 수 있다. 자세한 설명은 이곳에서 배우는 것이 좋다. GForth에서만 사용 가능한 것들도 포함되어있으나, 표준을 같이 병기해두므로 큰 문제는 없다.


3.1. 사칙연산[편집]


포스는 역폴란드 표기법으로 식을 기술한다. 단어들은 공백으로 구분된다.
'2 + 3 × 5'의 계산 결과를 출력하려면 다음과 같이 쓴다. 중위 표기법으로 적은 식에는 사칙연산의 우선 순위가 존재하기 때문에, 곱셈을 먼저 계산해주어야 한다.
2 3 5 * + .

  1. 스택에 값을 삽입해 (2, 3, 5)의 값이 있다.
  2. 연산자 * 에 의해 스택에는 (2, 15)의 값이 있다.
  3. 연산자 + 에 의해 스택에는 (17)의 값이 있다.
  4. 명령어 . 에 의해 스택의 가장 위에 있는 값 17이 출력된다. 스택에는 아무 값도 없다.

'( 2 + 3 ) × 5'를 계산할 때, 포스는 중위 표기법이 아니기 때문에 괄호가 필요하지 않다. 계산의 우선 순위를 고려하여 값을 삽입, 계산하면 된다.
2 3 + 5 * .

  1. 스택에 값을 삽입해 (2, 3)의 값이 있다.
  2. 연산자 + 에 의해 스택에는 (5)의 값이 있다.
  3. 스택에 값을 삽입해 (5, 5)의 값이 있다.
  4. 연산자 * 에 의해 스택에는 (25)의 값이 있다.
  5. 명령어 . 에 의해 스택의 가장 위에 있는 값 25가 출력된다. 스택에는 아무 값도 없다.


3.2. 단어[편집]


포스는 단어를 정의하여 그 단어가 무슨 행동을 할 지를 정한다. 이는 다른 언어에서의 함수처럼 쓰인다. 차이점은 다른 언어에서의 함수는 입력되는 매개변수와 출력값이 존재하지만, 포스는 스택 기반 언어이기에 코드에 기술한 만큼의 스택을 소비하고 값을 스택에 남긴다. 포스는 이러한 스택의 변화를 단어를 정의할 때 소괄호를 활용하여 주석으로 적어 놓는다.

포스의 장점은 확장성으로 손꼽히는데, 그 이유는 단어를 정의함에 있어서 큰 제한이 없기 때문이다. 기존의 단어를 다시 재정의할 수도 있기 때문이다. 예컨대 연산자 + 를 다른 역할을 하게 만들거나, '더하기'란 이름의 연산자를 새로 만들어 더하기의 역할을 하게 만들 수 있다. 다만 포스는 단어의 대소문자를 구별하지 않는 특징이 있고, 포스 자체가 앞서 설명했듯 공백으로 단어를 구분하기 때문에 단어 중간에는 공백이 들어갈 수 없다.

: addprint ( n1 n2 -- n1+n2 )
    + dup .
;

위의 코드에서는 두 정수를 더하고 출력한 뒤, 다시 그 값을 스택에 남기는 단어인 addprint를 정의한다. : (콜론)은 단어를 정의하는 단어이다. 세미콜론 뒤에는 단어의 이름을 기술하고, ; (세미콜론)이 오기 전까지 단어의 행동을 기술한다.
  1. : 를 통해 단어를 정의하기 시작한다.
  2. 단어의 이름은 addprint이다.
  3. 소괄호를 통해 스택의 변화를 주석으로 기술해놓았다.
    1. \-\- 앞은 소비할 값, 뒤는 내놓을 값이다.
    2. 두 정수 n1과 n2를 소비할 것임을 알려준다.
    3. n1과 n2를 더한 값을 내놓을 것임을 알려준다.
  4. 단어는 '+ dup .' 의 행동을 취한다.
    1. + 연산자를 통해 우선 스택의 두 값을 뽑아 더한다.
    2. dup 명령어를 통해 스택의 맨 위에 있는 값, 즉 앞에서 더한 값을 복사해 스택의 맨 위에 추가한다.
    3. . 명령어를 통해 스택의 맨 위에 있는 값을 뽑으면서 출력한다. dup을 하였으므로 스택에는 더한 값이 여전히 남았다.
  5. ; 를 통해 단어의 정의를 마친다.

스택 변화에 대한 주석으로 쓰이는 소괄호에는 상당히 구체적인 정보를 적을 수 있고 그것은 어느 정도의 형식을 갖고 있다. 하지만 실제로 스택에 미치는 영향은 없다.


3.2.1. 지역변수[편집]


단어에서 사용할 값이 여러 번의 소비를 거치는 경우에, dup 등의 스택 조작 명령어를 이용해 스택을 이용하는 것은 매우 번거로운 일이다. 따라서 단어에서의 지역 변수를 정의할 수 있다. 이는 다른 언어에서의 함수의 매개변수와 비슷한 사용법이다. Locals로 칭한다.

이를 이용해 앞서 정의한 addprint를 개조해본다.

: addprint2 { a b -- a+b }
    a .
    '+' emit space
    b .
    ." = "
    a b + dup .
;


  1. 중괄호 내에 들어가서 공백으로 구분된 단어들은 지역변수가 될 것이다.
여기서 a가 먼저 들어간 값, b가 나중에 들어간 값, 즉 스택의 맨 위에 있는 값이다.
중괄호 안에서 -- 뒤는 주석이다. 스택에 남는 값을 주석으로 기술한다.
  1. a 로 지역변수 a의 값을 스택에 넣는다. 이후 . 명령어로 출력했다. 이후 b도 마찬가지로 출력된다.
  2. '+' 는 문자 리터럴로 해당하는 문자의 아스키 코드값을 스택에 넣었다.
이후 emit 명령어로 그 코드에 맞는 문자를 출력한다.
  1. space 명령어로 공백 하나를 출력했다.
  2. ." 명령어는 공백 이후 " (쌍따옴표)까지 올 문자열을 출력한다.
따라서 "(a의 값) + (b의 값) = "의 문자열이 화면에 출력된다.
  1. a b 로 a와 b의 값을 스택에 넣고, 이전처럼 계산해서 출력한다.
스택에는 a와 b를 더한 값이 남는다.

예시로 적은 코드에 개행이 많이 되어있다. 개행엔 특별한 의미가 있는 것이 아니고 다른 공백과 하는 일이 같지만, 코드를 보기 쉽게 해 주는 장점이 있다. 실제로 코드를 한 줄로 이어 쓸 수도 있다. 간단한 단어의 경우에는 그렇게 정의하는 것이 선호된다.


3.2.2. 반환 스택[편집]


전통적으로 포스에서는 지역변수를 사용하지 않았다. 대신, 반환 스택(Return Stack)이라는 별도의 스택을 사용하였다.

예를 들어 스택의 맨 위에 있는 두 쌍의 값의 위치를 서로 바꾸는 단어인 '2swap'은 다음과 같이 구현할 수 있다.

: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
    rot >r rot r>
;


  1. rot으로 스택을 조작해 마지막 3개의 원소의 순서를 회전시켜 (x1, x2, x3, x4)에서 (x1, x3, x4, x2)가 된다.
  2. \>r로 반환 스택에 값을 집어넣는다. 스택에는 (x1, x3, x4)가 있고, 반환 스택에는 (x2)가 있다.
  3. rot으로 인해 (x1, x3, x4)의 스택이 (x3, x4, x1)이 된다.
  4. r>로 반환 스택에서 값을 가져온다. 스택에는 (x3, x4, x1, x2)가 남는다. 의도한 대로 두 쌍의 값들의 위치가 서로 바뀌었다.

지역변수를 사용한다면 반환 스택을 사용할 이유가 없고, 문제가 발생할 수도 있으므로 동시에 사용하지 않는다. 또한, 반환 스택은 단어의 정의나 반복문이 끝나기 전에 모두 소비하여야 한다.


3.3. 제어문[편집]


포스에서는 제어문을 단어 정의 내에서만 사용할 수 있다. 제어문의 조건으로는 정수형 값이 쓰이는데, 0은 거짓, 0이 아닌 값은 참으로 취급된다. true 와 false 단어를 통해 참값을 표현할 수도 있다.


3.3.1. 조건문[편집]



3.3.1.1. if 문[편집]

if, else, then 단어로 조건 실행을 할 수 있다. gforth에선 then 대신 endif를 사용할 수 있다. 두 숫자 중 큰 숫자만을 남기는 단어 max를 만들어본다.
: max ( n1 n2 -- n )
    2dup
    < if
        drop
    else
        nip
    then
;

  1. 2dup 명령어가 스택의 두 원소를 그대로 복제한다.
  2. < 를 통해 스택의 윗 값이 더 크면 true, 그렇지 않으면 false가 스택에 남는다.
  3. if 는 스택에서 값을 뽑아 참일 때와 거짓일 때를 가려 조건 실행을 한다.
if 이후 else나 then이 오기 전까지의 코드는 참이면 실행된다.
참일 경우 drop 명령어를 통해 스택의 맨 윗 값을 버린다.
  1. else 이후 then이 오기 전까지의 코드는 거짓일 경우 실행된다.
이때 else는 꼭 없어도 된다. 거짓일 경우 nip 명령어를 통해 스택의 맨 위에서 바로 아랫 값을 버린다.
  1. then으로 조건문을 종료한다. gforth 구현체에선 then 대신 endif를 사용해도 좋고, 오히려 더 권장된다.


3.3.1.2. case 문[편집]

다른 언어에서의 switch ~ case 문과 유사하다.
case
    1 of ." one" endof
    2 of ." two" endof
    3 of ." three" endof
    2 2 + of ." four" endof
    ." other number"
endcase

위 코드는 case 문에서 스택에서 값을 뽑아 일치하는 값에 따라 코드를 실행한다.


3.3.2. 반복문[편집]



3.3.2.1. begin~ 문[편집]

begin으로 시작하는 반복문은 간단한 반복문이다. C언어나 다른 비슷한 언어에서의 무한루프, while문, do-whlie문에 해당한다. begin 단어는 일단 런타임에서 만나면 아무 일을 하지 않으며, 루프가 끝나면 점프해서 되돌아올 위치를 알려주는 역할을 한다.

0
begin
    dup . 1 +
again

begin ~ again 문은 무한 루프이다. 이 코드는 0부터 숫자를 계속 증가시켜 나가면서 출력하는 코드이다. 'Ctrl + C'를 키보드로 눌러 인터럽트를 일으켜 탈출할 수 있다.

0
begin
    dup . 1 +
    dup 10 >
until

이 코드는 0부터 10까지 출력한다. begin ~ until 문은 until문에서 조건을 판단하여 거짓이면 반복한다. 다른 언어에서의 do-while문과 비슷하다.

0
begin
    1 +
    dup 10 <
while
    dup .
repeat

이 코드는 1부터 9까지 출력한다. 우선 begin문 이후의 코드를 실행한 뒤에 while문에서 조건을 판단하여 이면 repeat까지 실행하고 반복한다. while에서 거짓일 경우 repeat 이후로 점프한다. 다른 언어에서의 while문과 비슷하다.


3.3.2.2. 횟수 반복문[편집]

다른 언어에서의 for문과 유사하다. 횟수 반복문의 특징으로는 반복문을 세는 지역변수가 자동으로 생성된다는 점이다. 둘러싸인 순서대로 i, j, k의 세 가지 지역변수가 생성되어 사용할 수 있다.


3.3.2.2.1. ?do~ loop 문[편집]

다음은 0부터 9까지 10개의 숫자를 출력하는 반복문이다.
10 0 ?do
    i .
loop



GForth에서 숫자의 증감을 조절하려면 loop를 사용하는 대신 +loop 나 -loop 를 사용하면 된다. 이 둘은 앞에 오는 값을 뽑으면서 그 값만큼 증감하게 된다. +loop는 부호 있는 정수값, -loop는 부호 없는 정수값을 사용하며 '-1 +loop'와 '1 -loop'는 동일한 작동을 한다.

다음은 10부터 1까지 10개의 숫자를 출력하는 반복문이다.
0 10 -do
    i .
1 -loop


또한 GForth에서는 ?do 대신에 +do 나 -do 를 사용할 수 있다. 이는 안전한 방법으로써, 반복문의 시작과 끝을 잘못 적어 방향이 이상한 경우 반복을 허용하지 않는다.

leave 를 사용하면 반복문을 도중에 빠져나오는 것이 가능하다. 다른 언어에서의 break 와 같다.
unloop exit 를 사용하는 것도 같은 효과를 가진다.


3.3.2.2.2. for~ next 문[편집]

GForth 구현체에서, 보다 간단한 반복문으로는 for~ next 문을 사용할 수 있다. 다음은 이를 활용한 10부터 0까지 11개의 숫자를 출력하는 반복문이다.
10 for
    i .
next



3.4. 메모리[편집]


포스의 메모리 사용은 다른 언어와 이질감이 있다. 기본적으로 포스 시스템의 메모리는 스택 방식으로 사용된다.


3.4.1. 전역 변수[편집]


포스의 전역 변수는 variable 로 가능하다.
variable v
v .
10 v !
v @ .


위 코드를 각 줄에 대해서 해설하면 다음과 같다.

  1. 전역 변수 v를 생성하였다.
  2. v를 입력하면 스택에는 전역 변수 v의 주소가 남는다. 온점으로 출력시켰다. 포스에서는 모든 전역 변수를 포인터로 접근하는 셈이다.
  3. 10을 v에 저장하였다. 느낌표는 값을 주소에 집어넣는다. C에서의 포인터 대입 연산과 같다.
  4. v를 입력하여 스택에 주소를 남긴 다음, 주소에서 값을 가져오는 @을 입력했다. 스택에는 v에 들어간 값인 10이 남는다. 이를 출력하였다.


3.4.2. create와 allot[편집]


create는 변수 단어를 생성한다. allot은 생성한 주소로부터 스택 방식으로 공간을 할당한다. 포스에서는 사전 할당(Dictionary Allocation)이라고 한다.

create arr1 3 cells allot
1 arr1 !
2 arr1 cell+ !
3 arr1 2 cells + !


  1. create로 arr1이라는 단어를 생성하였고, 3 셀 만큼의 공간을 할당하였다. 각 공간은 초기화되어있지 않은 상태이다. [?, ?, ?]
  2. 1을 arr1의 주소에 저장하였다. 기본적으로 첫 번째 공간을 가리킨다. [1, ?, ?]
  3. arr1의 주소에 한 셀 만큼을 더해서 그 주소에 2를 저장하였다. [1, 2, ?]
  4. arr1의 주소에 두 셀 만큼을 더해서 그 주소에 3을 저장하였다. [1, 2, 3]

공간을 할당하고 값을 집어넣는 건 매우 귀찮은 일이다. 할당과 값 초기화를 동시에 할 수도 있다.

create arr2 1 , 2 , 3 ,


정확히 동일한 작업을 하는 코드이다. 마치 C에서 배열을 선언할 때 값을 초기화하는 것과 동일하다.

here는 스택의 맨 위 주소, 즉 할당되고 남은 공간의 주소를 나타낸다. 기존에 할당된 메모리를 해제하려면, 음수 공간을 allot하면 된다. 이 때, 포스의 사전 할당 방식은 스택 지향이므로 특정 메모리 X를 해제하려면, X 이후에 할당한 메모리도 전부 해제해야 한다.

3.4.3. 메모리 할당[편집]


C언어에서는 malloc, realloc, free 등으로 힙 메모리에 대해서 동적 할당을 할 수 있다. 포스도 마찬가지로 가능하다.

allocate와 resize, free 단어가 같은 역할을 한다.

10 cells allocate throw
20 cells resize throw
free throw


  1. allocate로 10 셀 만큼의 공간으로 동적 할당하였다. throw는 에러 처리를 수행하고, 스택에는 할당한 공간의 시작 주소가 남는다.
  2. resize로 20 셀 만큼의 공간으로 해당 주소의 공간을 재 할당하였다. throw는 에러 처리를 수행하고, 스택에는 다시 할당된 공간의 시작 주소가 남는다.
  3. free로 주소의 공간을 할당 해제하였다. throw는 에러 처리를 수행한다.

주소에 대한 접근은 앞서 나온 !, @로 가능하다.

4. 구현체[편집]



4.1. GForth[편집]


홈페이지
GNU 프로젝트 페이지

GNU 프로젝트의 일부로서 관리되는 구현체이다. 프리웨어, 자유 소프트웨어이다. 1992년 중반 Bernd Paysan, Anton Ertl 그리고 Jens Wilke에 의해 개발되었다. GForth는 ANSI/200x Forth 표준을 준수하며, 다양한 프로세서의 리눅스, 마이크로소프트 윈도, 맥 OS, 안드로이드, 그리고 GForth EC 임베디드 시스템에서 구동된다.

GForth는 GCC로부터 직접적으로 빠르게, 혹은 간접적으로 스레드된 코드를 컴파일한다.


4.2. SwiftForth, SwiftX[편집]


홈페이지

Forth, Inc.로부터 개발되는, 네이티브 코드를 생성하는 상용 구현체이다.

SwiftForth는 32비트 x86 호환 CPU에서 리눅스, 마이크로소프트 윈도, 맥 OS에서 사용할 수 있으며 IDE가 제공, 사용할 수 있다. 디버깅 툴로 역 어셈블러, 역 컴파일러, 단계별 디버거가 포함된다.

SwiftX는 ARM을 비롯한 임베디드 시스템을 타겟으로 한 구현체이다.


4.3. pForth[편집]


홈페이지
Github

Phil Burk에 의해 C로 쓰여진 오픈 소스 구현체이며, 이식성이 특징이다. 리눅스, 마이크로소프트 윈도, 맥 OS와 많은 임베디드 시스템에서 사용할 수 있다.

리눅스 터미널 안드로이드 앱인 Termux의 패키지 저장소에서 이 구현체를 설치할 수 있다.


4.4. 기타 구현체[편집]


  • SP-Forth: 러시아에서 개발된 구현체.
  • Mecrisp: MSP430를 위한 구현체.
  • VFX Forth: 고속의 네이티브 코드를 생성하는 구현체.


5. 외부 링크[편집]


파일:크리에이티브 커먼즈 라이선스__CC.png 이 문서의 내용 중 전체 또는 일부는 2023-10-26 06:24:18에 나무위키 Forth 문서에서 가져왔습니다.