Commit 9b5a6f72 authored by unknown's avatar unknown

WL#2002: Implement stored procedure GOTO.

Mostly done, it works, but the temporary LABEL syntax still to be fixed.


mysql-test/r/sp-error.result:
  New test case for WL#2002 (GOTO).
mysql-test/r/sp.result:
  New test case for WL#2002 (GOTO).
  (Also corrected another test)
mysql-test/t/sp-error.test:
  New test case for WL#2002 (GOTO).
mysql-test/t/sp.test:
  New test case for WL#2002 (GOTO).
  (Also corrected another test)
sql/lex.h:
  New symbol GOTO.
  Also a temporary symbol LABEL, which hopefully will go away soon.
sql/sp_head.cc:
  Fixed backpatching to cope with free GOTO labels an hpop and cpop instructions.
  Also optimized away pointless jump instructions.
sql/sp_head.h:
  Fixed backpatching to cope with free GOTO labels an hpop and cpop instructions.
  We now sometimes generate hpop/cpop 0 instructions but the optimizer removes them.
sql/sp_pcontext.cc:
  Added free GOTO labels, and support for coping with jumps out of blocks
  with handlers or cursors.
sql/sp_pcontext.h:
  Added free GOTO labels, and support for coping with jumps out of blocks
  with handlers or cursors.
sql/sql_yacc.yy:
  Added GOTO and LABEL, and adjusted backpatching accordingly. Also fixed LEAVE
  out of blocks. The LABEL syntax will go away soon, hopefully.
