Browse Source

code import

Ondřej Hruška 1 year ago
parent
commit
0390122728
Signed by: Ondřej Hruška <ondra@ondrovo.com> GPG key ID: 2C5FD5035250423D
6 changed files with 281 additions and 2 deletions
  1. 6 0
      .gitignore
  2. 5 0
      Makefile
  3. 36 2
      README.md
  4. 143 0
      nini.c
  5. 69 0
      nini.h
  6. 22 0
      test.c

+ 6 - 0
.gitignore View File

@@ -0,0 +1,6 @@
1
+*.obj
2
+*.so
3
+*.out
4
+*.a
5
+CMakeLists.txt
6
+cmake-build-*/

+ 5 - 0
Makefile View File

@@ -0,0 +1,5 @@
1
+a.out: test.c nini.c
2
+	cc -Og test.c nini.c
3
+
4
+run: a.out
5
+	./a.out

+ 36 - 2
README.md View File

@@ -1,2 +1,36 @@
1
-# nini
2
-Nano INI parser
1
+# NINI
2
+
3
+This parser aims to be the smallest possible while supporting a sufficient subset of the INI format.
4
+The parser is meant for use in embedded applications.
5
+
6
+When compiled for a bare metal Cortex-M0, **the whole parser fits in 336 bytes** of Flash and 30 (+buffers) bytes of RAM.
7
+
8
+## Features
9
+
10
+- Basic INI format support
11
+- Accepts both Unix and DOS newlines
12
+- Minimal memory footprint
13
+- The input file can be loaded in multiple pieces of any size.
14
+- Buffer sizes (section, key, value) can be configured in the header file.
15
+- Custom data `void *` for maintaining user context, passed to the callback
16
+
17
+## Sypported syntax
18
+
19
+Any whitespace, except inside a value, is discarded.
20
+
21
+- **Sections** - `[section.name]`
22
+- **Key-value pairs** - `key.foo-bar_baz123 = value lorem ipsum 123`
23
+  - Value can contain whitespace, leading and trailing whitespace is removed.
24
+  - Ends with either `\r` or `\n`
25
+- **Comment** `# comment ...`
26
+  - Ends with either `\r` or `\n`
27
+
28
+## Limitations
29
+
30
+- Not re-entrant, uses a static state variable
31
+- No checks for invalid syntax
32
+- Whitespace inside keys and section names is removed
33
+- Quoted strings and escape sequences are not supported, will be collected as plain text
34
+- Value can't be followed by an inline comment
35
+
36
+See the file `test.c` for an example of the most basic usage; see the header file for more details on the API.

+ 143 - 0
nini.c View File

@@ -0,0 +1,143 @@
1
+///
2
+/// nini - Nano INI parser
3
+/// 
4
+/// Written by MightyPork, 2018
5
+/// MIT license
6
+/// 
7
+
8
+
9
+#include "nini.h"
10
+
11
+enum nini_state {
12
+    NINI_IDLE,
13
+    NINI_SECTION,
14
+    NINI_KEY,
15
+    NINI_VALUE,
16
+    NINI_COMMENT,
17
+};
18
+
19
+static struct {
20
+    uint8_t section_i;
21
+    char section[INI_KEY_MAX];
22
+
23
+    uint8_t key_i;
24
+    char key[INI_KEY_MAX];
25
+
26
+    uint8_t value_i;
27
+    char value[INI_VALUE_MAX];
28
+    bool val_last_space;
29
+
30
+    IniParserCallback cb;
31
+    void *userdata;
32
+    enum nini_state state;
33
+} nini;
34
+
35
+void ini_parse_begin(IniParserCallback callback, void *userData)
36
+{
37
+    ini_parse_reset();
38
+    nini.cb = callback;
39
+    nini.userdata = userData;
40
+}
41
+
42
+void ini_parse(const char *data, size_t len)
43
+{
44
+    for (; len > 0; len--) {
45
+        char c = *data++;
46
+        if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
47
+            if (nini.state != NINI_VALUE && nini.state != NINI_COMMENT)
48
+                continue;
49
+        }
50
+
51
+        switch (nini.state) {
52
+            case NINI_IDLE:
53
+                if (c == '[') {
54
+                    nini.state = NINI_SECTION;
55
+                    nini.section_i = 0;
56
+                }
57
+                else if (c == '#') {
58
+                    nini.state = NINI_COMMENT;
59
+                }
60
+                else {
61
+                    nini.state = NINI_KEY;
62
+                    nini.key_i = 0;
63
+                    nini.value_i = 0;
64
+                    nini.val_last_space = false;
65
+                    nini.key[nini.key_i++] = c;
66
+                }
67
+                break;
68
+
69
+            case NINI_COMMENT:
70
+                if (c == '\n' || c == '\r') {
71
+                    nini.state = NINI_IDLE;
72
+                }
73
+                break;
74
+
75
+            case NINI_SECTION:
76
+                if (c == ']') {
77
+                    nini.section[nini.section_i] = 0;
78
+                    nini.state = NINI_COMMENT; // discard to EOL
79
+                    break;
80
+                }
81
+                else if (nini.section_i < INI_KEY_MAX - 1) {
82
+                    nini.section[nini.section_i++] = c;
83
+                }
84
+                break;
85
+
86
+            case NINI_KEY:
87
+                if (c == '=') {
88
+                    nini.key[nini.key_i] = 0;
89
+                    nini.state = NINI_VALUE;
90
+                }
91
+                else if (nini.key_i < INI_KEY_MAX - 1) {
92
+                    nini.key[nini.key_i++] = c;
93
+                }
94
+                break;
95
+
96
+            case NINI_VALUE:
97
+                switch (c) {
98
+                    case ' ':
99
+                    case '\t':
100
+                        if (nini.value_i) nini.val_last_space = true;
101
+                        break;
102
+                    case '\r':
103
+                    case '\n':
104
+                        nini.value[nini.value_i] = 0;
105
+                        nini.state = NINI_IDLE;
106
+                        nini.cb(nini.section, nini.key, nini.value, nini.userdata);
107
+                        break;
108
+                    default:
109
+                        if (nini.val_last_space && nini.value_i < INI_VALUE_MAX - 1) {
110
+                            nini.value[nini.value_i++] = ' ';
111
+                        }
112
+
113
+                        if (nini.value_i < INI_VALUE_MAX - 1) {
114
+                            nini.value[nini.value_i++] = c;
115
+                        }
116
+                        nini.val_last_space = false;
117
+                }
118
+        }
119
+    }
120
+}
121
+
122
+void *ini_parse_end(void)
123
+{
124
+    if (nini.state == NINI_VALUE) {
125
+        nini.value[nini.value_i] = 0;
126
+        nini.state = NINI_IDLE;
127
+        nini.cb(nini.section, nini.key, nini.value, nini.userdata);
128
+    }
129
+
130
+    return nini.userdata;
131
+}
132
+
133
+void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData)
134
+{
135
+    ini_parse_begin(callback, userData);
136
+    ini_parse(text, len);
137
+    ini_parse_end();
138
+}
139
+
140
+void ini_parse_reset(void)
141
+{
142
+    nini.state = NINI_IDLE;
143
+}

