about summary refs log tree commit diff stats
path: root/src/css
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-06-25 13:23:27 +0200
committerbptato <nincsnevem662@gmail.com>2023-06-25 13:23:27 +0200
commit468d0823bf5c16e0a568858704c073a4896fcb8d (patch)
tree7f0460c0678613bfd90e6e0c3141c81e009040b2 /src/css
parentfd837c155acca689839c5ce184fd5059caa83770 (diff)
downloadchawan-468d0823bf5c16e0a568858704c073a4896fcb8d.tar.gz
css/values: use Result instead of exceptions
Diffstat (limited to 'src/css')
-rw-r--r--src/css/mediaquery.nim7
-rw-r--r--src/css/values.nim643
2 files changed, 342 insertions, 308 deletions
diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim
index 8d75be2a..3a594e6c 100644
--- a/src/css/mediaquery.nim
+++ b/src/css/mediaquery.nim
@@ -3,6 +3,7 @@ import tables
 
 import css/cssparser
 import css/values
+import utils/opt
 import utils/twtstr
 
 type
@@ -199,10 +200,10 @@ template expect_int_range(range: var Slice[int], ismin, ismax: bool) =
 
 template expect_length(length: var CSSLength) =
   let cval = parser.consume()
-  try:
-    length = cssLength(cval)
-  except CSSValueError:
+  let r = cssLength(cval)
+  if r.isErr:
     return nil
+  length = r.get
 
 template expect_length_range(range: var Slice[CSSLength], lengthaeq, lengthbeq:
     var bool, ismin, ismax: bool) =
diff --git a/src/css/values.nim b/src/css/values.nim
index dd479509..f92119fe 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -9,6 +9,7 @@ import css/selectorparser
 import img/bitmap
 import io/window
 import types/color
+import utils/opt
 import utils/twtstr
 
 export selectorparser.PseudoElem
@@ -216,8 +217,6 @@ type
     importantProperties: array[CSSOrigin, CSSComputedValueBuilders]
     preshints*: CSSComputedValues
 
-  CSSValueError* = object of ValueError
-
 const ShorthandNames = {
   "all": SHORTHAND_ALL,
   "margin": SHORTHAND_MARGIN,
@@ -467,11 +466,10 @@ const Units = {
   "vmax": UNIT_VMAX,
 }.toTable()
 
-func cssLength(val: float64, unit: string): CSSLength =
+func cssLength(val: float64, unit: string): Result[CSSLength, string] =
   if unit in Units:
-    CSSLength(num: val, unit: Units[unit])
-  else:
-    raise newException(CSSValueError, "Invalid unit")
+    return ok(CSSLength(num: val, unit: Units[unit]))
+  return err("Invalid unit")
 
 const CSSLengthAuto* = CSSLength(auto: true)
 
@@ -504,18 +502,18 @@ func skipWhitespace(vals: seq[CSSComponentValue], i: var int) =
       break
     inc i
 
-func cssColor*(val: CSSComponentValue): RGBAColor =
+func cssColor*(val: CSSComponentValue): Result[RGBAColor, string] =
   if val of CSSToken:
     let tok = CSSToken(val)
     case tok.tokenType
     of CSS_HASH_TOKEN:
       let c = parseHexColor(tok.value)
       if c.isSome:
-        return c.get
+        return ok(c.get)
     of CSS_IDENT_TOKEN:
       let s = tok.value
       if s in Colors:
-        return Colors[s]
+        return ok(Colors[s])
     else: discard
   elif val of CSSFunction:
     let f = CSSFunction(val)
@@ -525,7 +523,7 @@ func cssColor*(val: CSSComponentValue): RGBAColor =
       #TODO calc, percentages, etc (cssnumber function or something)
       if not slash and i >= f.value.len or i < f.value.len and
           f.value[i] != CSS_NUMBER_TOKEN:
-        raise newException(CSSValueError, "Invalid color")
+        return err("Invalid color")
     template next_value(first = false, slash = false) =
       inc i
       f.value.skipWhitespace(i)
@@ -536,10 +534,10 @@ func cssColor*(val: CSSComponentValue): RGBAColor =
           f.value.skipWhitespace(i)
           commaMode = true
         elif commaMode:
-          raise newException(CSSValueError, "Invalid color")
+          return err("Invalid color")
         elif slash:
           if f.value[i] != CSS_DELIM_TOKEN or CSSToken(f.value[i]).rvalue != Rune('/'):
-            raise newException(CSSValueError, "Invalid color")
+            return err("Invalid color")
           inc i
           f.value.skipWhitespace(i)
       check_err slash
@@ -557,21 +555,35 @@ func cssColor*(val: CSSComponentValue): RGBAColor =
         CSSToken(f.value[i]).nvalue
       else:
         1
-      return rgba(int(r), int(g), int(b), int(a * 255))
+      return ok(rgba(int(r), int(g), int(b), int(a * 255)))
     else: discard
-  raise newException(CSSValueError, "Invalid color")
+  return err("Invalid color")
 
 func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
 
+func getToken(cval: CSSComponentValue): CSSToken = (CSSToken)cval
+
+func cssIdent[T](map: static Table[string, T], cval: CSSComponentValue):
+    Result[T, string] =
+  if isToken(cval):
+    let tok = getToken(cval)
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      let val = tok.value
+      if val in map:
+        return ok(map[val])
+  when T is CSSDisplay:
+    eprint "invalid", cval
+  return err("Invalid ident")
+
 func cssLength*(val: CSSComponentValue, has_auto: static bool = true,
-    allow_negative: static bool = true): CSSLength =
+    allow_negative: static bool = true): Result[CSSLength, string] =
   block nofail:
     if val of CSSToken:
       let tok = CSSToken(val)
       case tok.tokenType
       of CSS_NUMBER_TOKEN:
         if tok.nvalue == 0:
