Lookup Tables (Forth Dimensions XIX.3)

Ben Hoyt

룩업 테이블 (Forth Dimensions XIX.3)

원문은 Ben Hoyt님이 에 게재했습니다. 이 블로그 구독하기

이 글은 제가 16세 때 Hans Bezemer와 공동 집필한 글입니다. 이 글은 Forth Interest Group에서 발행하는 Forth 프로그래밍 잡지 Forth Dimensions 1997년 9월호에 실렸습니다. “4tH”라는 Forth 컴파일러를 만든 Hans가 문장을 다듬고 지나치게 열정적인 Forth 예찬조의 어조를 조금 누그러뜨리는 데 도움을 주었습니다. 저는 아마 사례 연구 II함정 부분을 쓴 것 같습니다. 원본 PDF를 참고하세요.

Forth에 대해 더 알고 싶다면 위키백과 문서를 읽은 뒤 Thinking Forth를 읽어 보세요.

Forth Dimensions XIX.3 (1997년 9월호)

“개인적으로 나는 case 문을 잘못된 문제에 대한 우아한 해법이라고 본다. 결정 테이블(decision table)로 설명하는 것이 훨씬 적절한 것을 억지로 알고리즘으로 표현하려는 문제 말이다.”

—Leo Brodie, Thinking Forth

서론

이 글이 오래된 논쟁에 대한 새로운 논의를 시작하려는 것이라고 생각한다면 완전히 오산이다. 대신 우리는 룩업 테이블이라는 단순한 개념을 활용해 문제를 해결하는 새로운 방법을 제시하려 한다.

무작위로 Forth 프로그램 몇 개를 살펴보면, 가끔 등장하는 요일 테이블을 제외하고는 이 기법이 거의 쓰이지 않는다는 것을 알 수 있다. 안타까운 일이다. 룩업 테이블을 활용한 프로그램은 설계하기도, 디버깅하기도, 유지보수하기도 더 쉽기 때문이다. 게다가 보통 더 작고 더 빠르게 동작한다. 몇 가지 예를 들어 우리의 주장을 뒷받침하겠다.

룩업 테이블이 상대적으로 드문 이유 중 하나는 Forth에서 구현하기가 까다롭다는 점일 수 있다. 특히 문자열을 다룰 때는 더욱 그렇다. 이 글에서는 그 문제에 대한 몇 가지 가능한 해결책도 함께 제시하겠다.

일부는 OOF나 다른 비표준 확장 기능이 이런 종류의 문제를 처리하는 더 나은 방법을 제공한다고 주장할지도 모른다. 하지만 ANS Forth는 객체지향도 아니고 (아직) Structure 확장도 없으며, 그 논의는 이 글의 범위를 벗어난다고 본다.

룩업 테이블이란?

룩업 테이블은 공통된 특성을 가진 객체들의 모음에 불과하다. 이를 활용한다는 것은 프로그램 안에서 사용되는 객체들과 그 객체들이 공유하는(그리고 공유하지 않는) 특성이 무엇인지 고민해야 한다는 뜻이다.

어드벤처 게임이 좋은 예다. 각 방마다 설명과 출구가 있다. 일부 출구는 특정 조건이 충족될 때까지 숨겨져 있을 수 있다. 방 안에는 객체들이 있다. 모든 객체는 설명을 가지고 있다. 어떤 객체는 옮길 수 있고 어떤 객체는 그렇지 않다. 어떤 객체는 특정 조건이 충족되지 않으면 어떤 동작을 수행하기도 한다.

이는 룩업 테이블을 사용해야 하는 애플리케이션의 완벽한 예다. 다른 방식으로 구현하면 분명 서툴고 디버깅하기 어려우며 유지보수가 불가능해질 것이다. 룩업 테이블을 활용한 어드벤처 게임의 예는 http://www.IAEhv.nl/users/mhx/adventur.frt [복원된 버전, Wayback Machine 제공]에서 볼 수 있다. 이 어드벤처 게임은 원래 4tH용으로 설계되었으며 Marcel Hendrix가 ANS Forth로 훌륭하게 포팅했다.

