Switching on strings...

S

slick_shoes

What would be the best way to switch program flow based on a string the user
enters? I found out the hard way that char*'s can't be used in a switch
statement, so i've resorted to stringing if-else statements together. Is
there a more efficient way to do this?

Thanks
slick_shoes
 
P

Patrick Frankenberger

slick_shoes said:
What would be the best way to switch program flow based on a string the user
enters? I found out the hard way that char*'s can't be used in a switch
statement, so i've resorted to stringing if-else statements together. Is
there a more efficient way to do this?

You can use std::map<std::string, functionpointer> to do this:

std::map<std::string, void(*)(void)> x;
x["bar"]=&function1;
x["foo"]=&function2; //and so on

//in input routine:
std::string input;
if(x.find(input)==x.end()) error(); // invalid input
x[input](); // calls the associated function
 
A

Allan Bruce

slick_shoes said:
What would be the best way to switch program flow based on a string the user
enters? I found out the hard way that char*'s can't be used in a switch
statement, so i've resorted to stringing if-else statements together. Is
there a more efficient way to do this?

Thanks
slick_shoes

You *could* do a switch statement, e.g.

char MyString[100]; // or whatever

switch (MyString[0])
{
case 'A':
// do whatever
break;
case 'B':
// do whatever
break;
case 'C':
switch (MyString[1])
{
case 'A':
// do whatever
break;
case 'B':
// do whatever
break;
}
}

Although this is quite messy. You could also set up an array of char[]
which may contain the first few letters of the string to decide which way to
switch, then loop through these with strncmp() and get the index, then
switch on the index
Allan
 
C

Cedric LEMAIRE

slick_shoes said:
What would be the best way to switch program flow based on a string the user
enters? I found out the hard way that char*'s can't be used in a switch
statement, so i've resorted to stringing if-else statements together. Is
there a more efficient way to do this?
To emulate a switch statement on string, I propose you a solution that
consists of:
- converting a string to a (non unique) integral value,
- using a switch statement on the value,
- resolving value-equivalence conflicts,
with no hand-typed code to implement these points. Just specify the
case labels and type the specific code for each of them.

To do it simpler, we'll suppose that each case block ends with a break
statement.

You'll just have to type the following lines by hand:
//##markup##"switch(<your-string-expression>)"
//##data##
//<case-label-1>
//...
//<case-label-n>
//##data##
and you'll obtain by code generation:
//##markup##"switch(<your-string-expression>)"
//##data##
//<case-label-1>
//...
//<case-label-n>
//##data##
//##begin##"switch(<your-string-expression>)"
{
// Computes a hashcode for <your-string-expression>
int iHashCode = 0;
std::string sKey = <your-string-expression>;
for (int i = 0; i < sKey.size(); i++) {
unsigned char c = sKey;
iHashCode = (31*iHashCode + (c%31)) % 64000000;
}
// if true, no case label have matched
bool bDefault = false;
//
switch(iHashCode) {
case <case-value-1>: // "<case-label-1>"
if (sKey == "<case-label-1>") {
//##protect##"case \"<case-label-1>\":"
//##protect##"case \"<case-label-1>\":"
} else { bDefault = true;
}
break;
...
case <case-value-n>: // "<case-label-n>"
if (sKey == "<case-label-n>") {
//##protect##"case \"<case-label-n>\":"
//##protect##"case \"<case-label-n>\":"
} else { bDefault = true;
}
break;
default:
bDefault = true;
}
if (bDefault) {
//##protect##"default:"
//##protect##"default:"
}
}
//##markup##"switch(<your-string-expression>)"

Example:
* Suppose that you want to write something like it:
switch(sText) {
case "Product":
...
break;
case "Customer":
...
break;
case "Figuring":
...
break;
default:
...
}

* Then write:
//##markup##"switch(sText)"
//##data##
//Product
//Customer
//Figurine
//##data##


* Which becomes:
//##markup##"switch(sText)"
//##data##
//Product
//Customer
//Figurine
//##data##
//##begin##"switch(sText)"
{
int iHashCode = 0;
std::string sKey = sText;
for (int i = 0; i < sKey.size(); i++) {
unsigned char c = sKey;
iHashCode = (31*iHashCode + (c%31)) % 64000000;
}
bool bDefault = false;
switch(iHashCode) {
case 17133617: // "Product"
if (sKey == "Product") {
//##protect##"case \"Product\":"
//##protect##"case \"Product\":"
} else { bDefault = true;
}
break;
case 26793087: // "Customer"
if (sKey == "Customer") {
//##protect##"case \"Customer\":"
//##protect##"case \"Customer\":"
} else { bDefault = true;
}
break;
case 20050752: // "Figurine"
if (sKey == "Figurine") {
//##protect##"case \"Figurine\":"
//##protect##"case \"Figurine\":"
} else { bDefault = true;
}
break;
default:
bDefault = true;
}
if (bDefault) {
//##protect##"default:"
//##protect##"default:"
}
}
//##end##"switch(sText)"


The specific code for each case must be written in protected
areas (bounded with //##protect##...) so that the generator
preserves them from a generation to another.

But what about the generator? We'll use a LGPL tool devoted
to work on generative programming and called 'CodeWorker',
available at "http://www.codeworker.org".

We'll write our tailor-made code generation in the scripting
language of CodeWorker. The following script looks like
a server page syntax: raw text with instructions inlayed into.
Here, instructions of the scripting language are put between
the symbol '@'. If you are interested in understanding the
script in detail, I could give you some explanations about it.
We'll call the script "script.gen":
{
int iHashCode = 0;
std::string sKey = @coreString(getMarkupKey(), 7, 1)@;
for (int i = 0; i < sKey.size(); i++) {
unsigned char c = sKey;
iHashCode = (31*iHashCode + (c%31)) % 64000000;
}
bool bDefault = false;
switch(iHashCode) {
@
local codes;
local sData = getMarkupValue();
while sData {
local iIndex = sData.findString('\n');
if $iIndex < 0$ || !sData.startString("//") error("syntax error");
local sKey = sData.midString(2, $iIndex - 2$);
if sKey.endString('\r') set sKey = sKey.rsubString(1);
local iHashCode = 0;
local i = 0;
while $i < sKey.length()$ {
local c = sKey.charAt(i);
iHashCode = $(31*iHashCode + (c.charToInt()%31)) % 64000000$;
increment(i);
}
pushItem codes[iHashCode].keys = "\"" + composeCLikeString(sKey) +
"\"";
set sData = sData.subString($iIndex + 1$);
}
foreach i in codes {
@ case @key(i)@: // @
foreach j in i.keys {
if !first(j) {
@, @
} @@j@@
}
@
@
foreach j in i.keys {
@ @
if !first(j) {
// resolve hashcode conflicts between 2 labels
@} else @
}
@if (sKey == @j@) {
@
setProtectedArea("case " + j + ":");
}
@ } else {
bDefault = true;
}
break;
@
}
@ default:
bDefault = true;
}
if (bDefault) {
@
setProtectedArea("default:");
@ }
}

Now, just type the following line on the console:
CodeWorker -expand script.gen <your-C++-file>

CodeWorker expands the lines you have typed at the beginning
in the developed form, following the directives of "script.gen".
You can add/remove as many case labels as you want, even after having
expanded the C++ file, with no loss of source code. You just have
to type the precedent command line to take it into account.
If you change a label, don't forget to change the name of the
corresponding protected area as well.

Now, we can extend easily our switch statement for not being obliged
to have a break statement at the end of each case block:
- in "script.gen", replace:
bool bDefault = false;
by:
int iRank;
- each if statement implements the line:
iRank = <rank-of-case-label>; // [1..n]
- replace:
bDefault = true;
by:
iRank = 0;
- add a second switch behind the first one, applied on iRank.
- moves the protected areas to each case and to default.

-- Cedric
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,126
Messages
2,570,752
Members
47,313
Latest member
SamualGriz

Latest Threads

Top