home readme diff tree note docs

0commit 3aeecff454c8d32c0165e8db4f3804b5c4c24cad
1Author: lakefox <mason@lakefox.net>
2Date:   Sat Feb 7 17:11:07 2026 -0700
3
4    html_parser converted
5
6diff --git a/include/parser.h b/include/parser.h
7index 2f5397e..a26ba1f 100755
8--- a/include/parser.h
9+++ b/include/parser.h
10@@ -3,6 +3,6 @@
11 #define PARSER_H
12 
13 // Declare the parsing functions
14-void parseDocument(SymbolTable*, Node*, char*, int);
15+void html_parser(SymbolTable*, Node*, char*, int);
16 
17 #endif
18diff --git a/playground/html_parser.c b/playground/html_parser.c
19index ec73536..3c91e6f 100644
20--- a/playground/html_parser.c
21+++ b/playground/html_parser.c
22@@ -32,13 +32,39 @@ char* read_file_to_string(const char* filename) {
23     return buffer;
24 }
25 
26+
27+void benchmark_html_parser(SymbolTable* sym, const char* file) {
28+	struct timespec start, end;
29+
30+	int iterations = 10000;
31+	
32+	// Get start time
33+	clock_gettime(CLOCK_MONOTONIC, &start);
34+
35+	for (int i = 0; i < iterations; i++) {
36+		Node* root = CREATE_ELEMENT(sym, "root");
37+		html_parser(sym, root, file, strlen(file));
38+
39+		FREE_NODE(root);
40+	}
41+
42+	// Get end time
43+	clock_gettime(CLOCK_MONOTONIC, &end);
44+
45+	// Calculate time difference in seconds
46+	double total_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
47+	printf("Average execution time: %.9f seconds (%f us)\n", 
48+		total_time / iterations, 
49+	       (total_time / iterations) * 1000000);
50+}
51+
52 int main() {
53 	SymbolTable* sym = create_symbol_table(4096);
54 	Node* root = CREATE_ELEMENT(sym, "root");
55 
56-	char* file = read_file_to_string("./tests/easy.html");
57+	char* file = read_file_to_string("./tests/index.html");
58 
59-	parseDocument(sym, root, file, strlen(file));
60+	html_parser(sym, root, file, strlen(file));
61 		
62 	size_t size = 10000;
63 	char* html = calloc(size, sizeof(char));
64@@ -47,6 +73,9 @@ int main() {
65 
66 	printf("%s\n", html);
67 
68+
69+	benchmark_html_parser(sym, file);
70+
71 	free(html);
72 	free(file);
73 	FREE_SYMBOL_TABLE(sym);
74diff --git a/src/grim.c b/src/grim.c
75index 28d751d..bc22ce7 100755
76--- a/src/grim.c
77+++ b/src/grim.c
78@@ -82,7 +82,7 @@ Node* CREATE_ELEMENT(SymbolTable* sym, const char* value) {
79 	Node* el = malloc(sizeof(Node));
80 	memset(el, 0, sizeof(Node));
81 	el->tag_name = intern_string(sym, value);
82-	el->innertext = NULL;
83+	el->innertext = '\0';
84 	el->id = -1;
85 	return el;
86 }
87@@ -168,8 +168,19 @@ char* INNER_HTML(SymbolTable* sym, Node* node, char** buffer, size_t* size, int
88 		append_to_buffer(buffer, size, temp);
89 	}
90 
91+	if (node->attr_count > 0) {
92+		for (int i = 0; i < node->attr_count; i++) {
93+			sprintf(temp, " %s=\"%s\"", node->attributes[i].key, node->attributes[i].value);
94+			append_to_buffer(buffer, size, temp);
95+		}
96+	}
97+
98 	append_to_buffer(buffer, size, ">\n");
99 
100+	if (node->innertext && strlen(node->innertext)) {
101+		append_to_buffer(buffer, size, node->innertext);
102+	}
103+
104 	Node* child = node->first_child;
105 
106 	while (child) {
107diff --git a/src/parser.c b/src/parser.c
108index 40e88f4..8c85c43 100755
109--- a/src/parser.c
110+++ b/src/parser.c
111@@ -3,104 +3,6 @@
112 #include <ctype.h>
113 
114 /*
115-std::unordered_map<std::string, std::string> parseAttributes(std::string_view token) {
116-	std::unordered_map<std::string, std::string> attrs;
117-	
118-	bool inQuote = false;
119-	bool escaped = false;
120-	char quoteType = '"';
121-
122-	std::string word;
123-	for (unsigned short i = 0; i < token.length(); i++) {
124-		char current = token[i];
125-		if ((current == '"' || current == '\'') && !escaped) {
126-			if (inQuote && current == quoteType) {
127-				inQuote = false;
128-			} else if (!inQuote) {
129-				inQuote = true;
130-				quoteType = current;
131-			}
132-		}
133-
134-		if (current == '\\') { //'
135-			escaped = true;
136-			continue;
137-		}
138-
139-		if ((!std::isspace(current) || inQuote) && i != token.length()-1) {
140-			word += current;
141-		} else {
142-			if (i == token.length()-1) {
143-				word += current;
144-			}
145-
146-			std::string name = "";
147-			std::string value = "";
148-			bool setValue = false;
149-			
150-			for (auto c : word) {
151-				if (c == '=') {
152-					setValue = true;
153-					continue;
154-				}
155-
156-				if (setValue) {
157-					value += c;
158-				} else {
159-					name += c;
160-				}
161-			}
162-
163-			word = "";
164-
165-			std::string trimmedName = "";
166-			for (auto c : name) {
167-				if (!std::isspace(c)) {
168-					trimmedName += c;
169-				}
170-			}
171-
172-			if (attrs.size() == 0) {
173-				value = '"'+trimmedName+'"';
174-				trimmedName = "tagName";
175-			}
176-
177-			std::string trimmedValue = "";
178-			int sliceStart = 0;
179-			int sliceEnd = value.length();
180-
181-			if (value.length() >= 2) {
182-				for (unsigned short t = 0; t < value.length(); t++) {
183-					if (!std::isspace(value[t])) {
184-						sliceStart = t;
185-						break;
186-					}
187-				}
188-
189-				for (int t = value.length()-1; t >= 0; t--) {
190-					// Trim the trailing / on self closing elements if there isn't a space in between
191-					if (!std::isspace(value[t]) && value[t] != '/') {
192-						sliceEnd = t;
193-						break;
194-					}
195-				}
196-
197-				// Add and subtract 1 from each side to remove quotes
198-				for (unsigned short t = sliceStart+1; t < sliceEnd; t++) {
199-					trimmedValue += value[t];
200-				}
201-			}
202-
203-			if (trimmedValue.length() == 0) {
204-				trimmedValue = "";
205-			}
206-
207-			attrs[trimmedName] = trimmedValue;
208-		}
209-	}
210-
211-	return attrs;
212-}
213 
214 static const std::unordered_map<std::string, KeyType> keySearch {
215 	{"accent-color", KeyType::ACCENT_COLOR},
216@@ -1254,7 +1156,7 @@ std::vector<Unit> getUnit(std::string value, SymbolTable* externalTable = nullpt
217 
218 	return units;
219 }
220-
221+*/
222 Style parseCSS(std::istream& inputStream) {
223 	// nowhitespace: value(can have whitespace); = property
224 	// a selector name is anything before a { up to a ; or a }
225@@ -1430,6 +1332,7 @@ Style parseCSS(std::istream& inputStream) {
226 	return root;
227 }
228 
229+/*
230 std::string get_working_path() {
231     char temp [ PATH_MAX ];
232 
233@@ -1477,8 +1380,7 @@ std::string pathJoin(const std::string& p1, const std::string& p2) {
234 }
235 */
236 
237-void parseDocument(SymbolTable* sym, Node* root, const char* input, int size) {
238-	printf("ROOT TAGNAME: %s, %i\n", symbol_lookup_string(sym, root->tag_name), root->tag_name);
239+void html_parser(SymbolTable* sym, Node* root, const char* input, int size) {
240 	Node* currentNode = root;
241 
242 	bool inTag = false;
243@@ -1498,9 +1400,42 @@ void parseDocument(SymbolTable* sym, Node* root, const char* input, int size) {
244 
245 	char attr_value[MAX_STR_LEN];
246 
247+	bool inRawMode = false;
248+	char rawTagName[MAX_STR_LEN] = "";
249+
250 	for (int i = 0; i < size; i++) {
251 		char current = input[i];
252-		
253+	
254+		if (inRawMode) {
255+			if (current == '<' && i + 2 < size && input[i+1] == '/') {
256+				int tagLen = strlen(rawTagName);
257+				if (strncmp(&input[i+2], rawTagName, tagLen) == 0) {
258+
259+					token[token_length] = '\0';
260+					if (token_length > 0) {
261+						Node* textNode = CREATE_ELEMENT(sym, "text");
262+						SET_INNERTEXT(textNode, token);
263+						APPEND_CHILD(currentNode, textNode);
264+					}
265+
266+					if (currentNode->parent != NULL) {
267+						currentNode = currentNode->parent;
268+					}
269+
270+					inRawMode = false;
271+					token_length = 0;
272+
273+					while (i < size && input[i] != '>') i++;
274+					continue;
275+				}
276+			}
277+			if (token_length < 1023) {
278+				token[token_length++] = current;
279+			}
280+			continue;
281+		}
282+
283+
284 		// Finds the --> and removes it then resets inComment
285 		if (inComment) {
286 			// added the peek to prevent hitting on every -
287@@ -1522,18 +1457,29 @@ void parseDocument(SymbolTable* sym, Node* root, const char* input, int size) {
288 
289 
290 		if (inTag) {
291+			if (token_length == 0 && current == '/') {
292+				if (currentNode->parent != NULL) {
293+					currentNode = currentNode->parent;
294+				}
295+
296+				while (i < size && input[i] != '>') i++;
297+				inTag = false;
298+				tagname_set = false;
299+				token_length = 0;
300+				continue;
301+			}
302+
303 			 if ((current == '"' || current == '\'') && !escaped) {
304 				if (inQuote && current == quoteType) {
305 					inQuote = false;
306 					if (attr_name[0] != '\0' && attr_name_set) {
307 						if (strcmp(attr_name, "id") == 0) {
308-							SET_ID(sym, currentNode, token+1);
309+							SET_ID(sym, currentNode, token);
310 						} else if (strcmp(attr_name, "class") == 0) {
311 							
312 						} else {
313-							SET_ATTRIBUTE(currentNode, attr_name, token+1);
314+							SET_ATTRIBUTE(currentNode, attr_name, token);
315 						}
316-						token[0] = '\0';
317 						token_length = 0;
318 						attr_name[0] = '\0';
319 						attr_name_set = false;
320@@ -1546,48 +1492,72 @@ void parseDocument(SymbolTable* sym, Node* root, const char* input, int size) {
321 				continue;
322 			}
323 
324-			printf("%c %i %i\n", current, inQuote, tagname_set);
325+			if (!tagname_set && !inQuote && (isspace(current) || current == '>')) {
326+				token[token_length] = '\0';
327+				if (token_length > 0) {
328+					bool self_closing = false;
329 
330-			if ((isspace(current) || current == '>') && !inQuote && !tagname_set) {
331-				if (token[0] == '/') {
332-					if (currentNode->parent != NULL) {
333-						currentNode = currentNode->parent;
334+					if (token[token_length - 1] == '/') {
335+						self_closing = true;
336+						token[token_length - 1] = '\0';
337 					}
338 
339-					token[0] = '\0';
340-					token_length = 0;
341+					strcpy(tagname, token);
342+
343+					if (tagname[0] != '!') {
344+						Node* node = CREATE_ELEMENT(sym, tagname);
345+						APPEND_CHILD(currentNode, node);
346+						if (!self_closing) {
347+							currentNode = node;
348+							if (strcmp(tagname, "script") == 0 ||
349+								strcmp(tagname, "style") == 0 ||
350+								strcmp(tagname, "textarea") == 0) {
351+								inRawMode = true;
352+								strcpy(rawTagName, tagname);
353+							}
354+						}
355+						tagname_set = true;
356+					} else {
357+						while (i < size && input[i] != '>') i++;
358+						inTag = false;
359+						tagname_set = false;
360+						token_length = 0;
361+						continue;
362+					}
363+				}
364 
365-					continue;
366+				token_length = 0;
367+				if (current == '>') {
368+					inTag = false;
369+					tagname_set = false;
370 				}
371+				continue;
372+			}
373 
374+			if (!inQuote && current == '=' && !attr_name_set) {
375 				token[token_length] = '\0';
376-				strcpy(tagname, token);
377 
378-				printf("TAGNAME %s\n", tagname);
379+				char* name_ptr = token;
380+				while(isspace(*name_ptr)) name_ptr++; 
381+				strcpy(attr_name, name_ptr);
382 
383-				token[0] = '\0';
384+				attr_name_set = true;
385 				token_length = 0;
386+				continue;
387+			}
388 
389-				if (tagname[0] == '!') continue;
390-
391-				tagname_set = true;
392-
393-				Node* node = CREATE_ELEMENT(sym, tagname);
394-				printf("CREATING ELEMENT: %s, PARENT: %s\n", tagname, symbol_lookup_string(sym, currentNode->tag_name));
395-				APPEND_CHILD(currentNode, node);
396-
397-				currentNode = node;
398-			} else if (current == '>' && !inQuote && !escaped) {
399+			if (!inQuote && current == '>') {
400+				if (token_length > 0 && token[token_length-1] == '/') {
401+					if (currentNode->parent != NULL) {
402+						currentNode = currentNode->parent;
403+					}
404+				}
405 				inTag = false;
406 				tagname_set = false;
407-				tagname[0] = '\0';
408-			} else if (current == '=' && !attr_name_set && !inQuote) {
409-				token[token_length] = '\0';
410-				strcpy(attr_name, token+1);
411-				token[0] = '\0';
412 				token_length = 0;
413-				attr_name_set = true;
414+				continue;
415 			}
416+
417 		} else if (!escaped && current == '<') {
418 			// if the next character is a !
419 			if (i+3 < size && input[i+1] == '!') {
420@@ -1599,28 +1569,22 @@ void parseDocument(SymbolTable* sym, Node* root, const char* input, int size) {
421 				}
422 			}
423 			inTag = true;
424-			
425-			// Here's where you actually make the text node and above the real nodes
426-			// can also prob make the vector of tokens a single variable
427-			// it really just needs to be a data string bc we know the type based on inTag
428 
429+			bool has_text = false;
430 
431-			bool hasText = false;
432 			for (int j = 0; j < token_length; j++) {
433-				if (!isspace(token[j])) {
434-					hasText = true;
435+				if (!isspace(token[j]) && token[j] != '\n') {
436+					has_text = true;
437 					break;
438 				}
439 			}
440-/*
441-			if (hasText) {
442+
443+			if (has_text) {
444 				Node* node = CREATE_ELEMENT(sym, "text");
445-				token[token_length++] = '\0';
446 				SET_INNERTEXT(node, token);
447 				APPEND_CHILD(currentNode, node);
448-			}*/
449-
450-
451+			}
452+			
453 			token[0] = '\0';
454 			token_length = 0;
455 			continue;
456diff --git a/tests/easy.html b/tests/easy.html
457index 54c0e15..1863b8e 100644
458--- a/tests/easy.html
459+++ b/tests/easy.html
460@@ -12,7 +12,7 @@
461 		Hello world
462 	</h1>
463 	<br/>
464-	<br />
465+	<br/>
466 	<input type="text"/>
467 	<p id="test2">
468 		this is a test