Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions css/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ type Lexer struct {
r *parse.Input
}

func CloneLexer(l *Lexer) *Lexer {
rr := parse.CloneInput(l.r)
return &Lexer{
r: rr,
}
}

// NewLexer returns a new Lexer for a given io.Reader.
func NewLexer(r *parse.Input) *Lexer {
return &Lexer{
Expand Down
57 changes: 54 additions & 3 deletions css/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ func (tt GrammarType) String() string {

////////////////////////////////////////////////////////////////

func isCombinator(data byte) bool {
return data == ',' || data == '/' || data == ':' || data == '!' || data == '='
}

////////////////////////////////////////////////////////////////

// State is the state function the parser currently is in.
type State func(*Parser) GrammarType

Expand Down Expand Up @@ -147,6 +153,22 @@ func (p *Parser) Values() []Token {
return p.buf
}

func (p *Parser) CloneParser() *Parser {
return &Parser{
l: CloneLexer(p.l),
state: append([]State{}, p.state...),
err: p.err,
buf: append([]Token{}, p.buf...),
data: append([]byte{}, p.data...),
tt: p.tt,
keepWS: p.keepWS,
prevWS: p.prevWS,
prevEnd: p.prevEnd,
prevComment: p.prevComment,
level: p.level,
}
}

func (p *Parser) popToken(allowComment bool) (TokenType, []byte) {
p.prevWS = false
p.prevComment = false
Expand Down Expand Up @@ -207,12 +229,41 @@ func (p *Parser) parseDeclarationList() GrammarType {
return ErrorGrammar
} else if p.tt == AtKeywordToken {
return p.parseAtRule()
} else if p.tt == IdentToken || p.tt == DelimToken {
return p.parseDeclaration()
} else if p.tt == CustomPropertyNameToken {
return p.parseCustomProperty()
}

pp := p.CloneParser()
// peek until we find a colon or semicolon or data length is 1 and is a combinator ->
// not a child rule, but a declaration
isDeclaration := true
for pp.tt != ErrorToken {
if pp.tt == SemicolonToken || pp.tt == RightBraceToken {
isDeclaration = true
break
} else if pp.tt == LeftBraceToken {
isDeclaration = false
break
}
if len(pp.data) == 1 {
if pp.data[0] == '&' {
isDeclaration = false
break
}
if isCombinator(pp.data[0]) {
isDeclaration = true
break
}
}
pp.tt, pp.data = pp.popToken(false)
}

if !isDeclaration {
return p.parseQualifiedRule()
} else if p.tt == IdentToken || p.tt == DelimToken {
return p.parseDeclaration()
}

// parse error
p.initBuf()
p.l.r.Move(-len(p.data))
Expand Down Expand Up @@ -426,7 +477,7 @@ func (p *Parser) parseDeclaration() GrammarType {
}
p.level--
}
if len(data) == 1 && (data[0] == ',' || data[0] == '/' || data[0] == ':' || data[0] == '!' || data[0] == '=') {
if len(data) == 1 && isCombinator(data[0]) {
skipWS = true
} else if (p.prevWS || p.prevComment) && !skipWS {
p.pushBuf(WhitespaceToken, wsBytes)
Expand Down
4 changes: 3 additions & 1 deletion css/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func TestParse(t *testing.T) {
{false, "@media { @viewport ; }", "@media{@viewport;}"},
{false, "@keyframes 'diagonal-slide' { from { left: 0; top: 0; } to { left: 100px; top: 100px; } }", "@keyframes 'diagonal-slide'{from{left:0;top:0;}to{left:100px;top:100px;}}"},
{false, "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}", "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}"},
{false, "a { &:hover { color: #f4a; } }", "a{&:hover{color:#f4a;}}"},
{false, ".foo { .bar > &:hover span { backgroud: orange } ; }", ".foo{.bar>&:hover span{backgroud:orange;}}"},
{false, ".foo { color: #fff;}", ".foo{color:#fff;}"},
{false, ".foo { ; _color: #fff;}", ".foo{_color:#fff;}"},
{false, "a { color: red; border: 0; }", "a{color:red;border:0;}"},
Expand Down Expand Up @@ -81,7 +83,7 @@ func TestParse(t *testing.T) {
{false, "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}", "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}"},
{false, "@media { @viewport }", "@media{@viewport;}"},
{false, "table { @unknown }", "table{@unknown;}"},
{false, "a{@media{width:70%;} b{width:60%;}}", "a{@media{ERROR(width:70%;})ERROR(b{width:60%;})}"},
{false, "a{@media{width:70%;} b{width:60%;}}", "a{@media{ERROR(width:70%;})b{width:60%;}}"},

// early endings
{false, "selector{", "selector{"},
Expand Down
9 changes: 9 additions & 0 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ func NewInputBytes(b []byte) *Input {
return z
}

func CloneInput(z *Input) *Input {
return &Input{
buf: append([]byte{}, z.buf...),
pos: z.pos,
start: z.start,
err: z.err,
}
}

// Restore restores the replaced byte past the end of the buffer by NULL.
func (z *Input) Restore() {
if z.restore != nil {
Expand Down