왜 룩업 테이블인가?

룩업 테이블의 활용은 특정 언어나 특정 문제 영역에만 국한되지 않는다. 우리는 Excel 스프레드시트에서도 룩업 테이블을 성공적으로 구현한 적이 있다. 이를 통해 대량의 데이터를 수동으로 조작하고 검증하는 횟수가 크게 줄었다.

한 시트의 데이터는 룩업 테이블과 대조되고, 알 수 없는 값이 발견되면 자동으로 “사용 불가” 오류가 발생한다. 다른 룩업 테이블에서 해당 카테고리를 찾아 전체 시트를 정렬하면 소계를 쉽게 만들 수 있다. 이전에는 모든 검사와 정렬을 수동으로 수행했고 오류가 발생하기 쉬웠다.

하지만 룩업 테이블은 다른 상황에서도 유용할 수 있다. Z80 프로세서는 1980년대 중반 기준으로도 고속 프로세서가 아니다. 그래서 Sinclair Spectrum용 Checkered Flag 게임의 엔진을 설계할 때 PSION의 Steve Townsend는 정해진 공식 대신 기어, 속도, 엔진 회전수를 연결하기 위해 룩업 테이블을 사용했다. 컨트롤을 변경할 때마다 재계산이 필요했을 텐데, 이는 부동소수점 하드웨어가 없는 3.5MHz 프로세서에서는 매우 비용이 큰 연산이다.

룩업 테이블이 얼마나 강력한지 정말로 실감하고 싶다면, 여러분이 사용하고 있는 파일 시스템 자체가 정교한 룩업 테이블들의 집합에 불과하다는 사실을 떠올려 보라.

룩업 테이블은 매우 유연하며 다양한 방식으로 구현할 수 있다. 필요하다면 관계형 데이터베이스를 위한 어떤 설계 기법이라도 적용할 수 있다. 직접 검색 루틴을 만들면 문자열을 비교하거나 특정 값에 가장 가까운 근사치를 찾는 등 원하는 방식으로 룩업 테이블에 접근하는 방법을 정의할 수 있다. 한계는 여러분의 상상력뿐이다.

사례 연구 I: 오류 핸들러

이 글의 공동 저자 중 한 명인 Hans Bezemer는 한 친구로부터 특이한 문제를 의뢰받았다. 그 친구는 한 회사에 고용되어 가장 중요한 애플리케이션 중 하나에서 나오는 오류 메시지들의 원인을 찾고 있었다.

원래 애플리케이션을 만들었던 회사는 오래전에 파산한 상태였다. 시스템 관리자가 애플리케이션의 주요 소스를 검토했지만 해당 메시지를 찾지 못했다. 잠시 후 친구는 메시지들이 라이브러리에서 비롯된 것임을 추적해냈고, 그것으로 일단락된 듯했다.

그러자 회사는 후속 작업을 맡기며 이런 종류의 문제가 재발하지 않도록 할 방안을 설계해 달라고 요청했다. 하지만 친구가 어떤 문서화 방안을 내놓아도 요구 사항을 충족하지 못했다.

함께 고민한 끝에 마침내 받아들여진 방안을 마련했다. 각 프로그래머에게 오류를 저마다의 방식으로 처리할 자유를 주는 대신, 중앙에서 관리되는 테이블 집합을 설계한 것이다. 모든 오류 메시지는 다음과 같이 정의된 오류 핸들러를 반드시 거쳐야 했다.

error-handler     ( c-addr u n1 n2 n3 -- )
\ c-addr u  additional information
\ nl        routine number
\ n2        error number
\ n3        severity

루틴 번호는 다음과 같은 형식의 중앙 관리 테이블에 대한 인덱스였다.

루틴 번호(CELL)
루틴 이름(STRING)