+ 69 - 0
nini.h View File

@@ -0,0 +1,69 @@
1
+///
2
+/// nini - Nano INI parser
3
+/// 
4
+/// Written by MightyPork, 2018
5
+/// MIT license
6
+/// 
7
+
8
+#ifndef INIPARSE_H
9
+#define INIPARSE_H
10
+
11
+#include <stdint.h>
12
+#include <stdbool.h>
13
+#include <stdlib.h>
14
+#include <stdio.h>
15
+
16
+// buffer sizes
17
+#define INI_KEY_MAX 20
18
+#define INI_VALUE_MAX 30
19
+
20
+/**
21
+ * INI parser callback, called for each found key-value pair.
22
+ *
23
+ * @param section - current section, empty string for global keys
24
+ * @param key - found key (trimmed of whitespace)
25
+ * @param value - value, trimmed of quotes or whitespace
26
+ * @param userData - opaque user data pointer, general purpose
27
+ */
28
+typedef void (*IniParserCallback)(const char *section, const char *key, const char *value, void *userData);
29
+
30
+/**
31
+ * Begin parsing a stream
32
+ *
33
+ * @param callback - key callback to assign
34
+ * @param userData - optional user data that will be passed to the callback
35
+ */
36
+void ini_parse_begin(IniParserCallback callback, void *userData);
37
+
38
+/**
39
+ * End parse stream.
40
+ * Flushes what remains in the buffer and removes callback.
41
+ *
42
+ * @returns userData or NULL if none
43
+ */
44
+void* ini_parse_end(void);
45
+
46
+/**
47
+ * Parse a string (needn't be complete line or file)
48
+ *
49
+ * @param data - string to parse
50
+ * @param len - string length (0 = use strlen)
51
+ */
52
+void ini_parse(const char *data, size_t len);
53
+
54
+/**
55
+ * Parse a complete file loaded to string
56
+ *
57
+ * @param text - entire file as string
58
+ * @param len - file length (0 = use strlen)
59
+ * @param callback - key callback
60
+ * @param userData - optional user data for key callback
61
+ */
62
+void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData);
63
+
64
+/**
65
+ * Explicitly reset the parser
66
+ */
67
+void ini_parse_reset(void);
68
+
69
+#endif // INIPARSE_H

+ 22 - 0
test.c View File

@@ -0,0 +1,22 @@
1
+#include "nini.h"
2
+#include <string.h>
3
+
4
+const char *testfile = 
5
+"# Comment  ...........\n"
6
+"rootkey = Value\r\n"
7
+"\r\n"
8
+" [ Section@name!@+12346   ]\r\n"
9
+"sec.key-a_b-1 = 444\r\n"
10
+"sec.key2 = Test 123456 with spaces\r\n"
11
+    "[ this is a section with spaces nthat will be removed ]\n"
12
+"this_one_has_no_eol = 123456";
13
+
14
+
15
+void test_cb(const char *section, const char *key, const char *value, void *userData)
16
+{
17
+    printf("[%s] >%s< = >%s<\r\n", section, key, value);
18
+}
19
+
20
+int main (void) {
21
+    ini_parse_file(testfile, strlen(testfile), test_cb, NULL);
22
+}