home readme diff tree note docs

0commit aa2a6b08d231cf3bf51cf51a1e469b8a5477584d
1Author: lakefox <mason@lakefox.net>
2Date:   Sat Jan 31 15:59:35 2026 -0700
3
4    Updated some docs
5
6diff --git a/docs/wrath.1 b/docs/wrath.1
7new file mode 100644
8index 0000000..af181b1
9--- /dev/null
10+++ b/docs/wrath.1
11@@ -0,0 +1,173 @@
12+'\" t
13+.\" Copyright 2025 Mason Wright (mason@lakefox.net)
14+.\"
15+.\" SPDX-License-Identifier: Linux-man-pages-copyleft
16+.\"
17+.\" Modified Wed Oct 15 16:19 2025, Mason Wright
18+.\"
19+.TH grim 7 2026-01-26 "Wrath Language Guide v1"
20+.SH NAME
21+Wrath \- Lisp like DOM DSL for the grim engine
22+.SH LIBRARY
23+Grim
24+.SH SYNOPSIS
25+.P
26+Wrath is a DSL designed to interface with grim's interal DOM and provide a interface with foreign functions. The interpter of wrath is the same one that is used to compute media queries and the values of properities, this drives a lot of the language design decisions. However, this also allows grim to remain lightweight.
27+A basic program is structured like below:
28+
29+.nf
30+(
31+	let(x 10vw)
32+	set(x (x / 3))
33+	print(x)
34+)
35+.fi
36+.SH TYPES
37+As wrath is designed to compute CSS property values all CSS types are supported to include:
38+
39+.B
40+Number Values
41+.nf
42+px deg turn cm mm q in pc pt em rem cap ch
43+ic lh rcap rch ric rlh ex rex cqh vh cqw cqi
44+cqb % vw vmax cqmax vmin cqmin vb vi
45+.fi
46+
47+.RS
48+	All numbers without a type are parsed as floats
49+.RE
50+
51+.B
52+Other Types
53+.nf
54+bool hex string char enum list dictionary function variable
55+.fi
56+
57+.B
58+Operations
59+.nf
60++ - / * < <= > >= == and && or || not only
61+.fi
62+
63+.B
64+Functions
65+.nf
66+calc() min() max() clamp() round() mod() progress() remd() sin() cos()
67+tan() asin() acos() atan() atan2() pow() sqrt() hypot() log() exp()
68+abs() sign() attr() var() if()
69+.fi
70+
71+.B
72+Non CSS Functions
73+.nf
74+let() set() print() list() at() set(at()) insert() size() for()
75+for(range()) for(each()) map() f() $() ()
76+.fi
77+
78+.SH Definitions
79+.RS
80+Note all comma usage is optional
81+.RE
82+.P
83+.B
84+calc(expr)
85+.br
86+calc() takes a mathmatical expression and returns the result.
87+.P
88+.B
89+min(v1, v2, ...)
90+.br
91+min() takes n numerical arguments or calculatable expression's that return a numerical value and returns the minimum value.
92+.P
93+.B
94+max(v1, v2, ...)
95+.br
96+max() takes n numerical arguments or calculatable expression's that return a numerical value and returns the maximum value.
97+.P
98+.B
99+clamp(min, expr, max)
100+.br
101+clamp() clamps the result of an expression or a single value to a minimum and a maximum value. If three arguments are not provided it will return the first value passed.
102+.P
103+.B
104+round(up|down|nearest*|to-zero, value, interval)
105+.br
106+round()
107+
108+.SH Overview
109+The wrath interperter is a byte-code style interpreter, there is a function that takes a program and compiles it into a 8 byte struct with a 2 byte enum as a type marker and a 4 byte float as the value. These are stored in a vector that is consumed by the interpreter. Most values are parsed into a single struct called a unit however, some need to be stored a little different. Strings are stored across multiple units as char's with a prefix of a unit with the type of string and a value of the length of the string. When a string is set as the value of a variable the string is loaded onto the heap where it may be modified there. Lists work in the same way, you can read values of a string or a list with the at() function and write a value with set(at()). List and strings can not be extended once allocated, if needed create a seperate list and join the two together to create a longer list. User defined function also span multiple units but they are never copied to the heap, instead a pointer to the start of the function is stored then the program jumps to the start, executes, and returns the value.
110+
111+Execution of a program is done via recursion and jumps in the byte-code array. Generally a program once parsed is executed left to right accumulates a value to be returned. This is why a function can only return one value at a time. You may notice every statement is a function, including an unnamed function written as "()", function calls create the scope of the program and allow data to be passed without hitting the heap. If you do not have to use a variable do not. While the heap is still quick and can mainly be prefetched by the CPU, directly passing a value will always be faster. As your program executes a value will accumulate as long as there is an operator before the calculated value, if there isn't then the accumulator will be overwritten by the currently value. All function will return the last value of the accumulator.
112+
113+Scope is managed inside of function calls, loops and unnamed functions. After a variable is created with either let() or list() a new object is created on the lookup table. It does not matter if a name is in use or not the set() method can be used to modify the most recent heap object. Once a variable goes out of scope (by exiting a function or loop) the heap pointer is shifted to the previous context start. Data will not be deallocated from the heap instead it will be marked as writable once again. This is to reduce the amount of allocations/deallocations in a given program.
114+
115+.SH Memory
116+
117+.B
118+Rules
119+- All variables are one Unit
120+- All lists must have its final size at initation
121+- All variables and lists used as values will be copied
122+
123+Memory is allocated as a array outside of the interperter using the arena pattern. Initally 1024 objects are allocated to the programs heap, variables are inserted via std::copy into the programs heap from the compiled byte array via let() or list(). One decleartaion of a variable, a cursor is created and the heap's pointer is shifted the length of the inserted data. The cursor tracks the id of the variable, where the data starts and the length, and a reference to the parent cursor. This allows you to use the same id to reference multiple locations on the heap. Once you exit a scope, the cursor's created up to the start of the scope are cleared and the heap pointer is set back to the previous location via a snapshot. Snapshots are taken before entering a new function call, loop or unamed function, the current cursor and heap pointer are saved then restored on exit. This makes it so there aren't any deallocations in this heap. 
124+
125+Most of the data is the exact same size, one unit so when a variable is modified inside one scope but declared in another the modification is done in the orginal position of the unit then the heap is reset there isn't any loss of data. The exeption is list, they can be any size, the way this is accounted for is by making lists non-resizable. Instead a list can be allocated to be any size with the list(empty(n)) function, all this does is moves the heap pointer back by n. Then data can be inserted into any position with the insert() function. This keeps all data constantly in scope at all times. To ensure you are able to allocate the proper amount of space when joining multiple lists together the target list need to be correctly size to fit all of the data. However because all memory is allocated at runtime you do not need to know the size of the list you will need at compile time.
126+
127+To get a variable from the heap the cursor chain is scanned from the end for the most recent one matching the id queried. Once it has the correct cursor the start and end index are returned. All data returned is one unit n size this even applies for lists. You can pass a reference to the list with the variable it was declared with or an item on the list with the at() function.
128+
129+While all variables are allocated on the programs heap not all program memory is. When calling a function and passing its results directly into another the values are stored in the CPU stack. Its encouraged for time sensistive tasks you limit the amount of variables you need. Passing information from one function to another will only store the result of the function in the interpeters accumulator.
130+
131+The heap is allocated by the calling function, this allows the calling function to modify the heap before execution. This can be used to setup global variables for the program that is executed. Grim uses this method for multiple processes including executing media queries and other at-rules.
132+
133+.SH Syntax
134+.nf
135+Declaring a value
136+let(x 1px)
137+
138+Changing a value
139+set(x 10px)
140+
141+Making a list
142+list(y 1 2 3 4)
143+
144+Change a value in a list
145+set(at(0) 15)
146+
147+Single line comment
148+// comment
149+
150+Multiline comments
151+/*
152+	Multiline
153+	comment
154+*/
155+
156+List of tagged values
157+list(x 
158+	:name "mason"
159+	:age "123"
160+)
161+
162+Printing a tagged value in a list
163+print(at(x :name))
164+
165+Setting a tagged value
166+set(at(x :name) "new value")
167+.fi
168+
169+
170+list(y x) // The issue is that the only information that says where x is in the context 
171+
172+
173+
174+
175+need to make set(at()) and list move the data into it's scope
176+
177+
178+
179+
180+.fi
181+.SH Execution
182+.SH DOM
183+.SH FFI
184+.SH Examples
185diff --git a/include/grim.h b/include/grim.h
186index 7ca03fd..6fee15f 100755
187--- a/include/grim.h
188+++ b/include/grim.h
189@@ -90,7 +90,7 @@ enum class UnitType : short {
190 		NEAREST, UP, DOWN, TO_ZERO,	
191 
192 	// Keyword
193-		PRINT, SCREEN, TOKEN, NO_SUPPORT, IMAGE, STRING, BOOL, VAR, STRING_TERM, NONOP, ELSE,
194+		PRINT, SCREEN, TOKEN, NO_SUPPORT, IMAGE, STRING, BOOL, VAR, NONOP, ELSE, TAG, CHAR,
195 
196 	FUNC_START,
197 		// General Functions
198@@ -98,15 +98,15 @@ enum class UnitType : short {
199 		HYPOT, LOG, EXP, ABS, SIGN, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, VAR_FUNC,
200 		BLUR, BRIGHTNESS, CONTRAST, DROP_SHADOW, GRAYSCALE, HUE_ROTATE, INVERT, 
201 		OPACITY, SEPIA, SATURATE, ATTR, TYPE, ENV, IF, FUNC, LET, RETURN, SET,
202-		LIST, AT,
203+		LIST, AT, EMPTY, INSERT, SIZE, WHILE,
204 		
205 		// Easing Functions
206 		EASING_FUNCTION, LINEAR_EASING_FUNCTION, LINEAR, EASE, EASE_IN, EASE_OUT, EASE_IN_OUT, CUBIC_BEZIER_FUNCTION, 
207-		STEP_START, STEP_END, STEPS_FUNCTION, JUMP_START, JUMP_END, JUMP_NONE, JUMP_BOTH, 	
208+		STEP_START, STEP_END, STEPS_FUNCTION, JUMP_START, JUMP_END, JUMP_NONE, JUMP_BOTH,
209 
210 		// Transform Functions
211 		MATRIX, MATRIX3D, PERSPECTIVE, ROTATE, ROTATE3D, ROTATEX, ROTATEY, ROTATEZ, 
212-		SCALE, SCALE3D, SCALEX, SCALEY, SCALEZ, TRANSLATE, TRANSLATE3D, TRANSLATEX, 
213+		SCALE, SCALE3D, SCALEX, SCALEY, SCALEZ, TRANSLATE, TRANSLATE3D, TRANSLATEX,
214 		TRANSLATEY, TRANSLATEZ, SKEW, SKEWX, SKEWY,
215 
216 		RGB, RGBA, HSL,
217@@ -158,6 +158,13 @@ class Memory {
218 			pointer += len;
219 		}
220 
221+		void alloc(int id, size_t size) {
222+			arena.push_back({id, pointer, size, current});
223+			current = &arena.back();
224+			pointer += size;
225+
226+		}
227+
228 		// Used to prefill the heap
229 		void push_back(int id, Unit unit) {
230 			arena.push_back({id, pointer, 1, current});
231diff --git a/playground/syntax.wr b/playground/syntax.wr
232index 7c6a2fe..9f26bf2 100644
233--- a/playground/syntax.wr
234+++ b/playground/syntax.wr
235@@ -1,6 +1,8 @@
236 Context -> width -> screen -> etc
237 
238 (
239+	import("../shared.so")
240+
241 	let(x (1 + 1))
242 	set(x (x + 1))
243 	print(x)
244@@ -12,48 +14,102 @@ Context -> width -> screen -> etc
245 		)
246 	)),
247 
248-	let(uppercase, func((str) (
249-				map(str, func((e)
250-					set(e (e + 20))
251-					)
252+	let(uppercase f(
253+				bind(str at(. 0))
254+				let(counter 0)
255+				while(
256+					set(at(str counter) (at(str counter) + 20))
257+					set(counter (counter + 1))
258+					(counter < size(str))
259 				)
260 			)
261 		)
262 	),
263 
264+
265+	let(obj (
266+		:data "Something"
267+		:ok true
268+	))
269+	
270+	print(at(obj:data 0))
271+
272+	$("body div#id"
273+		:html (map(getPersonel(value($(input#org))), def((v) (
274+			$(li (
275+				:class "lists"
276+				:text v.name
277+				:style (
278+					:background red
279+					)
280+			))
281+		))
282+	)
283+
284+
285+	$("#submit"
286+		:click (
287+			:text "Fetching"
288+			:disabled true
289+			for(each(getPersonel(value($("input#org")))) f(
290+				bind(person at(. 0))
291+				$("div#id ul" "li" (
292+					:class "lists"
293+					:text person:name
294+					:style (
295+						:background red
296+						)
297+				))
298+			)
299+			:text "Submit"
300+	)
301+
302+	for(range(0 10) (print(.)))
303+	
304+	list(x 1 2 3 4)
305+	for(each(x) (print(.)))
306+
307+
308+	// Elements are made and/or accessed with queryselectors
309+	// if no relationship is given then it won't be inserted
310+	// either way the a ref to the element is always returned
311+	$(body>modal (
312+		:click ((e) (
313+			:text ("You clicked me " x "times")
314+			:class (displayed)
315+
316+		))
317+		:text ("You can click me")
318+		:id main-box
319+	))
320+
321+
322 	let(string_test, "this is a string"),
323 	let(string_test, uppercase(string_test)),
324+
325 	print(string_test),
326 	print(width),
327 
328 	let(array, list(1,2,3)),
329 	print(array(0))
330-)
331 
332 
333-(
334-	let(0, (1 + 1)),
335-	let(0, 0 + 1),
336-	print(0),
337 
338-	func(1, (2), (
339-		map(2, func((3),
340-			3 + 20
341-			)
342-		)
343-	)),
344 
345-	let(4, "this is a string"),
346-	let(4, uppercase(4)),
347-	print(4),
348-	print(5),
349+	list(x  :name empty(10) 
350+		:age empty(3)
351+	)
352 
353-	let(6, list(1,2,3)),
354-	print(6(0))
355-)
356+	set(at(x :name) "Mason")
357 
358-let 0, (1 + 1) adds the computed value to the end
359-let 0, 0 + 1 get the value of 0 then add 1 then adds that to the end and updates 0's point to there
360-func adds a pointer to the function def to the end
361-anon functions do not have to add the function pointer
362+	print(at(x :name))
363 
364+
365+
366+	$("#button" fetch("http://api.com/query/" .:value (
367+		
368+	)))
369+
370+
371+
372+)
373diff --git a/playground/unitvalue.cc b/playground/unitvalue.cc
374index 9d56df9..5d63723 100644
375--- a/playground/unitvalue.cc
376+++ b/playground/unitvalue.cc
377@@ -55,7 +55,7 @@ int main() {
378 	m.push_back(sym.get("height"),{UnitType::NUMBER, 700.0f});
379 	m.push_back(sym.get("em"),{UnitType::NUMBER, 16.0f});
380 
381-	std::vector<Unit> units = getUnit("list(x 5 2 3 4) print(at(x 0)) set(at(x 0) 13) print(at(x 0))", &sym);
382+		std::vector<Unit> units = getUnit("list(x 1 7 3 4 5) list(y 7 8 x 9 0) at(at(y 2) 4)", &sym);
383 	
384 	Unit u = UnitValue(m, units, 0, units.size());
385 
386diff --git a/src/grim.cc b/src/grim.cc
387index 8ab1d5a..18f1ccd 100755
388--- a/src/grim.cc
389+++ b/src/grim.cc
390@@ -1412,29 +1412,26 @@ Unit UnitValue(Memory& m, std::vector<Unit>& units, size_t start, size_t end) {
391 
392 				Unit u1 = units[args[0].start];
393 
394-				if (u1.type == UnitType::VAR) {
395+				Range r = m.get(u1.value); // Pull the marker off the ctx
396+				Unit value = UnitValue(m, units, r.start, r.end);
397 
398-					Range r = m.get(u1.value); // Pull the marker off the ctx
399-					Unit value = UnitValue(m, units, r.start, r.end);
400+				if (r.end - r.start > 0) {
401+					if (found > 1) {
402+						// Get the passed type attr(0(value) 1(type), 2(default))
403+						Unit u2 = units[args[1].start];
404 
405-					if (r.end - r.start > 0) {
406-						if (found > 1) {
407-							// Get the passed type attr(0(value) 1(type), 2(default))
408-							Unit u2 = units[args[1].start];
409-
410-							// Cast the user defined type onto the value
411-							if (u2.type == UnitType::TYPE) {
412-								value.type = units[args[1].start+1].type;
413-							} else {
414-								value.type = u2.type;
415-							}
416+						// Cast the user defined type onto the value
417+						if (u2.type == UnitType::TYPE) {
418+							value.type = units[args[1].start+1].type;
419+						} else {
420+							value.type = u2.type;
421 						}
422-						
423-						iv = value;
424-					} else if (found == 3) {
425-						// Default value
426-						iv = UnitValue(m, units, args[2].start, args[2].end);
427 					}
428+					
429+					iv = value;
430+				} else if (found == 3) {
431+					// Default value
432+					iv = UnitValue(m, units, args[2].start, args[2].end);
433 				}
434 
435 				i += unit.value;
436@@ -1451,15 +1448,13 @@ Unit UnitValue(Memory& m, std::vector<Unit>& units, size_t start, size_t end) {
437 					args[found++] = UnitValue(m, units, r.start, r.end);
438 				}
439 
440-				if (args[0].type == UnitType::VAR) {
441-					Range r = m.get(args[0].value); // Pull the marker off the ctx
442+				Range r = m.get(args[0].value); // Pull the marker off the ctx
443 
444-					if (r.end > 0) {
445-						iv = UnitValue(m, units, r.start, r.end);
446-					} else if (found == 2) {
447-						// Default value
448-						iv = args[1];
449-					}
450+				if (r.end > 0) {
451+					iv = UnitValue(m, units, r.start, r.end);
452+				} else if (found == 2) {
453+					// Default value
454+					iv = args[1];
455 				}
456 
457 				i += unit.value;
458@@ -1474,40 +1469,6 @@ Unit UnitValue(Memory& m, std::vector<Unit>& units, size_t start, size_t end) {
459 
460 				break;
461 			}
462-			case UnitType::LET: {
463-				if (units[start + 1].type == UnitType::VAR) {
464-					Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
465-					m.push_back(units[i+1].value, u);
466-				}
467-
468-				i += unit.value;
469-				break;
470-			}
471-			case UnitType::SET: {
472-				if (units[i + 1].type == UnitType::VAR) {
473-					Range r = m.get(units[i + 1].value);
474-					Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
475-
476-					// r.end is the length
477-					if (r.end > 0) { // need to write over
478-						m.heap[r.start] = u;
479-					} else {
480-						m.push_back(units[i + 1].value, u);
481-					}
482-				} else if (units[i + 1].type == UnitType::AT && units[i + 2].type == UnitType::VAR) {
483-					// Set at in a list
484-					Range r = m.get(units[i+2].value);
485-
486-					if (r.end > 0) {
487-						Unit p = UnitValue(m, units, i + 2, i + 1 + units[i+2].value);
488-						Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
489-						m.heap[r.start + p.value] = u;
490-					}
491-				}
492-
493-				i += unit.value;
494-				break;
495-			}
496 			case UnitType::PRINT: {
497 				Unit u = UnitValue(m, units, i+1, i+1+unit.value);
498 				std::cout << "> " << u.value << std::endl;
499@@ -1542,31 +1503,146 @@ Unit UnitValue(Memory& m, std::vector<Unit>& units, size_t start, size_t end) {
500 			   	i += unit.value;
501 				break;
502 			}
503+			case UnitType::LET: {
504+				Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
505+				m.push_back(units[i+1].value, u);
506+
507+				i += unit.value;
508+				break;
509+			}
510+			case UnitType::SET: {
511+				if (units[i + 1].type == UnitType::VAR) {
512+					Range r = m.get(units[i + 1].value);
513+					Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
514+
515+					// r.end is the length
516+					if (r.end > 0) { // need to write over
517+						m.heap[r.start] = u;
518+					}
519+				} else if (units[i + 1].type == UnitType::AT && units[i + 2].type == UnitType::VAR) {
520+					// Set at in a list
521+					Range r = m.get(units[i+2].value);
522+
523+
524+
525+					// same at at()
526+					// for tagged props pad with TEST 0 values
527+
528+
529+
530+
531+					if (r.end > 0) {
532+						Unit p = UnitValue(m, units, i + 2, i + 1 + units[i+2].value);
533+						Unit u = UnitValue(m, units, i + 2, i + 1 + unit.value);
534+						m.heap[r.start + p.value] = u;
535+					}
536+				}
537+
538+				i += unit.value;
539+				break;
540+			}	
541 			case UnitType::LIST: {
542-				if (units[start + 1].type == UnitType::VAR) {
543 
544-				//	how to pre allocate a empty list????
545-				//	empty(5)
546-				//	link(links two lists) join(joins them in memory)???
547-				//	make a JUMP to padd the list
548 
549+
550+
551+
552+				// This needs to deference all values and store them
553+				// also needs to add a LIST length marker
554+				// needs to support
555+				//list(x  :name empty(10) 
556+				//	:age empty(3)
557+				//)
558+				// fill with TEST 0 so when at is ran it can trim the value because TEST is not valid
559+
560+
561+
562+
563+
564+
565+				if (units[i + 2].type == UnitType::EMPTY) {
566+					Unit emptyCount = UnitValue(m, units, i+3, i + 3 +units[i + 2].value);
567+					m.alloc(units[i+1].value, emptyCount.value);
568+				} else {
569 					m.push(units[i+1].value, units, i+2, i+1+unit.value);
570-				//	m.push_back({UnitType::JUMP, 0})
571 				}
572 
573 				i += unit.value;
574 				break;
575 			}
576 			case UnitType::AT: {
577-				if (units[start + 1].type == UnitType::VAR) {
578-					Range r = m.get(units[i+1].value);
579+				Range r = m.get(units[i+1].value);
580 
581-					if (r.end > 0) {
582-						Unit u = UnitValue(m, units, i + 1, i + 1 + unit.value);
583-						iv = m.heap[r.start + u.value];
584-					}
585+				if (r.end > 0) {
586+					Unit index = UnitValue(m, units, i + 1, i + 1 + unit.value);
587+					
588+					std::cout << r.start + index.value << std::endl;
589+
590+
591+
592+
593+					// this needs to use getNextUnit to find the correct unit
594+					// also need to find tags
595+
596+
597+
598+
599+
600+
601+					iv = m.heap[r.start + index.value];
602 				}
603 
604+				i += unit.value;
605+				break;
606+			}
607+			case UnitType::INSERT: {
608+				Range listRange = m.get(units[i+1].value);
609+				size_t size = listRange.end-listRange.start;
610+
611+				Range ri = m.get(units[i+3].value);
612+
613+				size_t offset = i + 2;
614+				Range iRange = getNextUnit(units, offset, i + 1 + unit.value);
615+				Unit index = UnitValue(m, units, iRange.start, iRange.end);
616+
617+
618+
619+
620+
621+
622+
623+				// this will also need to use getNextUnit to find the next value
624+				// will also have to support finding tags 
625+
626+
627+
628+
629+
630+
631+
632+
633+
634+				if (ri.end-ri.start + index.value < size) {
635+					std::copy(m.heap.begin() + ri.start, m.heap.begin() + ri.end, m.heap.begin()+listRange.start + index.value);
636+				} else {
637+					std::cerr << "Cannot insert data at index: " << index.value << " list size: " << size << " data to be inserted: " << ri.end-ri.start << std::endl;
638+				}
639+
640+				i += unit.value;
641+				break;
642+			}
643+			case UnitType::SIZE: {
644+
645+
646+
647+				// this can just return the value of LIST
648+
649+
650+
651+
652+			     	Range r = m.get(units[i+1].value);
653+				iv = {UnitType::NUMBER, (float)r.end-(float)r.start};
654+
655 				i += unit.value;
656 				break;
657 			}
658@@ -1849,8 +1925,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
659 				}
660 			}
661 			matched = found;
662-		} // !TODO: Need a case for : , this will run the :has... logic
663-		else if (p[0] == ':') {
664+		} else if (p[0] == ':') {
665 
666 			// need to get the ext one and shave off 1,-1
667 
668diff --git a/src/parser.cc b/src/parser.cc
669index 6508b7f..20957a7 100755
670--- a/src/parser.cc
671+++ b/src/parser.cc
672@@ -1053,6 +1053,9 @@ static const std::unordered_map<std::string_view, UnitType> kFunc{
673 	{"print", UnitType::PRINT},
674 	{"list", UnitType::LIST},
675 	{"at", UnitType::AT},
676+	{"empty", UnitType::EMPTY},
677+	{"insert", UnitType::INSERT},
678+	{"size", UnitType::SIZE},
679 	{"", UnitType::GROUP}
680 };
681 
682@@ -1079,26 +1082,27 @@ std::vector<Unit> getUnit(std::string value, SymbolTable* externalTable = nullpt
683 	// Needs to not be empty but can't be a quote
684 	char quoteType = 'a';
685 	bool escaped = false;
686+	bool inlineComment = false;
687+	bool multilineComment = false;
688 
689 	for (size_t i = 0; i < value.length(); i++) {
690 		char v = value[i];
691 		if (v == '\\' && !escaped) escaped = true;
692 		if (!escaped && (v == '"' || v == '\'') && parenDepth == 0) {
693 			if (inQuotes && v == quoteType) {
694+				Unit unit{};
695+				unit.type = UnitType::STRING;
696+				unit.value = str_buffer.length();
697+				units.push_back(unit);
698+
699 				for (size_t j = 0; j < str_buffer.length(); j++) {
700 					Unit unit{};
701-					unit.type = UnitType::STRING;
702+					unit.type = UnitType::CHAR;
703 					unit.value = str_buffer[j];
704 					units.push_back(unit);
705 				}
706 
707-				str_buffer.clear();
708-
709-				Unit unit{};
710-				unit.type = UnitType::STRING_TERM;
711-				unit.value = str_buffer.length();
712-				units.push_back(unit);
713-
714+				str_buffer.clear();	
715 				inQuotes = false;
716 			} else if (!inQuotes) {
717 				quoteType = v;
718@@ -1107,6 +1111,33 @@ std::vector<Unit> getUnit(std::string value, SymbolTable* externalTable = nullpt
719 			continue;
720 		}
721 
722+		if (i+1 < value.length()) {
723+			if (v == '/' && value[i+1] == '/') {
724+				inlineComment = true;
725+				continue;
726+			}
727+
728+			if (inlineComment) {
729+				if (v == '\n') {
730+					inlineComment = false;
731+				}
732+				continue;
733+			}
734+
735+			if (v == '/' && value[i+1] == '*') {
736+				multilineComment = true;
737+			}
738+
739+			if (multilineComment) {
740+				std::cout << i << std::endl;
741+				if (v == '*' && value[i+1] == '/') {
742+					i++;
743+					multilineComment = false;
744+				}
745+				continue;
746+			}
747+		}
748+
749 		if (!inQuotes) {
750 			if (v == '(') parenDepth++;
751 			else if (v == ')') parenDepth--;
752@@ -1217,8 +1248,14 @@ std::vector<Unit> getUnit(std::string value, SymbolTable* externalTable = nullpt
753 							// or a keyword like with the attr(size px) function. Here px would be
754 							// parsed but not size.
755 							if (!buffer.empty() && buffer.find_first_not_of(' ') != std::string::npos) {
756-								unit.type = UnitType::VAR;
757-								unit.value = table.get(buffer);
758+								if (buffer[0] == ':') {
759+									unit.type = UnitType::TAG;
760+									unit.value = table.get(buffer);
761+
762+								} else {
763+									unit.type = UnitType::VAR;
764+									unit.value = table.get(buffer);
765+								}
766 							} else {
767 								continue;
768 							}
769diff --git a/tests/unitvalue.cc b/tests/unitvalue.cc
770index 7b396c2..1ca49b9 100644
771--- a/tests/unitvalue.cc
772+++ b/tests/unitvalue.cc
773@@ -7,89 +7,89 @@
774 
775 
776 
777-/*
778 TEST_CASE("Benchmark UnitValue evaluation", "[performance][grim]") {
779-	Window window = createWindow();
780+	SymbolTable sym{};
781 
782-	Node document = window.document;
783-	document.cacheWidth(1000);
784-	document.cacheHeight(700);
785-	document.cacheEM(16);
786+	Memory m;
787+	// Preset width, height, and em
788+	m.push_back(sym.get("width"),{UnitType::NUMBER, 1000.0f});
789+	m.push_back(sym.get("height"),{UnitType::NUMBER, 700.0f});
790+	m.push_back(sym.get("em"),{UnitType::NUMBER, 16.0f});
791 
792 	SECTION("(3 >= min(1, 5)") {
793-		std::vector<Unit> units = getUnit("(3 >= min(1, 5))");
794+		std::vector<Unit> units = getUnit("(3 >= min(1, 5))", &sym);
795 
796-		REQUIRE(UnitValue(&document, units, 0, units.size()).value.BOOL);
797+		REQUIRE(UnitValue(m, units, 0, units.size()).value);
798 	}
799 }
800 
801 TEST_CASE("UnitValue Logic and Math", "[grim][logic]") {
802-    Window window = createWindow();
803-    Node document = window.document;
804-    document.cacheWidth(1000); // 1vw = 10px
805-    document.cacheHeight(500); // 1vh = 5px
806-    document.cacheEM(16);      // 1em = 16px
807+    SymbolTable sym{};
808+
809+    Memory m;
810+    // Preset width, height, and em
811+    m.push_back(sym.get("width"),{UnitType::NUMBER, 1000.0f});
812+    m.push_back(sym.get("height"),{UnitType::NUMBER, 500.0f});
813+    m.push_back(sym.get("em"),{UnitType::NUMBER, 16.0f});
814 
815     SECTION("Math Operators and Precedence (Group)") {
816         // (10 + 2 * 5) -> In your linear engine, without GROUP this is 60.
817         // With GROUP: (10 + (2 * 5)) = 20
818-        std::vector<Unit> units = getUnit("(10 + (2 * 5))");
819-        auto res = UnitValue(&document, units, 0, units.size());
820-        REQUIRE(res.value.FLOAT == 20.0f);
821+        std::vector<Unit> units = getUnit("(10 + (2 * 5))", &sym);
822+        auto res = UnitValue(m, units, 0, units.size());
823+        REQUIRE(res.value == 20.0f);
824     }
825 
826     SECTION("CSS Units Conversion") {
827         // 1in = 96px, 1cm = 37.8px
828-        std::vector<Unit> units = getUnit("1in + 4px");
829-        auto res = UnitValue(&document, units, 0, units.size());
830-        REQUIRE(res.value.FLOAT == 100.0f);
831+        std::vector<Unit> units = getUnit("1in + 4px", &sym);
832+        auto res = UnitValue(m, units, 0, units.size());
833+        REQUIRE(res.value == 100.0f);
834     }
835 
836     SECTION("Viewport Units") {
837         // 50vw (500px) + 10vh (50px) = 550px
838-        std::vector<Unit> units = getUnit("50vw + 10vh");
839-        auto res = UnitValue(&document, units, 0, units.size());
840-        REQUIRE(res.value.FLOAT == 550.0f);
841+        std::vector<Unit> units = getUnit("50vw + 10vh", &sym);
842+        auto res = UnitValue(m, units, 0, units.size());
843+        REQUIRE(res.value == 550.0f);
844     }
845 
846     SECTION("Clamping and Min/Max") {
847         // clamp(100, 50, 200) -> 100 (50 is below min)
848-        std::vector<Unit> units = getUnit("clamp(100, 50, 200)");
849-        auto res = UnitValue(&document, units, 0, units.size());
850-        REQUIRE(res.value.FLOAT == 100.0f);
851+        std::vector<Unit> units = getUnit("clamp(100, 50, 200)", &sym);
852+        auto res = UnitValue(m, units, 0, units.size());
853+        REQUIRE(res.value == 100.0f);
854 
855         // max(10, 20, 30, 5) -> 30
856-        units = getUnit("max(10, 20, 30, 5)");
857-        res = UnitValue(&document, units, 0, units.size());
858-        REQUIRE(res.value.FLOAT == 30.0f);
859+        units = getUnit("max(10, 20, 30, 5)", &sym);
860+        res = UnitValue(m, units, 0, units.size());
861+        REQUIRE(res.value == 30.0f);
862     }
863 
864     SECTION("Logical Operators (Boolean)") {
865         // (10 > 5) AND (2 <= 2) -> true
866-        std::vector<Unit> units = getUnit("(10 > 5) and (2 <= 2)");
867-        auto res1 = UnitValue(&document, units, 0, units.size());
868+        std::vector<Unit> units = getUnit("(10 > 5) and (2 <= 2)", &sym);
869+        auto res1 = UnitValue(m, units, 0, units.size());
870         REQUIRE(res1.type == UnitType::BOOL);
871-        REQUIRE(res1.value.BOOL == true);
872+        REQUIRE(res1.value == true);
873 
874         // (10 < 5) OR (1 == 1) -> true
875-        units = getUnit("(10 < 5) or (1 == 1)");
876-        auto res2 = UnitValue(&document, units, 0, units.size());
877-        REQUIRE(res2.value.BOOL == true);
878+        units = getUnit("(10 < 5) or (1 == 1)", &sym);
879+        auto res2 = UnitValue(m, units, 0, units.size());
880+        REQUIRE(res2.value == true);
881     }
882 
883     SECTION("Deep Recursion Stress") {
884 	    // (((1 + 1) + 1) + 1) ...
885 	    std::string deep = "(1 + (1 + (1 + (1 + (1 + (1 + 1))))))";
886-	    std::vector<Unit> units = getUnit(deep);
887+	    std::vector<Unit> units = getUnit(deep, &sym);
888 
889 	    BENCHMARK("Deep Math Recursion") {
890-		return UnitValue(&document, units, 0, units.size());
891+		return UnitValue(m, units, 0, units.size());
892 	    };
893     }
894 }
895 
896-*/
897-
898 TEST_CASE("BENCH") {
899 	SECTION("BENCH") {
900 		SymbolTable sym{};
901@@ -100,7 +100,7 @@ TEST_CASE("BENCH") {
902 		m.push_back(sym.get("height"),{UnitType::NUMBER, 700.0f});
903 		m.push_back(sym.get("em"),{UnitType::NUMBER, 16.0f});
904 
905-		std::vector<Unit> units = getUnit("list(x 5 2 3 4)  set(at(x 0) 13)", &sym);
906+		std::vector<Unit> units = getUnit("list(x empty(10)) list(y 1 2 3 4 5) insert(x 0 y) list(a 67 78 89) insert(x 5 a) at(x 6)", &sym);
907 
908 		Snapshot s = m.save();
909