오류 번호는 다음과 같은 형식의 또 다른 중앙 관리 테이블에 대한 인덱스였다.

오류 번호(CELL)
메시지(STRING)

심각도는 오류가 얼마나 심각한지를 나타냈다. 다섯 가지 값 중 하나를 가졌다.

치명적(프로그램 중단)
오류(계속 실행되지만 출력 결과가 의심스러움)
경고(주의, 복구를 시도함)
정보(단순 사용자 정보 제공)
디버그(디버깅 정보)

물론 숫자는 기억하기 어렵기 때문에 사람의 실수를 줄이기 위해 CONSTANT를 추가했다. 예를 들면 다음과 같다.

0     CONSTANT    S_DEBUG
1     CONSTANT    S_INFO
2     CONSTANT    S_WARN
3     CONSTANT    S_ERROR
4     CONSTANT    S_FATAL

0     CONSTANT    E_SOUTOFRANGE
1     CONSTANT    E_EOUTOFRANGE
2     CONSTANT    E ROUTOFRANGE
3     CONSTANT    E_NODATA
4     CONSTANT    E_ENDOFILE
( etc.)

0     CONSTANT    R_DATAENTRY
1     CONSTANT    R_PROCESS
( etc.)

오류 핸들러의 사용은 필수였지만, 추가 정보를 담은 문자열은 허용되었다. 그림 1은 오류 핸들러의 전형적인 사용 예를 보여준다.

그림 1. 중앙 집중식 오류 핸들러의 전형적인 사용 예

:     process                 ( c-addr u -- n)
      over over               \ duplicate filename
      file-status 0=          \ check file status
      if                      \ if ok; process the data
            drop drop         \ discard filename
            S" None" R_PROCESS E_DATAOK S_INFO error-handler
            ( other code)
      else                    \ if not ok; issue error
            R_PROCESS E_NODATA S_FATAL error-handler
            -1                \ return dummy value
      then
;

오류 핸들러는 여러 가지 일을 수행했다. 첫째, 오류, 루틴, 심각도 값의 유효성을 검사했다. 둘째, 심각도 수준을 메시지 수준과 비교했다. 심각도 수준이 메시지 수준과 같거나 더 높으면 메시지를 출력했다. 셋째, 심각도 수준을 중단 수준과 비교했다. 심각도 수준이 중단 수준과 같거나 더 높으면 프로그램을 종료했다.

그날 이후로 이 회사와 거래하려는 모든 소프트웨어 개발자는 이 방식을 따라야 했다. 이 방식은 매우 단순해서 대부분의 품질 보증 업무를 시스템 관리자 혼자서도 수행할 수 있을 정도였다(고백하자면 그 환경에서 선택된 언어는 Forth가 아니었다).

프로그램이 더미 값을 반환한다는 점에 주목하라. 이유는 두 가지다. 첫째, 원래 C 컴파일러는 반환문이 생략되면 경고를 출력했다. 우리는 경고를 좋아하지 않는다. 그것이 실제 오류를 나타내는지 알 수 없기 때문이다. 둘째, 어떤 영리한 프로그래머가 오류를 수정할 방법을 찾아내 심각도를 “치명적”에서 “오류”로 바꾸면 모호한 상황이 발생할 수 있다. Forth에서는 스택 언더플로가 발생하거나, 더 나쁜 경우 찾기 어려운 버그가 생길 수도 있다.

사례 연구 II: For32 디컴파일러

다른 공동 저자인 Benjamin Hoyt는 최근 자신의 For32 시스템을 위한 Forth 디컴파일러를 구현했다. 처음에는 메인 엔진을 하나의 큰 CASE 문으로 구현할 생각이었다. 그러다 룩업 테이블 구현이 장점이 있을 수 있다는 생각이 들었고, 한번 시도해 보기로 했다. 단순한 룩업 테이블을 만들었는데, 놀랍게도 한 번에 제대로 동작했다.

