Defined C token file for flex? - c

I want to split a C file into tokens, not for compiling but for analyzing. I feel like this should be pretty straight-forward, and tried looking online for a defined tokens.l (or something similar) file for flex with all the C grammar already defined, but couldn't find anything. I was wondering if there are any sort of defined grammars floating around, or if perhaps I'm going about this all wrong?

Yes, there's at least one around.
Edit:
Since there are a few issues that doesn't handle, perhaps it's worth looking at some (hand written) lexing code I wrote several years ago. This basically only handles phases 1, 2 and 3 of translation. If you define DIGRAPH, it also turns on some code to translate C++ digraphs. If memory serves, however, it's doing that earlier in translation than it should really happen, but you probably don't want it in any case. OTOH, this does not even attempt to recognize anywhere close to all tokens -- mostly it separates the source into comments, character literals, string literals, and pretty much everything else. OTOH, it does handle trigraphs, line splicing, etc.
I suppose I should also add that this leaves conversion of the platform's line-ending character into a new-line to the underlying implementation by opening the file in translated (text) mode. Under most circumstances, that's probably the right thing to do, but if you want to produce something like a cross-compiler where your source files have a different line-ending sequence than is normal for this host, you might have to change that.
First the header that defines the external interface to all this stuff:
/* get_src.h */
#ifndef GET_SRC_INCLUDED
#define GET_SRC_INCLUDED
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
/* This is the size of the largest token we'll attempt to deal with. If
* you want to deal with bigger tokens, change this, and recompile
* get_src.c. Note that an entire comment is treated as a single token,
* so long comments could overflow this. In case of an overflow, the
* entire comment will be read as a single token, but the part larger
* than this will not be stored.
*/
#define MAX_TOKEN_SIZE 8192
/* `last_token' will contain the text of the most recently read token (comment,
* string literal, or character literal).
*/
extern char last_token[];
/* This is the maximum number of characters that can be put back into a
* file opened with parse_fopen or parse_fdopen.
*/
#define MAX_UNGETS 5
#include <limits.h>
#include <stdio.h>
typedef struct {
FILE *file;
char peeks[MAX_UNGETS];
int last_peek;
} PFILE;
/* Some codes we return to indicate having found various items in the
* source code. ERROR is returned to indicate a newline found in the
* middle of a character or string literal or if a file ends inside a
* comment, or if a character literal contains more than two characters.
*
* Note that this starts at INT_MIN, the most negative number available
* in an int. This keeps these symbols from conflicting with any
* characters read from the file. However, one of these could
* theoretically conflict with EOF. EOF usually -1, and these are far
* more negative than that. However, officially EOF can be any value
* less than 0...
*/
enum {
ERROR = INT_MIN,
COMMENT,
CHAR_LIT,
STR_LIT
};
/* Opens a file for parsing and returns a pointer to a structure which
* can be passed to the other functions in the parser/lexer to identify
* the file being worked with.
*/
PFILE *parse_fopen(char const *name);
/* This corresponds closely to fdopen - it takes a FILE * as its
* only parameter, creates a PFILE structure identifying that file, and
* returns a pointer to that structure.
*/
PFILE *parse_ffopen(FILE *stream);
/* Corresponds to fclose.
*/
int parse_fclose(PFILE *stream);
/* returns characters from `stream' read as C source code. String
* literals, characters literals and comments are each returned as a
* single code from those above. All strings of any kind of whitespace
* are returned as a single space character.
*/
int get_source(PFILE *stream);
/* Basically, these two work just like the normal versions of the same,
* with the minor exception that unget_character can unget more than one
* character.
*/
int get_character(PFILE *stream);
void unget_character(int ch, PFILE *stream);
#ifdef __cplusplus
}
#endif
#endif
And then the implementation of all that:
/* get_src.c */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#define GET_SOURCE
#include "get_src.h"
static size_t current = 0;
char last_token[MAX_TOKEN_SIZE];
PFILE *parse_fopen(char const *name) {
PFILE *temp = malloc(sizeof(PFILE));
if ( NULL != temp ) {
temp->file = fopen(name, "r");
memset(temp->peeks, 0, sizeof(temp->peeks));
temp->last_peek = 0;
}
return temp;
}
PFILE *parse_ffopen(FILE *file) {
PFILE *temp = malloc(sizeof(PFILE));
if ( NULL != temp) {
temp->file = file;
memset(temp->peeks, 0, sizeof(temp->peeks));
temp->last_peek = 0;
}
return temp;
}
int parse_fclose(PFILE *stream) {
int retval = fclose(stream->file);
free(stream);
return retval;
}
static void addchar(int ch) {
/* adds the passed character to the end of `last_token' */
if ( current < sizeof(last_token) -1 )
last_token[current++] = (char)ch;
if ( current == sizeof(last_token)-1 )
last_token[current] = '\0';
}
static void clear(void) {
/* clears the previous token and starts building a new one. */
current = 0;
}
static int read_char(PFILE *stream) {
if ( stream->last_peek > 0 )
return stream->peeks[--stream->last_peek];
return fgetc(stream->file);
}
void unget_character(int ch, PFILE * stream) {
if ( stream->last_peek < sizeof(stream->peeks) )
stream->peeks[stream->last_peek++] = ch;
}
static int check_trigraph(PFILE *stream) {
/* Checks for trigraphs and returns the equivalant character if there
* is one. Expects that the leading '?' of the trigraph has already
* been read before this is called.
*/
int ch;
if ( '?' != (ch=read_char(stream))) {
unget_character(ch, stream);
return '?';
}
ch = read_char(stream);
switch( ch ) {
case '(': return '[';
case ')': return ']';
case '/': return '\\';
case '\'': return '^';
case '<': return '{';
case '>': return '}';
case '!': return '|';
case '-': return '~';
case '=': return '#';
default:
unget_character('?', stream);
unget_character(ch, stream);
return '?';
}
}
#ifdef DIGRAPH
static int check_digraph(PFILE *stream, int first) {
/* Checks for a digraph. The first character of the digraph is
* transmitted as the second parameter, as there are several possible
* first characters of a digraph.
*/
int ch = read_char(stream);
switch(first) {
case '<':
if ( '%' == ch )
return '{';
if ( ':' == ch )
return '[';
break;
case ':':
if ( '>' == ch )
return ']';
break;
case '%':
if ( '>' == ch )
return '}';
if ( ':' == ch )
return '#';
break;
}
/* If it's not one of the specific combos above, return the characters
* separately and unchanged by putting the second one back into the
* stream, and returning the first one as-is.
*/
unget_character(ch, stream);
return first;
}
#endif
static int get_char(PFILE *stream) {
/* Gets a single character from the stream with any trigraphs or digraphs converted
* to the single character represented. Note that handling digraphs this early in
* translation isn't really correct (and shouldn't happen in C at all).
*/
int ch = read_char(stream);
if ( ch == '?' )
return check_trigraph(stream);
#ifdef DIGRAPH
if (( ch == '<' || ch == ':' || ch == '%' ))
return check_digraph(stream, ch);
#endif
return ch;
}
int get_character(PFILE *stream) {
/* gets a character from `stream'. Any amount of any kind of whitespace
* is returned as a single space. Escaped new-lines are "eaten" here as well.
*/
int ch;
if ( !isspace(ch=get_char(stream)) && ch != '\\')
return ch;
// handle line-slicing
if (ch == '\\') {
ch = get_char(stream);
if (ch == '\n')
ch = get_char(stream);
else {
unget_character(ch, stream);
return ch;
}
}
/* If it's a space, skip over consecutive white-space */
while (isspace(ch) && ('\n' != ch))
ch = get_char(stream);
if ('\n' == ch)
return ch;
/* Then put the non-ws character back */
unget_character(ch, stream);
/* and return a single space character... */
return ' ';
}
static int read_char_lit(PFILE *stream) {
/* This is used internally by `get_source' (below) - it expects the
* opening quote of a character literal to have already been read and
* returns CHAR_LIT or ERROR if there's a newline before a close
* quote is found, or if the character literal contains more than two
* characters after escapes are taken into account.
*/
int ch;
int i;
clear();
addchar('\'');
for (i=0; i<2 && ('\'' != ( ch = read_char(stream))); i++) {
addchar(ch);
if ( ch == '\n' )
return ERROR;
if (ch == '\\' ) {
ch = get_char(stream);
addchar(ch);
}
}
addchar('\'');
addchar('\0');
if ( i > 2 )
return ERROR;
return CHAR_LIT;
}
static int read_str_lit(PFILE *stream) {
/* Used internally by get_source. Expects the opening quote of a string
* literal to have already been read. Returns STR_LIT, or ERROR if a
* un-escaped newline is found before the close quote.
*/
int ch;
clear();
addchar('"');
while ( '"' != ( ch = get_char(stream))) {
if ( '\n' == ch || EOF == ch )
return ERROR;
addchar(ch);
if( ch == '\\' ) {
ch = read_char(stream);
addchar(ch);
}
}
addchar('"');
addchar('\0');
return STR_LIT;
}
static int read_comment(PFILE *stream) {
/* Skips over a comment in stream. Assumes the leading '/' has already
* been read and skips over the body. If we're reading C++ source, skips
* C++ single line comments as well as normal C comments.
*/
int ch;
clear();
ch = get_char(stream);
/* Handle a single line comment.
*/
if ('/' == ch) {
addchar('/');
addchar('/');
while ( '\n' != ( ch = get_char(stream)))
addchar(ch);
addchar('\0');
return COMMENT;
}
if ('*' != ch ) {
unget_character(ch, stream);
return '/';
}
addchar('/');
do {
addchar(ch);
while ('*' !=(ch = get_char(stream)))
if (EOF == ch)
return ERROR;
else
addchar(ch);
addchar(ch);
} while ( '/' != (ch=get_char(stream)));
addchar('/');
addchar('\0');
return COMMENT;
}
int get_source(PFILE *stream) {
/* reads and returns a single "item" from the stream. An "item" is a
* comment, a literal or a single character after trigraph and possible
* digraph substitution has taken place.
*/
int ch = get_character(stream);
switch(ch) {
case '\'':
return read_char_lit(stream);
case '"':
return read_str_lit(stream);
case '/':
return read_comment(stream);
default:
return ch;
}
}
#ifdef TEST
int main(int argc, char **argv) {
PFILE *f;
int ch;
if (argc != 2) {
fprintf(stderr, "Usage: get_src <filename>\n");
return EXIT_FAILURE;
}
if (NULL==(f= parse_fopen(argv[1]))) {
fprintf(stderr, "Unable to open: %s\n", argv[1]);
return EXIT_FAILURE;
}
while (EOF!=(ch=get_source(f)))
if (ch < 0)
printf("\n%s\n", last_token);
else
printf("%c", ch);
parse_fclose(f);
return 0;
}
#endif
I'm not sure about how easy/difficult it would/will be to integrate that into a Flex-based lexer though -- I seem to recall Flex has some sort of hook to define what it uses to read a character, but I've never tried to use it, so I can't say much more about it (and ultimately, can't even say with anything approaching certainty that it even exists).

