diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e6d811 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build +/build-* diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..4a0e239 --- /dev/null +++ b/include/config.h @@ -0,0 +1,20 @@ +#ifndef KANSHI_CONFIG_H +#define KANSHI_CONFIG_H + +#include + +struct kanshi_profile_output { + struct wl_list link; + char *name; +}; + +struct kanshi_profile { + struct wl_list link; + struct wl_list outputs; +}; + +struct kanshi_config { + struct wl_list profiles; +}; + +#endif diff --git a/include/parser.h b/include/parser.h new file mode 100644 index 0000000..1dd2c35 --- /dev/null +++ b/include/parser.h @@ -0,0 +1,27 @@ +#ifndef KANSHI_PARSER_H +#define KANSHI_PARSER_H + +#include + +struct kanshi_config; + +enum kanshi_token_type { + KANSHI_TOKEN_LBRACKET, + KANSHI_TOKEN_RBRACKET, + KANSHI_TOKEN_STR, + KANSHI_TOKEN_NEWLINE, +}; + +struct kanshi_parser { + FILE *f; + int next; + int line, col; + + enum kanshi_token_type tok_type; + char tok_str[1024]; + size_t tok_str_len; +}; + +struct kanshi_config *parse_config(const char *path); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..7ecad83 --- /dev/null +++ b/main.c @@ -0,0 +1,12 @@ +#include + +#include "parser.h" + +int main(int argc, char *argv[]) { + struct kanshi_config *config = parse_config("/home/simon/.config/kanshi/config"); + if (config == NULL) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..35776b2 --- /dev/null +++ b/meson.build @@ -0,0 +1,50 @@ +project( + 'kanshi', + 'c', + version: '0.0.0', + license: 'MIT', + meson_version: '>=0.47.0', + default_options: [ + 'c_std=c99', + 'warning_level=3', + 'werror=true', + ], +) + +cc = meson.get_compiler('c') + +add_project_arguments(cc.get_supported_arguments([ + '-Wundef', + '-Wlogical-op', + '-Wmissing-include-dirs', + '-Wold-style-definition', + '-Wpointer-arith', + '-Winit-self', + '-Wfloat-equal', + '-Wstrict-prototypes', + '-Wredundant-decls', + '-Wimplicit-fallthrough=2', + '-Wendif-labels', + '-Wstrict-aliasing=2', + '-Woverflow', + '-Wformat=2', + + '-Wno-missing-braces', + '-Wno-missing-field-initializers', + '-Wno-unused-parameter', +]), language: 'c') + +wayland_client = dependency('wayland-client') + +kanshi_inc = include_directories('include') + +executable( + meson.project_name(), + files( + 'main.c', + 'parser.c', + ), + include_directories: kanshi_inc, + dependencies: [wayland_client], + install: true, +) diff --git a/parser.c b/parser.c new file mode 100644 index 0000000..6f29e88 --- /dev/null +++ b/parser.c @@ -0,0 +1,235 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "parser.h" + +const char *token_type_str(enum kanshi_token_type t) { + switch (t) { + case KANSHI_TOKEN_LBRACKET: + return "'{'"; + case KANSHI_TOKEN_RBRACKET: + return "'}'"; + case KANSHI_TOKEN_STR: + return "string"; + case KANSHI_TOKEN_NEWLINE: + return "newline"; + } + assert(0); +} + +int parser_read_char(struct kanshi_parser *parser) { + if (parser->next >= 0) { + int ch = parser->next; + parser->next = -1; + return ch; + } + + int ch = fgetc(parser->f); + if (ch == EOF) { + if (errno != 0) { + fprintf(stderr, "fgetc failed: %s\n", strerror(errno)); + } else { + return '\0'; + } + return -1; + } + + if (ch == '\n') { + parser->line++; + parser->col = 0; + } else { + parser->col++; + } + + return ch; +} + +int parser_peek_char(struct kanshi_parser *parser) { + int ch = parser_read_char(parser); + parser->next = ch; + return ch; +} + +bool parser_read_str(struct kanshi_parser *parser) { + while (1) { + int ch = parser_peek_char(parser); + if (ch < 0) { + return false; + } + + if (isspace(ch) || ch == '{' || ch == '}') { + parser->tok_str[parser->tok_str_len] = '\0'; + return true; + } + + // Always keep enough room for a terminating NULL char + if (parser->tok_str_len + 1 >= sizeof(parser->tok_str)) { + fprintf(stderr, "string too long\n"); + return false; + } + parser->tok_str[parser->tok_str_len] = parser_read_char(parser); + parser->tok_str_len++; + } +} + +bool parser_next_token(struct kanshi_parser *parser) { + while (1) { + int ch = parser_read_char(parser); + if (ch < 0) { + return ch; + } + + if (ch == '{') { + parser->tok_type = KANSHI_TOKEN_LBRACKET; + return true; + } else if (ch == '}') { + parser->tok_type = KANSHI_TOKEN_RBRACKET; + return true; + } else if (ch == '\n') { + parser->tok_type = KANSHI_TOKEN_NEWLINE; + return true; + } else if (isspace(ch)) { + continue; + } else { + parser->tok_type = KANSHI_TOKEN_STR; + parser->tok_str[0] = ch; + parser->tok_str_len = 1; + return parser_read_str(parser); + } + } +} + +bool parser_expect_token(struct kanshi_parser *parser, + enum kanshi_token_type want) { + if (!parser_next_token(parser)) { + return false; + } + if (parser->tok_type != want) { + fprintf(stderr, "expected %s, got %s\n", + token_type_str(want), token_type_str(parser->tok_type)); + return false; + } + return true; +} + +struct kanshi_profile_output *parse_profile_output(struct kanshi_parser *parser) { + struct kanshi_profile_output *output = calloc(1, sizeof(*output)); + + if (!parser_expect_token(parser, KANSHI_TOKEN_STR)) { + return NULL; + } + output->name = strdup(parser->tok_str); + + while (1) { + if (!parser_next_token(parser)) { + return NULL; + } + + switch (parser->tok_type) { + case KANSHI_TOKEN_STR: + // TODO + break; + case KANSHI_TOKEN_NEWLINE: + return output; + default: + fprintf(stderr, "unexpected %s in output\n", + token_type_str(parser->tok_type)); + return NULL; + } + } +} + +struct kanshi_profile *parse_profile(struct kanshi_parser *parser) { + if (!parser_expect_token(parser, KANSHI_TOKEN_LBRACKET)) { + return NULL; + } + + struct kanshi_profile *profile = calloc(1, sizeof(*profile)); + wl_list_init(&profile->outputs); + + while (1) { + if (!parser_next_token(parser)) { + return NULL; + } + + switch (parser->tok_type) { + case KANSHI_TOKEN_RBRACKET: + return profile; + case KANSHI_TOKEN_STR: + if (strcmp(parser->tok_str, "output") == 0) { + struct kanshi_profile_output *output = + parse_profile_output(parser); + if (output == NULL) { + return NULL; + } + wl_list_insert(&profile->outputs, &output->link); + } else { + fprintf(stderr, "unexpected directive '%s' in profile\n", + parser->tok_str); + return NULL; + } + break; + case KANSHI_TOKEN_NEWLINE: + break; // No-op + default: + fprintf(stderr, "unexpected %s in profile\n", + token_type_str(parser->tok_type)); + return NULL; + } + } +} + +struct kanshi_config *_parse_config(struct kanshi_parser *parser) { + struct kanshi_config *config = calloc(1, sizeof(*config)); + wl_list_init(&config->profiles); + + while (1) { + int ch = parser_peek_char(parser); + if (ch < 0) { + return NULL; + } else if (ch == 0) { + return config; + } else if (isspace(ch)) { + parser_read_char(parser); + continue; + } + + struct kanshi_profile *profile = parse_profile(parser); + if (!profile) { + return NULL; + } + + wl_list_insert(&config->profiles, &profile->link); + } +} + +struct kanshi_config *parse_config(const char *path) { + FILE *f = fopen(path, "r"); + if (f == NULL) { + fprintf(stderr, "failed to open file\n"); + return NULL; + } + + struct kanshi_parser parser = { + .f = f, + .next = -1, + .line = 1, + }; + + struct kanshi_config *config = _parse_config(&parser); + fclose(f); + if (config == NULL) { + fprintf(stderr, "failed to parse config file: " + "error on line %d, column %d\n", parser.line, parser.col); + return NULL; + } + + return config; +}