그가 사용한 테이블은 기본적으로 2차원 배열로, 첫 번째 필드에는 (LIT), (S"), (TO) 등 “특수한 경우”의 실행 토큰이, 두 번째 필드에는 디컴파일링 워드가 들어간다. 이해를 돕기 위해 그림 2에 정의를 실었다.

그림 2. 룩업 테이블 기반 디컴파일러

-1 constant EOT                     \ end of table delimiter

( search table for x, if found return corresp. value and true flag)
: search-table                ( x table -- value true | x false )
begin   dup @ EOT =                 \ is it end of table?
      if      drop false  exit      \ no match found
      then    2dup @ <>       \ compare x with value in table
while   [ 2 cells ] literal + \ move to next table entry
repeat  nip  cell+ @  true ;  \ fetch corresponding value

보다시피 정말 간단하다. 물론 모든 룩업 테이블에는 각자만의 검색 루틴이 필요하다. 직접 만들고 싶지 않다면 나중에 일반적인 정의를 제공하겠다. 많은 (ANS 호환) 시스템에서는 CASE조차 제공되지 않는다는 점을 기억하라. CASE를 직접 코딩해야 한다면, 우리의 조언을 받아들여 직접 검색 루틴을 만들어라. 전체 CASE 모음을 개발하는 것보다 훨씬 간단하다.

그뿐만 아니라, 모든 OFENDOF 쌍은 최소한 리터럴 하나, 비교 하나, 점프 두 개에 해당한다. 일부 시스템에서는 OFENDOF 쌍 하나당 최대 40바이트가 추가될 수 있다. 룩업 테이블에서 유사한 항목 하나는 8바이트만 있으면 된다. 예를 들어, CASE를 사용한 For32 디컴파일러와 룩업 테이블을 사용한 버전의 차이는 1.5Kb에 달한다.

CASE를 사용하지 말아야 할 또 다른 이유는 CASE가 종종 원하는 대로 동작하지 않기 때문이다. CASE는 정수만 비교한다. 아직 확신이 서지 않는다면, 룩업 테이블이 보통 더 빠르다는 점도 기억하라.

일부 C 컴파일러(예: R$/6000용 XL C)는 select() 문을 수많은 점프 및 비교 명령으로 구현한다. 이는 특히 항목 목록이 어느 정도 크기일 때 상당히 시간이 많이 걸린다. 룩업 테이블을 사용하면 검색이 제한된 실행 영역 안에서 수행되므로 확실히 속도 향상을 체감할 수 있다.

함정

룩업 테이블의 미묘한 우아함이 이제 분명해졌겠지만, 함정은 무엇인가? 왜 그렇게 적은 Forth 프로그래머만이 이를 사용하고 있을까? 사실 마주칠 수 있는 한두 가지 걸림돌이 있기 때문에 좋은 질문이다.

예를 들어 앞에서 언급한 문자열 비교 예시를 들어보자. 매크로 명령 처리기를 코딩한다고 하자. 룩업 테이블로 구현하기로 결정했다. 문자열 비교용 룩업 루틴을 코딩한 뒤 ,"를 이용해 테이블을 만들었다. 이 워드는 ANS Forth 표준에는 없지만 많은 Forth 시스템에서 제공된다.

create command-table  ( -- table )
      ," display"       ' do-display ,
      ," end"           ' do-end ,
      ," save"          ' do-save ,
      ," load"          ' do-load ,
      EOT ,

하지만 너무 성급했다는 것을 깨닫게 된다. 문자열 길이가 모두 같지 않아 정렬 문제가 생기고, 전체가 복잡하고 느린 검색 루틴으로 인해 어려움을 겪는다. 이 문제에 대한 해결책은 몇 가지 있으며, 그중 가장 간단한 것은 다음과 같이 고정 길이 문자열을 사용하는 것이다.

create command-table  ( -- table )
      ," display" ' do-display ,
      ," end    " ' do-end ,
      ," save   " ' do-save ,
      ," load   " ' do-load ,
      EOT ,

이렇게 하면 일이 단순해지고 빨라지며 일부 상황에서는 잘 동작할 수 있다. 하지만 긴 문자열과 짧은 문자열이 섞여 있을 때 낭비되는 공간은 어떻게 할 것인가? 문자열을 먼저 정의하고 그 주소를 가져와 테이블의 해당 필드에 컴파일하면 이를 피할 수 있다. 하지만 꽤 지저분해질 수 있다.

: push-address
      c" load"
      c" save"
      c" end"
      c" display"
;

push-address

create command-table  ( -- table )
      ,     ' do-display ,
      ,     ' do-end ,
      ,     ' do-save ,
      ,     ' do-load ,
      EOT ,

또 다른 해결책은 M"라는 정의를 작성하는 것이다. 이 정의는 문자열을 파싱해 사전에 컴파일하면서 그 주소를 스택에 남긴다. 그런 다음 이 주소들을 모두 테이블에 “콤마로 추가”하여 룩업 테이블을 만들면 된다. (그림 3 참고)

그림 3. M" 접근 방식

\ string compiling suite )