parent f43fe31e
......@@ -63,6 +63,11 @@ iterate foo;
end|
ERROR 42000: ITERATE with no matching label: foo
create procedure foo()
begin
goto foo;
end|
ERROR 42000: GOTO with no matching label: foo
create procedure foo()
foo: loop
foo: loop
set @x=2;
......
......@@ -398,6 +398,87 @@ id data
i 3
delete from t1|
drop procedure i|
create procedure j()
begin
declare y int;
label a;
select * from t1;
select count(*) into y from t1;
if y > 2 then
goto b;
end if;
insert into t1 values ("j", y);
goto a;
label b;
end|
call j()|
id data
id data
j 0
id data
j 0
j 1
id data
j 0
j 1
j 2
drop procedure j|
create procedure k(a int)
begin
declare x int default 0;
declare continue handler for sqlstate '42S98' set x = 1;
label a;
select * from t1;
b:
while x < 2 do
begin
declare continue handler for sqlstate '42S99' set x = 2;
if a = 0 then
set x = x + 1;
iterate b;
elseif a = 1 then
leave b;
elseif a = 2 then
set a = 1;
goto a;
end if;
end;
end while b;
select * from t1;
end|
call k(0)|
id data
j 0
j 1
j 2
id data
j 0
j 1
j 2
call k(1)|
id data
j 0
j 1
j 2
id data
j 0
j 1
j 2
call k(2)|
id data
j 0
j 1
j 2
id data
j 0
j 1
j 2
id data
j 0
j 1
j 2
drop procedure k|
delete from t1|
insert into t1 values ("foo", 3), ("bar", 19)|
insert into t2 values ("x", 9, 4.1), ("y", -1, 19.2), ("z", 3, 2.2)|
create procedure sel1()
......@@ -1487,7 +1568,7 @@ Grants for root@localhost
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment
File Position Binlog_Do_DB Binlog_Ignore_DB
master-bin.000001 22185
master-bin.000001 22451
Database Table In_use Name_locked
Privilege Context Comment
Alter Tables To alter the table
......@@ -1541,7 +1622,7 @@ Grants for root@localhost
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment
File Position Binlog_Do_DB Binlog_Ignore_DB
master-bin.000001 22185
master-bin.000001 22451
Database Table In_use Name_locked
Privilege Context Comment
Alter Tables To alter the table
......@@ -1580,10 +1661,10 @@ show processlist;
end|
call bug4902_2()|
Id User Host db Command Time State Info
# root localhost test Query 0 NULL call bug4902_2()
# root localhost test Query # NULL call bug4902_2()
call bug4902_2()|
Id User Host db Command Time State Info
# root localhost test Query 0 NULL call bug4902_2()
# root localhost test Query # NULL call bug4902_2()
drop procedure bug4902_2|
drop table if exists t3|
create procedure bug4904()
......
......@@ -82,7 +82,7 @@ drop procedure if exists foo|
--error 1304
show create procedure foo|
# LEAVE/ITERATE with no match
# LEAVE/ITERATE/GOTO with no match
--error 1307
create procedure foo()
foo: loop
......@@ -98,6 +98,11 @@ create procedure foo()
foo: begin
iterate foo;
end|
--error 1307
create procedure foo()
begin
goto foo;
end|
# Redefining label
--error 1308
......
......@@ -468,6 +468,65 @@ delete from t1|
drop procedure i|
# The non-standard GOTO, for compatibility
#
# QQQ The "label" syntax is temporary.
# QQQ This is no nearly enough, more tests are needed
#
create procedure j()
begin
declare y int;
label a;
select * from t1;
select count(*) into y from t1;
if y > 2 then
goto b;
end if;
insert into t1 values ("j", y);
goto a;
label b;
end|
call j()|
drop procedure j|
# With dummy handlers, just to test restore of contexts with jumps
create procedure k(a int)
begin
declare x int default 0;
declare continue handler for sqlstate '42S98' set x = 1;
label a;
select * from t1;
b:
while x < 2 do
begin
declare continue handler for sqlstate '42S99' set x = 2;
if a = 0 then
set x = x + 1;
iterate b;
elseif a = 1 then
leave b;
elseif a = 2 then
set a = 1;
goto a;
end if;
end;
end while b;
select * from t1;
end|
call k(0)|
call k(1)|
call k(2)|
drop procedure k|
delete from t1|
# SELECT with one of more result set sent back to the clinet
insert into t1 values ("foo", 3), ("bar", 19)|
insert into t2 values ("x", 9, 4.1), ("y", -1, 19.2), ("z", 3, 2.2)|
......@@ -1725,9 +1784,9 @@ create procedure bug4902_2()
begin
show processlist;
end|
--replace_column 1 #
--replace_column 1 # 6 #
call bug4902_2()|
--replace_column 1 #
--replace_column 1 # 6 #
call bug4902_2()|
drop procedure bug4902_2|
......
......@@ -209,6 +209,7 @@ static SYMBOL symbols[] = {
{ "GEOMETRYCOLLECTION",SYM(GEOMETRYCOLLECTION)},
{ "GET_FORMAT", SYM(GET_FORMAT)},
{ "GLOBAL", SYM(GLOBAL_SYM)},
{ "GOTO", SYM(GOTO_SYM)},
{ "GRANT", SYM(GRANT)},
{ "GRANTS", SYM(GRANTS)},
{ "GROUP", SYM(GROUP)},
......@@ -256,6 +257,7 @@ static SYMBOL symbols[] = {
{ "KEY", SYM(KEY_SYM)},
{ "KEYS", SYM(KEYS)},
{ "KILL", SYM(KILL_SYM)},
{ "LABEL", SYM(LABEL_SYM)},
{ "LANGUAGE", SYM(LANGUAGE_SYM)},
{ "LAST", SYM(LAST_SYM)},
{ "LEADING", SYM(LEADING)},
......
......@@ -846,12 +846,36 @@ sp_head::backpatch(sp_label_t *lab)
List_iterator_fast<bp_t> li(m_backpatch);
while ((bp= li++))
if (bp->lab == lab)
{
if (bp->lab == lab ||
(bp->lab->type == SP_LAB_REF &&
my_strcasecmp(system_charset_info, bp->lab->name, lab->name) == 0))
{
sp_instr_jump *i= static_cast<sp_instr_jump *>(bp->instr);
sp_scope_t sdiff;
i->set_destination(dest);
if (bp->lab->type == SP_LAB_REF)
bp->lab= lab;
m_pcont->diff_scopes(0, &sdiff);
bp->instr->backpatch(dest, sdiff.hndlrs, sdiff.curs);
}
}
}
int
sp_head::check_backpatch(THD *thd)
{
bp_t *bp;
List_iterator_fast<bp_t> li(m_backpatch);
while ((bp= li++))
{
if (bp->lab->type == SP_LAB_REF)
{
net_printf(thd, ER_SP_LILABEL_MISMATCH, "GOTO", bp->lab->name);
return -1;
}
}
return 0;
}
void
......@@ -1199,8 +1223,9 @@ sp_instr_jump::print(String *str)
uint
sp_instr_jump::opt_mark(sp_head *sp)
{
marked= 1;
m_dest= opt_shortcut_jump(sp);
if (m_dest != m_ip+1) /* Jumping to following instruction? */
marked= 1;
m_optdest= sp->get_instr(m_dest);
return m_dest;
}
......
......@@ -182,6 +182,13 @@ class sp_head :private Item_arena
void
backpatch(struct sp_label *);
// Check that no unresolved references exist.
// If none found, 0 is returned, otherwise errors have been issued
// and -1 is returned.
// This is called by the parser at the end of a create procedure/function.
int
check_backpatch(THD *thd);
char *name(uint *lenp = 0) const
{
if (lenp)
......@@ -272,7 +279,7 @@ class sp_instr : public Sql_alloc
virtual void print(String *str) = 0;
virtual void set_destination(uint dest)
virtual void backpatch(uint dest, uint hpop, uint cpop)
{}
virtual uint opt_mark(sp_head *sp)
......@@ -394,8 +401,7 @@ class sp_instr_jump : public sp_instr
virtual void opt_move(uint dst, List<sp_instr> *ibp);
virtual void
set_destination(uint dest)
virtual void backpatch(uint dest, uint hpop, uint cpop)
{
if (m_dest == 0) // Don't reset
m_dest= dest;
......@@ -575,6 +581,21 @@ class sp_instr_hpop : public sp_instr
virtual void print(String *str);
virtual void backpatch(uint dest, uint hpop, uint cpop)
{
if (hpop > m_count)
m_count= 0;
else
m_count-= hpop;
}
virtual uint opt_mark(sp_head *sp)
{
if (m_count)
marked= 1;
return m_ip+1;
}
private:
uint m_count;
......@@ -655,6 +676,21 @@ class sp_instr_cpop : public sp_instr
virtual void print(String *str);
virtual void backpatch(uint dest, uint hpop, uint cpop)
{
if (cpop > m_count)
m_count= 0;
else
m_count-= cpop;
}
virtual uint opt_mark(sp_head *sp)
{
if (m_count)
marked= 1;
return m_ip+1;
}
private:
uint m_count;
......
......@@ -27,12 +27,14 @@
#include "sp_head.h"
sp_pcontext::sp_pcontext()
: Sql_alloc(), m_params(0), m_framesize(0), m_handlers(0), m_cursmax(0)
: Sql_alloc(), m_params(0), m_framesize(0), m_handlers(0), m_cursmax(0),
m_hndlrlev(0)
{
VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cursor, sizeof(LEX_STRING), 16, 8));
VOID(my_init_dynamic_array(&m_scopes, sizeof(sp_scope_t), 16, 8));
VOID(my_init_dynamic_array(&m_glabel, sizeof(sp_label_t *), 16, 8));
m_label.empty();
}
......@@ -43,6 +45,7 @@ sp_pcontext::destroy()
delete_dynamic(&m_cond);
delete_dynamic(&m_cursor);
delete_dynamic(&m_scopes);
delete_dynamic(&m_glabel);
m_label.empty();
}
......@@ -53,14 +56,46 @@ sp_pcontext::push_scope()
s.vars= m_pvar.elements;
s.conds= m_cond.elements;
s.hndlrs= m_hndlrlev;
s.curs= m_cursor.elements;
s.glab= m_glabel.elements;
insert_dynamic(&m_scopes, (gptr)&s);
}
void
sp_pcontext::pop_scope()
sp_pcontext::pop_scope(sp_scope_t *sp)
{
(void)pop_dynamic(&m_scopes);
byte *p= pop_dynamic(&m_scopes);
if (sp && p)
memcpy(sp, p, sizeof(sp_scope_t));
}
void
sp_pcontext::diff_scopes(uint sold, sp_scope_t *diffs)
{
uint snew= m_scopes.elements;
sp_scope_t scope;
diffs->vars= diffs->conds= diffs->hndlrs= diffs->curs= diffs->glab= 0;
while (snew-- > sold)
{
get_dynamic(&m_scopes, (gptr)&scope, snew);
diffs->vars+= scope.vars;
diffs->conds+= scope.conds;
diffs->hndlrs+= scope.hndlrs;
diffs->curs+= scope.curs;
diffs->glab+= scope.glab;
}
if (sold)
{
get_dynamic(&m_scopes, (gptr)&scope, sold-1);
diffs->vars-= scope.vars;
diffs->conds-= scope.conds;
diffs->hndlrs-= scope.hndlrs;
diffs->curs-= scope.curs;
diffs->glab-= scope.glab;
}
}
......@@ -132,7 +167,8 @@ sp_pcontext::push_label(char *name, uint ip)
{
lab->name= name;
lab->ip= ip;
lab->isbegin= FALSE;
lab->type= SP_LAB_GOTO;
lab->scopes= 0;
m_label.push_front(lab);
}
return lab;
......@@ -245,3 +281,41 @@ sp_pcontext::find_cursor(LEX_STRING *name, uint *poff, my_bool scoped)
}
return FALSE;
}
sp_label_t *
sp_pcontext::push_glabel(char *name, uint ip)
{
sp_label_t *lab = (sp_label_t *)sql_alloc(sizeof(sp_label_t));
if (lab)
{
lab->name= name;
lab->ip= ip;
lab->type= SP_LAB_GOTO;
lab->scopes= 0;
insert_dynamic(&m_glabel, (gptr)&lab);
}
return lab;
}
sp_label_t *
sp_pcontext::find_glabel(char *name)
{
uint i= m_glabel.elements;
while (i--)
{
sp_label_t *lab;
get_dynamic(&m_glabel, (gptr)&lab, i);
if (my_strcasecmp(system_charset_info, name, lab->name) == 0)
return lab;
}
return NULL;
}
void
sp_pcontext::pop_glabel(uint count)
{
(void)pop_dynamic(&m_glabel);
}
......@@ -39,11 +39,18 @@ typedef struct sp_pvar
Item *dflt;
} sp_pvar_t;
#define SP_LAB_REF 0 // Unresolved reference (for goto)
#define SP_LAB_GOTO 1 // Free goto label
#define SP_LAB_BEGIN 2 // Label at BEGIN
#define SP_LAB_ITER 3 // Label at iteration control
typedef struct sp_label
{
char *name;
uint ip; // Instruction index
my_bool isbegin; // For ITERATE error checking
int type; // begin/iter or ref/free
uint scopes; // No. of scopes at label
} sp_label_t;
typedef struct sp_cond_type
......@@ -61,7 +68,7 @@ typedef struct sp_cond
typedef struct sp_scope
{
uint vars, conds, curs;
uint vars, conds, hndlrs, curs, glab;
} sp_scope_t;
class sp_pcontext : public Sql_alloc
......@@ -82,7 +89,18 @@ class sp_pcontext : public Sql_alloc
push_scope();
void
pop_scope();
pop_scope(sp_scope_t *sp = 0);
uint
scopes()
{
return m_scopes.elements;
}
// Sets '*diffs' to the differences between current scope index snew and sold
void
diff_scopes(uint sold, sp_scope_t *diffs);
//
// Parameters and variables
......@@ -223,6 +241,18 @@ class sp_pcontext : public Sql_alloc
return m_handlers;
}
inline void
push_handlers(uint count)
{
m_hndlrlev+= count;
}
inline void
pop_handlers(uint count)
{
m_hndlrlev-= count;
}
//
// Cursors
//
......@@ -246,17 +276,32 @@ class sp_pcontext : public Sql_alloc
return m_cursmax;
}
//
// GOTO labels
//
sp_label_t *
push_glabel(char *name, uint ip);
sp_label_t *
find_glabel(char *name);
void
pop_glabel(uint count);
private:
uint m_params; // The number of parameters
uint m_framesize; // The maximum framesize
uint m_handlers; // The total number of handlers
uint m_cursmax; // The maximum number of cursors
uint m_hndlrlev; // Current number of active handlers
DYNAMIC_ARRAY m_pvar; // Parameters/variables
DYNAMIC_ARRAY m_cond; // Conditions
DYNAMIC_ARRAY m_cursor; // Cursors
DYNAMIC_ARRAY m_scopes; // For error checking
DYNAMIC_ARRAY m_glabel; // Goto labels
List<sp_label_t> m_label; // The label list
......
......@@ -611,6 +611,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token CURSOR_SYM
%token ELSEIF_SYM
%token ITERATE_SYM
%token GOTO_SYM
%token LABEL_SYM
%token LEAVE_SYM
%token LOOP_SYM
%token REPEAT_SYM
......@@ -1180,13 +1182,16 @@ create:
sp_proc_stmt
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
lex->sphead->init_strings(YYTHD, lex, $3);
if (sp->check_backpatch(YYTHD))
YYABORT;
sp->init_strings(YYTHD, lex, $3);
lex->sql_command= SQLCOM_CREATE_PROCEDURE;
/* Restore flag if it was cleared above */
if (lex->sphead->m_old_cmq)
if (sp->m_old_cmq)
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
lex->sphead->restore_thd_mem_root(YYTHD);
sp->restore_thd_mem_root(YYTHD);
}
| CREATE or_replace algorithm VIEW_SYM table_ident
{
......@@ -1286,6 +1291,8 @@ create_function_tail:
LEX *lex= Lex;
sp_head *sp= lex->sphead;
if (sp->check_backpatch(YYTHD))
YYABORT;
lex->sql_command= SQLCOM_CREATE_SPFUNCTION;
sp->init_strings(YYTHD, lex, lex->spname);
/* Restore flag if it was cleared above */
......@@ -1816,7 +1823,8 @@ sp_proc_stmt:
{
LEX *lex= Lex;
sp_head *sp = lex->sphead;
sp_label_t *lab= lex->spcont->find_label($2.str);
sp_pcontext *ctx= lex->spcont;
sp_label_t *lab= ctx->find_label($2.str);
if (! lab)
{
......@@ -1825,8 +1833,20 @@ sp_proc_stmt:
}
else
{
sp_instr_jump *i= new sp_instr_jump(sp->instructions());
uint ip= sp->instructions();
sp_scope_t sdiff;
sp_instr_jump *i;
sp_instr_hpop *ih;
sp_instr_cpop *ic;
ctx->diff_scopes(0, &sdiff);
ih= new sp_instr_hpop(ip++, sdiff.hndlrs);
sp->push_backpatch(ih, lab);
sp->add_instr(ih);
ic= new sp_instr_cpop(ip++, sdiff.curs);
sp->push_backpatch(ic, lab);
sp->add_instr(ic);
i= new sp_instr_jump(ip);
sp->push_backpatch(i, lab); /* Jumping forward */
sp->add_instr(i);
}
......@@ -1834,19 +1854,101 @@ sp_proc_stmt:
| ITERATE_SYM IDENT
{
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->find_label($2.str);
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
sp_label_t *lab= ctx->find_label($2.str);
if (! lab || lab->isbegin)
if (! lab || lab->type != SP_LAB_ITER)
{
net_printf(YYTHD, ER_SP_LILABEL_MISMATCH, "ITERATE", $2.str);
YYABORT;
}
else
{
uint ip= lex->sphead->instructions();
sp_instr_jump *i= new sp_instr_jump(ip, lab->ip); /* Jump back */
sp_instr_jump *i;
uint ip= sp->instructions();
sp_scope_t sdiff;
ctx->diff_scopes(lab->scopes, &sdiff);
if (sdiff.hndlrs)
sp->add_instr(new sp_instr_hpop(ip++, sdiff.hndlrs));
if (sdiff.curs)
sp->add_instr(new sp_instr_cpop(ip++, sdiff.curs));
i= new sp_instr_jump(ip, lab->ip); /* Jump back */
sp->add_instr(i);
}
}
| LABEL_SYM IDENT
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
sp_label_t *lab= ctx->find_label($2.str);
lex->sphead->add_instr(i);
if (! lab)
lab= ctx->find_glabel($2.str);
if (lab)
{
net_printf(YYTHD, ER_SP_LABEL_REDEFINE, $2.str);
YYABORT;
}
else
{
lab= ctx->push_glabel($2.str, sp->instructions());
lab->type= SP_LAB_GOTO;
lab->scopes= ctx->scopes();
sp->backpatch(lab);
}
}
| GOTO_SYM IDENT
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
uint ip= lex->sphead->instructions();
sp_label_t *lab= ctx->find_label($2.str);
sp_scope_t sdiff;
sp_instr_jump *i;
sp_instr_hpop *ih;
sp_instr_cpop *ic;
if (! lab)
lab= ctx->find_glabel($2.str);
if (! lab)
{
lab= (sp_label_t *)YYTHD->alloc(sizeof(sp_label_t));
lab->name= $2.str;
lab->ip= 0;
lab->type= SP_LAB_REF;
lab->scopes= 0;
ctx->diff_scopes(0, &sdiff);
ih= new sp_instr_hpop(ip++, sdiff.hndlrs);
sp->push_backpatch(ih, lab);
sp->add_instr(ih);
ic= new sp_instr_cpop(ip++, sdiff.curs);
sp->add_instr(ic);
sp->push_backpatch(ic, lab);
i= new sp_instr_jump(ip);
sp->push_backpatch(i, lab); /* Jumping forward */
sp->add_instr(i);
}
else
{
ctx->diff_scopes(lab->scopes, &sdiff);
if (sdiff.hndlrs)
{
ih= new sp_instr_hpop(ip++, sdiff.hndlrs);
sp->add_instr(ih);
}
if (sdiff.curs)
{
ic= new sp_instr_cpop(ip++, sdiff.curs);
sp->add_instr(ic);
}
i= new sp_instr_jump(ip, lab->ip); /* Jump back */
sp->add_instr(i);
}
}
| OPEN_SYM ident
......@@ -2041,7 +2143,8 @@ sp_labeled_control:
IDENT ':'
{
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->find_label($1.str);
sp_pcontext *ctx= lex->spcont;
sp_label_t *lab= ctx->find_label($1.str);
if (lab)
{
......@@ -2050,8 +2153,10 @@ sp_labeled_control:
}
else
{
lex->spcont->push_label($1.str,
lex->sphead->instructions());
lab= lex->spcont->push_label($1.str,
lex->sphead->instructions());
lab->type= SP_LAB_ITER;
lab->scopes= ctx->scopes();
}
}
sp_unlabeled_control sp_opt_label
......@@ -2088,27 +2193,33 @@ sp_unlabeled_control:
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->last_label();
lab->isbegin= TRUE;
lab->type= SP_LAB_BEGIN;
/* Scope duplicate checking */
lex->spcont->push_scope();
}
sp_decls
{
Lex->spcont->push_handlers($3.hndlrs);
}
sp_proc_stmts
END
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
sp_scope_t scope;
sp->backpatch(ctx->last_label()); /* We always has a label */
sp->backpatch(ctx->last_label()); /* We always have a label */
ctx->pop_pvar($3.vars);
ctx->pop_cond($3.conds);
ctx->pop_handlers($3.hndlrs);
ctx->pop_cursor($3.curs);
if ($3.hndlrs)
sp->add_instr(new sp_instr_hpop(sp->instructions(),$3.hndlrs));
sp->add_instr(new sp_instr_hpop(sp->instructions(), $3.hndlrs));
if ($3.curs)
sp->add_instr(new sp_instr_cpop(sp->instructions(), $3.curs));
ctx->pop_scope();
ctx->pop_scope(&scope);
ctx->pop_glabel(scope.glab);
}
| LOOP_SYM
sp_proc_stmts END LOOP_SYM
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment