diff --git a/source/compiler/sc.h b/source/compiler/sc.h index d5576510..55c57546 100644 --- a/source/compiler/sc.h +++ b/source/compiler/sc.h @@ -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); diff --git a/source/compiler/sc1.c b/source/compiler/sc1.c index a50409ea..6cda7c31 100644 --- a/source/compiler/sc1.c +++ b/source/compiler/sc1.c @@ -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; diff --git a/source/compiler/sc3.c b/source/compiler/sc3.c index 11d7c03e..fb1e5286 100644 --- a/source/compiler/sc3.c +++ b/source/compiler/sc3.c @@ -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->valuenext) + /* 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->valuenext) + /* 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); diff --git a/source/compiler/sc5.c b/source/compiler/sc5.c index ae126ec9..afc85dcd 100644 --- a/source/compiler/sc5.c +++ b/source/compiler/sc5.c @@ -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[] = { diff --git a/source/compiler/tests/switch_expressions_pcode.meta b/source/compiler/tests/switch_expressions_pcode.meta new file mode 100644 index 00000000..2ecf2a67 --- /dev/null +++ b/source/compiler/tests/switch_expressions_pcode.meta @@ -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 +""" +} diff --git a/source/compiler/tests/switch_expressions_pcode.pwn b/source/compiler/tests/switch_expressions_pcode.pwn new file mode 100644 index 00000000..541a4c40 --- /dev/null +++ b/source/compiler/tests/switch_expressions_pcode.pwn @@ -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); +} diff --git a/source/compiler/tests/switch_expressions_runtime.meta b/source/compiler/tests/switch_expressions_runtime.meta new file mode 100644 index 00000000..5fce488d --- /dev/null +++ b/source/compiler/tests/switch_expressions_runtime.meta @@ -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 +} diff --git a/source/compiler/tests/switch_expressions_runtime.pwn b/source/compiler/tests/switch_expressions_runtime.pwn new file mode 100644 index 00000000..d381808e --- /dev/null +++ b/source/compiler/tests/switch_expressions_runtime.pwn @@ -0,0 +1,52 @@ +#include + +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); +} diff --git a/source/compiler/tests/switch_expressions_syntax.meta b/source/compiler/tests/switch_expressions_syntax.meta new file mode 100644 index 00000000..abb3e62d --- /dev/null +++ b/source/compiler/tests/switch_expressions_syntax.meta @@ -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 +""" +} diff --git a/source/compiler/tests/switch_expressions_syntax.pwn b/source/compiler/tests/switch_expressions_syntax.pwn new file mode 100644 index 00000000..40fbc769 --- /dev/null +++ b/source/compiler/tests/switch_expressions_syntax.pwn @@ -0,0 +1,126 @@ +#pragma semicolon 1 + +Func() return 0; +FuncNoRetVal(){} + +test_NonConstCaseValue(value) +{ + return switch (value; + Func(): 0; // error 008: must be a constant expression; assumed zero + _: 1 + ); +} + +test_CaseAfterDefault(value) +{ + return switch (value; + _: 0; + 1: 1 // error 015: "default" case must be the last case in switch statement + ); +} + +test_MultipleDefaults(value) +{ + return switch (value; + _: 0; + _: 1 // error 016: multiple defaults in "switch" + ); +} + +test_MissingSemicolon(value) +{ + return switch (value + 1: 0; // error 001: expected token: ";", but found "-integer value-" + 2: 1 + _: 2 // error 001: expected token: ";", but found "-label-" + ); +} + +test_MissingColon(value) +{ + return switch (value; + 1 0; // error 001: expected token: ":", but found "-integer value-" + 2: 1; + _ 2 // error 029: invalid expression, assumed zero + ); +} + +test_DuplicateCases(value) +{ + return switch (value; + 1: 0; + 1: 1; // error 040: duplicate "case" label (value 1) + _: 2 + ); +} + +test_InvalidRange(value) +{ + return switch (value; + 1..1: 0; // error 050: invalid range + _: 1 + ); +} + +test_OverlayingRanges(value) +{ + return switch (value; + 1..10: 0; + 0..9: 1; // error 040: duplicate "case" label (value 1) + 3..8: 2; // error 040: duplicate "case" label (value 3) + _: 3 + ); +} + +test_TagMismatchCase(value) +{ + const Tag:TaggedConst = Tag:1; + return switch (Tag:value; + 0: 0; // warning 213: tag mismatch: expected tag "Tag", but found none ("_") + TaggedConst: 1; + (Tag:2)..3: 2; // warning 213: tag mismatch: expected tag "Tag", but found none ("_") + 4..(Tag:5): 3; // warning 213: tag mismatch: expected tag "Tag", but found none ("_") + (Tag:6)..(Tag:7): 4; + _: 5 + ); +} + +Tag:test_TagMismatchExpr(value) +{ + return switch (value; + 0: Tag:0; + 1: 1; // warning 213: tag mismatch: expected tag "Tag", but found none ("_") + _: 2 // warning 213: tag mismatch: expected tag "Tag", but found none ("_") + ); +} + +test_MissingDefault(value) +{ + return switch (value; + 0: 0 + ); // error 095: switch expression must contain a "default" case +} + +test_NoReturnValue(value) +{ + return switch (value; + 0: FuncNoRetVal(); // warning 209: function "FuncNoRetVal" should return a value + _: FuncNoRetVal(); // warning 209: function "FuncNoRetVal" should return a value + ); +} + +main() +{ + test_NonConstCaseValue(0); + test_CaseAfterDefault(0); + test_MultipleDefaults(0); + test_MissingSemicolon(0); + test_MissingColon(0); + test_DuplicateCases(0); + test_InvalidRange(0); + test_OverlayingRanges(0); + test_TagMismatchCase(0); + test_TagMismatchExpr(0); + test_MissingDefault(0); + test_NoReturnValue(0); +}