Related

Is there any cross-platform approach for dealing with new line characters in text files in C language? [duplicate]

This question already has answers here:
Removing trailing newline character from fgets() input
(14 answers)
Closed 7 years ago.
I'm reading stdin and there are sometimes unix-style and sometimes windows-style newlines.
How to consume either type of newline?
Assuming you know there will be a newline, the solution is to consume one character, and then decide:
10 - LF ... Unix style newline
13 - CR ... Windows style newline
If it's 13, you have to consume one more character (10)
const char x = fgetc(stdin); // Consume LF or CR
if (x == 13) fgetc(stdin); // consume LF
There are a few more newline conventions than that. In particular, all four involving CR \r and LF \n -- \n, \r, \r\n, and \n\r -- are actually encoutered in the wild.
For reading text input, possibly interactively, and supporting all of those four newline encodings at the same time, I recommend using a helper function something like the following:
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
size_t get_line(char **const lineptr, size_t *const sizeptr, char *const lastptr, FILE *const in)
{
char *line;
size_t size, have;
int c;
if (!lineptr || !sizeptr || !in) {
errno = EINVAL; /* Invalid parameters! */
return 0;
}
if (*lineptr) {
line = *lineptr;
size = *sizeptr;
} else {
line = NULL;
size = 0;
}
have = 0;
if (lastptr) {
if (*lastptr == '\n') {
c = getc(in);
if (c != '\r' && c != EOF)
ungetc(c, in);
} else
if (*lastptr == '\r') {
c = getc(in);
if (c != '\n' && c != EOF)
ungetc(c, in);
}
*lastptr = '\0';
}
while (1) {
if (have + 2 >= size) {
/* Reallocation policy; my personal quirk here.
* You can replace this with e.g. have + 128,
* or (have + 2)*3/2 or whatever you prefer. */
size = (have | 127) + 129;
line = realloc(line, size);
if (!line) {
errno = ENOMEM; /* Out of memory */
return 0;
}
*lineptr = line;
*sizeptr = size;
}
c = getc(in);
if (c == EOF) {
if (lastptr)
*lastptr = '\0';
break;
} else
if (c == '\n') {
if (lastptr)
*lastptr = c;
else {
c = getc(in);
if (c != EOF && c != '\r')
ungetc(c, in);
}
break;
} else
if (c == '\r') {
if (lastptr)
*lastptr = c;
else {
c = getc(in);
if (c != EOF && c != '\n')
ungetc(c, in);
}
break;
}
if (iscntrl(c) && !isspace(c))
continue;
line[have++] = c;
}
if (ferror(in)) {
errno = EIO; /* I/O error */
return 0;
}
line[have] = '\0';
errno = 0; /* No errors, even if have were 0 */
return have;
}
int main(void)
{
char *data = NULL;
size_t size = 0;
size_t len;
char last = '\0';
setlocale(LC_ALL, "");
while (1) {
len = get_line(&data, &size, &last, stdin);
if (errno) {
fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (!len && feof(stdin))
break;
printf("Read %lu characters: '%s'\n", (unsigned long)len, data);
}
free(data);
data = NULL;
size = 0;
return EXIT_SUCCESS;
}
Except for the errno constants I used (EINVAL, ENOMEM, and EIO), the above code is C89, and should be portable.
The get_line() function dynamically reallocates the line buffer to be long enough when necessary. For interactive inputs, you must accept a newline at the first newline-ish character you encounter (as trying to read the second character would block, if the first character happens to be the only newline character). If specified, the one-character state at lastptr is used to detect and handle correctly any two-character newlines at the start of the next line read. If not specified, the function will attempt to consume the entire newline as part of the current line (which is okay for non-interactive inputs, especially files).
The newline is not stored or counted in the line length. For added ease of use, the function also skips non-whitespace control characters. Especially embedded nul characters (\0) often cause headaches, so having the function skip those altogether is often a robust approach.
As a final touch, the function always sets errno -- to zero if no error occurred, nonzero error code otherwise --, including ferror() cases, so detecting error conditions is trivial.
The above code snippet includes a main(), which reads and displays input lines, using the current locale for the meaning of "non-whitespace control character" (!isspace(c) && iscntrl(c)).
Although this is definitely not the fastest mechanism to read input, it is not that slow, and it is a very robust one.
Questions?

Use and explanation of getchar() function

I am writing a program to read a user input statement and extract all integers from the input. For example, if I enter "h3ll0", the program will output "30". I have used the fgets function to read the user input.
However, I am currently reading about getchar() and would like to know what would be the best way to use getchar() in my program to read user input instead of fgets. I am not really clear on how getchar() works and what situations it can be useful in.
This question is related to a project that specifically asks for getchar() as the method of reading user input. As I was unclear on how getchar() works, I built the rest of the program using fgets to ensure it was working.
#include <stdio.h>
int main()
{
char user_input[100];
int i;
int j = 0;
printf("Please enter your string: ");
fgets(user_input ,100, stdin);
for(i = 0; user_input[i] ; i++)
{
if(user_input[i] >= '0' && user_input[i] <= '9')
{
user_input[j] = user_input[i];
j++;
}
}
user_input[j] = '\0';
printf("Your output of only integers is: ");
printf("%s\n", user_input);
return 0;
}
OP: unclear on how getchar() works
int fgetc(FILE *stream) typically returns 1 of 257 different values.
"If ... a next character is present, the fgetc function obtains that character as an unsigned char converted to an int C11 §7.21.7.1 2
On end-of-file or input error (rare), EOF, is returned.
OP: to use getchar() in my program to read user input instead of fgets.
Create your own my_fgets() with the same function signature and same function as fgets() and then replace.
char *fgets(char * restrict s, int n, FILE * restrict stream);
The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array. C11 §7.21.7.2 2
Return the same value
The fgets function returns s if successful. If end-of-file is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned. §7.21.7.2 3
Sample untested code
#include <stdbool.h>
#include <stdio.h>
char *my_fgets(char * restrict s, int n, FILE * restrict stream) {
bool something_read = false;
int ch = 0;
char *dest = s;
// Room ("reads at most one less") and EOF not returned?
while (n > 1 && (ch = fgetc(stream)) != EOF) {
n--;
something_read = true;
*dest++ = (char) ch;
if (ch == '\n') {
break; // "No additional characters are read after a new-line character"
}
}
// Did code end the while loop due to EOF?
if (ch == EOF) {
// Was EOF due to end-of-file or rare input error?
if (feof(stream)) {
// "If end-of-file is encountered and no characters ... read into the array ..."
if (!something_read) {
return NULL;
}
} else {
// "If a read error ..."
return NULL; // ** Note 1
}
}
// room for \0?
if (n > 0) {
*dest = '\0'; //" A null character is written immediately after the last character"
}
return s;
}
Perhaps improve fgets() and use size_t for n.
char *my_fgets(char * restrict s, size_t n, FILE * restrict stream);
fgets() with n <= 0 is not clearly defined. Using size_t, an unsigned type, at least eliminates n < 0 concerns.
Note 1: or use s = NULL; instead of return NULL; and let the remaining code null terminate the buffer. We have that option as "array contents are indeterminate".
Something like this should work as a clunky replacement to fgets using only getchar. I don't guarantee the accuracy of the error handling.
I don't think you would ever want to use getchar over fgets in an application. Getchar is more limited and less secure.
#include <stdint.h>
void your_fgets(char *buffer, size_t buffer_size)
{
int i;
size_t j;
if (buffer_size == 0)
return ;
else if (buffer_size == 1)
{
buffer[0] = '\0';
return ;
}
j = 0;
while ((i = getchar()) != EOF)
{
buffer[j++] = i;
if (j == buffer_size - 1 || i == '\n')
{
buffer[j] = '\0';
return ;
}
}
buffer[j] = '\0';
}
I am baffled by the comments on this post suggesting that fgets is easier to use. Using fgets unnecessarily complicates the issue. Just do:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
int c;
while( ( c = getchar() ) != EOF ) {
if(isdigit(c) && (putchar(c) == EOF)) {
perror("stdout");
return EXIT_FAILURE;
}
}
return ferror(stdin);
}
There is absolutely no reason to use any additional buffering, or read the input one line at a time. Maybe you'll want to output newlines as they come in, but that would be an implementation detail that is left unspecified in the question. Either way, it's utterly trivial (if(( c == '\n' || isdigit(c)) && (putchar(c) == EOF))). Just read a character and decide if you want to output it. The logic is much easier if you don't think about the input as being more complicated than it is. (It's not line-oriented...it's just a stream of bytes.)
If, for some unknown reason you want to make this tool usable only in an interactive setting and load up your output with excess verbosity, you can easily do something like:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
int c;
do {
int want_header = 1;
printf("Please enter your string: ");
while( ( c = getchar() ) != EOF && c != '\n' ) {
if(! isdigit(c)) {
continue;
}
if(want_header) {
want_header=0;
printf("Your output of only integers is: ");
}
if(putchar(c) == EOF) {
perror("stdout");
return EXIT_FAILURE;
}
}
if( c == '\n')
putchar(c);
want_header = 0;
} while(c == '\n');
return ferror(stdin);
}
but, please, don't do that. (Imagine if grep started by emitting a prompt that said "please enter the regex you would like to search for"!)

