import std/options
import html/domexception
import types/opt
import utils/twtstr
type
CSSTokenType* = enum
cttIdent, cttFunction, cttAtKeyword, cttHash, cttString,
cttBadString, cttUrl, cttBadUrl, cttDelim, cttNumber, cttINumber,
cttPercentage, cttDimension, cttIDimension, cttWhitespace, cttCdo,
cttCdc, cttColon, cttSemicolon, cttComma, cttRbracket, cttLbracket,
cttLparen, cttRparen, cttLbrace, cttRbrace
CSSTokenizerState = object
at: int
buf: string
CSSParseState = object
tokens: seq[CSSComponentValue]
at: int
tflaga = enum
tflagaUnrestricted, tflagaId
CSSComponentValue* = ref object of RootObj
CSSToken* = ref object of CSSComponentValue
case t*: CSSTokenType
of cttIdent, cttFunction, cttAtKeyword, cttHash, cttString, cttUrl:
tflaga*: tflaga
value*: string
of cttDelim:
cvalue*: char
of cttNumber, cttINumber, cttPercentage, cttDimension, cttIDimension:
nvalue*: float32
unit*: string
else: discard
CSSRule* = ref object of CSSComponentValue
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
CSSFunctionType* = enum
cftUnknown = "-cha-unknown"
cftNot = "not"
cftIs = "is"
cftWhere = "where"
cftNthChild = "nth-child"
cftNthLastChild = "nth-last-child"
cftLang = "lang"
cftRgb = "rgb"
cftRgba = "rgba"
cftChaAnsi = "-cha-ansi"
cftUrl = "url"
cftSrc = "src"
cftVar = "var"
cftHsl = "hsl"
cftHsla = "hsla"
cftCalc = "calc"
cftCounter = "counter"
CSSFunction* = ref object of CSSComponentValue
name*: CSSFunctionType
value*: seq[CSSComponentValue]
CSSSimpleBlock* = ref object of CSSComponentValue
token*: CSSToken
value*: seq[CSSComponentValue]
CSSRawStylesheet* = object
value*: seq[CSSRule]
CSSAnB* = tuple[A, B: int32]
# For debugging
proc `$`*(c: CSSComponentValue): string =
result = ""
if c of CSSToken:
let c = CSSToken(c)
case c.t:
of cttFunction, cttAtKeyword:
result &= $c.t & 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, cttNumber:
result &= $c.nvalue & c.unit
of cttINumber, cttIDimension:
result &= $int32(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.t & '\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 &= ";"
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.t
of cttLbrace: result &= "{\n"
of cttLparen: result &= "("
of cttLbracket: result &= "["
else: discard
for s in CSSSimpleBlock(c).value:
result &= $s
case CSSSimpleBlock(c).token.t
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: CSSComponentValue; b: CSSTokenType): bool =
return a of CSSToken and CSSToken(a).t == b
const IdentStart = AsciiAlpha + NonAscii + {'_'}
const Ident = IdentStart + AsciiDigit + {'-'}
proc consume(state: var CSSTokenizerState): char =
let c = state.buf[state.at]
inc state.at
return c
proc seek(state: var CSSTokenizerState; n: int) =
state.at += n
proc consumeRChar(state: var CSSTokenizerState): char =
let u = state.buf.nextUTF8(state.at)
if u < 0x80:
return char(u)
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]
func has(state: CSSTokenizerState; i: int = 0): bool =
return state.at + i < state.buf.len
# next, next(1)
proc startsWithIdentSequenceDash(state: var CSSTokenizerState): bool =
return state.has() and state.peek() in IdentStart + {'-'} or
state.has(1) and state.peek() == '\\' and state.peek(1) != '\n'
# next, next(1), next(2)
proc startsWithIdentSequence(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 state.peek(1) == '\\' and state.peek(2) != '\n'
of IdentStart:
return true
of '\\':
return state.has(1) and state.peek(1) != '\n'
else:
return false
proc skipWhitespace(state: var CSSTokenizerState) =
while state.has() and state.peek() in AsciiWhitespace:
state.seek(1)
proc consumeEscape(state: var CSSTokenizerState): string =
if not state.has():
return "\uFFFD"
let c = state.consume()
if c in AsciiHexDigit:
var num = uint32(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 += uint32(hexValue(c))
inc i
if state.has() and state.peek() in AsciiWhitespace:
state.seek(1)
if num == 0 or num > 0x10FFFF or num in 0xD800u32..0xDFFFu32:
return "\uFFFD"
else:
return num.toUTF8()
else:
return $c #NOTE this assumes the caller doesn't care about non-ascii
proc consumeString(state: var CSSTokenizerState; ending: char): CSSToken =
var s = ""
while state.has():
let c = state.consume()
case c
of '\n':
state.reconsume()
return CSSToken(t: cttBadString)
of '\\':
if not state.has():
continue
elif state.peek() == '\n':
state.seek(1)
else:
s &= consumeEscape(state)
elif c == ending:
break
else:
s &= c
return CSSToken(t: cttString, value: s)
proc consumeIdentSequence(state: var CSSTokenizerState): string =
var s = ""
while state.has():
let c = state.consume()
if c == '\\' and state.has() and state.peek() != '\n':
s &= state.consumeEscape()
elif c in Ident:
s &= c
else:
state.reconsume()
break
return s
proc consumeNumber(state: var CSSTokenizerState):
tuple[isInt: bool; val: float32] =
var isInt = true
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()
isInt = false
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()
isInt = false
while state.has() and state.peek() in AsciiDigit:
repr &= state.consume()
let val = parseFloat32(repr)
return (isInt, val)
proc consumeNumericToken(state: var CSSTokenizerState): CSSToken =
let (isInt, val) = state.consumeNumber()
if state.startsWithIdentSequence():
let unit = state.consumeIdentSequence()
if isInt:
return CSSToken(t: cttIDimension, nvalue: val, unit: unit)
return CSSToken(t: cttDimension, nvalue: val, unit: unit)
if state.has() and state.peek() == '%':
state.seek(1)
return CSSToken(t: cttPercentage, nvalue: val)
if isInt:
return CSSToken(t: cttINumber, nvalue: val)
return CSSToken(t: cttNumber, nvalue: val)
proc consumeBadURL(state: var CSSTokenizerState) =
while state.has():
let c = state.consume()
if c == ')':
break
if c == '\\' and state.has() and state.peek() != '\n':
discard state.consumeEscape()
const NonPrintable = {
'\0'..char(0x08), '\v', char(0x0E)..char(0x1F), char(0x7F)
}
proc consumeURL(state: var CSSTokenizerState): CSSToken =
let res = CSSToken(t: cttUrl)
state.skipWhitespace()
while state.has():
let c = state.consume()
case c
of ')':
return res
of '"', '\'', '(', NonPrintable:
state.consumeBadURL()
return CSSToken(t: cttBadUrl)
of AsciiWhitespace:
state.skipWhitespace()
if not state.has():
return res
if state.peek() == ')':
state.seek(1)
return res
state.consumeBadURL()
return CSSToken(t: cttBadUrl)
of '\\':
if state.has() and state.peek() != '\n':
res.value &= state.consumeEscape()
else:
state.consumeBadURL()
return CSSToken(t: cttBadUrl)
else:
res.value &= c
return res
proc consumeIdentLikeToken(state: var CSSTokenizerState): CSSToken =
let s = state.consumeIdentSequence()
if s.equalsIgnoreCase("url") and state.has() and state.peek() == '(':
state.seek(1)
while state.has(1) and state.peek() in AsciiWhitespace and
state.peek(1) in AsciiWhitespace:
state.seek(1)
if state.has() and state.peek() in {'"', '\''} or
state.has(1) and state.peek() in {'"', '\''} + AsciiWhitespace and
state.peek(1) in {'"', '\''}:
return CSSToken(t: cttFunction, value: s)
return state.consumeURL()
if state.has() and state.peek() == '(':
state.seek(1)
return CSSToken(t: cttFunction, value: s)
return CSSToken(t: cttIdent, value: s)
proc nextToken(state: var CSSTokenizerState): bool =
while state.has(1) and state.peek() == '/' and state.peek(1) == '*':
state.seek(2)
while state.has() and not (state.has(1) and state.peek() == '*' and
state.peek(1) == '/'):
state.seek(1)
if state.has(1):
state.seek(1)
if state.has():
state.seek(1)
return state.has()
proc consumeToken(state: var CSSTokenizerState): CSSToken =
let c = state.consume()
case c
of AsciiWhitespace:
state.skipWhitespace()
return CSSToken(t: cttWhitespace)
of '"', '\'':
return consumeString(state, c)
of '#':
if state.has() and state.peek() in Ident or
state.has(1) and state.peek() == '\\' and state.peek(1) != '\n':
let flag = if state.startsWithIdentSequence():
tflagaId
else:
tflagaUnrestricted
return CSSToken(
t: cttHash,
value: state.consumeIdentSequence(),
tflaga: flag
)
else:
state.reconsume()
return CSSToken(t: cttDelim, cvalue: state.consumeRChar())
of '(': return CSSToken(t: cttLparen)
of ')': return CSSToken(t: cttRparen)
of '{': return CSSToken(t: cttLbrace)
of '}': return CSSToken(t: cttRbrace)
of '+':
# starts with a number
if state.has() and state.peek() in AsciiDigit or
state.has(1) and state.peek() == '.' and state.peek(1) in AsciiDigit:
state.reconsume()
return state.consumeNumericToken()
else:
return CSSToken(t: cttDelim, cvalue: c)
of ',': return CSSToken(t: cttComma)
of '-':
# starts with a number
if state.has() and state.peek() in AsciiDigit or
state.has(1) and state.peek() == '.' and state.peek(1) in AsciiDigit:
state.reconsume()
return state.consumeNumericToken()
elif state.has(1) and state.peek() == '-' and state.peek(1) == '>':
state.seek(2)
return CSSToken(t: cttCdc)
elif state.startsWithIdentSequenceDash():
state.reconsume()
return state.consumeIdentLikeToken()
else:
return CSSToken(t: cttDelim, cvalue: c)
of '.':
# starts with a number
if state.has() and state.peek() in AsciiDigit:
state.reconsume()
return state.consumeNumericToken()
else:
return CSSToken(t: cttDelim, cvalue: c)
of ':': return CSSToken(t: cttColon)
of ';': return CSSToken(t: cttSemicolon)
of '<':
if state.has(2) and state.peek() == '!' and state.peek(1) == '-' and
state.peek(2) == '-':
state.seek(3)
return CSSToken(t: cttCdo)
else:
return CSSToken(t: cttDelim, cvalue: c)
of '@':
if state.startsWithIdentSequence():
let name = state.consumeIdentSequence()
return CSSToken(t: cttAtKeyword, value: name)
else:
return CSSToken(t: cttDelim, cvalue: c)
of '[': return CSSToken(t: cttLbracket)
of '\\':
if state.has() and state.peek() != '\n':
state.reconsume()
return state.consumeIdentLikeToken()
else:
return CSSToken(t: cttDelim, cvalue: c)
of ']': return CSSToken(t: cttRbracket)
of AsciiDigit:
state.reconsume()
return state.consumeNumericToken()
of IdentStart:
state.reconsume()
return state.consumeIdentLikeToken()
else:
state.reconsume()
return CSSToken(t: cttDelim, cvalue: state.consumeRChar())
proc tokenizeCSS(ibuf: string): seq[CSSComponentValue] =
result = @[]
var state = CSSTokenizerState(buf: ibuf)
while state.nextToken():
result.add(state.consumeToken())
proc consume(state: var CSSParseState): CSSComponentValue =
result = state.tokens[state.at]
inc state.at
proc reconsume(state: var CSSParseState) =
dec state.at
func has(state: CSSParseState): bool =
return state.at < state.tokens.len
func peek(state: CSSParseState): CSSComponentValue =
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; tok: CSSToken):
CSSSimpleBlock =
var ending: CSSTokenType
case tok.t
of cttLbrace: ending = cttRbrace
of cttLparen: ending = cttRparen
of cttLbracket: ending = cttRbracket
else: doAssert false
result = CSSSimpleBlock(token: tok)
while state.has():
let tok = state.consume()
if tok == ending:
break
elif tok == cttLbrace or tok == cttLbracket or tok == cttLparen:
result.value.add(state.consumeSimpleBlock(CSSToken(tok)))
else:
state.reconsume()
result.value.add(state.consumeComponentValue())
proc consumeFunction(state: var CSSParseState): CSSFunction =
let t = CSSToken(state.consume())
let name = parseEnumNoCase[CSSFunctionType](t.value).get(cftUnknown)
let res = CSSFunction(name: name)
while state.has():
let t = state.consume()
if t == cttRparen:
break
state.reconsume()
res.value.add(state.consumeComponentValue())
return res
proc consumeComponentValue(state: var CSSParseState): CSSComponentValue =
let t = state.consume()
if t == cttLbrace or t == cttLbracket or t == cttLparen:
return state.consumeSimpleBlock(CSSToken(t))
elif t == cttFunction:
state.reconsume()
return state.consumeFunction()
return 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(CSSToken(t))
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.has():
let t = state.consume()
if t of CSSSimpleBlock:
result.oblock = CSSSimpleBlock(t)
break
elif t == cttSemicolon:
break
elif t == cttLbrace:
result.oblock = state.consumeSimpleBlock(CSSToken(t))
break
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.
#
# Currently we never use nested at-rules, so the result of consumeAtRule
# is just discarded. This should be changed if we ever need nested at
# rules (e.g. add a flag to include at rules).
proc consumeDeclarations(state: var CSSParseState): seq[CSSDeclaration] =
result = @[]
while state.has():
let t = state.consume()
if t == cttWhitespace or t == cttSemicolon:
continue
elif t == cttAtKeyword:
state.reconsume()
discard state.consumeAtRule() # see above
elif t == cttIdent:
var tempList = @[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()
while state.has() and state.peek() != cttSemicolon:
discard state.consumeComponentValue()
proc consumeListOfRules(state: var CSSParseState; topLevel = false):
seq[CSSRule] =
while state.has():
let t = state.consume()
if t == cttWhitespace:
continue
elif t == cttCdo or t == cttCdc:
if topLevel:
continue
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(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(tokens: 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(tokens: tokenizeCSS(ibuf))
return state.parseRule()
proc parseDeclarations*(cvals: seq[CSSComponentValue]): seq[CSSDeclaration] =
var state = CSSParseState(tokens: cvals)
return state.consumeDeclarations()
proc parseDeclarations*(ibuf: string): seq[CSSDeclaration] =
return parseDeclarations(tokenizeCSS(ibuf))
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 nextCommaSepComponentValue(state: var CSSParseState;
s: out seq[CSSComponentValue]): bool =
s = @[]
while state.has():
let cvl = state.consumeComponentValue()
if cvl == cttComma:
break
s.add(cvl)
return s.len > 0
iterator parseCommaSepComponentValues*(cvals: seq[CSSComponentValue]):
seq[CSSComponentValue] =
var state = CSSParseState(tokens: cvals)
var s: seq[CSSComponentValue]
while state.nextCommaSepComponentValue(s):
yield move(s)
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): int32 =
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.t != cttINumber:
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.t
of cttIdent:
case tok.value
of "odd":
fail_plus
return some((2i32, 1i32))
of "even":
fail_plus
return some((2i32, 0i32))
of "n", "N":
state.skipWhitespace()
if is_eof:
return some((1i32, 0i32))
let tok2 = get_tok_nows
if tok2.t == cttDelim:
let sign = case tok2.cvalue
of '+': 1i32
of '-': -1i32
else: return none(CSSAnB)
let tok3 = get_tok
fail_non_signless_integer tok3, some((1i32, 0i32))
return some((1i32, sign * int32(tok3.nvalue)))
else:
fail_non_integer tok2, some((1i32, 0i32))
return some((1i32, int32(tok2.nvalue)))
of "-n", "-N":
fail_plus
state.skipWhitespace()
if is_eof:
return some((-1i32, 0i32))
let tok2 = get_tok_nows
if tok2.t == cttDelim:
let sign = case tok2.cvalue
of '+': 1i32
of '-': -1i32
else: return none(CSSAnB)
let tok3 = get_tok
fail_non_signless_integer tok3, some((-1i32, 0i32))
return some((-1i32, sign * int32(tok3.nvalue)))
else:
fail_non_integer tok2, some((-1i32, 0i32))
return some((-1i32, int32(tok2.nvalue)))
of "n-", "N-":
let tok2 = get_tok
fail_non_signless_integer tok2, none(CSSAnB)
return some((1i32, -int32(tok2.nvalue)))
of "-n-", "-N-":
fail_plus
let tok2 = get_tok
fail_non_signless_integer tok2, none(CSSAnB)
return some((-1i32, -int32(tok2.nvalue)))
elif tok.value.startsWithIgnoreCase("n-"):
return some((1i32, -parse_sub_int(tok.value, "n-".len)))
elif tok.value.startsWithIgnoreCase("-n-"):
fail_plus
return some((-1i32, -parse_sub_int(tok.value, "n-".len)))
else:
return none(CSSAnB)
of cttINumber:
fail_plus
#
return some((0i32, int32(tok.nvalue)))
of cttIDimension:
fail_plus
case tok.unit
of "n", "N":
#
state.skipWhitespace()
if is_eof:
return some((int32(tok.nvalue), 0i32))
let tok2 = get_tok_nows
if tok2.t == cttDelim:
let sign = case tok2.cvalue
of '+': 1i32
of '-': -1i32
else: return none(CSSAnB)
let tok3 = get_tok
fail_non_signless_integer tok3, some((int32(tok.nvalue), 0i32))
return some((int32(tok.nvalue), sign * int32(tok3.nvalue)))
else:
fail_non_integer tok2, some((int32(tok.nvalue), 0i32))
return some((int32(tok.nvalue), int32(tok2.nvalue)))
of "n-", "N-":
#
let tok2 = get_tok
fail_non_signless_integer tok2, none(CSSAnB)
return some((int32(tok.nvalue), -int32(tok2.nvalue)))
elif tok.unit.startsWithIgnoreCase("n-"):
#
return some((int32(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: cvals)
let anb = state.parseAnB()
return (anb, state.at)