Skip to content

Switch expressions #640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/compiler/sc.h
Original file line number Diff line number Diff line change
@@ -705,6 +705,7 @@ SC_FUNC void check_tagmismatch(int formaltag,int actualtag,int allowcoerce,int e
SC_FUNC void check_tagmismatch_multiple(int formaltags[],int numtags,int actualtag,int errline);
SC_FUNC char *funcdisplayname(char *dest,char *funcname);
SC_FUNC int constexpr(cell *val,int *tag,symbol **symptr);
SC_FUNC constvalue *insert_constval(constvalue *prev,constvalue *next,const char *name,cell val,int index);
SC_FUNC constvalue *append_constval(constvalue_root *table,const char *name,cell val,int index);
SC_FUNC constvalue *find_constval(constvalue_root *table,char *name,int index);
SC_FUNC void delete_consttable(constvalue_root *table);
2 changes: 1 addition & 1 deletion source/compiler/sc1.c
Original file line number Diff line number Diff line change
@@ -5437,7 +5437,7 @@ static void destructsymbols(symbol *root,int level)
popreg(sPRI);
}

static constvalue *insert_constval(constvalue *prev,constvalue *next,
SC_FUNC constvalue *insert_constval(constvalue *prev,constvalue *next,
const char *name,cell val,int index)
{
constvalue *cur;
169 changes: 169 additions & 0 deletions source/compiler/sc3.c
Original file line number Diff line number Diff line change
@@ -1657,6 +1657,175 @@ static int hier2(value *lval)
} /* if */
return FALSE;
} /* case */
case tSWITCH: {
int swdefault,casecount,firstcase;
int swtag,csetag,exprtag;
int lbl_table,lbl_exit,lbl_case;
int ident,index;
int bck_allowtags;
cell cidx;
constvalue_root caselist = { NULL, NULL}; /* case list starts empty */
constvalue *cse,*csp,*newval;
char labelname[sNAMEMAX+1];
needtoken('(');
ident=expression(&val,&swtag,NULL,TRUE);
if (ident==iARRAY || ident==iREFARRAY)
error(33,"-unknown-"); /* array must be indexed */
/* generate the code for the switch statement, the label is the address
* of the case table (to be generated later).
*/
lbl_table=getlabel();
lbl_case=0; /* just to avoid a compiler warning */
ffswitch(lbl_table);
lbl_exit=getlabel(); /* get label number for jumping out of switch */
casecount=0;
swdefault=FALSE;
firstcase=TRUE;
do {
int got_cseval=FALSE; /* true if the case value gets misinterpreted by lex() as a label */
needtoken(tTERM);
ident=lex(&val,&st);
if (ident==')')
break;
lbl_case=getlabel();
setlabel(lbl_case);
bck_allowtags=sc_allowtags;
sc_allowtags=FALSE; /* do not allow tagnames here */
if (ident==tLABEL && st[0]=='_' && st[1]=='\0') {
if (swdefault!=FALSE)
error(16); /* multiple defaults in switch */
swdefault=TRUE;
} else {
if (ident!=tLABEL) {
lexpush();
} else {
symbol *csesym=findloc(st);
if (csesym==NULL)
csesym=findglb(st,sGLOBAL);
if (csesym!=NULL) {
markusage(csesym,uREAD);
ident=csesym->ident;
val=csesym->addr;
csetag=csesym->tag;
got_cseval=TRUE;
} else {
error(220); /* expression with tag override must appear between parentheses */
} /* if */
} /* if */
if (swdefault!=FALSE)
error(15); /* "default" case must be last in switch statement */
do {
if (!got_cseval) {
stgget(&index,&cidx); /* mark position in code generator */
ident=expression(&val,&csetag,NULL,TRUE);
} /* if */
/* if the next token is ";" or ")", then this must be an implicit default case */
if (matchtoken(';') || matchtoken(')')) {
lexpush();
if (swdefault!=FALSE)
error(16); /* multiple defaults in switch */
swdefault=TRUE;
exprtag=csetag;
sc_allowtags=bck_allowtags; /* reset */
goto skip_impl_default;
} /* if */
casecount++;
if (!got_cseval)
stgdel(index,cidx); /* scratch generated code */
if (ident!=iCONSTEXPR)
error(8); /* must be constant expression */
check_tagmismatch(swtag,csetag,TRUE,-1);
/* Search the insertion point (the table is kept in sorted order, so
* that advanced abstract machines can sift the case table with a
* binary search). Check for duplicate case values at the same time.
*/
for (csp=NULL, cse=caselist.first;
cse!=NULL && cse->value<val;
csp=cse, cse=cse->next)
/* nothing */;
if (cse!=NULL && cse->value==val)
error(40,val); /* duplicate "case" label */
/* Since the label is stored as a string in the "constvalue", the
* size of an identifier must be at least 8, as there are 8
* hexadecimal digits in a 32-bit number.
*/
#if sNAMEMAX < 8
#error Length of identifier (sNAMEMAX) too small.
#endif
assert(csp==NULL || csp->next==cse);
newval=insert_constval(csp,cse,itoh(lbl_case),val,0);
if (csp==NULL)
caselist.first=newval;
if (!got_cseval && matchtoken(tDBLDOT)) {
cell end;
stgget(&index,&cidx); /* mark position in code generator */
ident=expression(&end,&csetag,NULL,TRUE);
stgdel(index,cidx); /* scratch generated code */
if (ident!=iCONSTEXPR)
error(8); /* must be constant expression */
if (end<=val)
error(50); /* invalid range */
check_tagmismatch(swtag,csetag,TRUE,-1);
while (++val<=end) {
casecount++;
/* find the new insertion point */
for (csp=NULL, cse=caselist.first;
cse!=NULL && cse->value<val;
csp=cse, cse=cse->next)
/* nothing */;
if (cse!=NULL && cse->value==val)
error(40,val); /* duplicate "case" label */
assert(csp!=NULL && csp->next==cse);
newval=insert_constval(csp,cse,itoh(lbl_case),val,0);
} /* while */
} /* if */
} while (matchtoken(','));
if (!got_cseval)
needtoken(':'); /* ':' ends the case */
} /* if */
sc_allowtags=bck_allowtags; /* reset */
ident=expression(NULL,&exprtag,NULL,TRUE);
skip_impl_default:
if (ident==iARRAY || ident==iREFARRAY)
error(33,"-unknown-"); /* array must be indexed */
if (firstcase) {
tag=exprtag;
firstcase=FALSE;
} else {
check_tagmismatch(tag,exprtag,TRUE,-1);
} /* if */
jumplabel(lbl_exit);
} while (!matchtoken(')'));
#if !defined NDEBUG
/* verify that the case table is sorted (unfortunately, duplicates can
* occur; there really shouldn't be duplicate cases, but the compiler
* may not crash or drop into an assertion for a user error). */
for (cse=caselist.first; cse!=NULL && cse->next!=NULL; cse=cse->next)
assert(cse->value <= cse->next->value);
#endif
/* generate the table here, before lbl_exit (general jump target) */
setlabel(lbl_table);
assert(swdefault==FALSE || swdefault==TRUE);
if (swdefault==FALSE) {
error(95); /* switch expression must contain a "default" case */
/* store lbl_exit as the "none-matched" label in the switch table */
strcpy(labelname,itoh(lbl_exit));
} else {
/* lbl_case holds the label of the "default" clause */
strcpy(labelname,itoh(lbl_case));
} /* if */
ffcase(casecount,labelname,TRUE);
/* generate the rest of the table */
for (cse=caselist.first; cse!=NULL; cse=cse->next)
ffcase(cse->value,cse->name,FALSE);

setlabel(lbl_exit);
delete_consttable(&caselist); /* clear list of case labels */
clear_value(lval);
lval->ident=iEXPRESSION;
lval->tag=tag;
return FALSE;
} /* case */
case t__STATIC_ASSERT:
case t__STATIC_CHECK: {
int use_warning=(tok==t__STATIC_CHECK);
3 changes: 2 additions & 1 deletion source/compiler/sc5.c
Original file line number Diff line number Diff line change
@@ -133,7 +133,8 @@ static char *errmsg[] = {
/*091*/ "ambiguous constant; tag override is required (symbol \"%s\")\n",
/*092*/ "functions may not return arrays of unknown size (symbol \"%s\")\n",
/*093*/ "\"__addressof\" operator is invalid in preprocessor expressions\n",
/*094*/ "division by zero\n"
/*094*/ "division by zero\n",
/*095*/ "switch expression must contain a \"default\" case\n"
};

static char *fatalmsg[] = {
48 changes: 48 additions & 0 deletions source/compiler/tests/switch_expressions_pcode.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
'test_type': 'pcode_check',
'code_pattern': r"""
[0-9a-f]+ proc
[0-9a-f]+ load.s.pri 0000000c
[0-9a-f]+ switch [0-9a-f]+
[0-9a-f]+ zero.pri
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ const.pri 0000000a
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ const.pri 0000000f
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ casetbl 0000000a [0-9a-f]+
00000001 [0-9a-f]+
00000002 [0-9a-f]+
00000003 [0-9a-f]+
00000004 [0-9a-f]+
00000005 [0-9a-f]+
00000006 [0-9a-f]+
00000007 [0-9a-f]+
00000008 [0-9a-f]+
00000009 [0-9a-f]+
0000000a [0-9a-f]+
[0-9a-f]+ retn

[0-9a-f]+ proc
[0-9a-f]+ load.s.pri 0000000c
[0-9a-f]+ switch [0-9a-f]+
[0-9a-f]+ zero.pri
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ const.pri 0000000a
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ const.pri 0000000f
[0-9a-f]+ jump [0-9a-f]+
[0-9a-f]+ casetbl 0000000a [0-9a-f]+
00000001 [0-9a-f]+
00000002 [0-9a-f]+
00000003 [0-9a-f]+
00000004 [0-9a-f]+
00000005 [0-9a-f]+
00000006 [0-9a-f]+
00000007 [0-9a-f]+
00000008 [0-9a-f]+
00000009 [0-9a-f]+
0000000a [0-9a-f]+
[0-9a-f]+ retn
"""
}
25 changes: 25 additions & 0 deletions source/compiler/tests/switch_expressions_pcode.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
GetDiscount(numitems)
{
return switch (numitems;
1, 2, 3: 0;
4..10: 10;
_: 15;
);
}

GetDiscount2(numitems)
{
// Alternative syntax with implicit default case and optional ";" after it.
assert(numitems > 0);
return switch (numitems;
1, 2, 3: 0;
4..10: 10;
15
);
}

main()
{
GetDiscount(1);
GetDiscount2(1);
}
23 changes: 23 additions & 0 deletions source/compiler/tests/switch_expressions_runtime.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
'test_type': 'runtime',
'output': """
The discount for 1 items is 0%
The discount for 3 items is 0%
The discount for 4 items is 10%
The discount for 7 items is 10%
The discount for 10 items is 10%
The discount for 11 items is 15%
The discount for 20 items is 15%
The discount for 50 items is 15%
The discount for 1 items is 0%
The discount for 3 items is 0%
The discount for 4 items is 10%
The discount for 7 items is 10%
The discount for 10 items is 10%
The discount for 11 items is 15%
The discount for 20 items is 15%
The discount for 50 items is 15%
switch_expressions_runtime.amx returns 0
""",
'should_fail': False
}
52 changes: 52 additions & 0 deletions source/compiler/tests/switch_expressions_runtime.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <console>

