import std/options import std/unicode import js/domexception import types/opt import utils/twtstr type CSSTokenType* = enum cttIdent, cttFunction, cttAtKeyword, cttHash, cttString, cttBadString, cttUrl, cttBadUrl, cttDelim, cttNumber, cttPercentage, cttDimension, cttWhitespace, cttCdo, cttCdc, cttColon, cttSemicolon, cttComma, cttRbracket, cttLbracket, cttLparen, cttRparen, cttLbrace, cttRbrace CSSTokenizerState = object at: int buf: string curr: char CSSParseState = object tokens: seq[CSSParsedItem] at: int tflaga = enum tflagaUnrestricted, tflagaId tflagb* = enum tflagbInteger, tflagbNumber CSSParsedItem* = ref object of RootObj CSSComponentValue* = ref object of CSSParsedItem CSSToken* = ref object of CSSComponentValue case tokenType*: CSSTokenType of cttIdent, cttFunction, cttAtKeyword, cttHash, cttString, cttUrl: value*: string tflaga*: tflaga of cttDelim: cvalue*: char of cttNumber, cttPercentage, cttDimension: nvalue*: float64 tflagb*: tflagb unit*: string else: discard CSSRule* = ref object of CSSParsedItem prelude*: seq[CSSComponentValue] oblock*: CSSSimpleBlock CSSAtRule* = ref object of CSSRule name*: string CSSQualifiedRule* = ref object of CSSRule CSSDeclaration* = ref object of CSSComponentValue name*: string value*: seq[CSSComponentValue] important*: bool CSSFunction* = ref object of CSSComponentValue name*: string value*: seq[CSSComponentValue] CSSSimpleBlock* = ref object of CSSComponentValue token*: CSSToken value*: seq[CSSComponentValue] CSSRawStylesheet* = object value*: seq[CSSRule] CSSAnB* = tuple[A, B: int] # For debugging proc `$`*(c: CSSParsedItem): string = if c of CSSToken: let c = CSSToken(c) case c.tokenType: of cttFunction, cttAtKeyword: result &= $c.tokenType & c.value & '\n' of cttUrl: result &= "url(" & c.value & ")" of cttHash: result &= '#' & c.value of cttIdent: result &= c.value of cttString: result &= ("\"" & c.value & "\"") of cttDelim: if c.cvalue != char(128): result &= c.cvalue else: result &= "" of cttDimension: case c.tflagb of tflagbNumber: result &= $c.nvalue & c.unit of tflagbInteger: result &= $int64(c.nvalue) & c.unit of cttNumber: result &= $c.nvalue & c.unit of cttPercentage: result &= $c.nvalue & "%" of cttColon: result &= ":" of cttWhitespace: result &= " " of cttSemicolon: result &= ";\n" of cttComma: result &= "," else: result &= $c.tokenType & '\n' elif c of CSSDeclaration: let decl = CSSDeclaration(c) result &= decl.name result &= ": " for s in decl.value: result &= $s if decl.important: result &= " !important" result &= ";\n" elif c of CSSFunction: result &= CSSFunction(c).name & "(" for s in CSSFunction(c).value: result &= $s result &= ")" elif c of CSSSimpleBlock: case CSSSimpleBlock(c).token.tokenType of cttLbrace: result &= "{\n" of cttLparen: result &= "(" of cttLbracket: result &= "[" else: discard for s in CSSSimpleBlock(c).value: result &= $s case CSSSimpleBlock(c).token.tokenType of cttLbrace: result &= "\n}" of cttLparen: result &= ")" of cttLbracket: result &= "]" else: discard elif c of CSSRule: if c of CSSAtRule: result &= CSSAtRule(c).name & " " result &= $CSSRule(c).prelude & "\n" result &= $CSSRule(c).oblock func `==`*(a: CSSParsedItem, b: CSSTokenType): bool = return a of CSSToken and CSSToken(a).tokenType == b const IdentStart = AsciiAlpha + NonAscii + {'_'} const Ident = IdentStart + AsciiDigit + {'-'} proc consume(state: var CSSTokenizerState): char = state.curr = state.buf[state.at] inc state.at return state.curr proc consumeRChar(state: var CSSTokenizerState): char = var r: Rune fastRuneAt(state.buf, state.at, r) if int32(r) < 0x80: return char(r) return char(128) proc reconsume(state: var CSSTokenizerState) = dec state.at func peek(state: CSSTokenizerState; i: int = 0): char = return state.buf[state.at + i] proc has(state: var CSSTokenizerState; i: int = 0): bool = return state.at + i < state.buf.len proc isValidEscape(a, b: char): bool = return a == '\\' and b != '\n' proc isValidEscape(state: var CSSTokenizerState): bool = return state.has() and isValidEscape(state.curr, state.peek()) # current + next + next(1) proc startsWithIdentSequence(state: var CSSTokenizerState): bool = case state.curr of '-': return state.has() and state.peek() in IdentStart + {'-'} or state.has(1) and state.isValidEscape() of IdentStart: return true of '\\': return state.isValidEscape() else: return false # next, next(1), next(2) proc next3startsWithIdentSequence(state: var CSSTokenizerState): bool = if not state.has(): return false case state.peek() of '-': return state.has(1) and state.peek(1) in IdentStart + {'-'} or state.has(2) and isValidEscape(state.peek(1), state.peek(2)) of IdentStart: return true of '\\': return state.has(1) and isValidEscape(state.peek(), state.peek(1)) else: return false proc startsWithNumber(state: var CSSTokenizerState): bool = if state.has(): case state.peek() of '+', '-': if state.has(1): if state.peek(1) in AsciiDigit: return true elif state.peek(1) == '.': if state.has(2) and state.peek(2) in AsciiDigit: return true of '.': if state.has(1) and state.peek(1) in AsciiDigit: return true elif state.peek() in AsciiDigit: return true else: return false return false proc skipWhitespace(state: var CSSTokenizerState) = while state.has() and state.peek() in AsciiWhitespace: discard state.consume() proc consumeEscape(state: var CSSTokenizerState): string = if not state.has(): return $Rune(0xFFFD) let c = state.consume() if c in AsciiHexDigit: var num = hexValue(c) var i = 0 while i <= 5 and state.has(): let c = state.consume() if hexValue(c) == -1: state.reconsume() break num *= 0x10 num += hexValue(c) inc i if state.has() and state.peek() in AsciiWhitespace: discard state.consume() if num == 0 or num > 0x10FFFF or num in 0xD800..0xDFFF: return $Rune(0xFFFD) else: return $Rune(num) else: return $c #NOTE this assumes the caller doesn't care about non-ascii proc consumeString(state: var CSSTokenizerState): CSSToken = var s = "" let ending = state.curr while state.has(): let c = state.consume() case c of '\n': state.reconsume() return CSSToken(tokenType: cttBadString) of '\\': if not state.has(): continue elif state.peek() == '\n': discard state.consume() else: s &= consumeEscape(state) elif c == ending: break else: s &= c return CSSToken(tokenType: cttString, value: s) proc consumeIdentSequence(state: var CSSTokenizerState): string = var s = "" while state.has(): let c = state.consume() if state.isValidEscape(): s &= state.consumeEscape() elif c in Ident: s &= c else: state.reconsume() return s return s proc consumeNumber(state: var CSSTokenizerState): (tflagb, float64) = var t = tflagbInteger var repr = "" if state.has() and state.peek() in {'+', '-'}: repr &= state.consume() while state.has() and state.peek() in AsciiDigit: repr &= state.consume() if state.has(1) and state.peek() == '.' and state.peek(1) in AsciiDigit: repr &= state.consume() repr &= state.consume() t = tflagbNumber while state.has() and state.peek() in AsciiDigit: repr &= state.consume() if state.has(1) and state.peek() in {'E', 'e'} and state.peek(1) in AsciiDigit or state.has(2) and state.peek() in {'E', 'e'} and state.peek(1) in {'-', '+'} and state.peek(2) in AsciiDigit: repr &= state.consume() if state.peek() in {'-', '+'}: repr &= state.consume() repr &= state.consume() else: repr &= state.consume() t = tflagbNumber while state.has() and state.peek() in AsciiDigit: repr &= state.consume() let val = parseFloat64(repr) return (t, val) proc consumeNumericToken(state: var CSSTokenizerState): CSSToken = let (t, val) = state.consumeNumber() if state.next3startsWithIdentSequence(): result = CSSToken(tokenType: cttDimension, nvalue: val, tflagb: t) result.unit = state.consumeIdentSequence() elif state.has() and state.peek() == '%': discard state.consume() result = CSSToken(tokenType: cttPercentage, nvalue: val) else: result = CSSToken(tokenType: cttNumber, nvalue: val, tflagb: t) proc consumeBadURL(state: var CSSTokenizerState) = while state.has(): let c = state.consume() case c of ')': return elif state.isValidEscape(): discard state.consumeEscape() else: discard const NonPrintable = { char(0x00)..char(0x08), char(0x0B), char(0x0E)..char(0x1F), char(0x7F) } proc consumeURL(state: var CSSTokenizerState): CSSToken = result = CSSToken(tokenType: cttUrl) state.skipWhitespace() while state.has(): let c = state.consume() case c of ')': return result of '"', '\'', '(', NonPrintable: state.consumeBadURL() return CSSToken(tokenType: cttBadUrl) of AsciiWhitespace: state.skipWhitespace() if not state.has(): return result if state.peek() == ')': discard state.consume() return result state.consumeBadURL() return CSSToken(tokenType: cttBadUrl) of '\\': state.reconsume() if state.isValidEscape(): result.value &= state.consumeEscape() else: state.consumeBadURL() return CSSToken(tokenType: cttBadUrl) else: result.value &= c proc consumeIdentLikeToken(state: var CSSTokenizerState): CSSToken = let s = state.consumeIdentSequence() if s.equalsIgnoreCase("url") and state.has() and state.peek() == '(': discard state.consume() while state.has(1) and state.peek() in AsciiWhitespace and state.peek(1) in AsciiWhitespace: discard state.consume() if state.has() and state.peek() in {'"', '\''} or state.has(1) and state.peek() in {'"', '\''} + AsciiWhitespace and state.peek(1) in {'"', '\''}: return CSSToken(tokenType: cttFunction, value: s) else: return state.consumeURL() elif state.has() and state.peek() == '(': discard state.consume() return CSSToken(tokenType: cttFunction, value: s) return CSSToken(tokenType: cttIdent, value: s) proc consumeComments(state: var CSSTokenizerState) = if state.has(1) and state.peek() == '/' and state.peek(1) == '*': discard state.consume() discard state.consume() while state.has() and not (state.has(1) and state.peek() == '*' and state.peek(1) == '/'): discard state.consume() if state.has(1): discard state.consume() if state.has(): discard state.consume() proc consumeToken(state: var CSSTokenizerState): CSSToken = state.consumeComments() if not state.has(): return let c = state.consume() case c of AsciiWhitespace: state.skipWhitespace() return CSSToken(tokenType: cttWhitespace) of '"', '\'': return consumeString(state) of '#': if state.has() and state.peek() in Ident or state.isValidEscape(): result = CSSToken(tokenType: cttHash) if state.startsWithIdentSequence(): result.tflaga = tflagaId result.value = consumeIdentSequence(state) else: state.reconsume() return CSSToken(tokenType: cttDelim, cvalue: state.consumeRChar()) of '(': return CSSToken(tokenType: cttLparen) of ')': return CSSToken(tokenType: cttRparen) of '{': return CSSToken(tokenType: cttLbrace) of '}': return CSSToken(tokenType: cttRbrace) of '+': if state.startsWithNumber(): state.reconsume() return state.consumeNumericToken() else: return CSSToken(tokenType: cttDelim, cvalue: c) of ',': return CSSToken(tokenType: cttComma) of '-': if state.startsWithNumber(): state.reconsume() return state.consumeNumericToken() else: if state.has(1) and state.peek() == '-' and state.peek(1) == '>': discard state.consume() discard state.consume() return CSSToken(tokenType: cttCdc) elif state.startsWithIdentSequence(): state.reconsume() return state.consumeIdentLikeToken() else: return CSSToken(tokenType: cttDelim, cvalue: c) of '.': if state.startsWithNumber(): state.reconsume() return state.consumeNumericToken() else: return CSSToken(tokenType: cttDelim, cvalue: c) of ':': return CSSToken(tokenType: cttColon) of ';': return CSSToken(tokenType: cttSemicolon) of '<': if state.has(2) and state.peek() == '!' and state.peek(1) == '-' and state.peek(2) == '-': discard state.consume() discard state.consume() discard state.consume() return CSSToken(tokenType: cttCdo) else: return CSSToken(tokenType: cttDelim, cvalue: c) of '@': if state.next3startsWithIdentSequence(): let name = state.consumeIdentSequence() return CSSToken(tokenType: cttAtKeyword, value: name) else: return CSSToken(tokenType: cttDelim, cvalue: c) of '[': return CSSToken(tokenType: cttLbracket) of '\\': if state.isValidEscape(): state.reconsume() return state.consumeIdentLikeToken() else: return CSSToken(tokenType: cttDelim, cvalue: c) of ']': return CSSToken(tokenType: cttRbracket) of AsciiDigit: state.reconsume() return state.consumeNumericToken() of IdentStart: state.reconsume() return state.consumeIdentLikeToken() else: state.reconsume() return CSSToken(tokenType: cttDelim, cvalue: state.consumeRChar()) proc tokenizeCSS*(ibuf: string): seq[CSSParsedItem] = var state = CSSTokenizerState(buf: ibuf) while state.has(): let tok = state.consumeToken() if tok != nil: result.add(tok) proc consume(state: var CSSParseState): CSSParsedItem = result = state.tokens[state.at] inc state.at proc reconsume(state: var CSSParseState) = dec state.at func has(state: CSSParseState; i: int = 0): bool = return state.at + i < state.tokens.len func peek(state: CSSParseState): CSSParsedItem = return state.tokens[state.at] proc skipWhitespace(state: var CSSParseState) = while state.has() and state.peek() == cttWhitespace: discard state.consume() proc consumeComponentValue(state: var CSSParseState): CSSComponentValue proc consumeSimpleBlock(state: var CSSParseState): CSSSimpleBlock = state.reconsume() let t = CSSToken(state.consume()) var ending: CSSTokenType case t.tokenType of cttLbrace: ending = cttRbrace of cttLparen: ending = cttRparen of cttLbracket: ending = cttRbracket else: doAssert false result = CSSSimpleBlock(token: t) while state.at < state.tokens.len: let t = state.consume() if t == ending: return result else: if t == cttLbrace or t == cttLbracket or t == cttLparen: result.value.add(state.consumeSimpleBlock()) else: state.reconsume() result.value.add(state.consumeComponentValue()) return result proc consumeFunction(state: var CSSParseState): CSSFunction = let t = (CSSToken)state.consume() result = CSSFunction(name: t.value) while state.at < state.tokens.len: let t = state.consume() if t == cttRparen: return result else: state.reconsume() result.value.add(state.consumeComponentValue()) proc consumeComponentValue(state: var CSSParseState): CSSComponentValue = let t = state.consume() if t == cttLbrace or t == cttLbracket or t == cttLparen: return state.consumeSimpleBlock() elif t == cttFunction: state.reconsume() return state.consumeFunction() return CSSComponentValue(t) proc consumeQualifiedRule(state: var CSSParseState): Option[CSSQualifiedRule] = var r = CSSQualifiedRule() while state.has(): let t = state.consume() if t of CSSSimpleBlock and CSSSimpleBlock(t).token == cttLbrace: r.oblock = CSSSimpleBlock(t) return some(r) elif t == cttLbrace: r.oblock = state.consumeSimpleBlock() return some(r) else: state.reconsume() r.prelude.add(state.consumeComponentValue()) return none(CSSQualifiedRule) proc consumeAtRule(state: var CSSParseState): CSSAtRule = let t = CSSToken(state.consume()) result = CSSAtRule(name: t.value) while state.at < state.tokens.len: let t = state.consume() if t of CSSSimpleBlock: result.oblock = CSSSimpleBlock(t) elif t == cttSemicolon: return result elif t == cttLbrace: result.oblock = state.consumeSimpleBlock() return result else: state.reconsume() result.prelude.add(state.consumeComponentValue()) proc consumeDeclaration(state: var CSSParseState): Option[CSSDeclaration] = let t = CSSToken(state.consume()) var decl = CSSDeclaration(name: t.value) state.skipWhitespace() if not state.has() or state.peek() != cttColon: return none(CSSDeclaration) discard state.consume() state.skipWhitespace() while state.has(): decl.value.add(state.consumeComponentValue()) var i = decl.value.len - 1 var j = 2 var k = 0 var l = 0 while i >= 0 and j > 0: if decl.value[i] != cttWhitespace: dec j if decl.value[i] == cttIdent and k == 0: if CSSToken(decl.value[i]).value.equalsIgnoreCase("important"): inc k l = i elif k == 1 and decl.value[i] == cttDelim: if CSSToken(decl.value[i]).cvalue == '!': decl.important = true decl.value.delete(l) decl.value.delete(i) break dec i while decl.value.len > 0 and decl.value[^1] == cttWhitespace: decl.value.setLen(decl.value.len - 1) return some(decl) #> Note: Despite the name, this actually parses a mixed list of declarations #> and at-rules, as CSS 2.1 does for @page. Unexpected at-rules (which could be #> all of them, in a given context) are invalid and should be ignored by the #> consumer. #So we have two versions, one with at rules and one without. proc consumeDeclarations(state: var CSSParseState): seq[CSSParsedItem] = while state.has(): let t = state.consume() if t == cttWhitespace or t == cttSemicolon: continue elif t == cttAtKeyword: state.reconsume() result.add(state.consumeAtRule()) elif t == cttIdent: var tempList: seq[CSSParsedItem] tempList.add(CSSToken(t)) while state.has() and state.peek() != cttSemicolon: tempList.add(state.consumeComponentValue()) var tempState = CSSParseState(at: 0, tokens: tempList) let decl = tempState.consumeDeclaration() if decl.isSome: result.add(decl.get) else: state.reconsume() if state.peek() != cttSemicolon: discard state.consumeComponentValue() proc consumeDeclarations2(state: var CSSParseState): seq[CSSDeclaration] = while state.has(): let t = state.consume() if t == cttWhitespace or t == cttSemicolon: continue elif t == cttAtKeyword: state.reconsume() discard state.consumeAtRule() elif t == cttIdent: var tempList: seq[CSSParsedItem] let tok = CSSToken(t) tempList.add(tok) while state.has() and state.peek() != cttSemicolon: tempList.add(state.consumeComponentValue()) var tempState = CSSParseState(at: 0, tokens: tempList) let decl = tempState.consumeDeclaration() if decl.isSome: result.add(decl.get) else: state.reconsume() if state.peek() != cttSemicolon: discard state.consumeComponentValue() proc consumeListOfRules(state: var CSSParseState; topLevel = false): seq[CSSRule] = while state.at < state.tokens.len: let t = state.consume() if t == cttWhitespace: continue elif t == cttCdo or t == cttCdc: if topLevel: continue else: state.reconsume() let q = state.consumeQualifiedRule() if q.isSome: result.add(q.get) elif t == cttAtKeyword: state.reconsume() result.add(state.consumeAtRule()) else: state.reconsume() let q = state.consumeQualifiedRule() if q.isSome: result.add(q.get) proc parseStylesheet(state: var CSSParseState): CSSRawStylesheet = return CSSRawStylesheet(value: state.consumeListOfRules(true)) proc parseStylesheet*(ibuf: string): CSSRawStylesheet = var state = CSSParseState() state.tokens = tokenizeCSS(ibuf) return state.parseStylesheet() proc parseListOfRules(state: var CSSParseState): seq[CSSRule] = return state.consumeListOfRules() proc parseListOfRules*(cvals: seq[CSSComponentValue]): seq[CSSRule] = var state = CSSParseState() state.tokens = cast[seq[CSSParsedItem]](cvals) return state.parseListOfRules() proc parseRule(state: var CSSParseState): DOMResult[CSSRule] = state.skipWhitespace() if not state.has(): return errDOMException("Unexpected EOF", "SyntaxError") var res = if state.peek() == cttAtKeyword: state.consumeAtRule() else: let q = state.consumeQualifiedRule() if q.isNone: return errDOMException("No qualified rule found!", "SyntaxError") q.get state.skipWhitespace() if state.has(): return errDOMException("EOF not reached", "SyntaxError") return ok(res) proc parseRule*(ibuf: string): DOMResult[CSSRule] = var state = CSSParseState() state.tokens = tokenizeCSS(ibuf) return state.parseRule() proc parseDeclaration(state: var CSSParseState): DOMResult[CSSDeclaration] = state.skipWhitespace() if not state.has() or state.peek() != cttIdent: return errDOMException("No ident token found", "SyntaxError") let d = state.consumeDeclaration() if d.isSome: return ok(d.get) return errDOMException("No declaration found", "SyntaxError") proc parseDeclaration*(ibuf: string): DOMResult[CSSDeclaration] = var state = CSSParseState() state.tokens = tokenizeCSS(ibuf) return state.parseDeclaration() proc parseDeclarations(state: var CSSParseState): seq[CSSParsedItem] = return state.consumeDeclarations() proc parseDeclarations*(cvals: seq[CSSComponentValue]): seq[CSSParsedItem] = var state = CSSParseState(tokens: cast[seq[CSSParsedItem]](cvals)) return state.consumeDeclarations() proc parseDeclarations*(ibuf: string): seq[CSSParsedItem] = var state = CSSParseState() state.tokens = tokenizeCSS(ibuf) return state.parseDeclarations() proc parseDeclarations2(state: var CSSParseState): seq[CSSDeclaration] = return state.consumeDeclarations2() proc parseDeclarations2*(cvals: seq[CSSComponentValue]): seq[CSSDeclaration] = var state = CSSParseState(tokens: cast[seq[CSSParsedItem]](cvals)) return state.consumeDeclarations2() proc parseDeclarations2*(ibuf: string): seq[CSSDeclaration] = var state = CSSParseState(tokens: tokenizeCSS(ibuf)) return state.parseDeclarations2() proc parseComponentValue(state: var CSSParseState): DOMResult[CSSComponentValue] = state.skipWhitespace() if not state.has(): return errDOMException("Unexpected EOF", "SyntaxError") let res = state.consumeComponentValue() state.skipWhitespace() if state.has(): return errDOMException("EOF not reached", "SyntaxError") return ok(res) proc parseComponentValue*(ibuf: string): DOMResult[CSSComponentValue] = var state = CSSParseState(tokens: tokenizeCSS(ibuf)) return state.parseComponentValue() proc parseComponentValues(state: var CSSParseState): seq[CSSComponentValue] = result = @[] while state.has(): result.add(state.consumeComponentValue()) proc parseComponentValues*(ibuf: string): seq[CSSComponentValue] = var state = CSSParseState(tokens: tokenizeCSS(ibuf)) return state.parseComponentValues() proc parseCommaSepComponentValues(state: var CSSParseState): seq[seq[CSSComponentValue]] = result = @[] if state.has(): result.add(newSeq[CSSComponentValue]()) while state.has(): let cvl = state.consumeComponentValue() if cvl != cttComma: result[^1].add(cvl) else: result.add(newSeq[CSSComponentValue]()) proc parseCommaSepComponentValues*(cvals: seq[CSSComponentValue]): seq[seq[CSSComponentValue]] = var state = CSSParseState(tokens: cast[seq[CSSParsedItem]](cvals)) return state.parseCommaSepComponentValues() proc parseAnB*(state: var CSSParseState): Option[CSSAnB] = template is_eof: bool = not state.has() or not (state.peek() of CSSToken) template fail_eof = if is_eof: return none(CSSAnB) template get_plus: bool = let tok = state.peek() if tok == cttDelim and CSSToken(tok).cvalue == '+': discard state.consume() true else: false template get_tok: CSSToken = state.skipWhitespace() fail_eof CSSToken(state.consume()) template get_tok_nows: CSSToken = fail_eof CSSToken(state.consume()) template fail_plus = if is_plus: return none(CSSAnB) template parse_sub_int(sub: string; skip: int): int = let s = sub.substr(skip) let x = parseInt32(s) if x.isNone: return none(CSSAnB) x.get template fail_non_integer(tok: CSSToken; res: Option[CSSAnB]) = if tok.tokenType != cttNumber: state.reconsume() return res if tok.tflagb != tflagbInteger: state.reconsume() return res if int64(tok.nvalue) > high(int): state.reconsume() return res template fail_non_signless_integer(tok: CSSToken; res: Option[CSSAnB]) = fail_non_integer tok, res #TODO check if signless? fail_eof state.skipWhitespace() fail_eof let is_plus = get_plus let tok = get_tok_nows case tok.tokenType of cttIdent: case tok.value of "odd": fail_plus return some((2, 1)) of "even": fail_plus return some((2, 0)) of "n", "N": state.skipWhitespace() if is_eof: return some((1, 0)) let tok2 = get_tok_nows if tok2.tokenType == cttDelim: let sign = case tok2.cvalue of '+': 1 of '-': -1 else: return none(CSSAnB) let tok3 = get_tok fail_non_signless_integer tok3, some((1, 0)) return some((1, sign * int(tok3.nvalue))) else: fail_non_integer tok2, some((1, 0)) return some((1, int(tok2.nvalue))) of "-n", "-N": fail_plus state.skipWhitespace() if is_eof: return some((-1, 0)) let tok2 = get_tok_nows if tok2.tokenType == cttDelim: let sign = case tok2.cvalue of '+': 1 of '-': -1 else: return none(CSSAnB) let tok3 = get_tok fail_non_signless_integer tok3, some((-1, 0)) return some((-1, sign * int(tok3.nvalue))) else: fail_non_integer tok2, some((-1, 0)) return some((-1, int(tok2.nvalue))) of "n-", "N-": let tok2 = get_tok fail_non_signless_integer tok2, none(CSSAnB) return some((1, -int(tok2.nvalue))) of "-n-", "-N-": fail_plus let tok2 = get_tok fail_non_signless_integer tok2, none(CSSAnB) return some((-1, -int(tok2.nvalue))) elif tok.value.startsWithIgnoreCase("n-"): return some((1, -parse_sub_int(tok.value, "n-".len))) elif tok.value.startsWithIgnoreCase("-n-"): fail_plus return some((-1, -parse_sub_int(tok.value, "n-".len))) else: return none(CSSAnB) of cttNumber: fail_plus if tok.tflagb != tflagbInteger: return none(CSSAnB) if int64(tok.nvalue) > high(int): return none(CSSAnB) # return some((0, int(tok.nvalue))) of cttDimension: fail_plus if int64(tok.nvalue) > high(int): return none(CSSAnB) if tok.tflagb != tflagbInteger: return none(CSSAnB) case tok.unit of "n", "N": # state.skipWhitespace() if is_eof: return some((int(tok.nvalue), 0)) let tok2 = get_tok_nows if tok2.tokenType == cttDelim: let sign = case tok2.cvalue of '+': 1 of '-': -1 else: return none(CSSAnB) let tok3 = get_tok fail_non_signless_integer tok3, some((int(tok.nvalue), 0)) return some((int(tok.nvalue), sign * int(tok3.nvalue))) else: fail_non_integer tok2, some((int(tok.nvalue), 0)) return some((int(tok.nvalue), int(tok2.nvalue))) of "n-", "N-": # let tok2 = get_tok fail_non_signless_integer tok2, none(CSSAnB) return some((int(tok.nvalue), -int(tok2.nvalue))) elif tok.unit.startsWithIgnoreCase("n-"): # return some((int(tok.nvalue), -parse_sub_int(tok.unit, "n-".len))) else: return none(CSSAnB) else: return none(CSSAnB) proc parseAnB*(cvals: seq[CSSComponentValue]): (Option[CSSAnB], int) = var state = CSSParseState(tokens: cast[seq[CSSParsedItem]](cvals)) let anb = state.parseAnB() return (anb, state.at)