2009-12-20 01:13:38 -08:00
|
|
|
require 'strscan'
|
|
|
|
require 'rx'
|
|
|
|
|
|
|
|
module Sass
|
|
|
|
module SCSS
|
|
|
|
class Parser
|
|
|
|
def initialize(str)
|
|
|
|
@scanner = StringScanner.new(str)
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse
|
2009-12-20 05:45:58 -08:00
|
|
|
stylesheet
|
|
|
|
raise "Invalid CSS!" unless @scanner.eos?
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def stylesheet
|
2009-12-20 05:45:58 -08:00
|
|
|
if tok :charset
|
|
|
|
ss
|
|
|
|
tok! :string; ss
|
|
|
|
raw! ';'
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
s
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
s while import
|
|
|
|
s while namespace
|
|
|
|
s while ruleset || media || page || font_face
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def s
|
2009-12-20 05:45:58 -08:00
|
|
|
nil while tok(:s) || tok(:cdc) || tok(:cdo)
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def ss
|
2009-12-20 05:45:58 -08:00
|
|
|
nil while tok :s
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def import
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok(:import)
|
|
|
|
ss
|
|
|
|
tok(:string) || tok!(:uri); ss
|
|
|
|
if medium
|
|
|
|
while raw ','
|
|
|
|
ss; expr! :medium
|
|
|
|
end
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ';'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def namespace
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok(:namespace)
|
|
|
|
ss
|
|
|
|
ss if namespace_prefix
|
|
|
|
tok(:string) || tok!(:uri); ss
|
|
|
|
raw! ';'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def namespace_prefix
|
|
|
|
tok :ident
|
|
|
|
end
|
|
|
|
|
|
|
|
def media
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :media
|
|
|
|
ss
|
|
|
|
expr! :medium
|
|
|
|
while raw ','
|
|
|
|
ss; expr! :medium
|
|
|
|
end
|
|
|
|
|
|
|
|
raw! '{'; ss
|
|
|
|
nil while ruleset
|
|
|
|
raw! '}'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def medium
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :ident
|
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def page
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :page
|
|
|
|
ss
|
|
|
|
tok :ident
|
|
|
|
pseudo_page; ss
|
|
|
|
raw! '{'; ss
|
|
|
|
expr! :declaration
|
|
|
|
while raw ';'
|
|
|
|
ss; expr! :declaration
|
|
|
|
end
|
|
|
|
raw! '}'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def pseudo_page
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw ':'
|
|
|
|
tok! :ident
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def font_face
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :font_face
|
|
|
|
ss
|
|
|
|
raw! '{'; ss
|
|
|
|
expr! :declaration
|
|
|
|
while raw ';'
|
|
|
|
ss; expr! :declaration
|
|
|
|
end
|
|
|
|
raw! '}'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def operator
|
2009-12-20 05:45:58 -08:00
|
|
|
ss if raw('/') || raw(',')
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def unary_operator
|
2009-12-20 05:45:58 -08:00
|
|
|
raw('-') || raw('+')
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def property
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :ident
|
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def ruleset
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless selector
|
|
|
|
while raw ','
|
|
|
|
ss; expr! :selector
|
|
|
|
end
|
|
|
|
raw! '{'; ss
|
|
|
|
expr! :declaration
|
|
|
|
while raw ';'
|
|
|
|
ss; expr! :declaration
|
|
|
|
end
|
|
|
|
raw! '}'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def selector
|
2009-12-20 03:57:33 -08:00
|
|
|
return unless simple_selector_sequence
|
|
|
|
simple_selector_sequence while combinator
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def combinator
|
|
|
|
tok(:plus) || tok(:greater) || tok(:tilde) || tok(:s)
|
|
|
|
end
|
|
|
|
|
|
|
|
def simple_selector_sequence
|
|
|
|
return unless element_name || tok(:hash) || class_expr ||
|
|
|
|
attrib || negation || pseudo
|
|
|
|
nil while tok(:hash) || class_expr || attrib || negation || pseudo
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def class_expr
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw '.'
|
|
|
|
tok! :ident
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def element_name
|
2009-12-20 03:57:33 -08:00
|
|
|
res = tok(:ident) || raw('*')
|
|
|
|
res = tok(:ident) || raw!('*') if raw '|'
|
|
|
|
res
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def attrib
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw('[')
|
|
|
|
ss
|
2009-12-20 03:57:33 -08:00
|
|
|
attrib_name!; ss
|
2009-12-20 05:45:58 -08:00
|
|
|
if raw('=') ||
|
|
|
|
tok(:includes) ||
|
|
|
|
tok(:dashmatch) ||
|
|
|
|
tok(:prefixmatch) ||
|
|
|
|
tok(:suffixmatch) ||
|
2009-12-20 01:13:38 -08:00
|
|
|
tok(:substringmatch)
|
|
|
|
ss
|
2009-12-20 05:45:58 -08:00
|
|
|
tok(:ident) || tok!(:string); ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ']'
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def attrib_name!
|
|
|
|
if tok(:ident)
|
|
|
|
# E or E|E
|
|
|
|
tok! :ident if raw('|')
|
|
|
|
elsif raw('*')
|
|
|
|
# *|E
|
|
|
|
raw! '|'
|
|
|
|
tok! :ident
|
|
|
|
else
|
|
|
|
# |E or E
|
|
|
|
raw '|'
|
|
|
|
tok! :ident
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
def pseudo
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw ':'
|
2009-12-20 03:57:33 -08:00
|
|
|
raw ':'
|
|
|
|
|
|
|
|
functional_pseudo || tok!(:ident)
|
|
|
|
end
|
|
|
|
|
|
|
|
def functional_pseudo
|
|
|
|
return unless tok :function
|
|
|
|
ss
|
|
|
|
expr! :pseudo_expr
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ')'
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def pseudo_expr
|
|
|
|
return unless tok(:plus) || raw('-') || tok(:number) ||
|
|
|
|
tok(:string) || tok(:ident)
|
|
|
|
ss
|
|
|
|
ss while tok(:plus) || raw('-') || tok(:number) ||
|
|
|
|
tok(:string) || tok(:ident)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def negation
|
|
|
|
return unless tok(:not)
|
|
|
|
ss
|
|
|
|
element_name || tok(:hash) || class_expr || attrib || expr!(:pseudo)
|
|
|
|
end
|
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
def declaration
|
2009-12-20 05:45:58 -08:00
|
|
|
return true unless property
|
|
|
|
raw! ':'; ss
|
|
|
|
expr! :expr
|
|
|
|
prio
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def prio
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :important
|
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def expr
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless term
|
|
|
|
nil while operator && term
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def term
|
2009-12-20 05:45:58 -08:00
|
|
|
if unary_operator
|
|
|
|
tok(:number) || expr!(:function)
|
|
|
|
ss
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
return unless tok(:number) ||
|
|
|
|
tok(:uri) ||
|
|
|
|
function ||
|
|
|
|
tok(:string) ||
|
|
|
|
tok(:ident) ||
|
|
|
|
tok(:unicoderange) ||
|
2009-12-20 01:13:38 -08:00
|
|
|
hexcolor
|
2009-12-20 05:45:58 -08:00
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def function
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :function
|
|
|
|
ss
|
|
|
|
expr! :expr
|
|
|
|
raw! ')'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# There is a constraint on the color that it must
|
|
|
|
# have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
|
|
|
|
# after the "#"; e.g., "#000" is OK, but "#abcd" is not.
|
|
|
|
def hexcolor
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless tok :hash
|
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
def expr!(name)
|
|
|
|
return true if send(name)
|
|
|
|
raise "Expected #{name} expression, was #{@scanner.rest.inspect}"
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
def tok!(name)
|
|
|
|
return true if tok(name)
|
|
|
|
raise "Expected #{name} token at #{@scanner.rest.inspect}"
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
def raw!(chr)
|
|
|
|
return true if raw(chr)
|
|
|
|
raise "Expected #{chr.inspect} at #{@scanner.rest.inspect}"
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def tok(name)
|
2009-12-20 05:45:58 -08:00
|
|
|
@scanner.scan(RX.const_get(name.to_s.upcase))
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def raw(chr)
|
2009-12-20 05:45:58 -08:00
|
|
|
@scanner.scan(RX.quote(chr))
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|