Better parsing for the nth-child pseudo class.

This uses stricter (and more correct) rules in both the lexer and the parser.
The resulting AST has also received a small rework to make it more compact and
less confusing.
This commit is contained in:
Yorick Peterse 2014-10-06 23:52:46 +02:00
parent 60da2bdd3a
commit 16d66a7eb6
5 changed files with 210 additions and 52 deletions

View File

@ -138,9 +138,13 @@ module Oga
lbrack = '[' %{ add_token(:T_LBRACK) };
rbrack = ']' %{ add_token(:T_RBRACK) };
colon = ':' %{ add_token(:T_COLON) };
lparen = '(' %{ add_token(:T_LPAREN) };
rparen = ')' %{ add_token(:T_RPAREN) };
lparen = '(';
rparen = ')';
pipe = '|';
odd = 'odd';
even = 'even';
minus = '-';
nth = 'n';
# Identifiers
#
@ -208,23 +212,51 @@ module Oga
# "-1" and "+1" are handled by the `integer` type and the corresponding
# `emit_integer` action.
nth_integer = integer 'n';
nth_identifier = '+n' | '-n';
#nth_integer = integer 'n';
#nth_identifier = '+n' | '-n';
action emit_nth_integer {
value = slice_input(ts, te - 1).to_i
#action emit_nth_integer {
# value = slice_input(ts, te - 1).to_i
add_token(:T_INT, value)
add_token(:T_NTH, 'n')
# add_token(:T_INT, value)
# add_token(:T_NTH, nil)
#}
#action emit_nth_identifier {
# emit(:T_NTH, ts, te)
#}
# Pseudo Classes
#
# http://www.w3.org/TR/css3-selectors/#structural-pseudos
action emit_lparen {
add_token(:T_LPAREN)
fnext pseudo_args;
}
action emit_nth_identifier {
emit(:T_NTH, ts, te)
action emit_rparen {
add_token(:T_RPAREN)
fnext main;
}
# Machine used for processing the arguments of a pseudo class. These are
# handled in a separate machine as these arguments can contain data not
# allowed elsewhere. For example, "2n" is not allowed to appear outside
# of the arguments list.
pseudo_args := |*
nth => { add_token(:T_NTH) };
minus => { add_token(:T_MINUS) };
odd => { add_token(:T_ODD) };
even => { add_token(:T_EVEN) };
integer => emit_integer;
rparen => emit_rparen;
*|;
main := |*
whitespace | comma | hash | dot | lbrack | rbrack | colon;
lparen | rparen;
# Some of the operators have similar characters (e.g. the "="). As a
# result we can't use rules like the following:
@ -249,11 +281,10 @@ module Oga
# this is handled separately.
pipe => { add_token(:T_PIPE) };
identifier => emit_identifier;
nth_integer => emit_nth_integer;
nth_identifier => emit_nth_identifier;
integer => emit_integer;
string => emit_string;
lparen => emit_lparen;
identifier => emit_identifier;
integer => emit_integer;
string => emit_string;
any;
*|;

View File

@ -104,6 +104,24 @@ rule
pseudo_arg
: integer
| odd
| even
| nth
;
odd
: T_ODD { s(:odd) }
;
even
: T_EVEN { s(:even) }
;
nth
: T_NTH { s(:nth) }
| T_MINUS T_NTH { s(:nth) }
| integer T_NTH { s(:nth, val[0]) }
| integer T_NTH integer { s(:nth, val[0], val[2]) }
;
string

View File

@ -1,29 +0,0 @@
require 'spec_helper'
describe Oga::CSS::Lexer do
context 'nth numbers' do
example 'lex the 2n number' do
lex_css('2n').should == [[:T_INT, 2], [:T_NTH, 'n']]
end
example 'lex the 2n+1 number' do
lex_css('2n+1').should == [[:T_INT, 2], [:T_NTH, 'n'], [:T_INT, 1]]
end
example 'lex the 2n-1 number' do
lex_css('2n-1').should == [[:T_INT, 2], [:T_NTH, 'n'], [:T_INT, -1]]
end
example 'lex the 2n -1 number' do
lex_css('2n -1').should == [[:T_INT, 2], [:T_NTH, 'n'], [:T_INT, -1]]
end
example 'lex the -n number' do
lex_css('-n').should == [[:T_NTH, '-n']]
end
example 'lex the +n number' do
lex_css('+n').should == [[:T_NTH, '+n']]
end
end
end

View File

@ -19,26 +19,92 @@ describe Oga::CSS::Lexer do
]
end
example 'lex the :nth-child pseudo class using "odd" as an argument' do
example 'lex the :nth-child(odd) pseudo class' do
lex_css(':nth-child(odd)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_IDENT, 'odd'],
[:T_ODD, nil],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child pseudo class using "2n+1" as an argument' do
example 'lex the :nth-child(even) pseudo class' do
lex_css(':nth-child(even)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_EVEN, nil],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(n) pseudo class' do
lex_css(':nth-child(n)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_NTH, nil],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(-n) pseudo class' do
lex_css(':nth-child(-n)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_MINUS, nil],
[:T_NTH, nil],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(2n) pseudo class' do
lex_css(':nth-child(2n)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_INT, 2],
[:T_NTH, nil],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(2n+1) pseudo class' do
lex_css(':nth-child(2n+1)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_INT, 2],
[:T_NTH, 'n'],
[:T_NTH, nil],
[:T_INT, 1],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(2n-1) pseudo class' do
lex_css(':nth-child(2n-1)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_INT, 2],
[:T_NTH, nil],
[:T_INT, -1],
[:T_RPAREN, nil]
]
end
example 'lex the :nth-child(-2n-1) pseudo class' do
lex_css(':nth-child(-2n-1)').should == [
[:T_COLON, nil],
[:T_IDENT, 'nth-child'],
[:T_LPAREN, nil],
[:T_INT, -2],
[:T_NTH, nil],
[:T_INT, -1],
[:T_RPAREN, nil]
]
end
end
end

View File

@ -6,12 +6,84 @@ describe Oga::CSS::Parser do
parse_css('x:root').should == s(:pseudo, 'root', s(:test, nil, 'x'))
end
example 'parse the :nth-child pseudo class' do
parse_css('x:nth-child(2)').should == s(
example 'parse the x:nth-child pseudo class' do
parse_css('x:nth-child(1)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:int, 2)
s(:int, 1)
)
end
example 'parse the x:nth-child(odd) pseudo class' do
parse_css('x:nth-child(odd)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:odd)
)
end
example 'parse the x:nth-child(even) pseudo class' do
parse_css('x:nth-child(even)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:even)
)
end
example 'parse the x:nth-child(n) pseudo class' do
parse_css('x:nth-child(n)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth)
)
end
example 'parse the x:nth-child(-n) pseudo class' do
parse_css('x:nth-child(-n)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth)
)
end
example 'parse the x:nth-child(2n) pseudo class' do
parse_css('x:nth-child(2n)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth, s(:int, 2))
)
end
example 'parse the x:nth-child(2n+1) pseudo class' do
parse_css('x:nth-child(2n+1)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth, s(:int, 2), s(:int, 1))
)
end
example 'parse the x:nth-child(2n-1) pseudo class' do
parse_css('x:nth-child(2n-1)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth, s(:int, 2), s(:int, -1))
)
end
example 'parse the x:nth-child(-2n-1) pseudo class' do
parse_css('x:nth-child(-2n-1)').should == s(
:pseudo,
'nth-child',
s(:test, nil, 'x'),
s(:nth, s(:int, -2), s(:int, -1))
)
end
end