( c-addr u dest -- )
: place 2dup 2>r  char+ swap chars move  2r> c! ;

( c-addr u -- )
: name, here  over 1+ chars allot  place ;

( "ccc<quote>" -- c-addr )
: m" align here [char] " parse  name, ;

( table of macro commands )
m" display"
m" end"
m" save"
m" load"

( the addresses are all on the stack now, in reverse order)
create command-table  ( -- table )
      ,     ' do-load ,
      ,     ' do-save ,
      ,     ' do-end ,
      ,     ' do-display ,
      EOT ,

( search table for string c-addr u)
( give xt true if found else c-addr u false)

: string-search  ( c-addr u table -- xt true | c-addr u false )
begin   dup @ EOT =                      \ is it end of table?
if      drop false  exit                 \ no match found
then dup 2over rot @ count compare       \ compare with c-addr u
while   [ 2 cells] literal +             \ move to next table entry
repeat  nip nip  cell+ @  true ;         \ fetch xt from column 2

항목이 몇 개뿐이라면 이 방식이 동작할 수 있지만, 항목이 수십 개가 되면 유지보수가 어려워진다. 문제는 ,"가 그 자리에서 문자열을 컴파일하고, S"는 인터프리테이션 모드에서는 문자열을 임시로만 저장하며, C"는 인터프리테이션 시맨틱이 전혀 없다는 점이다. 다시 시도해 보면 다음과 같은 형태가 될 수도 있다.

: display-s       c" display" ;
: end-s           c" end" ;
: save-s          c" save" ;
: load-s          c" load" ;

create command-table  ( -- table )
      display-s   ,     ' do-display ,
      end-s       ,     ' do-end ,
      save-s      ,     ' do-save ,
      load-s      ,     ' do-load ,
      EOT ,

좋다, 이것도 동작하고 약간의 수고를 들이면 유지보수도 가능하지만, 낭비되는 헤더가 많아 썩 마음에 들지는 않는다. 이를 개선할 수 있는지 살펴보자.

해결책과 구현

4tH 컴파일러는 매우 독특한 Forth 컴파일러다. 어떤 이들은 이를 Forth 컴파일러라고 할 수도 없다고 주장한다. 우리는 이 맥락에서는 그것을 학문적인 논쟁이라고 본다.

중요한 점은 4tH가 룩업 테이블을 쉽게 정의할 수 있는 방법을 제공한다는 것이다. 문자열과 정수를 위한 별도의 세그먼트가 있고, 컴파일과 인터프리테이션 시맨틱의 구분이 없다. ANS Forth에서 이 기능 중 일부를 구현하는 방법은 여러 가지가 있다.

LMI Forth의 해결책을 구현해 볼 수도 있다. 이 컴파일러에는 대략 C"에 인터프리테이션 시맨틱을 더한 것처럼 동작하는 "라는 워드가 있다. 인터프리테이션 중에는 원형 버퍼를 사용한다. 문제는 시스템이 언제 버퍼를 되감게 될지 전혀 알 수 없다는 것이다.

또 다른 임시방편은 직접 문자열 공간을 ALLOT하고 거기에 문자열을 컴파일하는 것이다. 문제는 할당한 문자열 공간의 크기에 따라 환경이 제한된다는 점이다. 문자열 공간이 바닥나면 시스템을 다시 시작해야 한다. 이는 Wil Baden이 제안한 해결책 중 하나다(그림 4).

그림 4. Wil Baden의 해결책

( Reserve  STRING-SPACE  in data-space. )
2000 CONSTANT /STRING-SPACE
CREATE STRING-SPACE           /STRING-SPACE CHARS ALLOT
VARIABLE NEXT-STRING          0 NEXT-STRING !

( caddr n addr -- )
: PLACE 2DUP 2>R CHAR+ SWAP CHARS MOVE 2R> C! ;

( "ccc<quote>" -- caddr )
: STRING" [CHAR] " PARSE
DUP 1+ NEXT-STRING @ + /STRING-SPACE >
      ABORT" String Space Exhausted. "
      STRING-SPACE NEXT-STRING @ CHARS + >R
            DUP 1+ NEXT-STRING +!
            R@ PLACE
      R>
;

CREATE months

      STRING" January" ,   31 ,
      STRING" February" ,  28 ,
      STRING" March" ,     31 ,
      STRING" April" ,     30 ,
      STRING" May" ,       31 ,
      STRING" June" ,      30 ,
      STRING" July" ,      31 ,
      STRING" August" ,    31 ,
      STRING" September" , 30 ,
      STRING" October" ,   31 ,
      STRING" November" ,  30 ,
      STRING" December" ,  31 ,

: .Month 1- 2* CELLS months + @ COUNT TYPE SPACE ;

제한된 문자열 공간을 우회하는 방법은 여러 가지가 있다. /STRING-SPACE를 재정의하는 것도 하나의 가능성이다. 당연한 방법은 동적 메모리에 문자열 공간을 할당하고 필요할 때 재할당하는 것이다. 하지만 재할당은 이전에 컴파일된 모든 주소를 무효화할 수 있으며, 이는 분명히 원치 않는 결과다.

동적 메모리를 활용하는 또 다른 기발한 방법은 Marcel Hendrix에게서 나왔다. 그는 앞서 언급한 어드벤처 게임에서 보여준 것처럼 각 문자열을 동적 메모리에 개별적으로 할당한다.

해결책은 많다. 자신에게 가장 잘 맞는 것을 사용하라.

여전히 검색 루틴이라는 문제가 남아 있다. 편의를 위해 사실상 모든 Forth 시스템에서 구현할 수 있는 일반적인 해결책을 제시한다. 문자열의 경우 직접 해결책을 만들거나 우리의 해결책 중 하나를 사용해야 한다(그림 5).

그림 5. 일반적인 해결책

\ : th cells + ;

0 Constant NULL

create MonthTable
   1 , "  January " , 31 ,
   2 , " February " , 28 ,
   3 , "   March  " , 31 ,
   4 , "   April  " , 30 ,
   5 , "    May   " , 31 ,
   6 , "   June   " , 30 ,
   7 , "   July   " , 31 ,
   8 , "  August  " , 31 ,
   9 , " September" , 30 ,
   10 , "  October " , 31 ,
   11 , " November " , 30 ,
   12 , " December " , 31 ,
   NULL ,

\ Generic table-search routine

\ Parameters:  n1 = cell value to search
\        a1 =  address of table
\        n2 =  number of fields in table
\        n3 =  number of field to return

\ Returns:  n4 =  value of field
         f  =  true flag if found

