diff options
author | bptato <nincsnevem662@gmail.com> | 2025-04-02 18:14:39 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-04-02 18:14:39 +0200 |
commit | 7cf2d3856f3a39fcca14c28889a9c1ba08e9e8f1 (patch) | |
tree | 07dfbfae872500f4d1d5cdc70748512d65366ddd /src | |
parent | 10a38a9d260487635bbdd24c912bda640b09f602 (diff) | |
download | chawan-7cf2d3856f3a39fcca14c28889a9c1ba08e9e8f1.tar.gz |
cssparser: optimize out some copies
The input buffer is no longer copied, and parseFloat32 gets an openArray instead of a copy of the string to parse. I've also simplified the parser somewhat so it now operates on openArrays of CSSComponentValues. It's still far more complex and wasteful than it should be, but it's progress.
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 2 | ||||
-rw-r--r-- | src/css/cssparser.nim | 787 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 37 | ||||
-rw-r--r-- | src/css/selectorparser.nim | 3 | ||||
-rw-r--r-- | src/css/sheet.nim | 31 |
5 files changed, 403 insertions, 457 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index 073aea1a..4333678f 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -802,7 +802,7 @@ proc parseConfigValue(ctx: var ConfigParser; x: var StyleString; v: TomlValue; break let ending = s[i] inc i - let tok = s.parseCSSString(ending, i) + let tok = s.consumeCSSString(ending, i) if tok.t != cttString: break while s.nextCSSToken(i): diff --git a/src/css/cssparser.nim b/src/css/cssparser.nim index 1f21b79b..dc23d0da 100644 --- a/src/css/cssparser.nim +++ b/src/css/cssparser.nim @@ -12,14 +12,6 @@ type 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 @@ -39,12 +31,13 @@ type CSSRule* = ref object of CSSComponentValue prelude*: seq[CSSComponentValue] - oblock*: CSSSimpleBlock CSSAtRule* = ref object of CSSRule name*: string + oblock*: CSSSimpleBlock CSSQualifiedRule* = ref object of CSSRule + decls*: seq[CSSDeclaration] CSSDeclaration* = ref object of CSSComponentValue name*: string @@ -78,12 +71,14 @@ type token*: CSSToken value*: seq[CSSComponentValue] - CSSRawStylesheet* = object - value*: seq[CSSRule] - CSSAnB* = tuple[A, B: int32] -# For debugging +# Forward declarations +proc consumeDeclarations(cvals: openArray[CSSComponentValue]): + seq[CSSDeclaration] +proc consumeComponentValue(cvals: openArray[CSSComponentValue]; i: var int): + CSSComponentValue + proc `$`*(c: CSSComponentValue): string = result = "" if c of CSSToken: @@ -151,7 +146,10 @@ proc `$`*(c: CSSComponentValue): string = if c of CSSAtRule: result &= CSSAtRule(c).name & " " result &= $CSSRule(c).prelude & "\n" - result &= $CSSRule(c).oblock + if c of CSSAtRule: + result &= $CSSAtRule(c).oblock + else: + result &= $CSSQualifiedRule(c).decls func `==`*(a: CSSComponentValue; b: CSSTokenType): bool = return a of CSSToken and CSSToken(a).t == b @@ -159,92 +157,69 @@ func `==`*(a: CSSComponentValue; b: CSSTokenType): bool = 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) +proc consumeRChar(iq: openArray[char]; n: var int): char = + let u = iq.nextUTF8(n) 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' +proc startsWithIdentSequenceDash(iq: openArray[char]; n: int): bool = + return n < iq.len and iq[n] in IdentStart + {'-'} or + n + 1 < iq.len and iq[n] == '\\' and iq[n + 1] != '\n' # next, next(1), next(2) -proc startsWithIdentSequence(state: var CSSTokenizerState): bool = - if not state.has(): +proc startsWithIdentSequence(iq: openArray[char]; n: int): bool = + if n >= iq.len: return false - case state.peek() + case iq[n] of '-': - return state.has(1) and state.peek(1) in IdentStart + {'-'} or - state.has(2) and state.peek(1) == '\\' and state.peek(2) != '\n' + return n + 1 < iq.len and iq[n] in IdentStart + {'-'} or + n + 2 < iq.len and iq[n + 1] == '\\' and iq[n + 2] != '\n' of IdentStart: return true of '\\': - return state.has(1) and state.peek(1) != '\n' + return n + 1 < iq.len and iq[n + 1] != '\n' else: return false -proc skipWhitespace(state: var CSSTokenizerState) = - while state.has() and state.peek() in AsciiWhitespace: - state.seek(1) - -proc consumeEscape(buf: openArray[char]; n: var int): string = - if n >= buf.len: +proc consumeEscape(iq: openArray[char]; n: var int): string = + if n >= iq.len: return "\uFFFD" - let c = buf[n] + let c = iq[n] inc n if c in AsciiHexDigit: var num = uint32(hexValue(c)) var i = 0 - while i <= 5 and n < buf.len: - let val = hexValue(buf[n]) + while i <= 5 and n < iq.len: + let val = hexValue(iq[n]) if val == -1: break num *= 0x10 num += uint32(val) inc n inc i - if n < buf.len and buf[n] in AsciiWhitespace: + if n < iq.len and iq[n] in AsciiWhitespace: inc n if num == 0 or num > 0x10FFFF or num in 0xD800u32..0xDFFFu32: return "\uFFFD" return num.toUTF8() return $c # assume the caller doesn't care about non-ascii -proc consumeEscape(state: var CSSTokenizerState): string = - return consumeEscape(state.buf, state.at) - -proc consumeString(buf: openArray[char]; ending: char; n: var int): CSSToken = +proc consumeCSSString*(iq: openArray[char]; ending: char; n: var int): + CSSToken = var s = "" - while n < buf.len: - let c = buf[n] + while n < iq.len: + let c = iq[n] case c of '\n': return CSSToken(t: cttBadString) of '\\': - if n + 1 >= buf.len or buf[n + 1] == '\n': + if n + 1 >= iq.len or iq[n + 1] == '\n': discard else: inc n - s &= buf.consumeEscape(n) + s &= iq.consumeEscape(n) continue elif c == ending: inc n @@ -254,256 +229,279 @@ proc consumeString(buf: openArray[char]; ending: char; n: var int): CSSToken = inc n return CSSToken(t: cttString, value: move(s)) -proc consumeString(state: var CSSTokenizerState; ending: char): CSSToken = - return state.buf.consumeString(ending, state.at) - -proc consumeIdentSequence(state: var CSSTokenizerState): string = +proc consumeIdentSequence(iq: openArray[char]; n: var int): string = var s = "" - while state.has(): - let c = state.consume() - if c == '\\' and state.has() and state.peek() != '\n': - s &= state.consumeEscape() + while n < iq.len: + let c = iq[n] + if c == '\\' and n + 1 < iq.len and iq[n + 1] != '\n': + inc n + s &= iq.consumeEscape(n) + continue elif c in Ident: s &= c else: - state.reconsume() break - return s + inc n + return move(s) -proc consumeNumber(state: var CSSTokenizerState): +proc consumeNumber(iq: openArray[char]; n: var int): 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() + let start = n + if n < iq.len and iq[n] in {'+', '-'}: + inc n + while n < iq.len and iq[n] in AsciiDigit: + inc n + if n + 1 < iq.len and iq[n] == '.' and iq[n + 1] in AsciiDigit: + n += 2 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() + while n < iq.len and iq[n] in AsciiDigit: + inc n + if n + 1 < iq.len and iq[n] in {'E', 'e'} and iq[n + 1] in AsciiDigit or + n + 2 < iq.len and iq[n] in {'E', 'e'} and iq[n + 1] in {'-', '+'} and + iq[n + 2] in AsciiDigit: + inc n + if iq[n] in {'-', '+'}: + n += 2 else: - repr &= state.consume() + inc n isInt = false - while state.has() and state.peek() in AsciiDigit: - repr &= state.consume() - let val = parseFloat32(repr) + while n < iq.len and iq[n] in AsciiDigit: + inc n + let val = parseFloat32(iq.toOpenArray(start, n - 1)) return (isInt, val) -proc consumeNumericToken(state: var CSSTokenizerState): CSSToken = - let (isInt, val) = state.consumeNumber() - if state.startsWithIdentSequence(): - let unit = state.consumeIdentSequence() +proc consumeNumericToken(iq: openArray[char]; n: var int): CSSToken = + let (isInt, val) = iq.consumeNumber(n) + if iq.startsWithIdentSequence(n): + let unit = iq.consumeIdentSequence(n) 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) + if n < iq.len and iq[n] == '%': + inc n 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() +proc consumeBadURL(iq: openArray[char]; n: var int) = + while n < iq.len: + let c = iq[n] + inc n if c == ')': break - if c == '\\' and state.has() and state.peek() != '\n': - discard state.consumeEscape() + if c == '\\' and n < iq.len and iq[n] != '\n': + discard iq.consumeEscape(n) const NonPrintable = { '\0'..char(0x08), '\v', char(0x0E)..char(0x1F), char(0x7F) } -proc consumeURL(state: var CSSTokenizerState): CSSToken = +proc consumeURL(iq: openArray[char]; n: var int): CSSToken = let res = CSSToken(t: cttUrl) - state.skipWhitespace() - while state.has(): - let c = state.consume() + n = iq.skipBlanks(n) + while n < iq.len: + let c = iq[n] + inc n case c of ')': return res of '"', '\'', '(', NonPrintable: - state.consumeBadURL() + iq.consumeBadURL(n) return CSSToken(t: cttBadUrl) of AsciiWhitespace: - state.skipWhitespace() - if not state.has(): + n = iq.skipBlanks(n) + if n >= iq.len: return res - if state.peek() == ')': - state.seek(1) + if iq[n] == ')': + inc n return res - state.consumeBadURL() + iq.consumeBadURL(n) return CSSToken(t: cttBadUrl) of '\\': - if state.has() and state.peek() != '\n': - res.value &= state.consumeEscape() + if n < iq.len and iq[n] != '\n': + res.value &= iq.consumeEscape(n) else: - state.consumeBadURL() + iq.consumeBadURL(n) 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 {'"', '\''}: +proc consumeIdentLikeToken(iq: openArray[char]; n: var int): CSSToken = + let s = iq.consumeIdentSequence(n) + if s.equalsIgnoreCase("url") and n < iq.len and iq[n] == '(': + inc n + while n + 1 < iq.len and iq[n] in AsciiWhitespace and + iq[n + 1] in AsciiWhitespace: + inc n + if n < iq.len and iq[n] in {'"', '\''} or + n + 1 < iq.len and iq[n] in {'"', '\''} + AsciiWhitespace and + iq[n + 1] in {'"', '\''}: return CSSToken(t: cttFunction, value: s) - return state.consumeURL() - if state.has() and state.peek() == '(': - state.seek(1) + return iq.consumeURL(n) + if n < iq.len and iq[n] == '(': + inc n return CSSToken(t: cttFunction, value: s) return CSSToken(t: cttIdent, value: s) -proc nextCSSToken*(buf: openArray[char]; n: var int): bool = +proc nextCSSToken*(iq: openArray[char]; n: var int): bool = var m = n - while m + 1 < buf.len and buf[m] == '/' and buf[m + 1] == '*': + while m + 1 < iq.len and iq[m] == '/' and iq[m + 1] == '*': m += 2 - while m < buf.len and not (m + 1 < buf.len and buf[m] == '*' and - buf[m + 1] == '/'): + while m < iq.len and not (m + 1 < iq.len and iq[m] == '*' and + iq[m + 1] == '/'): inc m - if m + 1 < buf.len: + if m + 1 < iq.len: inc m - if m < buf.len: + if m < iq.len: inc m n = m - return m < buf.len + return m < iq.len -proc consumeToken(state: var CSSTokenizerState): CSSToken = - let c = state.consume() +proc consumeToken(iq: openArray[char]; n: var int): CSSToken = + let c = iq[n] + inc n case c of AsciiWhitespace: - state.skipWhitespace() + n = iq.skipBlanks(n) return CSSToken(t: cttWhitespace) of '"', '\'': - return consumeString(state, c) + return iq.consumeCSSString(c, n) 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(): + if n < iq.len and iq[n] in Ident or + n + 1 < iq.len and iq[n] == '\\' and iq[n + 1] != '\n': + let flag = if iq.startsWithIdentSequence(n): tflagaId else: tflagaUnrestricted return CSSToken( t: cttHash, - value: state.consumeIdentSequence(), + value: iq.consumeIdentSequence(n), tflaga: flag ) else: - state.reconsume() - return CSSToken(t: cttDelim, cvalue: state.consumeRChar()) + dec n + return CSSToken(t: cttDelim, cvalue: iq.consumeRChar(n)) 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() + if n < iq.len and iq[n] in AsciiDigit or + n + 1 < iq.len and iq[n] == '.' and iq[n + 1] in AsciiDigit: + dec n + return iq.consumeNumericToken(n) 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) + if n < iq.len and iq[n] in AsciiDigit or + n + 1 < iq.len and iq[n] == '.' and iq[n + 1] in AsciiDigit: + dec n + return iq.consumeNumericToken(n) + elif n + 1 < iq.len and iq[n] == '-' and iq[n + 1] == '>': + n += 2 return CSSToken(t: cttCdc) - elif state.startsWithIdentSequenceDash(): - state.reconsume() - return state.consumeIdentLikeToken() + elif iq.startsWithIdentSequenceDash(n): + dec n + return iq.consumeIdentLikeToken(n) 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() + if n < iq.len and iq[n] in AsciiDigit: + dec n + return iq.consumeNumericToken(n) 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) + if n + 2 < iq.len and iq[n] == '!' and iq[n + 1] == '-' and + iq[n + 2] == '-': + n += 3 return CSSToken(t: cttCdo) else: return CSSToken(t: cttDelim, cvalue: c) of '@': - if state.startsWithIdentSequence(): - let name = state.consumeIdentSequence() + if iq.startsWithIdentSequence(n): + let name = iq.consumeIdentSequence(n) 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() + if n < iq.len and iq[n] != '\n': + dec n + return iq.consumeIdentLikeToken(n) else: return CSSToken(t: cttDelim, cvalue: c) of ']': return CSSToken(t: cttRbracket) of AsciiDigit: - state.reconsume() - return state.consumeNumericToken() + dec n + return iq.consumeNumericToken(n) of IdentStart: - state.reconsume() - return state.consumeIdentLikeToken() + dec n + return iq.consumeIdentLikeToken(n) else: - state.reconsume() - return CSSToken(t: cttDelim, cvalue: state.consumeRChar()) + dec n + return CSSToken(t: cttDelim, cvalue: iq.consumeRChar(n)) -proc tokenizeCSS(ibuf: string): seq[CSSComponentValue] = +proc tokenizeCSS(iq: openArray[char]): seq[CSSComponentValue] = result = @[] - var state = CSSTokenizerState(buf: ibuf) - while state.buf.nextCSSToken(state.at): - 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 + var n = 0 + while iq.nextCSSToken(n): + result.add(iq.consumeToken(n)) + +func skipBlanks*(vals: openArray[CSSComponentValue]; i: int): int = + var i = i + while i < vals.len: + if vals[i] != cttWhitespace: + break + inc i + return i -proc consumeSimpleBlock(state: var CSSParseState; tok: CSSToken): - CSSSimpleBlock = +func findBlank*(vals: openArray[CSSComponentValue]; i: int): int = + var i = i + while i < vals.len: + if vals[i] == cttWhitespace: + break + inc i + return i + +func getToken*(cvals: openArray[CSSComponentValue]; i: int): Opt[CSSToken] = + if i < cvals.len: + let cval = cvals[i] + if cval of CSSToken: + return ok(CSSToken(cval)) + return err() + +proc consumeToken*(cvals: openArray[CSSComponentValue]; i: var int): + Opt[CSSToken] = + let tok = ?cvals.getToken(i) + inc i + return ok(tok) + +func getToken*(cvals: openArray[CSSComponentValue]; i: int; + tt: set[CSSTokenType]): Opt[CSSToken] = + let tok = ?cvals.getToken(i) + if tok.t in tt: + return ok(tok) + return err() + +func getToken*(cvals: openArray[CSSComponentValue]; i: int; t: CSSTokenType): + Opt[CSSToken] = + let tok = ?cvals.getToken(i) + if t == tok.t: + return ok(tok) + return err() + +proc consumeSimpleBlock(cvals: openArray[CSSComponentValue]; tok: CSSToken; + i: var int): CSSSimpleBlock = var ending: CSSTokenType case tok.t of cttLbrace: ending = cttRbrace @@ -511,97 +509,109 @@ proc consumeSimpleBlock(state: var CSSParseState; tok: CSSToken): of cttLbracket: ending = cttRbracket else: doAssert false result = CSSSimpleBlock(token: tok) - while state.has(): - let tok = state.consume() + while i < cvals.len: + let tok = cvals[i] if tok == ending: + inc i break elif tok == cttLbrace or tok == cttLbracket or tok == cttLparen: - result.value.add(state.consumeSimpleBlock(CSSToken(tok))) + inc i + result.value.add(cvals.consumeSimpleBlock(CSSToken(tok), i)) else: - state.reconsume() - result.value.add(state.consumeComponentValue()) + result.value.add(cvals.consumeComponentValue(i)) -proc consumeFunction(state: var CSSParseState): CSSFunction = - let t = CSSToken(state.consume()) +proc consumeFunction(cvals: openArray[CSSComponentValue]; i: var int): + CSSFunction = + let t = CSSToken(cvals[i]) + inc i let name = parseEnumNoCase[CSSFunctionType](t.value).get(cftUnknown) let res = CSSFunction(name: name) - while state.has(): - let t = state.consume() + while i < cvals.len: + let t = cvals[i] if t == cttRparen: + inc i break - state.reconsume() - res.value.add(state.consumeComponentValue()) + res.value.add(cvals.consumeComponentValue(i)) return res -proc consumeComponentValue(state: var CSSParseState): CSSComponentValue = - let t = state.consume() +proc consumeComponentValue(cvals: openArray[CSSComponentValue]; i: var int): + CSSComponentValue = + let t = cvals[i] if t == cttLbrace or t == cttLbracket or t == cttLparen: - return state.consumeSimpleBlock(CSSToken(t)) + inc i + return cvals.consumeSimpleBlock(CSSToken(t), i) elif t == cttFunction: - state.reconsume() - return state.consumeFunction() + return cvals.consumeFunction(i) + inc i return t -proc consumeQualifiedRule(state: var CSSParseState): Option[CSSQualifiedRule] = +proc consumeQualifiedRule(cvals: openArray[CSSComponentValue]; i: var int): + Option[CSSQualifiedRule] = var r = CSSQualifiedRule() - while state.has(): - let t = state.consume() + while i < cvals.len: + let t = cvals[i] if t of CSSSimpleBlock and CSSSimpleBlock(t).token == cttLbrace: - r.oblock = CSSSimpleBlock(t) + inc i + let oblock = CSSSimpleBlock(t) + r.decls = oblock.value.consumeDeclarations() return some(r) elif t == cttLbrace: - r.oblock = state.consumeSimpleBlock(CSSToken(t)) + inc i + let oblock = cvals.consumeSimpleBlock(CSSToken(t), i) + r.decls = oblock.value.consumeDeclarations() return some(r) else: - state.reconsume() - r.prelude.add(state.consumeComponentValue()) + r.prelude.add(cvals.consumeComponentValue(i)) return none(CSSQualifiedRule) -proc consumeAtRule(state: var CSSParseState): CSSAtRule = - let t = CSSToken(state.consume()) +proc consumeAtRule(cvals: openArray[CSSComponentValue]; i: var int): CSSAtRule = + let t = CSSToken(cvals[i]) + inc i result = CSSAtRule(name: t.value) - while state.has(): - let t = state.consume() + while i < cvals.len: + let t = cvals[i] + inc i if t of CSSSimpleBlock: result.oblock = CSSSimpleBlock(t) break elif t == cttSemicolon: break elif t == cttLbrace: - result.oblock = state.consumeSimpleBlock(CSSToken(t)) + result.oblock = cvals.consumeSimpleBlock(CSSToken(t), i) 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: + dec i + result.prelude.add(cvals.consumeComponentValue(i)) + +proc consumeDeclaration(cvals: openArray[CSSComponentValue]; i: var int): + Option[CSSDeclaration] = + let t = CSSToken(cvals[i]) + i = cvals.skipBlanks(i + 1) + if i >= cvals.len or cvals[i] != 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 + i = cvals.skipBlanks(i + 1) + let decl = CSSDeclaration(name: t.value) + while i < cvals.len: + decl.value.add(cvals.consumeComponentValue(i)) + var j = 0 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 + for i in countdown(decl.value.high, 0): + if decl.value[i] == cttWhitespace: + continue + inc 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 + if j == 2: + break while decl.value.len > 0 and decl.value[^1] == cttWhitespace: decl.value.setLen(decl.value.len - 1) return some(decl) @@ -614,275 +624,248 @@ proc consumeDeclaration(state: var CSSParseState): Option[CSSDeclaration] = # 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] = +proc consumeDeclarations(cvals: openArray[CSSComponentValue]): + seq[CSSDeclaration] = + var i = 0 result = @[] - while state.has(): - let t = state.consume() + while i < cvals.len: + let t = cvals[i] + inc i if t == cttWhitespace or t == cttSemicolon: continue elif t == cttAtKeyword: - state.reconsume() - discard state.consumeAtRule() # see above + dec i + discard cvals.consumeAtRule(i) # 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() + while i < cvals.len and cvals[i] != cttSemicolon: + tempList.add(cvals.consumeComponentValue(i)) + var j = 0 + let decl = tempList.consumeDeclaration(j) if decl.isSome: result.add(decl.get) else: - state.reconsume() - while state.has() and state.peek() != cttSemicolon: - discard state.consumeComponentValue() + dec i + while i < cvals.len and cvals[i] != cttSemicolon: + discard cvals.consumeComponentValue(i) -proc consumeListOfRules(state: var CSSParseState; topLevel = false): +proc consumeListOfRules(cvals: openArray[CSSComponentValue]; topLevel: bool): seq[CSSRule] = - while state.has(): - let t = state.consume() + var i = 0 + while i < cvals.len: + let t = cvals[i] + inc i if t == cttWhitespace: continue elif t == cttCdo or t == cttCdc: if topLevel: continue - state.reconsume() - let q = state.consumeQualifiedRule() + dec i + let q = cvals.consumeQualifiedRule(i) if q.isSome: result.add(q.get) elif t == cttAtKeyword: - state.reconsume() - result.add(state.consumeAtRule()) + dec i + result.add(cvals.consumeAtRule(i)) else: - state.reconsume() - let q = state.consumeQualifiedRule() + dec i + let q = cvals.consumeQualifiedRule(i) if q.isSome: result.add(q.get) -proc parseStylesheet(state: var CSSParseState): CSSRawStylesheet = - return CSSRawStylesheet(value: state.consumeListOfRules(true)) +proc parseListOfRules*(iq: openArray[char]; topLevel: bool): seq[CSSRule] = + return tokenizeCSS(iq).consumeListOfRules(topLevel) -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 parseListOfRules*(cvals: openArray[CSSComponentValue]; topLevel: bool): + seq[CSSRule] = + return cvals.consumeListOfRules(topLevel) -proc parseRule(state: var CSSParseState): DOMResult[CSSRule] = - state.skipWhitespace() - if not state.has(): +proc parseRule(cvals: openArray[CSSComponentValue]): DOMResult[CSSRule] = + var i = cvals.skipBlanks(0) + if i >= cvals.len: return errDOMException("Unexpected EOF", "SyntaxError") - var res = if state.peek() == cttAtKeyword: - state.consumeAtRule() + var res = if cvals[i] == cttAtKeyword: + cvals.consumeAtRule(i) else: - let q = state.consumeQualifiedRule() + let q = cvals.consumeQualifiedRule(i) if q.isNone: - return errDOMException("No qualified rule found!", "SyntaxError") + return errDOMException("No qualified rule found", "SyntaxError") q.get - state.skipWhitespace() - if state.has(): + if cvals.skipBlanks(i) < cvals.len: 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 parseRule*(iq: openArray[char]): DOMResult[CSSRule] = + return tokenizeCSS(iq).parseRule() -proc parseDeclarations*(cvals: seq[CSSComponentValue]): seq[CSSDeclaration] = - var state = CSSParseState(tokens: cvals) - return state.consumeDeclarations() +proc parseDeclarations*(iq: openArray[char]): seq[CSSDeclaration] = + return tokenizeCSS(iq).consumeDeclarations() -proc parseDeclarations*(ibuf: string): seq[CSSDeclaration] = - return parseDeclarations(tokenizeCSS(ibuf)) - -proc parseComponentValue(state: var CSSParseState): - DOMResult[CSSComponentValue] = - state.skipWhitespace() - if not state.has(): +proc parseComponentValue*(iq: openArray[char]): DOMResult[CSSComponentValue] = + let cvals = tokenizeCSS(iq) + var i = cvals.skipBlanks(0) + if i >= cvals.len: return errDOMException("Unexpected EOF", "SyntaxError") - let res = state.consumeComponentValue() - state.skipWhitespace() - if state.has(): + let res = cvals.consumeComponentValue(i) + if cvals.skipBlanks(i) < cvals.len: 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] = +proc parseComponentValues*(iq: openArray[char]): seq[CSSComponentValue] = + let cvals = tokenizeCSS(iq) result = @[] - while state.has(): - result.add(state.consumeComponentValue()) + var i = 0 + while i < cvals.len: + result.add(cvals.consumeComponentValue(i)) -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 = +proc nextCommaSepComponentValue(cvals: openArray[CSSComponentValue]; + s: out seq[CSSComponentValue]; i: var int): bool = s = @[] - while state.has(): - let cvl = state.consumeComponentValue() + while i < cvals.len: + let cvl = cvals.consumeComponentValue(i) if cvl == cttComma: break s.add(cvl) return s.len > 0 -iterator parseCommaSepComponentValues*(cvals: seq[CSSComponentValue]): +iterator parseCommaSepComponentValues*(cvals: openArray[CSSComponentValue]): seq[CSSComponentValue] = - var state = CSSParseState(tokens: cvals) + var i = 0 var s: seq[CSSComponentValue] - while state.nextCommaSepComponentValue(s): + while cvals.nextCommaSepComponentValue(s, i): yield move(s) -proc parseAnB*(state: var CSSParseState): Option[CSSAnB] = +proc parseAnB*(cvals: openArray[CSSComponentValue]; i: var int): + Opt[CSSAnB] = template is_eof: bool = - not state.has() or not (state.peek() of CSSToken) + i >= cvals.len or not (cvals[i] of CSSToken) template fail_eof = if is_eof: - return none(CSSAnB) + return err() template get_plus: bool = - let tok = state.peek() - if tok == cttDelim and CSSToken(tok).cvalue == '+': - discard state.consume() + let tok = cvals.getToken(i, cttDelim) + if tok.isSome and tok.get.cvalue == '+': + inc i true else: false template get_tok: CSSToken = - state.skipWhitespace() - fail_eof - CSSToken(state.consume()) - template get_tok_nows: CSSToken = - fail_eof - CSSToken(state.consume()) + i = cvals.skipBlanks(i) + ?cvals.consumeToken(i) template fail_plus = if is_plus: - return none(CSSAnB) + return err() template parse_sub_int(sub: string; skip: int): int32 = let s = sub.substr(skip) let x = parseInt32(s) if x.isNone: - return none(CSSAnB) + return err() x.get - template fail_non_integer(tok: CSSToken; res: Option[CSSAnB]) = + template fail_non_integer(tok: CSSToken; res: Opt[CSSAnB]) = if tok.t != cttINumber: - state.reconsume() + dec i return res if int64(tok.nvalue) > high(int): - state.reconsume() + dec i return res - template fail_non_signless_integer(tok: CSSToken; res: Option[CSSAnB]) = + template fail_non_signless_integer(tok: CSSToken; res: Opt[CSSAnB]) = fail_non_integer tok, res #TODO check if signless? - fail_eof - state.skipWhitespace() + i = cvals.skipBlanks(i) fail_eof let is_plus = get_plus - let tok = get_tok_nows + let tok = ?cvals.consumeToken(i) case tok.t of cttIdent: case tok.value of "odd": fail_plus - return some((2i32, 1i32)) + return ok((2i32, 1i32)) of "even": fail_plus - return some((2i32, 0i32)) + return ok((2i32, 0i32)) of "n", "N": - state.skipWhitespace() + i = cvals.skipBlanks(i) if is_eof: - return some((1i32, 0i32)) - let tok2 = get_tok_nows + return ok((1i32, 0i32)) + let tok2 = ?cvals.consumeToken(i) if tok2.t == cttDelim: let sign = case tok2.cvalue of '+': 1i32 of '-': -1i32 - else: return none(CSSAnB) + else: return err() let tok3 = get_tok - fail_non_signless_integer tok3, some((1i32, 0i32)) - return some((1i32, sign * int32(tok3.nvalue))) + fail_non_signless_integer tok3, ok((1i32, 0i32)) + return ok((1i32, sign * int32(tok3.nvalue))) else: - fail_non_integer tok2, some((1i32, 0i32)) - return some((1i32, int32(tok2.nvalue))) + fail_non_integer tok2, ok((1i32, 0i32)) + return ok((1i32, int32(tok2.nvalue))) of "-n", "-N": fail_plus - state.skipWhitespace() + i = cvals.skipBlanks(i) if is_eof: - return some((-1i32, 0i32)) - let tok2 = get_tok_nows + return ok((-1i32, 0i32)) + let tok2 = ?cvals.consumeToken(i) if tok2.t == cttDelim: let sign = case tok2.cvalue of '+': 1i32 of '-': -1i32 - else: return none(CSSAnB) + else: return err() let tok3 = get_tok - fail_non_signless_integer tok3, some((-1i32, 0i32)) - return some((-1i32, sign * int32(tok3.nvalue))) + fail_non_signless_integer tok3, ok((-1i32, 0i32)) + return ok((-1i32, sign * int32(tok3.nvalue))) else: - fail_non_integer tok2, some((-1i32, 0i32)) - return some((-1i32, int32(tok2.nvalue))) + fail_non_integer tok2, ok((-1i32, 0i32)) + return ok((-1i32, int32(tok2.nvalue))) of "n-", "N-": let tok2 = get_tok - fail_non_signless_integer tok2, none(CSSAnB) - return some((1i32, -int32(tok2.nvalue))) + fail_non_signless_integer tok2, err() + return ok((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))) + fail_non_signless_integer tok2, err() + return ok((-1i32, -int32(tok2.nvalue))) elif tok.value.startsWithIgnoreCase("n-"): - return some((1i32, -parse_sub_int(tok.value, "n-".len))) + return ok((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))) + return ok((-1i32, -parse_sub_int(tok.value, "n-".len))) else: - return none(CSSAnB) + return err() of cttINumber: fail_plus # <integer> - return some((0i32, int32(tok.nvalue))) + return ok((0i32, int32(tok.nvalue))) of cttIDimension: fail_plus case tok.unit of "n", "N": # <n-dimension> - state.skipWhitespace() + i = cvals.skipBlanks(i) if is_eof: - return some((int32(tok.nvalue), 0i32)) - let tok2 = get_tok_nows + return ok((int32(tok.nvalue), 0i32)) + let tok2 = ?cvals.consumeToken(i) if tok2.t == cttDelim: let sign = case tok2.cvalue of '+': 1i32 of '-': -1i32 - else: return none(CSSAnB) + else: return err() let tok3 = get_tok - fail_non_signless_integer tok3, some((int32(tok.nvalue), 0i32)) - return some((int32(tok.nvalue), sign * int32(tok3.nvalue))) + fail_non_signless_integer tok3, ok((int32(tok.nvalue), 0i32)) + return ok((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))) + fail_non_integer tok2, ok((int32(tok.nvalue), 0i32)) + return ok((int32(tok.nvalue), int32(tok2.nvalue))) of "n-", "N-": # <ndash-dimension> let tok2 = get_tok - fail_non_signless_integer tok2, none(CSSAnB) - return some((int32(tok.nvalue), -int32(tok2.nvalue))) + fail_non_signless_integer tok2, err() + return ok((int32(tok.nvalue), -int32(tok2.nvalue))) elif tok.unit.startsWithIgnoreCase("n-"): # <ndashdigit-dimension> - return some((int32(tok.nvalue), -parse_sub_int(tok.unit, "n-".len))) + return ok((int32(tok.nvalue), -parse_sub_int(tok.unit, "n-".len))) else: - return none(CSSAnB) + return err() 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) - -proc parseCSSString*(buf: openArray[char]; ending: char; n: var int): CSSToken = - return buf.consumeString(ending, n) + return err() diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index ed437f34..6ac078fe 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -1032,43 +1032,6 @@ func parseDimensionValues*(s: string): Option[CSSLength] = return some(CSSLength(num: n, u: clPerc)) return some(cssLength(n)) -func skipBlanks*(vals: openArray[CSSComponentValue]; i: int): int = - var i = i - while i < vals.len: - if vals[i] != cttWhitespace: - break - inc i - return i - -func findBlank(vals: openArray[CSSComponentValue]; i: int): int = - var i = i - while i < vals.len: - if vals[i] == cttWhitespace: - break - inc i - return i - -func getToken(cvals: openArray[CSSComponentValue]; i: int): Opt[CSSToken] = - if i < cvals.len: - let cval = cvals[i] - if cval of CSSToken: - return ok(CSSToken(cval)) - return err() - -func getToken(cvals: openArray[CSSComponentValue]; i: int; - tt: set[CSSTokenType]): Opt[CSSToken] = - let tok = ?cvals.getToken(i) - if tok.t in tt: - return ok(tok) - return err() - -func getToken(cvals: openArray[CSSComponentValue]; i: int; t: CSSTokenType): - Opt[CSSToken] = - let tok = ?cvals.getToken(i) - if t == tok.t: - return ok(tok) - return err() - func getColorToken(cvals: openArray[CSSComponentValue]; i: int; legacy = false): Opt[CSSToken] = let tok = ?cvals.getToken(i) diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim index 3ce44a91..00326721 100644 --- a/src/css/selectorparser.nim +++ b/src/css/selectorparser.nim @@ -279,7 +279,8 @@ proc parseRecursiveSelectorFunction(state: var SelectorParser; proc parseNthChild(state: var SelectorParser; cssfunction: CSSFunction; data: PseudoData): Selector = var data = data - var (anb, i) = parseAnB(cssfunction.value) + var i = 0 + var anb = cssfunction.value.parseAnB(i) if anb.isNone: fail data.anb = anb.get var nthchild = Selector(t: stPseudoClass, pseudo: data) diff --git a/src/css/sheet.nim b/src/css/sheet.nim index 438b3f77..5e3287a3 100644 --- a/src/css/sheet.nim +++ b/src/css/sheet.nim @@ -204,30 +204,29 @@ proc add*(sheet, sheet2: CSSStylesheet) = proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) = var sels = parseSelectors(rule.prelude) if sels.len > 0: - let decls = rule.oblock.value.parseDeclarations() - let rule = CSSRuleDef(sels: move(sels), idx: sheet.len) - for decl in decls: + let ruleDef = CSSRuleDef(sels: move(sels), idx: sheet.len) + for decl in rule.decls: if decl.name.startsWith("--"): let cvar = CSSVariable( name: decl.name.substr(2).toAtom(), cvals: decl.value ) if decl.important: - rule.importantVars.add(cvar) + ruleDef.importantVars.add(cvar) else: - rule.normalVars.add(cvar) + ruleDef.normalVars.add(cvar) else: if decl.important: - let olen = rule.importantVals.len - if rule.importantVals.parseComputedValues(decl.name, decl.value, + let olen = ruleDef.importantVals.len + if ruleDef.importantVals.parseComputedValues(decl.name, decl.value, sheet.attrs[]).isNone: - rule.importantVals.setLen(olen) + ruleDef.importantVals.setLen(olen) else: - let olen = rule.normalVals.len - if rule.normalVals.parseComputedValues(decl.name, decl.value, + let olen = ruleDef.normalVals.len + if ruleDef.normalVals.parseComputedValues(decl.name, decl.value, sheet.attrs[]).isNone: - rule.normalVals.setLen(olen) - sheet.add(rule) + ruleDef.normalVals.setLen(olen) + sheet.add(ruleDef) inc sheet.len proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = @@ -248,7 +247,7 @@ proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = elif atrule.name.equalsIgnoreCase("media"): if atrule.oblock != nil: let query = parseMediaQueryList(atrule.prelude, sheet.attrs) - let rules = atrule.oblock.value.parseListOfRules() + let rules = atrule.oblock.value.parseListOfRules(topLevel = false) if rules.len > 0: var media = CSSMediaQueryDef() media.children = newStylesheet(rules.len, sheet.attrs) @@ -264,9 +263,9 @@ proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = proc parseStylesheet*(ibuf: string; base: URL; attrs: ptr WindowAttributes): CSSStylesheet = - let raw = parseStylesheet(ibuf) - let sheet = newStylesheet(raw.value.len, attrs) - for v in raw.value: + let rules = parseListOfRules(ibuf, topLevel = true) + let sheet = newStylesheet(rules.len, attrs) + for v in rules: if v of CSSAtRule: sheet.addAtRule(CSSAtRule(v), base) else: |