1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

[Sass] Add a validating parser for CSS.

This is a long way from actually parsing anything;
it just raises an exception if the document is invalid.
Nor is it likely to be particularly efficient,
due to heavy use of blocks and throw/catch.
This commit is contained in:
Nathan Weizenbaum 2009-12-20 01:13:38 -08:00
parent 0463180482
commit f07c8755d1

263
lib/sass/scss/parser.rb Normal file
View file

@ -0,0 +1,263 @@
require 'strscan'
require 'rx'
module Sass
module SCSS
class Parser
def initialize(str)
@scanner = StringScanner.new(str)
end
def parse
raise @error unless catch {stylesheet}
end
private
def stylesheet
try do
tok :charset; ss
tok :string; ss
raw ';'
end
s
any {import; s}
any {namespace; s}
any do
catch {ruleset} ||
catch {media} ||
catch {page} ||
font_face
s
end
end
def s
any do
catch {tok :s} ||
catch {tok :cdc} ||
tok(:cdo)
end
end
def ss
any {tok :s}
end
def import
tok :import; ss
catch {tok :string} || tok(:uri); ss
try do
medium
any {raw ','; ss; medium}
end
raw ';'; ss
end
def namespace
tok :namespace; ss
try {namespace_prefix; ss}
catch {tok :string} || tok(:uri); ss
raw ';'; ss
end
def namespace_prefix
tok :ident
end
def media
tok :media; ss
medium
any {raw ','; ss; medium}
raw '{'; ss
any {ruleset}
raw '}'; ss
end
def medium
tok :ident; ss
end
def page
tok :page; ss
try {tok :ident}
try {pseudo_page}; ss
raw '{'; ss
declaration
any {raw ';'; ss; declaration}
raw '}'; ss
end
def pseudo_page
raw ':'
tok :ident
end
def font_face
tok :font_face; ss
raw '{'; ss
declaration
any {raw ';'; ss declaration}
raw '}'; ss
end
def operator
catch {raw '/'; ss} ||
catch {raw ','; ss} ||
empty
end
def combinator
catch {raw '+'; ss} ||
catch {raw '>'; ss} ||
empty
end
def unary_operator
catch {raw '-'} || raw('+')
end
def property
tok :ident; ss
end
def ruleset
selector
any {raw ','; ss; selector}
raw '{'; ss
declaration
any {raw ';'; ss; declaration}
raw '}'; ss
end
def selector
simple_selector
any {combinator; simple_selector}
end
def simple_selector
try {element_name}
any do
catch {tok :hash} ||
catch {class_expr} ||
catch {attrib} ||
pseudo
end
ss
end
def class_expr
raw '.'
tok :ident
end
def element_name
catch {tok :ident} || raw('*')
end
def attrib
raw '['; ss
tok :ident; ss
try do
catch {raw '='} ||
catch {tok :includes} ||
catch {tok :dashmatch} ||
catch {tok :prefixmatch} ||
catch {tok :suffixmatch} ||
tok(:substringmatch)
ss
catch {tok :ident} || tok(:string); ss
end
raw ']'
end
def pseudo
raw ':'
catch {tok :ident} ||
begin
tok :function; ss
tok :ident; ss
raw ')'
end
end
def declaration
try do
property
raw ':'; ss
expr
try {prio}
end
end
def prio
tok :important; ss
end
def expr
term
any {operator; term}
end
def term
catch do
try {unary_operator}
catch {tok :number; ss} || function
end ||
catch {tok :string; ss} ||
catch {tok :ident; ss} ||
catch {tok :uri; ss} ||
catch {tok :unicoderange; ss} ||
hexcolor
end
def function
tok :function; ss
expr
raw ')'; ss
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
tok :hash; ss
end
def empty
true
end
def catch(&block)
pos = @scanner.pos
res = Kernel.catch(:fail, &block)
return false if res && @scanner.pos == pos
@scanner.pos = pos unless res
res
end
def try(&block)
catch(&block) || empty
end
def any(&block)
nil while catch(&block)
true
end
def tok(name)
return true if @scanner.scan(RX.const_get(name.to_s.upcase))
@error = "Expected #{name} token at #{@scanner.rest.inspect}"
throw :fail, false
end
def raw(chr)
return true if @scanner.scan(RX.quote(chr))
@error = "Expected #{chr.inspect} at #{@scanner.rest.inspect}"
throw :fail, false
end
end
end
end