Add sources for C account creation programs
[mspang/pyceo.git] / src / parser.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <ctype.h>
5 #include <string.h>
6 #include <limits.h>
7
8 #include "parser.h"
9 #include "util.h"
10 #include "config.h"
11
12 #define VAR_MAX 256
13
14 void config_var(const char *, const char *);
15
16 struct config_file {
17     FILE *p;
18     char *name;
19     int line;
20     struct config_file *parent;
21     int comment;
22 };
23
24 static void parse_config_file(char *, struct config_file *);
25 static void parse_error(struct config_file *file, char *msg) {
26     fatal("parse error on line %d of %s: %s", file->line, file->name, msg);
27 }
28
29 static int parse_char(struct config_file *file) {
30     int c = getc(file->p);
31     if (c == '\n')
32         (file->line)++;
33     return c;
34 }
35
36 static void unparse_char(struct config_file *file, int c) {
37     if (c == EOF)
38         return;
39     ungetc(c, file->p);
40     if (c == '\n')
41         (file->line)--;
42 }
43
44 static void parse_name(struct config_file *file, char *name, size_t maxlen) {
45     int len = 0;
46     int c;
47
48     for (;;) {
49         c = parse_char(file);
50
51         if (c == EOF || c == '\n') {
52             unparse_char(file, c);
53             break;
54         }
55
56         if (!isalpha(c) && c != '_' && c != '-') {
57             unparse_char(file, c);
58             break;
59         }
60
61         if (len == maxlen - 1)
62             parse_error(file, "max name length exceeded");
63
64         name[len++] = c;
65     }
66
67     if (len == 0)
68         parse_error(file, "expected name");
69
70     name[len++] = '\0';
71 }
72
73 static void parse_value(struct config_file *file, char *value, size_t maxlen) {
74     int len = 0;
75     int quote = 0;
76     int comment = 0;
77     int space = 0;
78     int c;
79
80     for (;;) {
81         c = parse_char(file);
82
83         if (c == EOF || c == '\n')
84             break;
85
86         if (c == '#')
87             comment = 1;
88
89         if ((isspace(c) && !quote) || comment) {
90             space = 1;
91             continue;
92         }
93
94         if (c == '"') {
95             quote = ! quote;
96             continue;
97         }
98
99         if (len == maxlen - space - 1)
100             parse_error(file, "max value length exceeded");
101
102         if (space && len) {
103             value[len++] = ' ';
104         }
105
106         space = 0;
107         value[len++] = c;
108     }
109
110     if (quote)
111         parse_error(file, "unbalanced quotes");
112
113     value[len++] = '\0';
114 }
115
116 static void parse_include(struct config_file *file) {
117     char path[PATH_MAX];
118     struct config_file *parent = file->parent;
119
120     parse_value(file, path, sizeof(path));
121
122     while (parent != NULL) {
123         if (!strcmp(file->name, parent->name))
124             return;
125         parent = parent->parent;
126     }
127
128     parse_config_file(path, file);
129 }
130
131 static void parse_config(struct config_file *file) {
132     int c;
133     int comment = 0;
134
135     char var[VAR_MAX];
136     char value[VAR_MAX];
137
138     for (;;) {
139         c = parse_char(file);
140
141         if (c == '\n') {
142             comment = 0;
143             continue;
144         }
145
146         if (c == EOF)
147             return;
148
149         if (isspace(c) | comment)
150             continue;
151
152         if (c == '#') {
153             comment = 1;
154             continue;
155         }
156
157         unparse_char(file, c);
158         parse_name(file, var, sizeof(var));
159
160         if (!strcmp(var, "include")) {
161             parse_include(file);
162             continue;
163         }
164
165         for (;;) {
166             c = parse_char(file);
167             if (c == EOF || c == '\n')
168                 parse_error(file, "expected '=' before line end");
169             if (c == '=')
170                 break;
171             if (!isspace(c))
172                 parse_error(file, "expected '='");
173         }
174
175         parse_value(file, value, sizeof(value));
176         config_var(var, value);
177     }
178 }
179
180 static void parse_config_file(char *name, struct config_file *parent) {
181     struct config_file file;
182
183     file.p = fopen(name, "r");
184     file.name = name;
185     file.line = 1;
186     file.parent = parent;
187
188     if (!file.p) {
189         if (parent)
190             parse_error(parent, strerror(errno));
191         else
192             fatal("failed to open configuration file '%s': %s", name, strerror(errno));
193     }
194
195     parse_config(&file);
196
197     fclose(file.p);
198 }
199
200 long config_long(char *var, char *val) {
201     char *endptr;
202     long longval;
203
204     longval = strtol(val, &endptr, 0);
205
206     if (*val == '\0' || *endptr != '\0')
207         fatal("expected integer value for %s", var);
208
209     return longval;
210 }
211
212 void config_parse(char *filename) {
213     parse_config_file(filename, NULL);
214 }