[루아2.2] lua.stx

5 분 소요

Lua.stx

루아 2.2까지도 계속 yacc을 이용해서 구문 분석을 하고 있습니다. 마이너 업데이트인데도 꽤 변경이 많습니다. 그래도 여러편으로 나눠서 읽을 정도 분량은 아닙니다.

[2.1]
static Word    localvar[STACKGAP];   /* store local variable names */

[2.2]
#define MAXLOCALS 32
static Word    localvar[MAXLOCALS];  /* store local variable names */

기본적으로 다른 코드는 아니지만 localvar 배열 변수로 지정하는 상수 리터럴 선언을 MAXLOCALS로 새로 만들었습니다. 이름을 더 명확하게 하려는 목적으로 보입니다.

[2.1]
static void code_code (Byte *b)
{
 CodeCode code;
 code.b = b;
 code_byte(code.m.c1);
 code_byte(code.m.c2);
 code_byte(code.m.c3);
 code_byte(code.m.c4);
}

[2.2]
static void code_code (TFunc *tf)
{
 CodeCode code;
 code.tf = tf;
 code_byte(code.m.c1);
 code_byte(code.m.c2);
 code_byte(code.m.c3);
 code_byte(code.m.c4);
}

마찬가지로 기본적으로 차이없는 코드입니다. 루아 함수 오브젝트 포인터를 다루는 타입으로 TFunc을 새로 만들어서 코드의 의미를 조금 더 명확히 한 것으로 보입니다.

static void add_localvar (Word name)
{
 if (nlocalvar < MAXLOCALS)
  localvar[nlocalvar++] = name;
 else
  lua_error ("too many local variables");
}

static void store_localvar (Word name, int n)
{
 if (nlocalvar+n < MAXLOCALS)
  localvar[nlocalvar+n] = name;
 else
  lua_error ("too many local variables");
}

static void add_varbuffer (Long var)
{
 if (nvarbuffer < MAXVAR)
  varbuffer[nvarbuffer++] = var;
 else
  lua_error ("variable buffer overflow");
}

루아 2.2에 새로 추가한 함수입니다. 루아 2.1에 비슷한 동작을 하는 함수가 있으나 아예 이름을 바꿔서 새로 구현한 것이라 새로 추가한 함수로 간주했습니다. add_localvar() 함수는 그냥 localvar 배열에 로컬 변수 심볼 인덱스를 저장합니다. store_localvar() 함수는 기본적으로 add_localvar() 함수와 같은 함수입니다. 다만 파라메터로 n을 하나 더 받아서 nlocalvar 보다 더 넘어간 인덱스에 로컬 변수 심볼 인덱스를 저장합니다. add_varbuffer()는 전역 변수 심볼 인덱스를 버퍼에 저장합니다.

[2.1]
static void init_function (TreeNode *func)
{
 if (funcCode == NULL)	/* first function */
 {
  funcCode = newvector(CODE_BLOCK, Byte);
  maxcode = CODE_BLOCK;
 }
 pc=0; basepc=funcCode; maxcurr=maxcode; 
 nlocalvar=0;
  if (lua_debug)
  {
    code_byte(SETFUNCTION); 
    code_code((Byte *)luaI_strdup(lua_file[lua_nfile-1]));
    code_word(luaI_findconstant(func));
  }
}

[2.2]
static void change2main (void)
{
  /* (re)store main values */
  pc=maincode; basepc=*initcode; maxcurr=maxmain;
  nlocalvar=0;
}

static void savemain (void)
{
  /* save main values */
  maincode=pc; *initcode=basepc; maxmain=maxcurr;
}

static void init_func (void)
{
  if (funcCode == NULL)	/* first function */
  {
   funcCode = newvector(CODE_BLOCK, Byte);
   maxcode = CODE_BLOCK;
  }
  savemain();  /* save main values */
  /* set func values */
  pc=0; basepc=funcCode; maxcurr=maxcode; 
  nlocalvar = 0;
  luaI_codedebugline(lua_linenumber);
}

함수를 호출하고 나면 caller 컨택스트로 돌아가는 동작하는 코드를 더 명확히 한것 같습니다. 역시 그냥 읽기만 하니 이런 문제가 생기는 군요. 괜찮습니다. 예상 못했던 일도 아니니까요. 아무튼, 위 코드는 루아에서 함수를 호출할 때(init_func), caller의 컨택스트를 백업하고(savemain) 함수 수행을 다 끝내고 나서 다시 caller 컨택스트로 돌아가는(change2main) 동작을 함수로 구분해서 구현한 것으로 보입니다.

[2.1]
static void lua_codestore (int i)
{
 if (varbuffer[i] > 0)		/* global var */
 {
  code_byte(STOREGLOBAL);
  code_word(varbuffer[i]-1);
 }
 else if (varbuffer[i] < 0)      /* local var */
 {
  int number = (-varbuffer[i]) - 1;
  if (number < 10) code_byte(STORELOCAL0 + number);
  else
  {
   code_byte(STORELOCAL);
   code_byte(number);
  }
 }
 else				  /* indexed var */
 {
  int j;
  int upper=0;     	/* number of indexed variables upper */
  int param;		/* number of itens until indexed expression */
  for (j=i+1; j <nvarbuffer; j++)
   if (varbuffer[j] == 0) upper++;
  param = upper*2 + i;
  if (param == 0)
   code_byte(STOREINDEXED0);
  else
  {
   code_byte(STOREINDEXED);
   code_byte(param);
  }
 }
}

