ruby/test/racc/assets/csspool.y

730 строки
21 KiB
Plaintext

class CSSPool::CSS::Parser
token CHARSET_SYM IMPORT_SYM STRING SEMI IDENT S COMMA LBRACE RBRACE STAR HASH
token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH LPAREN RPAREN FUNCTION GREATER PLUS
token SLASH NUMBER MINUS LENGTH PERCENTAGE ANGLE TIME FREQ URI
token IMPORTANT_SYM MEDIA_SYM NOT ONLY AND NTH_PSEUDO_CLASS
token DOCUMENT_QUERY_SYM FUNCTION_NO_QUOTE
token TILDE
token PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH
token NOT_PSEUDO_CLASS
token KEYFRAMES_SYM
token MATCHES_PSEUDO_CLASS
token NAMESPACE_SYM
token MOZ_PSEUDO_ELEMENT
token RESOLUTION
token COLON
token SUPPORTS_SYM
token OR
token VARIABLE_NAME
token CALC_SYM
token FONTFACE_SYM
token UNICODE_RANGE
token RATIO
rule
document
: { @handler.start_document }
stylesheet
{ @handler.end_document }
;
stylesheet
: charset stylesheet
| import stylesheet
| namespace stylesheet
| charset
| import
| namespace
| body
|
;
charset
: CHARSET_SYM STRING SEMI { @handler.charset interpret_string(val[1]), {} }
;
import
: IMPORT_SYM import_location medium SEMI {
@handler.import_style val[2], val[1]
}
| IMPORT_SYM import_location SEMI {
@handler.import_style [], val[1]
}
;
import_location
: import_location S
| STRING { result = Terms::String.new interpret_string val.first }
| URI { result = Terms::URI.new interpret_uri val.first }
;
namespace
: NAMESPACE_SYM ident import_location SEMI {
@handler.namespace val[1], val[2]
}
| NAMESPACE_SYM import_location SEMI {
@handler.namespace nil, val[1]
}
;
medium
: medium COMMA IDENT {
result = val[0] << MediaType.new(val[2])
}
| IDENT {
result = [MediaType.new(val[0])]
}
;
media_query_list
: media_query { result = MediaQueryList.new([ val[0] ]) }
| media_query_list COMMA media_query { result = val[0] << val[2] }
| { result = MediaQueryList.new }
;
media_query
: optional_only_or_not media_type optional_and_exprs { result = MediaQuery.new(val[0], val[1], val[2]) }
| media_expr optional_and_exprs { result = MediaQuery.new(nil, val[0], val[1]) }
;
optional_only_or_not
: ONLY { result = :only }
| NOT { result = :not }
| { result = nil }
;
media_type
: IDENT { result = MediaType.new(val[0]) }
;
media_expr
: LPAREN optional_space IDENT optional_space RPAREN { result = MediaType.new(val[2]) }
| LPAREN optional_space IDENT optional_space COLON optional_space expr RPAREN { result = MediaFeature.new(val[2], val[6][0]) }
;
optional_space
: S { result = val[0] }
| { result = nil }
;
optional_and_exprs
: optional_and_exprs AND media_expr { result = val[0] << val[2] }
| { result = [] }
;
resolution
: RESOLUTION {
unit = val.first.gsub(/[\s\d.]/, '')
number = numeric(val.first)
result = Terms::Resolution.new(number, unit)
}
;
body
: ruleset body
| conditional_rule body
| keyframes_rule body
| fontface_rule body
| ruleset
| conditional_rule
| keyframes_rule
| fontface_rule
;
conditional_rule
: media
| document_query
| supports
;
body_in_media
: body
| empty_ruleset
;
media
: start_media body_in_media RBRACE { @handler.end_media val.first }
;
start_media
: MEDIA_SYM media_query_list LBRACE {
result = val[1]
@handler.start_media result
}
;
document_query
: start_document_query body RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) }
| start_document_query RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) }
;
start_document_query
: start_document_query_pos url_match_fns LBRACE {
@handler.start_document_query(val[1], after_pos(val))
}
;
start_document_query_pos
: DOCUMENT_QUERY_SYM {
@handler.node_start_pos = before_pos(val)
}
;
url_match_fns
: url_match_fn COMMA url_match_fns {
result = [val[0], val[2]].flatten
}
| url_match_fn {
result = val
}
;
url_match_fn
: function_no_quote
| function
| uri
;
supports
: start_supports body RBRACE { @handler.end_supports }
| start_supports RBRACE { @handler.end_supports }
;
start_supports
: SUPPORTS_SYM supports_condition_root LBRACE {
@handler.start_supports val[1]
}
;
supports_condition_root
: supports_negation { result = val.join('') }
| supports_conjunction_or_disjunction { result = val.join('') }
| supports_condition_in_parens { result = val.join('') }
;
supports_condition
: supports_negation { result = val.join('') }
| supports_conjunction_or_disjunction { result = val.join('') }
| supports_condition_in_parens { result = val.join('') }
;
supports_condition_in_parens
: LPAREN supports_condition RPAREN { result = val.join('') }
| supports_declaration_condition { result = val.join('') }
;
supports_negation
: NOT supports_condition_in_parens { result = val.join('') }
;
supports_conjunction_or_disjunction
: supports_conjunction
| supports_disjunction
;
supports_conjunction
: supports_condition_in_parens AND supports_condition_in_parens { result = val.join('') }
| supports_conjunction_or_disjunction AND supports_condition_in_parens { result = val.join('') }
;
supports_disjunction
: supports_condition_in_parens OR supports_condition_in_parens { result = val.join('') }
| supports_conjunction_or_disjunction OR supports_condition_in_parens { result = val.join('') }
;
supports_declaration_condition
: LPAREN declaration_internal RPAREN { result = val.join('') }
| LPAREN S declaration_internal RPAREN { result = val.join('') }
;
keyframes_rule
: start_keyframes_rule keyframes_blocks RBRACE
| start_keyframes_rule RBRACE
;
start_keyframes_rule
: KEYFRAMES_SYM IDENT LBRACE {
@handler.start_keyframes_rule val[1]
}
;
keyframes_blocks
: keyframes_block keyframes_blocks
| keyframes_block
;
keyframes_block
: start_keyframes_block declarations RBRACE { @handler.end_keyframes_block }
| start_keyframes_block RBRACE { @handler.end_keyframes_block }
;
start_keyframes_block
: keyframes_selectors LBRACE {
@handler.start_keyframes_block val[0]
}
;
keyframes_selectors
| keyframes_selector COMMA keyframes_selectors {
result = val[0] + ', ' + val[2]
}
| keyframes_selector
;
keyframes_selector
: IDENT
| PERCENTAGE { result = val[0].strip }
;
fontface_rule
: start_fontface_rule declarations RBRACE { @handler.end_fontface_rule }
| start_fontface_rule RBRACE { @handler.end_fontface_rule }
;
start_fontface_rule
: FONTFACE_SYM LBRACE {
@handler.start_fontface_rule
}
;
ruleset
: start_selector declarations RBRACE {
@handler.end_selector val.first
}
| start_selector RBRACE {
@handler.end_selector val.first
}
;
empty_ruleset
: optional_space {
start = @handler.start_selector([])
@handler.end_selector(start)
}
;
start_selector
: S start_selector { result = val.last }
| selectors LBRACE {
@handler.start_selector val.first
}
;
selectors
: selector COMMA selectors
{
sel = Selector.new(val.first, {})
result = [sel].concat(val[2])
}
| selector
{
result = [Selector.new(val.first, {})]
}
;
selector
: simple_selector combinator selector
{
val.flatten!
val[2].combinator = val.delete_at 1
result = val
}
| simple_selector
;
combinator
: S { result = :s }
| GREATER { result = :> }
| PLUS { result = :+ }
| TILDE { result = :~ }
;
simple_selector
: element_name hcap {
selector = val.first
selector.additional_selectors = val.last
result = [selector]
}
| element_name { result = val }
| hcap
{
ss = Selectors::Simple.new nil, nil
ss.additional_selectors = val.flatten
result = [ss]
}
;
simple_selectors
: simple_selector COMMA simple_selectors { result = [val[0], val[2]].flatten }
| simple_selector
;
ident_with_namespace
: IDENT { result = [interpret_identifier(val[0]), nil] }
| IDENT '|' IDENT { result = [interpret_identifier(val[2]), interpret_identifier(val[0])] }
| '|' IDENT { result = [interpret_identifier(val[1]), nil] }
| STAR '|' IDENT { result = [interpret_identifier(val[2]), '*'] }
;
element_name
: ident_with_namespace { result = Selectors::Type.new val.first[0], nil, val.first[1] }
| STAR { result = Selectors::Universal.new val.first }
| '|' STAR { result = Selectors::Universal.new val[1] }
| STAR '|' STAR { result = Selectors::Universal.new val[2], nil, val[0] }
| IDENT '|' STAR { result = Selectors::Universal.new val[2], nil, interpret_identifier(val[0]) }
;
hcap
: hash { result = val }
| class { result = val }
| attrib { result = val }
| pseudo { result = val }
| hash hcap { result = val.flatten }
| class hcap { result = val.flatten }
| attrib hcap { result = val.flatten }
| pseudo hcap { result = val.flatten }
;
hash
: HASH {
result = Selectors::Id.new interpret_identifier val.first.sub(/^#/, '')
}
class
: '.' IDENT {
result = Selectors::Class.new interpret_identifier val.last
}
;
attrib
: LSQUARE ident_with_namespace EQUAL IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::EQUALS,
val[1][1]
)
}
| LSQUARE ident_with_namespace EQUAL STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::EQUALS,
val[1][1]
)
}
| LSQUARE ident_with_namespace INCLUDES STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::INCLUDES,
val[1][1]
)
}
| LSQUARE ident_with_namespace INCLUDES IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::INCLUDES,
val[1][1]
)
}
| LSQUARE ident_with_namespace DASHMATCH IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::DASHMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace DASHMATCH STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::DASHMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace PREFIXMATCH IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::PREFIXMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace PREFIXMATCH STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::PREFIXMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace SUFFIXMATCH IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::SUFFIXMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace SUFFIXMATCH STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::SUFFIXMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace SUBSTRINGMATCH IDENT RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_identifier(val[3]),
Selectors::Attribute::SUBSTRINGMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace SUBSTRINGMATCH STRING RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
interpret_string(val[3]),
Selectors::Attribute::SUBSTRINGMATCH,
val[1][1]
)
}
| LSQUARE ident_with_namespace RSQUARE {
result = Selectors::Attribute.new(
val[1][0],
nil,
Selectors::Attribute::SET,
val[1][1]
)
}
;
pseudo
: COLON IDENT {
result = Selectors::pseudo interpret_identifier(val[1])
}
| COLON COLON IDENT {
result = Selectors::PseudoElement.new(
interpret_identifier(val[2])
)
}
| COLON FUNCTION RPAREN {
result = Selectors::PseudoClass.new(
interpret_identifier(val[1].sub(/\($/, '')),
''
)
}
| COLON FUNCTION IDENT RPAREN {
result = Selectors::PseudoClass.new(
interpret_identifier(val[1].sub(/\($/, '')),
interpret_identifier(val[2])
)
}
| COLON NOT_PSEUDO_CLASS simple_selector RPAREN {
result = Selectors::PseudoClass.new(
'not',
val[2].first.to_s
)
}
| COLON NTH_PSEUDO_CLASS {
result = Selectors::PseudoClass.new(
interpret_identifier(val[1].sub(/\(.*/, '')),
interpret_identifier(val[1].sub(/.*\(/, '').sub(/\).*/, ''))
)
}
| COLON MATCHES_PSEUDO_CLASS simple_selectors RPAREN {
result = Selectors::PseudoClass.new(
val[1].split('(').first.strip,
val[2].join(', ')
)
}
| COLON MOZ_PSEUDO_ELEMENT optional_space any_number_of_idents optional_space RPAREN {
result = Selectors::PseudoElement.new(
interpret_identifier(val[1].sub(/\($/, ''))
)
}
| COLON COLON MOZ_PSEUDO_ELEMENT optional_space any_number_of_idents optional_space RPAREN {
result = Selectors::PseudoElement.new(
interpret_identifier(val[2].sub(/\($/, ''))
)
}
;
any_number_of_idents
:
| multiple_idents
;
multiple_idents
: IDENT
| IDENT COMMA multiple_idents
;
# declarations can be separated by one *or more* semicolons. semi-colons at the start or end of a ruleset are also allowed
one_or_more_semis
: SEMI
| SEMI one_or_more_semis
;
declarations
: declaration one_or_more_semis declarations
| one_or_more_semis declarations
| declaration one_or_more_semis
| declaration
| one_or_more_semis
;
declaration
: declaration_internal { @handler.property val.first }
;
declaration_internal
: property COLON expr prio
{ result = Declaration.new(val.first, val[2], val[3]) }
| property COLON S expr prio
{ result = Declaration.new(val.first, val[3], val[4]) }
| property S COLON expr prio
{ result = Declaration.new(val.first, val[3], val[4]) }
| property S COLON S expr prio
{ result = Declaration.new(val.first, val[4], val[5]) }
;
prio
: IMPORTANT_SYM { result = true }
| { result = false }
;
property
: IDENT { result = interpret_identifier val[0] }
| STAR IDENT { result = interpret_identifier val.join }
| VARIABLE_NAME { result = interpret_identifier val[0] }
;
operator
: COMMA
| SLASH
| EQUAL
;
expr
: term operator expr {
result = [val.first, val.last].flatten
val.last.first.operator = val[1]
}
| term expr { result = val.flatten }
| term { result = val }
;
term
: ident
| ratio
| numeric
| string
| uri
| hexcolor
| calc
| function
| resolution
| VARIABLE_NAME
| uranges
;
function
: function S { result = val.first }
| FUNCTION expr RPAREN {
name = interpret_identifier val.first.sub(/\($/, '')
if name == 'rgb'
result = Terms::Rgb.new(*val[1])
else
result = Terms::Function.new name, val[1]
end
}
| FUNCTION RPAREN {
name = interpret_identifier val.first.sub(/\($/, '')
result = Terms::Function.new name
}
;
function_no_quote
: function_no_quote S { result = val.first }
| FUNCTION_NO_QUOTE {
parts = val.first.split('(')
name = interpret_identifier parts.first
result = Terms::Function.new(name, [Terms::String.new(interpret_string_no_quote(parts.last))])
}
;
uranges
: UNICODE_RANGE COMMA uranges
| UNICODE_RANGE
;
calc
: CALC_SYM calc_sum RPAREN optional_space {
result = Terms::Math.new(val.first.split('(').first, val[1])
}
;
# plus and minus are supposed to have whitespace around them, per http://dev.w3.org/csswg/css-values/#calc-syntax, but the numbers are eating trailing whitespace, so we inject it back in
calc_sum
: calc_product
| calc_product PLUS calc_sum { val.insert(1, ' '); result = val.join('') }
| calc_product MINUS calc_sum { val.insert(1, ' '); result = val.join('') }
;
calc_product
: calc_value
| calc_value optional_space STAR calc_value { result = val.join('') }
| calc_value optional_space SLASH calc_value { result = val.join('') }
;
calc_value
: numeric { result = val.join('') }
| function { result = val.join('') } # for var() variable references
| LPAREN calc_sum RPAREN { result = val.join('') }
;
hexcolor
: hexcolor S { result = val.first }
| HASH { result = Terms::Hash.new val.first.sub(/^#/, '') }
;
uri
: uri S { result = val.first }
| URI { result = Terms::URI.new interpret_uri val.first }
;
string
: string S { result = val.first }
| STRING { result = Terms::String.new interpret_string val.first }
;
numeric
: unary_operator numeric {
result = val[1]
val[1].unary_operator = val.first
}
| NUMBER {
result = Terms::Number.new numeric val.first
}
| PERCENTAGE {
result = Terms::Number.new numeric(val.first), nil, '%'
}
| LENGTH {
unit = val.first.gsub(/[\s\d.]/, '')
result = Terms::Number.new numeric(val.first), nil, unit
}
| ANGLE {
unit = val.first.gsub(/[\s\d.]/, '')
result = Terms::Number.new numeric(val.first), nil, unit
}
| TIME {
unit = val.first.gsub(/[\s\d.]/, '')
result = Terms::Number.new numeric(val.first), nil, unit
}
| FREQ {
unit = val.first.gsub(/[\s\d.]/, '')
result = Terms::Number.new numeric(val.first), nil, unit
}
;
ratio
: RATIO {
result = Terms::Ratio.new(val[0], val[1])
}
;
unary_operator
: MINUS { result = :minus }
| PLUS { result = :plus }
;
ident
: ident S { result = val.first }
| IDENT { result = Terms::Ident.new interpret_identifier val.first }
;
---- inner
def numeric thing
thing = thing.gsub(/[^\d.]/, '')
Integer(thing) rescue Float(thing)
end
def interpret_identifier s
interpret_escapes s
end
def interpret_uri s
interpret_escapes s.match(/^url\((.*)\)$/mui)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
end
def interpret_string_no_quote s
interpret_escapes s.match(/^(.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
end
def interpret_string s
interpret_escapes s.match(/^(['"])((?:\\.|.)*)\1$/mu)[2]
end
def interpret_escapes s
token_exp = /\\(?:([0-9a-fA-F]{1,6}(?:\r\n|\s)?)|(.))/mu
return s.gsub(token_exp) do |escape_sequence|
if !$1.nil?
code = $1.chomp.to_i 16
code = 0xFFFD if code > 0x10FFFF
next [code].pack('U')
end
next '' if $2 == "\n"
next $2
end
end
# override racc's on_error so we can have context in our error messages
def on_error(t, val, vstack)
errcontext = (@ss.pre_match[-10..-1] || @ss.pre_match) +
@ss.matched + @ss.post_match[0..9]
line_number = @ss.pre_match.lines.count
raise ParseError, sprintf("parse error on value %s (%s) " +
"on line %s around \"%s\"",
val.inspect, token_to_str(t) || '?',
line_number, errcontext)
end
def before_pos(val)
# don't include leading whitespace
return current_pos - val.last.length + val.last[/\A\s*/].size
end
def after_pos(val)
# don't include trailing whitespace
return current_pos - val.last[/\s*\z/].size
end
# charpos will work with multibyte strings but is not available until ruby 2
def current_pos
@ss.respond_to?('charpos') ? @ss.charpos : @ss.pos
end