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