[2.2]
static void storesinglevar (Long v)
{
 if (v > 0)		/* global var */
 {
   code_byte(STOREGLOBAL);
   code_word(v-1);
 }
 else if (v < 0)      /* local var */
 {
   int number = (-v) - 1;
   if (number < 10) code_byte(STORELOCAL0 + number);
   else
   {
     code_byte(STORELOCAL);
     code_byte(number);
   }
 }
 else 
   code_byte(STOREINDEXED0);
}

static void lua_codestore (int i)
{
 if (varbuffer[i] != 0)  /* global or local var */
  storesinglevar(varbuffer[i]);
 else				  /* indexed var */
 {
  int j;
  int upper=0;     	/* number of indexed variables upper */
  int param;		/* number of itens until indexed expression */
  for (j=i+1; j <nvarbuffer; j++)
   if (varbuffer[j] == 0) upper++;
  param = upper*2 + i;
  if (param == 0)
   code_byte(STOREINDEXED0);
  else
  {
   code_byte(STOREINDEXED);
   code_byte(param);
  }
 }
}

루아 2.1에 길었던 lua_codestore() 함수의 절반을 루아 2.2에서는 storesinglevar()로 분리했습니다.

[2.1]
%type <vLong> PrepJump
%type <vInt>  expr, exprlist, exprlist1, varlist1, funcParams, funcvalue
%type <vInt>  fieldlist, localdeclist, decinit
%type <vInt>  ffieldlist1
%type <vInt>  lfieldlist1
%type <vLong> var, singlevar
%type <pByte> body

[2.2]
%type <vLong> PrepJump
%type <vLong> exprlist, exprlist1  /* if > 0, points to function return
	counter (which has list length); if <= 0, -list lenght */
%type <vLong> functioncall, expr  /* if != 0, points to function return
					 counter */
%type <vInt>  varlist1, funcParams, funcvalue
%type <vInt>  fieldlist, localdeclist, decinit
%type <vInt>  ffieldlist, ffieldlist1, semicolonpart
%type <vInt>  lfieldlist, lfieldlist1
%type <vInt>  parlist
%type <vLong> var, singlevar, funcname
%type <pFunc> body

문법 요소가 루아 2.1에 비해 많아졌습니다. 문법 자체에 대한 수정을 많이 한것 같진 않고 yacc 문법 기술 방법을 바꾸려다보니 문법 요소를 추가로 만들 수 밖에 없었던 것으로 보입니다.

[2.1]
functionlist : /* empty */
	     | functionlist 
	        {
	  	  pc=maincode; basepc=*initcode; maxcurr=maxmain;
		  nlocalvar=0;
	        }
	       stat sc 
		{
		  maincode=pc; *initcode=basepc; maxmain=maxcurr;
		}
	     | functionlist function
	     | functionlist method
	     | functionlist setdebug
	     ;

[2.2]
functionlist : /* empty */
	     | functionlist globalstat
	     | functionlist function
	     ;

대표적으로 이렇게 바꿨습니다. 제가 루아의 yacc 문법 기술 코드를 보면서 계속 문제 삼았던 부분이 BNF 문법 요소와 해당 요소를 구현하는 C 언어 코드를 섞어놔서 코드 가독성이 떨어진다는 것이었습니다. 그 부분을 깔끔하게 정리한 것으로 보입니다. BNF 문법 요소를 먼저 다 기술하고 뒤에 C 언어 코드를 작성하는 방식으로요.

[2.1]
function     : FUNCTION NAME 
               {
		init_function($2);
	       }
               body 	 
	       { 
		Word func = luaI_findsymbol($2);
	        s_tag(func) = LUA_T_FUNCTION;
	        s_bvalue(func) = $4;
	       }
	       ;

[2.2]
function     : FUNCTION funcname body 	 
	       { 
		code_byte(PUSHFUNCTION);
		code_code($3);
		storesinglevar($2);
	       }
	       ;

funcname	: var { $$ =$1; init_func(); }
		| varexp ':' NAME
	{
	  code_byte(PUSHSTRING);
	  code_word(luaI_findconstant($3));
	  $$ = 0;  /* indexed variable */
	  init_func();
	  add_localvar(luaI_findsymbolbyname("self"));
	}
		;

루아 2.1 코드를 보면 FUNCTION 토큰 다음에 NAME 토큰까지 yacc이 처리하면 그 시점에서 init_function()을 호출합니다. 이러면 yacc 파일 기술 자체는 짧아질 수 있어도 코드 자체는 복잡해 보입니다. NAME 다음에 나오는 문법 요소가 뭔지 잘 안보이죠. 그 다음 문법 요소인 body 문법으로 갔다가 다 처리하고 나면 다시 C 코드가 나오는데 $4가 BNF 갯수에 맞지 않는 등 읽기가 어렵습니다. 루아 2.2에서 바꾼 코드를 보면 funcname이라는 새로운 문법 요소를 만들긴 했지만 해석하기엔 더 쉽습니다. storesinglevar에 $2를 넘겼지요? $2는 funcname 문법 요소를 처리할 때 $$에 넘기는 값입니다. funcname 문법도 계속 꼬리를 물고 펼쳐지는데 일단 여기까지만 보면, var와 varexp 두 개고 각각 BNF 기술이 끝나고 C 언어 코드가 나옵니다. 깔끔하죠.

기본적으로 lua.stx 파일의 변경 요소는 이런 패턴입니다. 변경 사항이 많아서 살짝 긴장했는데 다행입니다.

댓글남기기