I'm currently working on project for school with an Arduino Uno and came across an issue with C sscanf function. The code bellow is parsing a char array sent over the serial interface to the Arduino. Each data block is separated with a blankspace. I also checked similar question on Stackoverflow but found nothing helpful.
char testInput[] = "t 1 3 65 1";
int registerIndex;
int locoAddr;
int locoSpeed;
int locoDirection;
if(sscanf(testInput, "t %d %d %d %d", ®isterIndex, &locoAddr, &locoSpeed, &locoDirection) != 4) {
return;
}
// print for demo:
Serial.print(registerIndex);
Serial.print(": ");
Serial.print("loco:");
Serial.print(locoAddr);
Serial.print(" speed:");
Serial.print(locoSpeed);
Serial.print(" direction:");
Serial.println(locoDirection);
// expected output: "1: loco:3 speed:65 direction:1"
// output: "-18248: loco:-18248 speed:-18248 direction:-18248"
the sscanf function returns the expected integer 4 but doesn't fill the variables with the values so only the default values are printed.
I'm out of ideas what the issue could be. Maybe someone on the internet has a solution.
Edit 1: For better understanding of the complexity of the sketch this might help to understand the code a bit better:
loop in .ino file calls static Commander::process().
Commander::_readLine(...); get called and reads the data from serial.
some more validation is done but should not matter in this case
Commander::parseCommand(command); get called with the command.
registers->setThrottle(command); gets called when the command starts with a "t". registers is volatile so I'm able to call it from the interupt.
in PackageRegisterList::setThrottle is the sscanf method located.
I usually use this version stolen from the Linux kernel:
int ASCII_vsscanf(const char * buf, const char * fmt, va_list args)
{
const char *str = buf;
char *next;
char digit;
int num = 0;
int qualifier;
int base;
int field_width;
int is_sign = 0;
while(*fmt && *str) {
/* skip any white space in format */
/* white space in format matchs any amount of
* white space, including none, in the input.
*/
if (isspace(*fmt)) {
while (isspace(*fmt))
++fmt;
while (isspace(*str))
++str;
}
/* anything that is not a conversion must match exactly */
if (*fmt != '%' && *fmt) {
if (*fmt++ != *str++)
break;
continue;
}
if (!*fmt)
break;
++fmt;
/* skip this conversion.
* advance both strings to next white space
*/
if (*fmt == '*') {
while (!isspace(*fmt) && *fmt)
fmt++;
while (!isspace(*str) && *str)
str++;
continue;
}
/* get field width */
field_width = -1;
if (isdigit(*fmt))
field_width = skip_atoi(&fmt);
/* get conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' ||
*fmt == 'Z' || *fmt == 'z') {
qualifier = *fmt++;
if (unlikely(qualifier == *fmt)) {
if (qualifier == 'h') {
qualifier = 'H';
fmt++;
} else if (qualifier == 'l') {
qualifier = 'L';
fmt++;
}
}
}
base = 10;
is_sign = 0;
if (!*fmt || !*str)
break;
switch(*fmt++) {
case 'c':
{
char *s = (char *) va_arg(args,char*);
if (field_width == -1)
field_width = 1;
do {
*s++ = *str++;
} while (--field_width > 0 && *str);
num++;
}
continue;
case 's':
{
char *s = (char *) va_arg(args, char *);
if(field_width == -1)
field_width = INT_MAX;
/* first, skip leading white space in buffer */
while (isspace(*str))
str++;
/* now copy until next white space */
while (*str && !isspace(*str) && field_width--) {
*s++ = *str++;
}
*s = '\0';
num++;
}
continue;
case 'n':
/* return number of characters read so far */
{
int *i = (int *)va_arg(args,int*);
*i = str - buf;
}
continue;
case 'o':
base = 8;
break;
case 'x':
case 'X':
base = 16;
break;
case 'i':
base = 0;
case 'd':
is_sign = 1;
case 'u':
break;
case '%':
/* looking for '%' in str */
if (*str++ != '%')
return num;
continue;
default:
/* invalid format; stop here */
return num;
}
/* have some sort of integer conversion.
* first, skip white space in buffer.
*/
while (isspace(*str))
str++;
digit = *str;
if (is_sign && digit == '-')
digit = *(str + 1);
if (!digit
|| (base == 16 && !isxdigit(digit))
|| (base == 10 && !isdigit(digit))
|| (base == 8 && (!isdigit(digit) || digit > '7'))
|| (base == 0 && !isdigit(digit)))
break;
switch(qualifier) {
case 'H': /* that's 'hh' in format */
if (is_sign) {
signed char *s = (signed char *) va_arg(args,signed char *);
*s = (signed char) ASCII_simple_strtol(str,&next,base);
} else {
unsigned char *s = (unsigned char *) va_arg(args, unsigned char *);
*s = (unsigned char) ASCII_simple_strtoul(str, &next, base);
}
break;
case 'h':
if (is_sign) {
short *s = (short *) va_arg(args,short *);
*s = (short) ASCII_simple_strtol(str,&next,base);
} else {
unsigned short *s = (unsigned short *) va_arg(args, unsigned short *);
*s = (unsigned short) ASCII_simple_strtoul(str, &next, base);
}
break;
case 'l':
if (is_sign) {
long *l = (long *) va_arg(args,long *);
*l = ASCII_simple_strtol(str,&next,base);
} else {
unsigned long *l = (unsigned long*) va_arg(args,unsigned long*);
*l = ASCII_simple_strtoul(str,&next,base);
}
break;
case 'L':
if (is_sign) {
long long *l = (long long*) va_arg(args,long long *);
*l = ASCII_simple_strtoll(str,&next,base);
} else {
unsigned long long *l = (unsigned long long*) va_arg(args,unsigned long long*);
*l = ASCII_simple_strtoull(str,&next,base);
}
break;
case 'Z':
case 'z':
{
size_t *s = (size_t*) va_arg(args,size_t*);
*s = (size_t) ASCII_simple_strtoul(str,&next,base);
}
break;
default:
if (is_sign) {
int *i = (int *) va_arg(args, int*);
*i = (int) ASCII_simple_strtol(str,&next,base);
} else {
unsigned int *i = (unsigned int*) va_arg(args, unsigned int*);
*i = (unsigned int) ASCII_simple_strtoul(str,&next,base);
}
break;
}
num++;
if (!next)
break;
str = next;
}
return num;
}
Limited but small and good enough for me.
I am using LittleFS library and ESP32 on arduino IDE.
I am reading a file using the example readFile function of LittleFS but I am trying to convert it for my needs.
The text written to the file is of this form:
LettersAndNumbersMax30&LettersAndNumbersMax30&00&00&01&01
Seperated by &. 2 text values of max 30 characters and 4 integers.
I want to build:
char *mytest1 containing the first text
char *mytest2 containing the second text
int mytest3 containing the first integer (2digits)
int mytest4 containing the second integer (2digits)
int mytest5 containing the third integer (2digits)
int mytest5 containing the forth integer (2digits)
file.read() returns and integer always. for example 38 for &.
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\r\n", path);
File file = fs.open(path);
if(!file || file.isDirectory()){
Serial.println("- failed to open file for reading");
return;
}
Serial.println("- read from file:");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
Its fairly straightforward. Test each byte read and act accordingly. Code below doesn't handle signs nor negative numbers. It also doesn't check if there are only digits for integers in the file.
#include ....
struct record_t
{
char myState1[31];
char myState2[31];
int myState3;
int myState4;
int myState5;
int myState6;
};
record_t record;
bool readFile(fs::FS &fs, const char * path);
void setup()
{
// ...
}
void loop()
{
//...
if (readFile(/*...*/))
{
Serial.printf("file reads OK\r\n");
//...
}
}
bool readFile(fs::FS &fs, const char * path)
{
Serial.printf("Reading file: %s\r\n", path);
File file = fs.open(path);
if (!file || file.isDirectory())
{
Serial.println("- failed to open file for reading");
return;
}
Serial.println("- read from file:");
int state = 0;
int index = 0;
// clear record.
record.myState1[0] = 0;
record.myState2[0] = 0;
record.myState3 = 0;
record.myState4 = 0;
record.myState5 = 0;
record.myState6 = 0;
bool valid = false;
for (int i = file.read(); i != -1; i = file.read())
{
char c = i & 0xFF;
Serial.write(c); // file.read() returns an int, that's why Serial.write()
// was printing numbers.
switch(state)
{
case 0:
if (index > sizeof(record.myState1) - 1) // avoid buffer overflow
index = sizeof(record.myState1) - 1;
if (c != '&')
{
record.myState1[index++] = c;
}
else
{
record.myState1[index] = 0;
++state;
index = 0;
}
break;
case 1:
if (index > sizeof(record.myState2) - 1) // avoid buffer overflow
index = sizeof(record.myState2) - 1;
if (c != '&')
{
record.myState2[index++] = c;
}
else
{
record.myState2[index] = 0;
++state;
index = 0;
}
break;
case 2:
if (c != '&')
record.myState3 = record.myState3 * 10 + (c - '0');
else
++state;
break;
case 3:
if (c != '&')
record.myState4 = record.myState4 * 10 + (c - '0');
else
++state;
break;
case 4:
if (c != '&')
record.myState5 = record.myState5 * 10 + (c - '0');
else
++state;
break;
case 5:
valid = true;
if (c != '&')
record.myState6 = record.myState6 * 10 + (c - '0');
else
++state;
break;
default: // reaching here is an error condition? You decide.
return false;
}
}
file.close();
if (!valid)
{
// clear record.
record.myState1[0] = 0;
record.myState2[0] = 0;
record.myState3 = 0;
record.myState4 = 0;
record.myState5 = 0;
record.myState6 = 0;
}
return valid;
}
file.read returns integer. So the integer is printed.
You to convert it to the string.
while(file.available()){
char s[2] = {0};
s[0] = file.read();
Serial.write(s);
}
I've a small C-program which just reads numbers from stdin, one at each loop cycle. If the user inputs some NaN, an error should be printed to the console and the input prompt should return again. On input of "0", the loop should end and the number of given positive/negative values should be printed to the console. Here's the program:
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
My problem is, that on entering some non-number (like "a"), this results in an infinite loop writing "-> Err..." over and over. I guess it's a scanf() issue and I know this function could be replace by a safer one, but this example is for beginners, knowing just about printf/scanf, if-else and loops.
I've already read the answers to the questionscanf() skips every other while loop in C and skimmed through other questions, but nothing really answer this specific problem.
scanf consumes only the input that matches the format string, returning the number of characters consumed. Any character that doesn't match the format string causes it to stop scanning and leaves the invalid character still in the buffer. As others said, you still need to flush the invalid character out of the buffer before you proceed. This is a pretty dirty fix, but it will remove the offending characters from the output.
char c = '0';
if (scanf("%d", &number) == 0) {
printf("Err. . .\n");
do {
c = getchar();
}
while (!isdigit(c));
ungetc(c, stdin);
//consume non-numeric chars from buffer
}
edit: fixed the code to remove all non-numeric chars in one go. Won't print out multiple "Errs" for each non-numeric char anymore.
Here is a pretty good overview of scanf.
scanf() leaves the "a" still in the input buffer for next time. You should probably use getline() to read a line no matter what and then parse it with strtol() or similar instead.
(Yes, getline() is GNU-specific, not POSIX. So what? The question is tagged "gcc" and "linux". getline() is also the only sensible option to read a line of text unless you want to do it all by hand.)
I think you just have to flush the buffer before you continue with the loop. Something like that would probably do the job, although I can't test what I am writing from here:
int c;
while((c = getchar()) != '\n' && c != EOF);
Due to the problems with scanf pointed out by the other answers, you should really consider using another approach. I've always found scanf way too limited for any serious input reading and processing. It's a better idea to just read whole lines in with fgets and then working on them with functions like strtok and strtol (which BTW will correctly parse integers and tell you exactly where the invalid characters begin).
Rather than using scanf() and have to deal with the buffer having invalid character, use fgets() and sscanf().
/* ... */
printf("0 to quit -> ");
fflush(stdout);
while (fgets(buf, sizeof buf, stdin)) {
if (sscanf(buf, "%d", &number) != 1) {
fprintf(stderr, "Err...\n");
} else {
work(number);
}
printf("0 to quit -> ");
fflush(stdout);
}
/* ... */
I had similar problem. I solved by only using scanf.
Input "abc123<Enter>" to see how it works.
#include <stdio.h>
int n, num_ok;
char c;
main() {
while (1) {
printf("Input Number: ");
num_ok = scanf("%d", &n);
if (num_ok != 1) {
scanf("%c", &c);
printf("That wasn't a number: %c\n", c);
} else {
printf("The number is: %d\n", n);
}
}
}
On some platforms (especially Windows and Linux) you can use fflush(stdin);:
#include <stdio.h>
int main(void)
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fflush(stdin);
printf("Err...\n");
continue;
}
fflush(stdin);
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
The Solution: You need to add fflush(stdin); when 0 is returned from scanf.
The Reason: It appears to be leaving the input char in the buffer when an error is encountered, so every time scanf is called it just keeps trying to handle the invalid character but never removing it form the buffer. When you call fflush, the input buffer(stdin) will be cleared so the invalid character will no longer be handled repeatably.
You Program Modified: Below is your program modified with the needed change.
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fflush(stdin);
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
try using this:
if (scanf("%d", &number) == 0) {
printf("Err...\n");
break;
}
this worked fine for me... try this..
the continue statement is not appropiate as the Err.. should only execute once. so, try break which I tested... this worked fine for you.. i tested....
When a non-number is entered an error occurs and the non-number is still kept in the input buffer. You should skip it. Also even this combination of symbols as for example 1a will be read at first as number 1 I think you should also skip such input.
The program can look the following way.
#include <stdio.h>
#include <ctype.h>
int main(void)
{
int p = 0, n = 0;
while (1)
{
char c;
int number;
int success;
printf("-> ");
success = scanf("%d%c", &number, &c);
if ( success != EOF )
{
success = success == 2 && isspace( ( unsigned char )c );
}
if ( ( success == EOF ) || ( success && number == 0 ) ) break;
if ( !success )
{
scanf("%*[^ \t\n]");
clearerr(stdin);
}
else if ( number > 0 )
{
++p;
}
else if ( number < n )
{
++n;
}
}
printf( "\nRead %d positive and %d negative numbers\n", p, n );
return 0;
}
The program output might look like
-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0
Read 3 positive and 3 negative numbers
I had the same problem, and I found a somewhat hacky solution. I use fgets() to read the input and then feed that to sscanf(). This is not a bad fix for the infinite loop problem, and with a simple for loop I tell C to search for any none numeric character. The code below won't allow inputs like 123abc.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(int argc, const char * argv[]) {
char line[10];
int loop, arrayLength, number, nan;
arrayLength = sizeof(line) / sizeof(char);
do {
nan = 0;
printf("Please enter a number:\n");
fgets(line, arrayLength, stdin);
for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
if(line[loop] == '\n') { // stop the search if there is a carrage return
break;
}
if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
continue;
}
if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
nan++;
break;
}
}
} while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
sscanf(line, "%d", &number);
printf("You enterd number %d\n", number);
return 0;
}
To solve partilly your problem I just add this line after the scanf:
fgetc(stdin); /* to delete '\n' character */
Below, your code with the line:
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fgetc(stdin); /* to delete '\n' character */
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
But if you enter more than one character, the program continues one by one character until the "\n".
So I found a solution here: How to limit input length with scanf
You can use this line:
int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);
// all you need is to clear the buffer!
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
char clearBuf[256]; //JG:
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fgets(stdin, 256, clearBuf); //JG:
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Flush the input buffer before you scan:
while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
...
I was going to suggest fflush(stdin), but apparently that results in undefined behavior.
In response to your comment, if you'd like the prompt to show up, you have to flush the output buffer. By default, that only happens when you print a newline. Like:
while (1) {
printf("-> ");
fflush(stdout);
while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
...
Hi I know this is an old thread but I just finished a school assignment where I ran into this same problem.
My solution is that I used gets() to pick up what scanf() left behind.
Here is OP code slightly re-written; probably no use to him but perhaps it will help someone else out there.
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
char unwantedCharacters[40]; //created array to catch unwanted input
unwantedCharacters[0] = 0; //initialzed first byte of array to zero
while (1)
{
printf("-> ");
scanf("%d", &number);
gets(unwantedCharacters); //collect what scanf() wouldn't from the input stream
if (unwantedCharacters[0] == 0) //if unwantedCharacters array is empty (the user's input is valid)
{
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
else
printf("Err...\n");
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
I've recently been through the same problem, and I found a solution that might help a lot of people. The function "scanf" leaves a buffer in memory ... and that's why the infinite loop is caused. So you actually have to "store" this buffer to another variable IF your initial scanf contains the "null" value. Here's what I mean:
#include <stdio.h>
int n;
char c[5];
int main() {
while (1) {
printf("Input Number: ");
if (scanf("%d", &n)==0) { //if you type char scanf gets null value
scanf("%s", &c); //the abovementioned char stored in 'c'
printf("That wasn't a number: %s\n", c);
}
else printf("The number is: %d\n", n);
}
}
Is there an easy way to escape all special characters in the printf() function?
The reason why I would like to know how to do this is because I am printing a number of characters which may include special characters such as the null character (\0) and the beep character and I just want to see the contents of the string.
Currently I am using the following code
It works for null characters. What would be the easiest way to escape all special characters?
int length;
char* data = GetData( length ); // Fills the length as reference
for( int i = 0; i < length; i++ )
{
char c = data[ i ];
printf( "%c", ( c == 0 ? '\\0' : data[ i ] ) );
}
First of all, '\\0' is a two-character literal, which should really be a two-character string. As for printing all special characters as escape code, you need some more code:
switch (data[i])
{
case '\0':
printf("\\0");
break;
case '\n':
printf("\\n");
break;
/* Etc. */
default:
/* Now comes the "hard" part, because not all characters here
* are actually printable
*/
if (isprint(data[i]))
printf("%c", data[i]); /* Printable character, print it as usual */
else
printf("\\x%02x", data[i]); /* Non-printable character, print as hex value */
break;
}
Use the isprint library function to determine if the character is printable:
#include <ctype.h>
...
if (isprint(data[i]))
printf(" %c", data[i]); // prints character
else
printf(" %d", data[i]); // prints code value for character
In case code needs to write with no ambiguity, using C syntax:
#include <ctype.h>
#include <string.h>
#include <stdio.h>
void EscapePrint(int ch) {
// Delete or adjust these 2 arrays per code's goals
// All simple-escape-sequence C11 6.4.4.4
static const char *escapev = "\a\b\t\n\v\f\r\"\'\?\\";
static const char *escapec = "abtnvfr\"\'\?\\";
char *p = strchr(escapev, ch);
if (p && *p) {
printf("\\%c", escapec[p - escapev]);
} else if (isprint(ch)) {
fputc(ch, stdout);
} else {
// Use octal as hex is problematic reading back
printf("\\%03o", ch);
}
}
void EscapePrints(const char *data, int length) {
while (length-- > 0) {
EscapePrint((unsigned char) *data++);
}
}
Alternatively, code could
void EscapePrint(char sch) {
int ch = (unsigned char) sch;
...
}
void EscapePrints(const char *data, int length) {
while (length-- > 0) {
EscapePrint(*data++);
}
}
To use a hexadecimal-escape-sequence or a shorten octal-escape-sequence, code needs to insure that the next character does not create ambiguity. That complication does not occur in the above code as it uses 3-digit octal-escape-sequences. Amended code would be something like:
} else {
if ((ch == 0) && (nextch < '0' || nextch > '7')) {
fputs("\\0", stdout);
}
else if (!isxdigit((unsigned char) nextch)) {
printf("\\x%X", ch);
}
else {
// Use octal as hex is problematic reading back
printf("\\%03o", ch);
}
}
#include <stdio.h>
#include <ctype.h>
/* Converts a buffer of specified lenth to
* ASCII representation as it was a C string literal.
* Returns how much bytes from source was processed
* (ideally ret == src_sz)
*/
int binbuf_to_escaped_C_literal(const char *src_buf, size_t src_sz, char *dst_str, size_t dst_sz)
{
const char *src = src_buf;
char *dst = dst_str;
while (src < src_buf + src_sz)
{
if (*src == '\\')
{
*dst++ = '\\';
*dst++ = *src++;
}
else if (isprint(*src))
{
*dst++ = *src++;
}
else
{
switch(*src)
{
case '\n':
*dst++ = '\\';
*dst++ = 'n';
break;
case '\r':
*dst++ = '\\';
*dst++ = 'r';
break;
case '\t':
*dst++ = '\\';
*dst++ = 't';
break;
case '\0':
*dst++ = '\\';
*dst++ = '0';
break;
default:
sprintf(dst, "0x%x", *src);
dst += 4;
}
src++;
}
// next iteration requires up to 5 chars in dst buffer, for ex. "0xab\0"
if (dst > (dst_str + dst_sz - 5)) {
break;
}
}
*dst = '\0';
return src - src_buf;
}
int main(int argc, char **argv)
{
const char binbuf[] = "strange \n\r\t\0\0\0\0\0\\\\ string";
size_t sz = sizeof(binbuf) - 1; // drop trailing nul terminator
char escaped[128];
if (binbuf_to_escaped_C_literal(binbuf, sz, escaped, sizeof(escaped)) != sz) {
fprintf(stderr, "Destination string buffer is too small\n");
return 1;
}
printf("Escaped: %s\n", escaped);
// $ ./escape-binary-buf //
// Escaped: strange \n\r\t\0\0\0\0\0\\\\ string //
return 0;
}
I working my way through a C++ and Operating Systems book and I've come upon an assignment that requires creation, writing, and reading from pipes. However my program stalls on reading from the second pipe. My program is to accept input and parse out a space delimited string into tokens and classifying those tokens accordingly. My code is bellow with my problem area marked. Any help is as always very appreciated.
edit: This is supposed to have two children. One for processing the space delimited tokens and the other for determining the type of delimited tokens. As far as debugging goes I only have access to cout as a debugger. So I inserted a cout before the read and after the one before the read appeared but the one after did not.
#include <iostream>
#include <fstream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
//declaring the pipes
int pipeOne[2];
int pipeTwo[2];
struct inputStruct {
char str[256]; /* one extra spot for \n */
int len; /* length of str */
int flag; /* 0 for normal input, 1 to indicate “done” */
};
struct tokenStruct {
char token[256]; /* tokens can be 255 max */
int flag; /* same as inputStruct */
int tokenType; /* a code value */
};
void dataProcess(){
//new input struct to contain the the input from the parent
inputStruct input;
//the intial read from the pipe to populate the input stuct
read( pipeOne[0], (char*)&input, sizeof(inputStruct));
//set the flag
int flag = input.flag;
while (flag != 1){
int size = 0;
//get the size of the array up until the null character
while (input.str[size] != '\0'){
size++;
}
//Here's the parsing of each token
for (int i=0; i<size; i++) {
int tokenLength;
tokenStruct token;
//while the char isn't white space or null increment through it
while (input.str[i] != ' ' && input.str[i] != '\0') {
//a is the index of the string token
int a = 0;
//write the parsed string
token.token[a] = input.str[i];
a++;
i++;
}
//write to process 2
write(pipeTwo[1], (char*)&token, sizeof(tokenStruct));
}
//read again and store the results
read(pipeOne[0], (char*)&input, sizeof(inputStruct));
flag = input.flag;
}
tokenStruct token;
token.flag = flag;
//final write to the second child to tell it to commit suicide
write(pipeTwo[1], (char*)&token, sizeof(tokenStruct));
exit(0);
}
void tokenClassifer(){
tokenStruct token;
//Problem area is here on ****************************************************
//the initial read
read(pipeTwo[0], (char*)&token, sizeof(tokenStruct));
while (token.flag != 1){
int size = 0;
//get the size of the array up until the null character
while (token.token[size] != '\0'){
size++;
}
if (size == 1) {
//check for the one char things first
switch (token.token[0])
{
case '(':
token.tokenType = 0;
break;
case ')':
token.tokenType = 0;
break;
case ';':
token.tokenType = 0;
break;
case '+':
token.tokenType = 1;
break;
case '-':
token.tokenType = 1;
break;
case '/':
token.tokenType = 1;
break;
case '*':
token.tokenType = 1;
break;
default:
if (isdigit(token.token[0])) {
token.tokenType = 2;
} else {
token.tokenType = 3;
}
break;
}
} else {
bool isStr;
int i = 0;
//check for the more than one character
while (token.token[i] != '\0'){
//check if it's a string or digits
if (isdigit(token.token[0])) {
isStr=false;
} else{
//set up the bools to show it is a string
isStr=true;
break;
}
}
//if it is a string token type 3
if (isStr) {
token.tokenType = 3;
} else {
//if not then it's digits and token type 2
token.tokenType = 2;
}
}
//print out the token and token type
cout << "Token type is: " << token.tokenType << "Token value is: " << token.token << "\n";
//read the pipe again and start the process all over
read(pipeTwo[0], (char*)&token, sizeof(tokenStruct));
}
exit(0);
}
int main()
{
//create the pipes for reading and writing between processes
pipe(pipeOne);
pipe(pipeTwo);
//fork off both processes
int value = fork();
int value2 = fork();
//do the process for the first fork
if(value == 0){
//fork one
dataProcess();
} else {
wait(0);
}
//do the process for the second fork
if (value2 == 0) {
//fork two
//the token classifer function for the second fork
tokenClassifer();
} else {
cout << "Type some tokens (or just press enter to quit) \n";
//this is all of the parent functions
for (string line; getline(cin, line); )
{
inputStruct input;
if (line.empty())
{
// if the line is empty, that means the user didn't
// press anything before hitting the enter key
input.flag = 1;
write( pipeOne[1], (char*)&input, sizeof(inputStruct));
break;
} else {
//else copy the string into an array
strcpy(input.str, line.c_str());
//set the flag to zero to show everthing is ok
input.flag = 0;
}
//write the stuct to the pipe
write( pipeOne[1], (char*)&input, sizeof(inputStruct));
cout << "Type some tokens (or just press enter to quit) \n";
}
wait(0);
}
}
One problem that is evident:
//fork off both processes
int value = fork();
int value2 = fork();
This will fork 3 new processes. The initial fork will leave you with two processes, each of which go on to fork a new process.
EDIT:
Proper forking:
int value = fork();
if (value == 0) {
// do child stuff
exit(0);
} else if (value == -1) {
//fork failed
}
int value2 = fork();
if (value2 == 0) {
//do child stuff
exit(0);
} else if (value2 == -1) {
//fork failed
}
I'm actually not quite clear about how data goes through your program, so I'll leave it to you to add the waits. I'd actually change the names of value and value2, but that's just me. Also, I'm only addressing the forking issue here so there may be other problems with your code (which I kind of suspect since you have two pipes).
EDIT 2:
Another issue that I see is that you're not closing the ends of the pipes that you don't use. If you never close the write end of a pipe, your reads will block until the pipe has data (or there are no more writers to the pipe, that is, the write end is not open). This means that the write end of the pipe should be closed in all processes when you are not using it or are finished with it.