GetDiscount(numitems)
{
assert(numitems > 0);
return switch (numitems;
1, 2, 3: 0;
4..10: 10;
_: 15;
);
}

PrintDiscount(numitems)
{
printf("The discount for %d items is %d%c\n", numitems, GetDiscount(numitems), '%');
}

GetDiscount2(numitems)
{
// Alternative syntax with implicit default case and optional ";" after it.
assert(numitems > 0);
return switch (numitems;
1, 2, 3: 0;
4..10: 10;
15
);
}

PrintDiscount2(numitems)
{
printf("The discount for %d items is %d%c\n", numitems, GetDiscount2(numitems), '%');
}

main()
{
PrintDiscount(1);
PrintDiscount(3);
PrintDiscount(4);
PrintDiscount(7);
PrintDiscount(10);
PrintDiscount(11);
PrintDiscount(20);
PrintDiscount(50);
PrintDiscount2(1);
PrintDiscount2(3);
PrintDiscount2(4);
PrintDiscount2(7);
PrintDiscount2(10);
PrintDiscount2(11);
PrintDiscount2(20);
PrintDiscount2(50);
}
24 changes: 24 additions & 0 deletions source/compiler/tests/switch_expressions_syntax.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
'test_type': 'output_check',
'errors': """
switch_expressions_syntax.pwn(8 -- 9) : error 008: must be a constant expression; assumed zero
switch_expressions_syntax.pwn(16 -- 18) : error 015: "default" case must be the last case in switch statement
switch_expressions_syntax.pwn(24 -- 26) : error 016: multiple defaults in "switch"
switch_expressions_syntax.pwn(32 -- 33) : error 001: expected token: ";", but found "-integer value-"
switch_expressions_syntax.pwn(32 -- 35) : error 001: expected token: ";", but found "-label-"
switch_expressions_syntax.pwn(41 -- 42) : error 001: expected token: ":", but found "-integer value-"
switch_expressions_syntax.pwn(41 -- 44) : error 029: invalid expression, assumed zero
switch_expressions_syntax.pwn(50 -- 52) : error 040: duplicate "case" label (value 1)
switch_expressions_syntax.pwn(59 -- 60) : error 050: invalid range
switch_expressions_syntax.pwn(67 -- 69) : error 040: duplicate "case" label (value 1)
switch_expressions_syntax.pwn(67 -- 70) : error 040: duplicate "case" label (value 3)
switch_expressions_syntax.pwn(78 -- 79) : warning 213: tag mismatch: expected tag "Tag", but found none ("_")
switch_expressions_syntax.pwn(78 -- 81) : warning 213: tag mismatch: expected tag "Tag", but found none ("_")
switch_expressions_syntax.pwn(78 -- 82) : warning 213: tag mismatch: expected tag "Tag", but found none ("_")
switch_expressions_syntax.pwn(90 -- 92) : warning 213: tag mismatch: expected tag "Tag", but found none ("_")
switch_expressions_syntax.pwn(90 -- 94) : warning 213: tag mismatch: expected tag "Tag", but found none ("_")
switch_expressions_syntax.pwn(99 -- 101) : error 095: switch expression must contain a "default" case
switch_expressions_syntax.pwn(106 -- 107) : warning 209: function "FuncNoRetVal" should return a value
switch_expressions_syntax.pwn(106 -- 108) : warning 209: function "FuncNoRetVal" should return a value
"""
}
Loading