C parsing a comma-separated-values with line breaks

I have a CSV data file that have the following data:
H1,H2,H3
a,"b
c
d",e
When I open through Excel as CSV file, it is able to show the sheet with column headings as H1, H2, H3 and column values as: a for H1,
multi line value as
b
c
d
for H2
and c for H3
I need to parse this file using a C program and have the values picked up like this.
But, my following code snippet will not work, as I have multi line values for a column:
char buff[200];
char tokens[10][30];
fgets(buff, 200, stdin);
char *ptok = buff; // for iterating
char *pch;
int i = 0;
while ((pch = strchr(ptok, ',')) != NULL) {
*pch = 0;
strcpy(tokens[i++], ptok);
ptok = pch+1;
}
strcpy(tokens[i++], ptok);
How to modify this code snippet to accommodate multi-line values of columns?
Please don't get bothered by the hard-coded values for the string buffers, this is the test code as POC.
Instead of any 3rd party library, I would like to do it the hard way from first principle.
Please help.
The main complication in parsing "well-formed" CSV in C is precisely the handling of variable-length strings and arrays which you are avoiding by using fixed-length strings and arrays. (The other complication is handling not well-formed CSV.)
Without those complications, the parsing is really quite simple:
(untested)
/* Appends a non-quoted field to s and returns the delimiter */
int readSimpleField(struct String* s) {
for (;;) {
int ch = getc();
if (ch == ',' || ch == '\n' || ch == EOF) return ch;
stringAppend(s, ch);
}
}
/* Appends a quoted field to s and returns the delimiter.
* Assumes the open quote has already been read.
* If the field is not terminated, returns ERROR, which
* should be a value different from any character or EOF.
* The delimiter returned is the character after the closing quote
* (or EOF), which may not be a valid delimiter. Caller should check.
*/
int readQuotedField(struct String* s) {
for (;;) {
int ch;
for (;;) {
ch = getc();
if (ch == EOF) return ERROR;
if (ch == '"') {
ch = getc();
if (ch != '"') break;
}
stringAppend(s, ch);
}
}
}
/* Reads a single field into s and returns the following delimiter,
* which might be invalid.
*/
int readField(struct String* s) {
stringClear(s);
int ch = getc();
if (ch == '"') return readQuotedField(s);
if (ch == '\n' || ch == EOF) return ch;
stringAppend(s, ch);
return readSimpleField(s);
}
/* Reads a single row into row and returns the following delimiter,
* which might be invalid.
*/
int readRow(struct Row* row) {
struct String field = {0};
rowClear(row);
/* Make sure there is at least one field */
int ch = getc();
if (ch != '\n' && ch != EOF) {
ungetc(ch, stdin);
do {
ch = readField(s);
rowAppend(row, s);
} while (ch == ',');
}
return ch;
}
/* Reads an entire CSV file into table.
* Returns true if the parse was successful.
* If an error is encountered, returns false. If the end-of-file
* indicator is set, the error was an unterminated quoted field;
* otherwise, the next character read will be the one which
* triggered the error.
*/
bool readCSV(struct Table* table) {
tableClear(table);
struct Row row = {0};
/* Make sure there is at least one row */
int ch = getc();
if (ch != EOF) {
ungetc(ch, stdin);
do {
ch = readRow(row);
tableAppend(table, row);
} while (ch == '\n');
}
return ch == EOF;
}
The above is "from first principles" -- it does not even use standard C library string functions. But it takes some effort to understand and verify. Personally, I would use (f)lex and maybe even yacc/bison (although it's a bit of overkill) to simplify the code and make the expected syntax more obvious. But handling variable-length structures in C will still need to be the first step.

How to calculate number of lines in file?

I work in C-language at first time and have some question.
How can I get the number of lines in file?
FILE *in;
char c;
int lines = 1;
...
while (fscanf(in,"%c",&c) == 1) {
if (c == '\n') {
lines++;
}
}
Am I right? I actually don't know how to get the moment , when string cross to the new line.
OP's code functions well aside from maybe an off-by-one issue and a last line issue.
Standard C library definition
A text stream is an ordered sequence of characters composed into lines, each line consisting of zero or more characters plus a terminating new-line character. Whether the last line requires a terminating new-line character is implementation-defined. C11dr §7.21.2 2
A line ends with a '\n' and the last line may or may not end with a '\n'.
If using the idea that the last line of a file does not require a final '\n, then the goal is to count the number of occurrences that a character is read after a '\n'.
// Let us use a wide type
// Start at 0 as the file may be empty
unsigned long long line_count = 0;
int previous = '\n';
int ch;
while ((ch = fgetc(in)) != EOF) {
if (previous == '\n') line_count++;
previous = ch;
}
printf("Line count:%llu\n", line_count);
Reading a file one character at a time may be less efficient than other means, but functionally meets OP goal.
This answer uses (ch = fgetc(in)) != EOF instead of fscanf(in,"%c",&c) == 1 which is typically ""faster", but with an optimizing compiler, either may emit similar performance code. Such details of speed can be supported with analysis or profiling. When in doubt, code for clarity.
Can use this utility function
/*
* count the number of lines in the file called filename
*
*/
int countLines(char *filename)
{
FILE *in = fopen(filename,"r");
int ch=0;
int lines=0;
if(in == NULL){
return 0; // return lines;
}
while((ch = fgetc(in)) != EOF){
if(ch == '\n'){
lines++;
}
}
fclose(in);
return lines;
}
When counting the 'number of \n characters', you have to remember that you are counting the separators, and not the items. See 'Fencepost Error'
Your example should work, but:
if the file does not end with a \n, then you might be off-by-one (depending on your definition of 'a line').
depending on your definition of 'a line' you may be missing \r characters in the file (typically used by Macs)
it will not be very efficient or quick (calling scanf() is expensive)
The example below will ingest a buffer each time, looking for \r and \n characters. There is some logic to latch these characters, so that the following line endings should be handled correctly:
\n
\r
\r\n
#include <stdio.h>
#include <errno.h>
int main(void) {
FILE *in;
char buf[4096];
int buf_len, buf_pos;
int line_count, line_pos;
int ignore_cr, ignore_lf;
in = fopen("my_file.txt", "rb");
if (in == NULL) {
perror("fopen()");
return 1;
}
line_count = 0;
line_pos = 0;
ignore_cr = 0;
ignore_lf = 0;
/* ingest a buffer at a time */
while ((buf_len = fread(&buf, 1, sizeof(buf), in)) != 0) {
/* walk through the buffer, looking for newlines */
for (buf_pos = 0; buf_pos < buf_len; buf_pos++) {
/* look for '\n' ... */
if (buf[buf_pos] == '\n') {
/* ... unless we've already seen '\r' */
if (!ignore_lf) {
line_count += 1;
line_pos = 0;
ignore_cr = 1;
}
/* look for '\r' ... */
} else if (buf[buf_pos] == '\r') {
/* ... unless we've already seen '\n' */
if (!ignore_cr) {
line_count += 1;
line_pos = 0;
ignore_lf = 1;
}
/* on any other character, count the characters per line */
} else {
line_pos += 1;
ignore_lf = 0;
ignore_cr = 0;
}
}
}
if (line_pos > 0) {
line_count += 1;
}
fclose(in);
printf("lines: %d\n", line_count);
return 0;
}

Parsing simple name/value pair settings in config file with leading and terminating spaces - C

This is the code I made so far. I apologize if my buffer sizes are an overkill.
The idea is to read the entire configuration file (in this example, it's file.conf), and for now we assume it exists. I'll add error checking later.
Once the file is read into stack space, then the getcfg() function searches the configuration data for the specified name, and if it's found, returns the corresponding value. My function works when the configuration file contains leading spaces before names or values; such spaces are ignored.
Say this is my configuration file:
something=data
apples=oranges
fruit=banana
animals= cats
fried =chicken
My code will work correctly with the first four entries of the config file. for example, if I use "something" as the name, then "data" will be returned.
The last item won't work as of yet because of the trailing spaces after "fried" and before the =. I want to be able to have my function automatically remove those spaces, too, especially in case an option format such as
somethingelse = items
begins to be used. (Note the spaces on both sides of the = sign.)
What can I do to make a less CPU-intensive version of my program that also detects and removes trailing spaces from the name and value when processing the name and values?
Here's my current code:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int getcfg(char* buf, char *name, char *val) {
int fl = 0, n = 0;
char cfg[1][10000], *p = buf;
memset(cfg, 0, sizeof(cfg));
while (*p) {
if (*p == '\n') {
if (strcmp(cfg[0], name) == 0) {
strcpy(val, cfg[1]);
return 1;
}
memset(cfg, 0, sizeof(cfg));
n = 0;
fl = 0;
} else {
if (*p == '=') {
n = 0;
fl = 1;
} else {
if (n != 0 || *p != ' ') {
cfg[fl][n] = *p;
n++;
}
}
}
p++;
}
return 0;
}
int main() {
char val[10000], buf[100000]; //val=value of config item, buf=buffer for entire config file ( > 100KB config file is nuts)
memset(buf, 0, sizeof(buf));
memset(val, 0, sizeof(val));
int h = open("file.conf", O_RDONLY);
if (read(h, buf, sizeof(buf)) < 1) {
printf("Can't read\n");
}
close(h);
printf("Value stat = %d ", getcfg(buf, "Item", val));
printf("Result = '%s'\n", val);
return 0;
}
Behold is a small (~15 lines) sscanf-based read_params() function which does the job. As a bonus, it understands the comments and complains about erroneous lines (if any):
$ cat config_file.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/errno.h>
#define ARRAY_SIZE(a) ((sizeof (a)) / (sizeof (a)[0]))
enum { MAX_LEN=128 };
struct param {
char name[MAX_LEN];
char value[MAX_LEN];
};
void strtrim(char *s)
{
char *p = s + strlen(s);
while (--p >= s && isspace(*p))
*p = '\0';
}
int read_params(FILE *in, struct param *p, int max_params)
{
int ln, n=0;
char s[MAX_LEN];
for (ln=1; max_params > 0 && fgets(s, MAX_LEN, in); ln++) {
if (sscanf(s, " %[#\n\r]", p->name)) /* emty line or comment */
continue;
if (sscanf(s, " %[a-z_A-Z0-9] = %[^#\n\r]",
p->name, p->value) < 2) {
fprintf(stderr, "error at line %d: %s\n", ln, s);
return -1;
}
strtrim(p->value);
printf("%d: name='%s' value='%s'\n", ln, p->name, p->value);
p++, max_params--, n++;
}
return n;
}
int main(int argc, char *argv[])
{
FILE *f;
struct param p[32];
f = argc == 1 ? stdin : fopen(argv[1], "r");
if (f == NULL) {
fprintf(stderr, "failed to open `%s': %s\n", argv[1],
strerror(errno));
return 1;
}
if (read_params(f, p, ARRAY_SIZE(p)) < 0)
return 1;
return 0;
}
Let's see how it works (quotes mark the beginning and the end of each line for clarity):
$ cat bb | sed -e "s/^/'/" -e "s/$/'/" | cat -n
1 'msg = Hello World! '
2 'p1=v1'
3 ' p2=v2 # comment'
4 ' '
5 'P_3 =v3'
6 'p4= v4#comment'
7 ' P5 = v5 '
8 ' # comment'
9 'p6 ='
$ ./config_file bb
1: name='msg' value='Hello World!'
2: name='p1' value='v1'
3: name='p2' value='v2'
5: name='P_3' value='v3'
6: name='p4' value='v4'
7: name='P5' value='v5'
error at line 9: p6 =
Note: as an additional bonus, the value can be anything, except #\n\r chars, including spaces, as can be seen above with the 'Hello World!' example. If it's not what needed, add space and tab into the exception list at the second sscanf() for the value (or specify accepted characters there instead) and drop strtrim() function.
I'll provide a straight-forward version, with everything being done in main and no key:value saving - the function only recognizes where they are and print them. I used the input file you gave and added one more line in the end as something = more_data.
This version of the parser does not recognize multiple data itens (itens separated by spaces in the data fields, you'll have to figure it out as an exercise).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd = open("file.conf", O_RDONLY, 0);
int i = 0;
char kv[100];
char c;
while (read(fd,&c,1) == 1) {
/* ignoring spaces and tabs */
if (c == '\t' || c == ' ') continue;
else if (c == '=') {
/* finished reading a key */
kv[i] = 0x0;
printf("key found [%s] ", kv);
i = 0;
continue;
} else if (c == '\n') {
/* finished reading a value */
kv[i] = 0x0;
printf(" with data [%s]\n", kv);
i = 0;
continue;
}
kv[i++] = c;
}
close(fd);
return 0;
}
And the output is:
key found [something] with data [data]
key found [apples] with data [oranges]
key found [fruit] with data [banana]
key found [animals] with data [cats]
key found [fried] with data [chicken]
key found [something] with data [more_data]
Explanation
while (read(fd,&c,1) == 1): reads one character at a time from the file.
if (c == '\t' || c == ' ') continue;: this is responsible for ignoring the white-spaces and tabs wherever they are.
else if (c == '='): If the program finds a = character, it concludes that what it just read was a key and treats it. What's inside that if should be easy to understand.
else if (c == '\n'): Then it uses a new-line character to recognize the end of a value. Again, what's inside the if is not hard to understand.
kv[i++] = c;: This is where we save the char value into the buffer kv.
So, with some minor changes, you can adapt this bit of code to become a parsing function that will suit your needs.
Edit and new code
As pointed out by John Bollinger in the comments, using read inside a while to read one character at a time is very costly. I'll post a second version of the program using the same input method OP was using (reading the whole file at once into a buffer) and then parsing it with another function.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void parse(char *s)
{
char c, kv[100];
int i;
while ((c = *s++)) {
/* ignoring spaces and tabs */
if (c == '\t' || c == ' ') continue;
else if (c == '=') {
/* finished reading a key */
kv[i] = 0x0;
printf("key found [%s] ", kv);
i = 0;
continue;
} else if (c == '\n') {
/* finished reading a value */
kv[i] = 0x0;
printf(" with data [%s]\n", kv);
i = 0;
continue;
}
kv[i++] = c;
}
}
int main(void)
{
int fd = open("file.conf", O_RDONLY, 0);
char buffer[1000];
/* use the reading method that suits you best */
read(fd, buffer, sizeof buffer);
/* only thing parse() expects is a null-terminated string */
parse(buffer);
close(fd);
return 0;
}
It is very unusual to read a whole config file into memory as a flat image, and especially to keep such an image as the internal representation. One would ordinarily parse the file contents into key/value pairs as you go, and store a representation of those pairs.
Also, your use of read() is incorrect, as you cannot safely assume that it will read all bytes of the file in one call. One normally must call read() in a loop, keeping track of the return value from each call to know both when the end of the file is reached and where in the buffer to put the next bytes read.
If the configuration is supposed to be completely generic, so that you don't know in advance what keywords to expect, then you might organize the configuration data in a hash table or a binary search tree, with the parameter names as the keys. If you do know what parameters to expect (or at least which to allow), then you might have a variable or a struct member for each one.
Naturally, the approach to parameter lookup must be paired correctly with the data structure in which you store the parameters. Any of the approaches I suggested will make looking up multiple configuration parameters far faster. They would also avoid wasting memory, and would adapt to extremely large configurations (or at least could do so).
How best to approach reading the file depends on details of your config file format, such as whether keys and/or values are permitted to contain internal spaces, whether more than one key/value pair may appear on the same line, and whether there is an upper bound on the allowed length of config file lines or of keys and values. Here's an approach that expects one key/value pair per line, supports keys and values that contain internal whitespace (but not newlines), but neither of which is longer than 1023 characters, and where keys are not permitted to contain the '=' character:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
int main() {
char key[1024];
char value[1024];
FILE *config;
int done;
config = fopen("file.conf", "r");
if (!config) {
perror("while opening file.conf");
return 1;
}
do {
char nl = '\0';
int nfields = fscanf(config, " %1023[^=\n]= %1023[^\n]%c", key, value, &nl);
int i;
done = 1;
if (nfields == EOF) {
if (ferror(config)) {
/* handle read error ... */
perror("while reading file.conf");
} else {
/* trailing empty line(s); ignore ... */
}
break;
} else if (nfields == 3) {
if (nl != '\n') {
/* handle excessive-length value ... */
} else {
done = 0;
}
} else if (nfields == 1) {
/* handle excessive-length key ... */
break;
} else {
assert(nfields == 2);
/* last key/value pair, not followed by a newline */
}
if (key[0] == '=') {
/* handle missing key ... */
break;
}
/* successfully read a key / value pair; truncate trailing whitespace */
for (i = strlen(key); key[--i] == ' '; ) {
/* nothing */
}
key[i + 1] ='\0';
for (i = strlen(value); value[--i] == ' '; ) {
/* nothing */
}
value[i + 1] ='\0';
/* record the key / value pair somewhere (but here we just print it) ... */
printf("key: [%s] value: [%s]\n", key, value);
} while (!done);
fclose(config);
return 0;
}
Important points to note about that include:
No mechanism for storing the key / value pairs is provided. I gave you a few options, and there are others, but you must decide what's best for your own purposes. Rather, the program above addresses the problem of parsing your config data once for all, so that you can avoid parsing it de novo every time you perform a lookup.
The code relies on fscanf() to consume any leading whitespace before the key and value, but in order to accommodate internal whitespace in the key and value, it cannot do the same for trailing whitespace.
Instead, it manually trims trailing whitespace from key and value.
The fscanf() format uses explicit field widths to avoid buffer overruns. It uses the %[ and %c field descriptors to scan data that may be or include whitespace.
Although it may look longish, do note how much of that code is dedicated to error handling.
Divide and conquer.
Getting the data and parsing it are best handled with 2 separate routines.
1) Use fgets() or other code with read() to read a line
int foo(FILE *inf) {
char buffer[1000];
while (fgets(buffer, sizeof buffer, inf)) {
if (Parse_KeyValue(buffer, &key_offset, &value_offset)) {
fprintf(stderr, "Bad Line '%s'\n", buffer);
return 1;
}
printf("'%s'='%s'\n", &buffer[key_offset], &buffer[value_offset]);
}
}
2) Parse the line. (Sample unchecked code)
// 0: Success
// 1: failure
int Parse_KeyValue(char *line, size_t *key_offset, size_t *value_offset) {
char *p = line;
while (isspace((unsigned char) *p)) p++;
*key_offset = p - line;
const char *end = p;
while (*p != '=') {
if (*p == '\0') return 1; // fail, no `=` found
if (!isspace((unsigned char) *p)) {
end = p+1;
}
p++;
}
*end = '\0';
p++; // consume `=`
while (isspace((unsigned char) *p)) p++;
*value_offset = p - line;
end = p;
while (*p) {
if (!isspace((unsigned char) *p)) {
end = p+1;
}
p++;
}
*end = '\0';
return 0;
}
This does allow for valid "" key and value. Adjust as needed.

Resources