: Search-Table       ( n1 a1 n2 n3 -- n4 f )
   swap >r        ( n1 a1 n3 )
   rot rot        ( n3 n1 a1 )
   over over         ( n3 n1 a1 n1 a1 )
   0           ( n3 n1 a1 n1 a1 n2 )
   begin       ( n3 n1 a1 n1 a1 n2)
      swap over   ( n3 n1 a1 n1 n2 a1 n2)
      th       ( n3 n1 a1 n1 n2 a2)
      @ dup    ( n3 n1 a1 n1 n2 n3 n3)
      0> >r    ( n3 n1 a1 n1 n2 n3)
      rot <>      ( n3 n1 a1 n2 f)
      r@ and      ( n3 n1 a1 n2 f)
   while       ( n3 n1 a1 n2)
      r> drop     ( n3 n1 a1 n2)
      r@ +        ( n3 n1 a1 n2+2)
      >r over over   ( n3 n1 a1 n1 a1)
      r>       ( n3 n1 a1 n1 a1 n2+2)
   repeat         ( n3 n1 a1 n2)

   r@ if
      >r rot r>      ( nl a1 n3 n2)
      + th @      ( n1 n4)
      swap drop      ( n3)
   else
      drop drop drop ( n1)
   then

   r>          ( n f)
   r> drop        ( n f)
;

: Search-Month       ( n --)
   MonthTable 3 2 Search-Table

   if
      .
   else
      drop ." Not Found"
   then cr
;

한 걸음 더 나아가 TABLE이라는 워드를 정의할 수도 있다. 이 워드는 룩업 테이블을 CREATE하고, 실행될 때 자기 자신을 검색하여 필요한 값을 반환한다.

( create search table called "name")
( when executed, searches its table for x)

( returning the table value and true if)
( found, else x and false )

: table  ( "name" -- )  create
      does>  ( x -- value true | x false )
search-table ;

다양한 종류의 룩업 테이블에 대해 서로 다른 검색 방법을 사용하는 여러 버전을 구현할 수 있다. 이를 통해 매우 강력한 애플리케이션을 매우 빠르게 만들 수 있다. Forth를 사용할 때 누릴 수 있는 특권 중 하나라는 점을 기억하라!

에필로그

룩업 테이블을 이용해 Forth 사전을, 심지어 전체 Forth 시스템을 구현하는 것을 생각해 본 적이 있는가? 충분히 가능하다고 장담한다. 실제로 전체 4tH 컴파일러는 네 개의 서로 다른 룩업 테이블을 중심으로 구성되어 있다.

룩업 테이블을 이용하면 빠르고, 작으며, 유지보수가 쉬운 애플리케이션을 만들 수 있다. 우리의 의견으로는, 부분적으로 이를 지원할 수단이 부족했기 때문에 이 방법이 Forth에서 광범위하게 사용되지 않았다. 이 글이 여러분에게 새로운 아이디어를 제공하고 바로 시작할 수 있을 만큼 충분한 자료가 되었기를 바란다.

Benjamin Hoyt는 프로그래밍을 취미로 즐기는 식스폼(sixth-form) 학생이다. Forth를 발견하기 전에는 80x86 어셈블러로 그래픽과 AdLib 프로그래밍을 실험했다. 1년여 전에 첫 Forth 컴파일러를 만들었고 그 이후로 줄곧 이 언어에 충실해 왔다. 현재 MS-DOS에서 동작하는 자신의 ANS Forth인 For32를 사용하고 개발 중이다. 현재 진행 중인 또 다른 주요 프로젝트는 MS-DOS에서 동작하는 작고 ANS 호환인 Forth 컴파일러 For16이다.

Hans “the Beez” Bezemer는 1980년대 중반부터 Forth와 C를 사용해 왔다. 여러 셰어웨어 프로그램과 프리웨어 4tH 컴파일러의 저자이다. 4tH는 ftp.taygeta.com에서 구할 수 있다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글