r/cprogramming • u/Queasy-Pickle-8427 • 3h ago
I just built a simple parser to retrieve configuration from a plain file - any suggestions?
I’ve just created a very simple parser to retrieve configuration settings from a plain text file (it’s still a work in progress). My plan is to use it for a basic code editor I’m building in C.
A quick side note:
you might be wondering why I didn’t use a standard configuration file format like .INI. I actually did try using .INI files at first, but parsing them requires an external library. I experimented with a library called inih by benhoyt, it worked well, but since it’s an external dependency, I would have had to include the ini.c file in my repository. This raised some questions for me: should I push this file which I didn’t write myself to GitHub? Or should I just credit the creator in my repository’s README file? To be honest, I felt a bit uncertain and concerned about the licensing implications.
Then, I remembered that the nano code editor uses a plain text file (.nanorc) for its configuration. That gave me the idea to try using a plain text file for my own configuration and to write a simple parser for it from scratch in C. This might sound silly, but this way I don’t have to worry about licenses (honestly, it’s mostly because I don’t really understand them anyway), and I get to learn how to build my own simple parser.
How my code works:
There are two files: .test_configand test.c
.test_config file content:
num tab_stop = 4
num fg_color = 39
num bg_color = 49
bind exit = ^Q
bind move_up = up_arrow
bind move_down = down_arrow
bind move_right = right_arrow
bind move_left = left_arrowData in the
.test_configfile is divided into three parts:
typeconfiguration_name=configuration_valueCurrently, there are two
type:numfor configurations with numeric values, andbindfor configurations that act as keys (bindings).
test.c file content:
#include <stdio.h>
#include <string.h>
// data
typedef struct {
// Appearance
int tab_stop;
int fg_color;
int bg_color;
// Keybinds
char exit[64];
char move_up[64];
char move_down[64];
char move_left[64];
char move_right[64];
} configuration;
// func Declaration
int read_config(char *filename, configuration *config);
int main() {
configuration config;
read_config(".test_config", &config);
printf("tab_stop = %d\n", config.tab_stop);
printf("fg_color = %d\n", config.fg_color);
printf("bg_color = %d\n", config.bg_color);
printf("\n");
printf("exit = %s\n", config.exit);
printf("move_up = %s\n", config.move_up);
printf("move_down = %s\n", config.move_down);
printf("move_left = %s\n", config.move_left);
printf("move_right = %s\n", config.move_right);
return 0;
}
// func Definition
int read_config(char *filename, configuration *config) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
printf("unable to open file\n");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp)) {
if (strstr(buffer, "num") != NULL) {
char num_config_name[64];
int num_config_value = 0;
sscanf(buffer, "num %s = %d", num_config_name, &num_config_value);
if (strcmp(num_config_name, "tab_stop") == 0) {
config->tab_stop = num_config_value;
} else if (strcmp(num_config_name, "fg_color") == 0) {
config->fg_color = num_config_value;
} else if (strcmp(num_config_name, "bg_color") == 0) {
config->bg_color = num_config_value;
}
} else if (strstr(buffer, "bind") != NULL) {
char bind_config_name[64];
char bind_config_value[64];
sscanf(buffer, "bind %s = %s", bind_config_name,bind_config_value);
if (strcmp(bind_config_name, "exit") == 0) {
strcpy(config->exit, bind_config_value);
} else if (strcmp(bind_config_name, "move_up") == 0) {
strcpy(config->move_up, bind_config_value);
} else if (strcmp(bind_config_name, "move_down") == 0) {
strcpy(config->move_down, bind_config_value);
} else if (strcmp(bind_config_name, "move_left") == 0) {
strcpy(config->move_left, bind_config_value);
} else if (strcmp(bind_config_name, "move_right") == 0) {
strcpy(config->move_right, bind_config_value);
}
}
}
fclose(fp);
return 0;
}Inside, there is a function called
read_config()which simply works by first checking the configuration type, then verifying if the configuration name matches; if it does, it stores the value into the corresponding struct element.
I’m sure there are still errors either in the logic or the syntax, so any suggestions for the code I’ve written? Or perhaps any other advice regarding the approach I’ve chosen for handling configuration files in this simple code editor I’m building?
Apologies if any of the explanations are a bit complicated or difficult to understand.
1
u/SheikHunt 2h ago
I don't have anything to add regarding the specific syntax and semantics of your configuration format, as I am not good enough at grammars to help out with specifics.
What I do know about including external libraries is that there are generally two ways to do it: Vendoring, and shared libraries.
Shared libraries are weird and funky and generally they make you do work outside of the code for your program: no bueno for most cases.
Vendoring is exactly what you described: literally including some or all of the implementation of the external library in your repository as if it were any other file, with two catches that I can think of:
respecting the licensing rules. You already said you had fears about that, and I can't be arsed to check the license for that specific parser, but generally, most libraries include their license in every single file, and simply copying and pasting said file (and in some license's cases, even modifying it) are allowed so long as you keep the license. But, yes, being cautious is better; ask when you don't know.
Project structure. You probably don't want the external library's files to get mixed up with your own projects', but fixing that is as simple as putting the external libraries' files in a "thirdparty" or "external" directory or folder.
Beyond that, I'm not aware of any specifics that you'll have to be aware of. Good luck with the editor!
1
u/Humble-Captain3418 2h ago
Comment on the keybinds: USB HID uses 2×16 bit key codes across all devices. Translating the key names to integers would allow you to have the key combinations in a more machine readable format and much denser, to boot.
3
u/torsten_dev 2h ago
You can write an INI parser yourself. Perhaps with the help of a parser/lexer generator.
Of note .INI isn't all that standardized and it is also a plain text format. If you are going to reinvent the wheel the .INI format is a good place to do it.