[루아1.1] lua.stx 읽기 (1)

4 분 소요

Lua.stx (1)

Lua.stx는 루아 1.1에서 루아 문법을 구현한 yacc 인풋 파일입니다. 보통은 Lua.y 혹은 Lua.yacc라고 확장자를 정하는데 루아 개발자들은 특이하게 Lua.stx라고 붙였네요. 아마도 syntax라는 의미로 확장자를 stx로 했는가봅니다. 이 파일이 루아 1.1 소스 코드에서는 제일 깁니다. 그리고 훑어 봐도 이 파일에 중요한 구현이 많아 보입니다. 파일이 길기 때문에 글은 아마 여러 편에 나누어 올라갈 것 같습니다.

%{

char *rcs_luastx = "$Id: lua.stx,v 2.4 1994/04/20 16:22:21 celes Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mm.h"

#include "opcode.h"
#include "hash.h"
#include "inout.h"
#include "table.h"
#include "lua.h"

#define LISTING 0

#ifndef GAPCODE
#define GAPCODE 50
#endif
static Word   maxcode;
static Word   maxmain;
static Word   maxcurr ;
static Byte  *code = NULL;
static Byte  *initcode;
static Byte  *basepc;
static Word   maincode;
static Word   pc;

#define MAXVAR 32
static long    varbuffer[MAXVAR];    /* variables in an assignment list;
				it's long to store negative Word values */
static int     nvarbuffer=0;	     /* number of variables at a list */

static Word    localvar[STACKGAP];   /* store local variable names */
static int     nlocalvar=0;	     /* number of local variables */

#define MAXFIELDS FIELDS_PER_FLUSH*2
static Word    fields[MAXFIELDS];     /* fieldnames to be flushed */
static int     nfields=0;
static int     ntemp;		     /* number of temporary var into stack */
static int     err;		     /* flag to indicate error */

위 코드는 Lua.stx 파일 시작 부분입니다. #include 구문과 정적 전역 변수 선언 부분입니다. #include로 포함하는 헤더 파일들은 lexer와 크게 다르지 않습니다. lua.h가 추가로 있습니다. 이 파일은 루아 용어로 호스트 프로그램에서 루아를 라이브러리로 임베드 할 때 사용하는 헤더 파일입니다. 아마 Lua.stx에서 루아 라이브러리 함수도 직접 구현하는가 봅니다.

LISTING은 디버그용 플래그 같습니다. 사용하는 코드를 보면 모두 아래와 같은 형태입니다.

#if LISTING
PrintCode(code,code+pc);
#endif

현재 0으로 값이 정해져 있으므로 앞으로 나오는 위 코드는 무시하고 읽어도 될 듯합니다.

50으로 설정하는 GAPCODE는 대충 읽어서는 뭘 하는 상수인지 바로 파악이 안되는 군요. 코드를 더 읽으면서 다른 변수나 함수와의 관계를 알아야 파악할 수 있을 것 같습니다. 쓰이는 패턴을 보면 아래 코드 같은 패턴으로 쓰입니다.

		if (code == NULL)	/* first function */
		{
		 code = (Byte *) calloc(GAPCODE, sizeof(Byte));
		 if (code == NULL)
		 {
		  lua_error("not enough memory");
		  err = 1;
		 }
		 maxcode = GAPCODE;
		}

그냥 추정해 보건데, 바이트 코드가 들어갈 메모리 공간을 확보할 때 기본값으로 지정하는 메모리 공간 크기 같긴합니다만, 나중에 자세히 읽어봐야 의미를 알겠지요. 일단 지금은 이정도로 알아두고 넘어가겠습니다. 왜 상수값이 50인지도 궁금하네요.

이어서 maxcode, maxmain, maxcurr, code, initcode, basepc, maincode, pc 등 Word 타입과 Byte 타입으로 선언한 정적 전역 변수들이 죽 나옵니다. 변수들을 어떻게 쓰는지 읽기 전에 타입부터 확인해 봅니다. 타입은 Opcode.h에 선언되 있습니다.

typedef unsigned char  Byte;

typedef unsigned short Word;

Byte 타입은 1바이트고 Word 타입은 2바이트 입니다. unsigned short가 4바이트인 머신도 있을지 모르겠으나 루아 1.1이 나온 95년 당시에는 unsigned short은 의심의 여지없이 2바이트 입니다. 요즘이었으면 uint16_t로 명확하게 코딩했을 텐데 아마 95년은 stdint 표준이 나오기 전일겁니다. 아니면 어떻습니까, 아무튼 2바이트 워드입니다. 아마도 루아 VM을 2바이트 워드 머신으로 디자인했는가 봅니다.

변수명에 max가 들어가는 maxcode, maxmain, maxcurr는 번역하는 바이트 코드의 최대 갯수와 관련된 무엇을 저장하는 것 같은 느낌입니다. 나중에 보도록 하지요. 그리고 code, pc 등 용어가 등장하는 code, initcode, basepc, maincode, pc는 현재 실행 중인 바이트 코드 혹은 지금까지 번역한 바이트 코드 위치와 관련이 있는것 같습니다. 그냥 이름만 보고 추정한 것이니 나중에 코드를 읽으면서 확인해보겠습니다.

이어서 나오는 var가 들어가는 변수들은 루아 변수 처리 관련 값을 저장하는 것들 같습니다. 진짜 그런지 하나 골라서 어떻게 사용하는지 보죠. 그냥 먼저 나오는 varbuffer 배열을 보겠습니다. 이름과 주석으로 미루어 짐작컨데, 루아 코드에서 선언한 변수들의 심볼 테이블 인덱스를 저장하고 있을 것 같습니다.

varlist1  :	var			
	  {
	   nvarbuffer = 0; 
           varbuffer[nvarbuffer] = $1; incr_nvarbuffer();

나중에 위 코드 부분은 다시 읽을 것입니다. 지금은 varbuffer 배열 변수를 이해하려고 일단 가져왔습니다. 위 yacc 문법 코드를 보면 var 규칙의 최종 결과를 varbuffer[nvarbuffer]에 넣습니다. 예상대로네요. var 규칙은 아마 다 처리하고 나면 최종적으로 심볼 테이블 인덱스로 끝날 것 같습니다. 그 때 가서 보죠. 지금은 일단 지나가겠습니다.

위 문법만 봐서는 MAXVAR가 32로 정의된 이유를 아직 모르겠습니다. 루아 코드 파일 하나당 변수를 32개만 쓸 수 있다는 말인지, 한 줄에 연속해서 선언할 수 있는 변수 개수가 32개 제한인건지 확실치 않네요. 아마 문법 코드를 읽어야 알 것 같습니다.

이름에 fields가 들어가는 마지막 그룹은 이름과 주석으로는 용도를 파악하기 어렵네요. 어디에 쓰는지 추적해보고 관련 코드를 대충 본 다음 뭐하는 녀석들인지 알아만 보고 넘어가겠습니다. 데이터가 저장되는 것으로 보이는 field 배열을 추적하겠습니다.

static void push_field (Word name)
{
  if (nfields < STACKGAP-1)
    fields[nfields++] = name;
  else
  {
   lua_error ("too many fields in a constructor");
   err = 1;
  }
}

push_field()라는 함수에서 씁니다. 함수 내용 자체는 어렵지 않습니다. 그냥 field 배열에 파라메터로 넘어오는 name을 넣고 끝입니다. 그러면 push_field()는 어디서 쓸까요?

ffield      : NAME {$<vWord>$ = lua_findconstant($1);} '=' expr1 
	      { 
	       push_field($<vWord>2);
	      }
           ;

여기서 유일하게 push_field() 함수를 쓰는데요. 문법 자체가 한 번에 이해가 안됩니다. 저런 문법이 yacc에서 가능한가? yacc 문서를 더 봐야겠네요. 다 파악하려면 ffield 문법을 따라서 다 봐야 할 것 같아서 어차피 나중에 읽을 거니까 일단 넘어가고 루아 문서에서 field라는 용어를 어떻게 쓰는지 봤습니다.

Variables are places that store values. There are three kinds of variables in Lua: global variables, local variables, and table fields.

아! 필드가 이거 였군요. 루아에 테이블 자료 구조가 있습니다. 연관 배열(associated array) 같은 겁니다. 그래서 아이템 하나가 이름을 가질 수 있습니다.

phone['john'] = '111-222-3333'
phone['ann'] = '123-123-1234'

이런식으로 선언할 수 있습니다. 여기서 ‘john’과 ‘ann’이 필드라는 겁니다. 일종의 변수 이름이기 때문에 따로 관리하는 것이었습니다.

여기까지 정적 전역 변수 선언 부분 코드를 읽었습니다. 파일이 꽤 기니까 여기쯤에서 끊어야 겠네요.

댓글남기기