-          return CSSLength(num: 0, unit: UNIT_PX)
+          return ok(CSSLength(num: 0, unit: UNIT_PX))
       of CSS_PERCENTAGE_TOKEN:
         when not allow_negative:
           if tok.nvalue < 0:
@@ -585,24 +597,24 @@ func cssLength*(val: CSSComponentValue, has_auto: static bool = true,
       of CSS_IDENT_TOKEN:
         when has_auto:
           if tok.value == "auto":
-            return CSSLengthAuto
+            return ok(CSSLengthAuto)
       else: discard
-  raise newException(CSSValueError, "Invalid length")
+  return err("Invalid length")
 
-func cssAbsoluteLength(val: CSSComponentValue): CSSLength =
+func cssAbsoluteLength(val: CSSComponentValue): Result[CSSLength, string] =
   if val of CSSToken:
     let tok = CSSToken(val)
     case tok.tokenType
     of CSS_NUMBER_TOKEN:
       if tok.nvalue == 0:
-        return CSSLength(num: 0, unit: UNIT_PX)
+        return ok(CSSLength(num: 0, unit: UNIT_PX))
     of CSS_DIMENSION_TOKEN:
       if tok.nvalue >= 0:
         return cssLength(tok.nvalue, tok.unit)
     else: discard
-  raise newException(CSSValueError, "Invalid length")
+  return err("Invalid length")
 
-func cssWordSpacing(cval: CSSComponentValue): CSSLength =
+func cssWordSpacing(cval: CSSComponentValue): Result[CSSLength, string] =
   if cval of CSSToken:
     let tok = CSSToken(cval)
     case tok.tokenType
@@ -610,11 +622,9 @@ func cssWordSpacing(cval: CSSComponentValue): CSSLength =
       return cssLength(tok.nvalue, tok.unit)
     of CSS_IDENT_TOKEN:
       if tok.value == "normal":
-        return CSSLengthAuto
+        return ok(CSSLengthAuto)
     else: discard
-  raise newException(CSSValueError, "Invalid word spacing")
-
-func getToken(cval: CSSComponentValue): CSSToken = (CSSToken)cval
+  return err("Invalid word spacing")
 
 func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
   if d.value.len > 0 and isToken(d.value[0]):
@@ -627,9 +637,9 @@ func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
       of "revert": return VALUE_REVERT
   return VALUE_NOGLOBAL
 
-func cssQuotes(d: CSSDeclaration): CSSQuotes =
+func cssQuotes(d: CSSDeclaration): Result[CSSQuotes, string] =
   template die =
-    raise newException(CSSValueError, "Invalid quotes")
+    return err("Invalid quotes")
   var res: CSSQuotes
   var sa = false
   var pair: tuple[s, e: string]
@@ -656,7 +666,7 @@ func cssQuotes(d: CSSDeclaration): CSSQuotes =
       else: die
   if sa:
     die
-  return res
+  return ok(res)
 
 func cssContent(d: CSSDeclaration): seq[CSSContent] =
   for cval in d.value:
@@ -674,62 +684,62 @@ func cssContent(d: CSSDeclaration): seq[CSSContent] =
         result.add(CSSContent(t: CONTENT_STRING, s: tok.value))
       else: return
 
-func cssDisplay(cval: CSSComponentValue): CSSDisplay =
-  if isToken(cval):
-    let tok = getToken(cval)
-    if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "block": return DISPLAY_BLOCK
-      of "inline": return DISPLAY_INLINE
-      of "list-item": return DISPLAY_LIST_ITEM
-      of "inline-block": return DISPLAY_INLINE_BLOCK
-      of "table": return DISPLAY_TABLE
-      of "table-row": return DISPLAY_TABLE_ROW
-      of "table-cell": return DISPLAY_TABLE_CELL
-      of "table-column": return DISPLAY_TABLE_COLUMN
-      of "table-column-group": return DISPLAY_TABLE_COLUMN_GROUP
-      of "inline-table": return DISPLAY_INLINE_TABLE
-      of "table-row-group": return DISPLAY_TABLE_ROW_GROUP
-      of "table-header-group": return DISPLAY_TABLE_HEADER_GROUP
-      of "table-footer-group": return DISPLAY_TABLE_FOOTER_GROUP
-      of "table-caption": return DISPLAY_TABLE_CAPTION
-      of "none": return DISPLAY_NONE
-  raise newException(CSSValueError, "Invalid display")
-
-func cssFontStyle(cval: CSSComponentValue): CSSFontStyle =
+
+func cssDisplay(cval: CSSComponentValue): Result[CSSDisplay, string] =
+  const DisplayMap = {
+    "block": DISPLAY_BLOCK,
+    "inline": DISPLAY_INLINE,
+    "list-item": DISPLAY_LIST_ITEM,
+    "inline-block": DISPLAY_INLINE_BLOCK,
+    "table": DISPLAY_TABLE,
+    "table-row": DISPLAY_TABLE_ROW,
+    "table-cell": DISPLAY_TABLE_CELL,
+    "table-column": DISPLAY_TABLE_COLUMN,
+    "table-column-group": DISPLAY_TABLE_COLUMN_GROUP,
+    "inline-table": DISPLAY_INLINE_TABLE,
+    "table-row-group": DISPLAY_TABLE_ROW_GROUP,
+    "table-header-group": DISPLAY_TABLE_HEADER_GROUP,
+    "table-footer-group": DISPLAY_TABLE_FOOTER_GROUP,
+    "table-caption": DISPLAY_TABLE_CAPTION,
+    "none": DISPLAY_NONE
+  }.toTable()
+  return cssIdent(DisplayMap, cval)
+
+func cssFontStyle(cval: CSSComponentValue): Result[CSSFontStyle, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "normal": return FONTSTYLE_NORMAL
-      of "italic": return FONTSTYLE_ITALIC
-      of "oblique": return FONTSTYLE_OBLIQUE
-  raise newException(CSSValueError, "Invalid font style")
+      of "normal": return ok(FONTSTYLE_NORMAL)
+      of "italic": return ok(FONTSTYLE_ITALIC)
+      of "oblique": return ok(FONTSTYLE_OBLIQUE)
+  return err("Invalid font style")
 
-func cssWhiteSpace(cval: CSSComponentValue): CSSWhitespace =
+func cssWhiteSpace(cval: CSSComponentValue): Result[CSSWhitespace, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "normal": return WHITESPACE_NORMAL
-      of "nowrap": return WHITESPACE_NOWRAP
-      of "pre": return WHITESPACE_PRE
-      of "pre-line": return WHITESPACE_PRE_LINE
-      of "pre-wrap": return WHITESPACE_PRE_WRAP
-  raise newException(CSSValueError, "Invalid whitespace")
-
-func cssFontWeight(cval: CSSComponentValue): int =
+      of "normal": return ok(WHITESPACE_NORMAL)
+      of "nowrap": return ok(WHITESPACE_NOWRAP)
+      of "pre": return ok(WHITESPACE_PRE)
+      of "pre-line": return ok(WHITESPACE_PRE_LINE)
+      of "pre-wrap": return ok(WHITESPACE_PRE_WRAP)
+  return err("Invalid whitespace")
+
+func cssFontWeight(cval: CSSComponentValue): Result[int, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "normal": return 400
-      of "bold": return 700
-      of "lighter": return 400
-      of "bolder": return 700
+      of "normal": return ok(400)
+      of "bold": return ok(700)
+      of "lighter": return ok(400)
+      of "bolder": return ok(700)
     elif tok.tokenType == CSS_NUMBER_TOKEN:
-      return int(tok.nvalue)
-  raise newException(CSSValueError, "Invalid font weight")
+      if tok.nvalue in 1f64..1000f64:
+        return ok(int(tok.nvalue))
+  return err("Invalid font weight")
 
 func cssTextDecoration(d: CSSDeclaration): set[CSSTextDecoration] =
   for cval in d.value:
@@ -743,53 +753,54 @@ func cssTextDecoration(d: CSSDeclaration): set[CSSTextDecoration] =
         of "line-through": result.incl(TEXT_DECORATION_LINE_THROUGH)
         of "blink": result.incl(TEXT_DECORATION_BLINK)
 
-func cssWordBreak(cval: CSSComponentValue): CSSWordBreak =
+func cssWordBreak(cval: CSSComponentValue): Result[CSSWordBreak, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "normal": return WORD_BREAK_NORMAL
-      of "break-all": return WORD_BREAK_BREAK_ALL
-      of "keep-all": return WORD_BREAK_KEEP_ALL
-  raise newException(CSSValueError, "Invalid text decoration")
-
-func cssListStyleType(cval: CSSComponentValue): CSSListStyleType =
-  if isToken(cval):
-    let tok = getToken(cval)
-    if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "none": return LIST_STYLE_TYPE_NONE
-      of "disc": return LIST_STYLE_TYPE_DISC
-      of "circle": return LIST_STYLE_TYPE_CIRCLE
-      of "square": return LIST_STYLE_TYPE_SQUARE
-      of "decimal": return LIST_STYLE_TYPE_DECIMAL
-      of "upper-roman": return LIST_STYLE_TYPE_UPPER_ROMAN
-      of "lower-roman": return LIST_STYLE_TYPE_LOWER_ROMAN
-      of "japanese-informal": return LIST_STYLE_TYPE_JAPANESE_INFORMAL
-  raise newException(CSSValueError, "Invalid list style")
-
-func cssVerticalAlign(cval: CSSComponentValue): CSSVerticalAlign =
+      of "normal": return ok(WORD_BREAK_NORMAL)
+      of "break-all": return ok(WORD_BREAK_BREAK_ALL)
+      of "keep-all": return ok(WORD_BREAK_KEEP_ALL)
+  return err("Invalid text decoration")
+
+func cssListStyleType(cval: CSSComponentValue):
+    Result[CSSListStyleType, string] =
+  const ListStyleMap = {
+    "none": LIST_STYLE_TYPE_NONE,
+    "disc": LIST_STYLE_TYPE_DISC,
+    "circle": LIST_STYLE_TYPE_CIRCLE,
+    "square": LIST_STYLE_TYPE_SQUARE,
+    "decimal": LIST_STYLE_TYPE_DECIMAL,
+    "upper-roman": LIST_STYLE_TYPE_UPPER_ROMAN,
+    "lower-roman": LIST_STYLE_TYPE_LOWER_ROMAN,
+    "japanese-informal": LIST_STYLE_TYPE_JAPANESE_INFORMAL
+  }.toTable()
+  return cssIdent(ListStyleMap, cval)
+
+func cssVerticalAlign(cval: CSSComponentValue):
+    Result[CSSVerticalAlign, string] =
   if isToken(cval):
+    var res = CSSVerticalAlign()
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "baseline": result.keyword = VERTICAL_ALIGN_BASELINE
-      of "sub": result.keyword = VERTICAL_ALIGN_SUB
-      of "super": result.keyword = VERTICAL_ALIGN_SUPER
-      of "text-top": result.keyword = VERTICAL_ALIGN_TEXT_BOTTOM
-      of "middle": result.keyword = VERTICAL_ALIGN_MIDDLE
-      of "top": result.keyword = VERTICAL_ALIGN_TOP
-      of "bottom": result.keyword = VERTICAL_ALIGN_BOTTOM
+      of "baseline": res.keyword = VERTICAL_ALIGN_BASELINE
+      of "sub": res.keyword = VERTICAL_ALIGN_SUB
+      of "super": res.keyword = VERTICAL_ALIGN_SUPER
+      of "text-top": res.keyword = VERTICAL_ALIGN_TEXT_BOTTOM
+      of "middle": res.keyword = VERTICAL_ALIGN_MIDDLE
+      of "top": res.keyword = VERTICAL_ALIGN_TOP
+      of "bottom": res.keyword = VERTICAL_ALIGN_BOTTOM
       else:
-        raise newException(CSSValueError, "Invalid vertical align")
-      return result
+        return err("Invalid vertical align")
+      return ok(res)
     else:
-      result.keyword = VERTICAL_ALIGN_BASELINE
-      result.length = cssLength(tok, has_auto = false)
-      return result
-  raise newException(CSSValueError, "Invalid vertical align")
+      res.keyword = VERTICAL_ALIGN_BASELINE
+      res.length = ?cssLength(tok, has_auto = false)
+      return ok(res)
+  return err("Invalid vertical align")
 
-func cssLineHeight(cval: CSSComponentValue): CSSLength =
+func cssLineHeight(cval: CSSComponentValue): Result[CSSLength, string] =
   if cval of CSSToken:
     let tok = CSSToken(cval)
     case tok.tokenType
@@ -797,76 +808,74 @@ func cssLineHeight(cval: CSSComponentValue): CSSLength =
       return cssLength(tok.nvalue * 100, "%")
     of CSS_IDENT_TOKEN:
       if tok.value == "normal":
-        return CSSLengthAuto
+        return ok(CSSLengthAuto)
     else:
       return cssLength(tok, has_auto = false)
-  raise newException(CSSValueError, "Invalid line height")
-
-func cssTextAlign(cval: CSSComponentValue): CSSTextAlign =
+  return err("Invalid line height")
+
+func cssTextAlign(cval: CSSComponentValue): Result[CSSTextAlign, string] =
+  const TextAlignMap = {
+    "start": TEXT_ALIGN_START,
+    "end": TEXT_ALIGN_END,
+    "left": TEXT_ALIGN_LEFT,
+    "right": TEXT_ALIGN_RIGHT,
+    "center": TEXT_ALIGN_CENTER,
+    "justify": TEXT_ALIGN_JUSTIFY,
+    "-cha-center": TEXT_ALIGN_CHA_CENTER
+  }.toTable()
+  return cssIdent(TextAlignMap, cval)
+
+func cssListStylePosition(cval: CSSComponentValue):
+    Result[CSSListStylePosition, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "start": return TEXT_ALIGN_START
-      of "end": return TEXT_ALIGN_END
-      of "left": return TEXT_ALIGN_LEFT
-      of "right": return TEXT_ALIGN_RIGHT
-      of "center": return TEXT_ALIGN_CENTER
-      of "justify": return TEXT_ALIGN_JUSTIFY
-      of "-cha-center": return TEXT_ALIGN_CHA_CENTER
-  raise newException(CSSValueError, "Invalid text align")
-
-func cssListStylePosition(cval: CSSComponentValue): CSSListStylePosition =
+      if tok.value == "inside":
+        return ok(LIST_STYLE_POSITION_INSIDE)
+      elif tok.value == "outside":
+        return ok(LIST_STYLE_POSITION_OUTSIDE)
+  return err("Invalid ident")
+
+func cssPosition(cval: CSSComponentValue): Result[CSSPosition, string] =
+  const PositionMap = {
+    "static": POSITION_STATIC,
+    "relative": POSITION_RELATIVE,
+    "absolute": POSITION_ABSOLUTE,
+    "fixed": POSITION_FIXED,
+    "sticky": POSITION_STICKY
+  }.toTable()
+  return cssIdent(PositionMap, cval)
+
+func cssCaptionSide(cval: CSSComponentValue): Result[CSSCaptionSide, string] =
+  const CaptionSideMap = {
+    "top": CAPTION_SIDE_TOP,
+    "bottom": CAPTION_SIDE_BOTTOM,
+    "left": CAPTION_SIDE_LEFT,
+    "right": CAPTION_SIDE_RIGHT,
+    "block-start": CAPTION_SIDE_BLOCK_START,
+    "block-end": CAPTION_SIDE_BLOCK_END,
+    "inline-start": CAPTION_SIDE_INLINE_START,
+    "inline-end": CAPTION_SIDE_INLINE_END
+  }.toTable()
+  return cssIdent(CaptionSideMap, cval)
+
+func cssBorderCollapse(cval: CSSComponentValue):
+    Result[CSSBorderCollapse, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "outside": return LIST_STYLE_POSITION_OUTSIDE
-      of "inside": return LIST_STYLE_POSITION_INSIDE
-  raise newException(CSSValueError, "Invalid list style position")
+      if tok.value == "collapse":
+        return ok(BORDER_COLLAPSE_COLLAPSE)
+      elif tok.value == "separate":
+        return ok(BORDER_COLLAPSE_SEPARATE)
+  return err("Invalid ident")
 
-func cssPosition(cval: CSSComponentValue): CSSPosition =
-  if isToken(cval):
-    let tok = getToken(cval)
-    if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "static": return POSITION_STATIC
-      of "relative": return POSITION_RELATIVE
-      of "absolute": return POSITION_ABSOLUTE
-      of "fixed": return POSITION_FIXED
-      of "sticky": return POSITION_STICKY
-      else: raise newException(CSSValueError, "Invalid list style position")
-  raise newException(CSSValueError, "Invalid list style position")
-
-func cssCaptionSide(cval: CSSComponentValue): CSSCaptionSide =
-  if isToken(cval):
-    let tok = getToken(cval)
-    if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "top": return CAPTION_SIDE_TOP
-      of "bottom": return CAPTION_SIDE_BOTTOM
-      of "left": return CAPTION_SIDE_LEFT
-      of "right": return CAPTION_SIDE_RIGHT
-      of "block-start": return CAPTION_SIDE_BLOCK_START
-      of "block-end": return CAPTION_SIDE_BLOCK_END
-      of "inline-start": return CAPTION_SIDE_INLINE_START
-      of "inline-end": return CAPTION_SIDE_INLINE_END
-  raise newException(CSSValueError, "Invalid caption side")
-
-func cssBorderCollapse(cval: CSSComponentValue): CSSBorderCollapse =
-  if isToken(cval):
-    let tok = getToken(cval)
-    if tok.tokenType == CSS_IDENT_TOKEN:
-      case tok.value
-      of "collapse": return BORDER_COLLAPSE_COLLAPSE
-      of "separate": return BORDER_COLLAPSE_SEPARATE
-  raise newException(CSSValueError, "Invalid border collapse")
-
-func cssCounterReset(d: CSSDeclaration): seq[CSSCounterReset] =
+func cssCounterReset(d: CSSDeclaration): Result[seq[CSSCounterReset], string] =
   template die =
-    raise newException(CSSValueError, "Invalid counter-reset")
+    return err("Invalid counter-reset")
   var r: CSSCounterReset
   var s = false
+  var res: seq[CSSCounterReset]
   for cval in d.value:
     if isToken(cval):
       let tok = getToken(cval)
@@ -881,22 +890,23 @@ func cssCounterReset(d: CSSDeclaration): seq[CSSCounterReset] =
         if not s:
           die
         r.num = int(tok.nvalue)
-        result.add(r)
+        res.add(r)
         s = false
       else:
         die
+  return ok(res)
 
-func cssMaxMinSize(cval: CSSComponentValue): CSSLength =
+func cssMaxMinSize(cval: CSSComponentValue): Result[CSSLength, string] =
   if isToken(cval):
     let tok = getToken(cval)
     case tok.tokenType
     of CSS_IDENT_TOKEN:
       if tok.value == "none":
-        return CSSLengthAuto
+        return ok(CSSLengthAuto)
     of CSS_NUMBER_TOKEN, CSS_DIMENSION_TOKEN:
       return cssLength(tok, allow_negative = false)
     else: discard
-  raise newException(CSSValueError, "Invalid min/max-size")
+  return err("Invalid min/max-size")
 
 #TODO should be URL (parsed with baseurl of document...)
 func cssURL(cval: CSSComponentValue): Option[string] =
@@ -919,104 +929,125 @@ func cssURL(cval: CSSComponentValue): Option[string] =
           break
 
 #TODO this should be bg-image, add gradient, etc etc
-func cssImage(cval: CSSComponentValue): CSSContent =
+func cssImage(cval: CSSComponentValue): Result[CSSContent, string] =
   if isToken(cval):
     #TODO bg-image only
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN and tok.value == "none":
-      return CSSContent(t: CONTENT_IMAGE, s: "")
+      return ok(CSSContent(t: CONTENT_IMAGE, s: ""))
   let url = cssURL(cval)
   if url.isSome:
-    return CSSContent(t: CONTENT_IMAGE, s: url.get)
-  raise newException(CSSValueError, "Invalid image")
+    return ok(CSSContent(t: CONTENT_IMAGE, s: url.get))
+  return err("Invalid image")
 
-func cssInteger(cval: CSSComponentValue, range: Slice[int]): int =
+func cssInteger(cval: CSSComponentValue, range: Slice[int]):
+    Result[int, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_NUMBER_TOKEN:
-      let i = int(tok.nvalue)
-      if float64(i) == tok.nvalue and i in range:
-        return i
-  raise newException(CSSValueError, "Invalid integer")
+      if tok.nvalue in float64(range.a)..float64(range.b):
+        return ok(int(tok.nvalue))
+  return err("Invalid integer")
 
-func cssFloat(cval: CSSComponentValue): CSSFloat =
+func cssFloat(cval: CSSComponentValue): Result[CSSFloat, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "none": return FLOAT_NONE
-      of "left": return FLOAT_LEFT
-      of "right": return FLOAT_RIGHT
-  raise newException(CSSValueError, "Invalid float")
+      of "none": return ok(FLOAT_NONE)
+      of "left": return ok(FLOAT_LEFT)
+      of "right": return ok(FLOAT_RIGHT)
+  return err("Invalid float")
 
-func cssVisibility(cval: CSSComponentValue): CSSVisibility =
+func cssVisibility(cval: CSSComponentValue): Result[CSSVisibility, string] =
   if isToken(cval):
     let tok = getToken(cval)
     if tok.tokenType == CSS_IDENT_TOKEN:
       case tok.value
-      of "visible": return VISIBILITY_VISIBLE
-      of "hidden": return VISIBILITY_HIDDEN
-      of "collapse": return VISIBILITY_COLLAPSE
-  raise newException(CSSValueError, "Invalid visibility")
+      of "visible": return ok(VISIBILITY_VISIBLE)
+      of "hidden": return ok(VISIBILITY_HIDDEN)
+      of "collapse": return ok(VISIBILITY_COLLAPSE)
+  return err("Invalid visibility")
 
-proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration, vtype: CSSValueType, ptype: CSSPropertyType) =
+proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
+    vtype: CSSValueType, ptype: CSSPropertyType): Err[string] =
   var i = 0
   d.value.skipWhitespace(i)
   if i >= d.value.len: 
-    raise newException(CSSValueError, "Empty value")
+    return err("Empty value")
   let cval = d.value[i]
   inc i
   case vtype
   of VALUE_COLOR:
-    val.color = cssColor(cval)
+    val.color = ?cssColor(cval)
   of VALUE_LENGTH:
     case ptype
     of PROPERTY_WORD_SPACING:
-      val.length = cssWordSpacing(cval)
+      val.length = ?cssWordSpacing(cval)
     of PROPERTY_LINE_HEIGHT:
-      val.length = cssLineHeight(cval)
+      val.length = ?cssLineHeight(cval)
     of PROPERTY_MAX_WIDTH, PROPERTY_MAX_HEIGHT, PROPERTY_MIN_WIDTH,
        PROPERTY_MIN_HEIGHT:
-      val.length = cssMaxMinSize(cval)
+      val.length = ?cssMaxMinSize(cval)
     of PROPERTY_PADDING_LEFT, PROPERTY_PADDING_RIGHT, PROPERTY_PADDING_TOP,
        PROPERTY_PADDING_BOTTOM:
-      val.length = cssLength(cval, has_auto = false)
+      val.length = ?cssLength(cval, has_auto = false)
     else:
-      val.length = cssLength(cval)
-  of VALUE_FONT_STYLE: val.fontstyle = cssFontStyle(cval)
-  of VALUE_DISPLAY: val.display = cssDisplay(cval)
-  of VALUE_CONTENT: val.content = cssContent(d)
-  of VALUE_WHITE_SPACE: val.whitespace = cssWhiteSpace(cval)
+      val.length = ?cssLength(cval)
+  of VALUE_FONT_STYLE:
+    val.fontstyle = ?cssFontStyle(cval)
+  of VALUE_DISPLAY:
+    val.display = ?cssDisplay(cval)
+  of VALUE_CONTENT:
+    val.content = cssContent(d)
+  of VALUE_WHITE_SPACE:
+    val.whitespace = ?cssWhiteSpace(cval)
   of VALUE_INTEGER:
     if ptype == PROPERTY_FONT_WEIGHT:
-      val.integer = cssFontWeight(cval)
+      val.integer = ?cssFontWeight(cval)
     elif ptype == PROPERTY_CHA_COLSPAN:
-      val.integer = cssInteger(cval, 1 .. 1000)
+      val.integer = ?cssInteger(cval, 1 .. 1000)
     elif ptype == PROPERTY_CHA_ROWSPAN:
-      val.integer = cssInteger(cval, 0 .. 65534)
-  of VALUE_TEXT_DECORATION: val.textdecoration = cssTextDecoration(d)
-  of VALUE_WORD_BREAK: val.wordbreak = cssWordBreak(cval)
-  of VALUE_LIST_STYLE_TYPE: val.liststyletype = cssListStyleType(cval)
-  of VALUE_VERTICAL_ALIGN: val.verticalalign = cssVerticalAlign(cval)
-  of VALUE_TEXT_ALIGN: val.textalign = cssTextAlign(cval)
-  of VALUE_LIST_STYLE_POSITION: val.liststyleposition = cssListStylePosition(cval)
-  of VALUE_POSITION: val.position = cssPosition(cval)
-  of VALUE_CAPTION_SIDE: val.captionside = cssCaptionSide(cval)
-  of VALUE_BORDER_COLLAPSE: val.bordercollapse = cssBorderCollapse(cval)
+      val.integer = ?cssInteger(cval, 0 .. 65534)
+  of VALUE_TEXT_DECORATION:
+    val.textdecoration = cssTextDecoration(d)
+  of VALUE_WORD_BREAK:
+    val.wordbreak = ?cssWordBreak(cval)
+  of VALUE_LIST_STYLE_TYPE:
+    val.liststyletype = ?cssListStyleType(cval)
+  of VALUE_VERTICAL_ALIGN:
+    val.verticalalign = ?cssVerticalAlign(cval)
+  of VALUE_TEXT_ALIGN:
+    val.textalign = ?cssTextAlign(cval)
+  of VALUE_LIST_STYLE_POSITION:
+    val.liststyleposition = ?cssListStylePosition(cval)
+  of VALUE_POSITION:
+    val.position = ?cssPosition(cval)
+  of VALUE_CAPTION_SIDE:
+    val.captionside = ?cssCaptionSide(cval)
+  of VALUE_BORDER_COLLAPSE:
+    val.bordercollapse = ?cssBorderCollapse(cval)
   of VALUE_LENGTH2:
-    val.length2.a = cssAbsoluteLength(cval)
+    val.length2.a = ?cssAbsoluteLength(cval)
     d.value.skipWhitespace(i)
     if i >= d.value.len:
       val.length2.b = val.length2.a
     else:
       let cval = d.value[i]
-      val.length2.b = cssAbsoluteLength(cval)
-  of VALUE_QUOTES: val.quotes = cssQuotes(d)
-  of VALUE_COUNTER_RESET: val.counterreset = cssCounterReset(d)
-  of VALUE_IMAGE: val.image = cssImage(cval)
-  of VALUE_FLOAT: val.float = cssFloat(cval)
-  of VALUE_VISIBILITY: val.visibility = cssVisibility(cval)
-  of VALUE_NONE: discard
+      val.length2.b = ?cssAbsoluteLength(cval)
+  of VALUE_QUOTES:
+    val.quotes = ?cssQuotes(d)
+  of VALUE_COUNTER_RESET:
+    val.counterreset = ?cssCounterReset(d)
+  of VALUE_IMAGE:
+    val.image = ?cssImage(cval)
+  of VALUE_FLOAT:
+    val.float = ?cssFloat(cval)
+  of VALUE_VISIBILITY:
+    val.visibility = ?cssVisibility(cval)
+  of VALUE_NONE:
+    discard
+  return ok()
 
 func getInitialColor(t: CSSPropertyType): RGBAColor =
   case t
@@ -1074,100 +1105,95 @@ let defaultTable = getInitialTable()
 template getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}:
   defaultTable[t]
 
-# WARNING: may raise an exception.
-func getComputedValue(d: CSSDeclaration, ptype: CSSPropertyType, vtype: CSSValueType): (CSSComputedValue, CSSGlobalValueType) =
+func getComputedValue(d: CSSDeclaration, ptype: CSSPropertyType,
+    vtype: CSSValueType):
+    Result[(CSSComputedValue, CSSGlobalValueType), string] =
+  let global = cssGlobal(d)
   var val = CSSComputedValue(t: ptype, v: vtype)
-  try:
-    val.getValueFromDecl(d, vtype, ptype)
-  except CSSValueError:
-    let global = cssGlobal(d)
+  let r = val.getValueFromDecl(d, vtype, ptype)
+  if r.isErr:
     if global != VALUE_NOGLOBAL:
-      return (val, cssGlobal(d))
+      return ok((val, global))
     else:
-      raise
-  return (val, cssGlobal(d))
+      return err(r.error)
+  return ok((val, global))
 
-func lengthShorthand(d: CSSDeclaration, props: array[4, CSSPropertyType]): seq[(CSSComputedValue, CSSGlobalValueType)] =
+func lengthShorthand(d: CSSDeclaration, props: array[4, CSSPropertyType]):
+    Result[seq[(CSSComputedValue, CSSGlobalValueType)], string] =
   var i = 0
   var cvals: seq[CSSComponentValue]
   while i < d.value.len:
     if d.value[i] != CSS_WHITESPACE_TOKEN:
       cvals.add(d.value[i])
     inc i
+  var res: seq[(CSSComputedValue, CSSGlobalValueType)]
   case cvals.len
   of 1: # top, bottom, left, right
-    try:
-      for ptype in props:
-        let vtype = valueType(ptype)
-        let val = CSSComputedValue(t: ptype, v: vtype)
-        val.getValueFromDecl(d, vtype, ptype)
-        result.add((val, cssGlobal(d)))
-    except CSSValueError: discard
+    for ptype in props:
+      let vtype = valueType(ptype)
+      let val = CSSComputedValue(t: ptype, v: vtype)
+      ?val.getValueFromDecl(d, vtype, ptype)
+      res.add((val, cssGlobal(d)))
   of 2: # top, bottom | left, right
-    try:
-      for i in 0 ..< props.len:
-        let ptype = props[i]
-        let vtype = valueType(ptype)
-        let val = CSSComputedValue(t: ptype, v: vtype)
-        val.length = cssLength(cvals[i mod 2])
-        result.add((val, cssGlobal(d)))
-    except CSSValueError:
-      discard
+    for i in 0 ..< props.len:
+      let ptype = props[i]
+      let vtype = valueType(ptype)
+      let val = CSSComputedValue(t: ptype, v: vtype)
+      val.length = ?cssLength(cvals[i mod 2])
+      res.add((val, cssGlobal(d)))
   of 3: # top | left, right | bottom
-    try:
-      for i in 0 ..< props.len:
-        let ptype = props[i]
-        let vtype = valueType(ptype)
-        let val = CSSComputedValue(t: ptype, v: vtype)
-        let j = if i == 0:
-          0 # top
-        elif i == 3:
-          2 # bottom
-        else:
-          1 # left, right
-        val.length = cssLength(cvals[j])
-        result.add((val, cssGlobal(d)))
-    except CSSValueError:
-      discard
+    for i in 0 ..< props.len:
+      let ptype = props[i]
+      let vtype = valueType(ptype)
+      let val = CSSComputedValue(t: ptype, v: vtype)
+      let j = if i == 0:
+        0 # top
+      elif i == 3:
+        2 # bottom
+      else:
+        1 # left, right
+      val.length = ?cssLength(cvals[j])
+      res.add((val, cssGlobal(d)))
   of 4: # top | right | bottom | left
-    try:
-      for i in 0 ..< props.len:
-        let ptype = props[i]
-        let vtype = valueType(ptype)
-        let val = CSSComputedValue(t: ptype, v: vtype)
-        val.length = cssLength(cvals[i])
-        result.add((val, cssGlobal(d)))
-    except CSSValueError:
-      discard
+    for i in 0 ..< props.len:
+      let ptype = props[i]
+      let vtype = valueType(ptype)
+      let val = CSSComputedValue(t: ptype, v: vtype)
+      val.length = ?cssLength(cvals[i])
+      res.add((val, cssGlobal(d)))
   else: discard
+  return ok(res)
+
+const PropertyMarginSpec = [
+  PROPERTY_MARGIN_TOP, PROPERTY_MARGIN_RIGHT, PROPERTY_MARGIN_BOTTOM,
+  PROPERTY_MARGIN_LEFT
+]
+
+const PropertyPaddingSpec = [
+  PROPERTY_PADDING_TOP, PROPERTY_PADDING_RIGHT, PROPERTY_PADDING_BOTTOM,
+  PROPERTY_PADDING_LEFT
+]
 
-proc getComputedValues(d: CSSDeclaration): seq[(CSSComputedValue, CSSGlobalValueType)] =
+proc getComputedValues0(d: CSSDeclaration):
+    Result[seq[(CSSComputedValue, CSSGlobalValueType)], string] =
   let name = d.name
+  var res: seq[(CSSComputedValue, CSSGlobalValueType)]
   case shorthandType(name)
   of SHORTHAND_NONE:
     let ptype = propertyType(name)
     let vtype = valueType(ptype)
-    try:
-      result.add(getComputedValue(d, ptype, vtype))
-    except CSSValueError:
-      discard
+    res.add(?getComputedValue(d, ptype, vtype))
   of SHORTHAND_ALL:
     let global = cssGlobal(d)
     if global != VALUE_NOGLOBAL:
       for ptype in CSSPropertyType:
         let vtype = valueType(ptype)
         let val = CSSComputedValue(t: ptype, v: vtype)
-        result.add((val, global))
+        res.add((val, global))
   of SHORTHAND_MARGIN:
-    result.add(lengthShorthand(d, [PROPERTY_MARGIN_TOP,
-                                   PROPERTY_MARGIN_RIGHT,
-                                   PROPERTY_MARGIN_BOTTOM,
-                                   PROPERTY_MARGIN_LEFT]))
+    res.add(?lengthShorthand(d, PropertyMarginSpec))
   of SHORTHAND_PADDING:
-    result.add(lengthShorthand(d, [PROPERTY_PADDING_TOP,
-                                   PROPERTY_PADDING_RIGHT,
-                                   PROPERTY_PADDING_BOTTOM,
-                                   PROPERTY_PADDING_LEFT]))
+    res.add(?lengthShorthand(d, PropertyPaddingSpec))
   of SHORTHAND_BACKGROUND:
     let global = cssGlobal(d)
     let bgcolorptype = PROPERTY_BACKGROUND_COLOR
@@ -1178,19 +1204,17 @@ proc getComputedValues(d: CSSDeclaration): seq[(CSSComputedValue, CSSGlobalValue
       for tok in d.value:
         if tok == CSS_WHITESPACE_TOKEN:
           continue
-        try:
-          let img = cssImage(tok)
-          bgimageval.image = img
-          result.add((bgimageval, global))
-        except CSSValueError:
-          try:
-            let color = cssColor(tok)
-            bgcolorval.color = color
-            result.add((bgcolorval, global))
-          except CSSValueError:
-            discard
+        let r = cssImage(tok)
+        if r.isOk:
+          bgimageval.image = r.get
+          res.add((bgimageval, global))
+        else:
+          let r = cssColor(tok)
+          if r.isOk:
+            bgcolorval.color = r.get
+            res.add((bgcolorval, global))
     else:
-      result.add((bgcolorval, global))
+      res.add((bgcolorval, global))
   of SHORTHAND_LIST_STYLE:
     let global = cssGlobal(d)
     let positionptype = PROPERTY_LIST_STYLE_POSITION
@@ -1199,19 +1223,28 @@ proc getComputedValues(d: CSSDeclaration): seq[(CSSComputedValue, CSSGlobalValue
     let typeval = CSSComputedValue(t: typeptype, v: valueType(typeptype))
     if global == VALUE_NOGLOBAL:
       for tok in d.value:
-        try:
-          positionval.liststyleposition = cssListStylePosition(tok)
-          result.add((positionval, global))
-        except CSSValueError:
-          try:
-            typeval.liststyletype = cssListStyleType(tok)
-            result.add((typeval, global))
-          except CSSValueError:
+        let r = cssListStylePosition(tok)
+        if r.isOk:
+          positionval.liststyleposition = r.get
+          res.add((positionval, global))
+        else:
+          let r = cssListStyleType(tok)
+          if r.isOk:
+            typeval.liststyletype = r.get
+            res.add((typeval, global))
+          else:
             #TODO list-style-image
             discard
+  return ok(res)
+
+proc getComputedValues(d: CSSDeclaration):
+    seq[(CSSComputedValue, CSSGlobalValueType)] =
+  return getComputedValues0(d).get(@[])
 
 proc newComputedValueBuilder*(parent: CSSComputedValues): CSSComputedValuesBuilder =
-  result.parent = parent
+  return CSSComputedValuesBuilder(
+    parent: parent
+  )
 
 proc addValuesImportant*(builder: var CSSComputedValuesBuilder, decls: seq[CSSDeclaration], origin: CSSOrigin) =
   for decl in decls: