lua case statement redux

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

lua case statement redux

Norman Ramsey-3
Ignoring all the syntax questions, I've just extended my case-statement
patch so you can label a single arm of a case statement with multiple
expressions, again as in Modula-3.  This took a bit more code; I changed
about 50 lines of code.  

This patch also contains an improvement to print.c, to show targets of
jumps, and a fix for a bug in src/luac/Makefile.

Norman



I felt the need for a case statement in Lua, so I added one.
Unfortunately I had to change the virtual machine as well as the
compiler, because I couldn't figure out how to duplicate the element
on top of the stack using only the existing operators.
Still, Lua is so clean that I added or changed only 50 lines
of code. (I changed another 20 lines to improve luac -l.)

I borrowed syntax from Modula-3 for the case statement (with one
slight change); here's a rather silly example:

  x = 0
  
  while type(x) == "number" do
    case x of
    | 0, 1 => print(x, "x < 2")
              x = x + 1
    | 2 =>  print (x, "x = 2")  
            x = 2 * x
    else => print(x, "x got big")
            x = "foobar"
    end
  end
  print(x)

Each arm of the case statement is tagged with a list of expressions,
and the first arm that matches any expression is executed, so the
expected output for this program is:

  0
  x < 2
  1
  x < 2
  2
  x = 2
  4
  x got big
  foobar

While debugging the code, I discovered a bug in src/luac/Makefile; the
generated luac should depend on $(LIB)/liblua.a.  I also improved the
code in print.c used to support luac -l; I figured that the computer
is better than I am at doing arithmetic, so jump instructions now
print as in these examples:
    63  IFFJMP  24              ; target address 90
   109  UPJMP   108             ; target address 4
    57  JMP     49              ; target address 109


This is a patch file for lua-2.5.  To build it, unpack the lua-2.5
distribution, change to the toplevel directory (called 'lua' in the
distribution), and run

  patch < this_patch_file
  (cd src; make parser)
  make

Norman Ramsey
[hidden email]

*** ./src/lex.c	1997/04/14 21:59:31	1.1
--- ./src/lex.c	1997/04/14 21:59:40
***************
*** 48,53 ****
--- 48,54 ----
      int token;
    } reserved [] = {
        {"and", AND},
+       {"case", CASE},
        {"do", DO},
        {"else", ELSE},
        {"elseif", ELSEIF},
***************
*** 57,62 ****
--- 58,64 ----
        {"local", LOCAL},
        {"nil", NIL},
        {"not", NOT},
+       {"of", OF},
        {"or", OR},
        {"repeat", REPEAT},
        {"return", RETURN},
***************
*** 187,194 ****
  
        case '=':
          save_and_next();
!         if (current != '=') return '=';
!         else { save_and_next(); return EQ; }
  
        case '<':
          save_and_next();
--- 189,197 ----
  
        case '=':
          save_and_next();
!         if (current == '>') { save_and_next(); return ARROW; }
!         else if (current == '=') { save_and_next(); return EQ; }
!         else return '=';
  
        case '<':
          save_and_next();
*** ./src/lua.stx	1997/04/14 21:59:33	1.1
--- ./src/lua.stx	1997/04/14 23:07:42
***************
*** 386,391 ****
--- 386,395 ----
    code_word_at(basepc+thenAdd+1,elseinit-(thenAdd+sizeof(Word)+1));
  }
  
+ static void codeIfEqJmp(Long label_address, Long target) {
+   /*assert(target >= label_address + 2);*/
+   code_word_at(basepc+label_address,target-(label_address+2));
+ }
  
  /*
  ** Parse LUA code.
***************
*** 426,441 ****
  
  %token WRONGTOKEN
  %token NIL
! %token IF THEN ELSE ELSEIF WHILE DO REPEAT UNTIL END
  %token RETURN
  %token LOCAL
  %token FUNCTION
  %token DOTS
  %token <vFloat> NUMBER
  %token <vWord>  STRING
  %token <pTStr>  NAME 
  
! %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
--- 430,447 ----
  
  %token WRONGTOKEN
  %token NIL
! %token IF THEN ELSE ELSEIF WHILE DO REPEAT UNTIL CASE OF END
  %token RETURN
  %token LOCAL
  %token FUNCTION
  %token DOTS
+ %token ARROW
  %token <vFloat> NUMBER
  %token <vWord>  STRING
  %token <pTStr>  NAME 
  
! %type <vLong> PrepJump, PrepIfEqJump
! %type <vLong> tagged_arm_and_body /* label just before ARROW */
  %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
***************
*** 515,520 ****
--- 521,528 ----
  stat   : IF expr1 THEN PrepJump block PrepJump elsepart END
  	{ codeIf($4, $6); }
  
+        | CASE expr1 OF case_body END
+ 
         | WHILE {$<vLong>$=pc;} expr1 DO PrepJump block PrepJump END
         {
          basepc[$5] = IFFJMP;
***************
*** 553,558 ****
--- 561,584 ----
  	{ codeIf($4, $6); }
           ;
       
+  /* keeps a copy of expr1 (from CASE expr1 of) on stack until arm is chosen */
+  /* we have to insert DUP *before* tagged_arm_and_body to avoid parser confusion */
+ case_body : '|' {code_byte(DUP);} tagged_arm_and_body
+           | ELSE ARROW {code_byte(POP);} block 
+           | /* empty */ {code_byte(POP); /* should code for fallback here */ }
+           ;
+ 
+ tagged_arm_and_body
+        : expr1 PrepIfEqJump ',' /* if it equals tag, will jump to backpatched label */
+          {code_byte(DUP);} tagged_arm_and_body
+    	 {codeIfEqJmp($2,$5); $$=$5;}  /* backpatch and return the label */
+        | expr1 {code_byte(EQOP);} PrepJump ARROW /* if equal, jump to block */
+          {code_byte(POP);} /* we found an arm; pop expr1 (from CASE expr1 OF) */
+          block PrepJump    /* execute block, then jump to end of case stmt */
+          case_body 
+          {codeIf($3,$7);$$=$3+3;} /* patch jumps; return label for other patch */
+        ;                      
+ 
  block    : {$<vInt>$ = nlocalvar;} statlist ret 
           {
  	  if (nlocalvar != $<vInt>1)
***************
*** 583,588 ****
--- 609,620 ----
           }
  	 ;
  	   
+ PrepIfEqJump : /* empty --- returns address to patch label */ 
+              { code_byte(EQOP); code_byte(NOTOP); code_byte(IFFJMP); 
+                $$ = pc; code_word(0);
+              }
+              ;
+ 
  expr1	 : expr { adjust_functioncall($1, 1); }
  	 ;
  				
*** ./src/luac/dump.c	1997/04/14 21:59:30	1.1
--- ./src/luac/dump.c	1997/04/14 21:59:40
***************
*** 42,47 ****
--- 42,48 ----
  	case PUSH0:
  	case PUSH1:
  	case PUSH2:
+ 	case DUP:
  	case PUSHLOCAL0:
  	case PUSHLOCAL1:
  	case PUSHLOCAL2:
*** ./src/luac/print.c	1997/04/14 21:59:30	1.1
--- ./src/luac/print.c	1997/04/14 22:23:19
***************
*** 32,37 ****
--- 32,38 ----
  	case PUSH0:
  	case PUSH1:
  	case PUSH2:
+         case DUP:
  	case PUSHINDEXED:
  	case STOREINDEXED0:
  	case ADJUST0:
***************
*** 102,119 ****
  		break;
  	case PUSHWORD:
  	case CREATEARRAY:
  	case ONTJMP:
  	case ONFJMP:
  	case JMP:
- 	case UPJMP:
  	case IFFJMP:
  	case IFFUPJMP:
- 	case SETLINE:
  	{
  		Word w;
  		p++;
  		get_word(w,p);
! 		printf("\t%d",w);
  		break;
  	}
  	case PUSHFLOAT:
--- 103,134 ----
  		break;
  	case PUSHWORD:
  	case CREATEARRAY:
+ 	case SETLINE:
+ 	{
+ 		Word w;
+ 		p++;
+ 		get_word(w,p);
+ 		printf("\t%d",w);
+ 		break;
+ 	}
  	case ONTJMP:
  	case ONFJMP:
  	case JMP:
  	case IFFJMP:
+ 	{
+ 		Word w;
+ 		p++;
+ 		get_word(w,p);
+ 		printf("\t%d\t\t; target address %d",w,(p-code)+w);
+ 		break;
+ 	}
+ 	case UPJMP:
  	case IFFUPJMP:
  	{
  		Word w;
  		p++;
  		get_word(w,p);
! 		printf("\t%d\t\t; target address %d",w,(p-code)-w);
  		break;
  	}
  	case PUSHFLOAT:
*** ./src/luac/print.h	1997/04/14 21:59:30	1.1
--- ./src/luac/print.h	1997/04/14 21:59:40
***************
*** 9,14 ****
--- 9,15 ----
   "PUSH0",
   "PUSH1",
   "PUSH2",
+  "DUP",
   "PUSHBYTE",
   "PUSHWORD",
   "PUSHFLOAT",
*** ./src/opcode.c	1997/04/14 21:59:31	1.1
--- ./src/opcode.c	1997/04/14 21:59:41
***************
*** 950,955 ****
--- 950,958 ----
       incr_top;
       break;
  
+    case DUP:
+      *top = *(top-1); incr_top; break;
+ 
     case PUSHBYTE: 
       tag(top) = LUA_T_NUMBER; nvalue(top) = *pc++; incr_top; break;
  
*** ./src/opcode.h	1997/04/14 21:59:31	1.1
--- ./src/opcode.h	1997/04/14 21:59:41
***************
*** 23,28 ****
--- 23,29 ----
  PUSH0,/*		-		0.0  */
  PUSH1,/*		-		1.0  */
  PUSH2,/*		-		2.0  */
+ DUP,/*                  x               - x x */
  PUSHBYTE,/*	b	-		(float)b  */
  PUSHWORD,/*	w	-		(float)w  */
  PUSHFLOAT,/*	f	-		f  */
*** ./src/undump.c	1997/04/14 21:59:33	1.1
--- ./src/undump.c	1997/04/14 21:59:41
***************
*** 36,41 ****
--- 36,42 ----
  	case PUSH0:
  	case PUSH1:
  	case PUSH2:
+ 	case DUP:
  	case PUSHLOCAL0:
  	case PUSHLOCAL1:
  	case PUSHLOCAL2:
*** src/luac/Makefile	1997/04/14 22:34:00	1.1
--- src/luac/Makefile	1997/04/14 22:35:33
***************
*** 11,17 ****
  
  all: $T
  
! $T: $(OBJS)
  	$(CC) -o $@ $(OBJS) -L$(LIB) -llua
  
  clean:
--- 11,17 ----
  
  all: $T
  
! $T: $(OBJS) $(LIB)/liblua.a
  	$(CC) -o $@ $(OBJS) -L$(LIB) -llua
  
  clean: