# Character classes ### NOTE: These definitions are implemented in Python as an optimization, ### because lots of | has the most parsley overhead. The Python ### implementations are intended to match the behavior of the rules below. #_space = ' ' | '\r' | '\n' | '\f' | '\t' #ws = < _space+ > #ows = < _space* > #hex = DIGIT | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' #letterish = letter | '_' | '-' escape = '\\' ( '\n' -> '' # TODO this is supposed to replace junk with FFFD | :cp ws? -> unichr(int(cp, 16)) # TODO i think this should be... more specific? ascii + valid unicode? # see also: rx.rb | anything ) ### Tokens identifier = < letterish (letterish | digit)* > number = < digit+ ('.' digit*)? | '.' digit+ > variable = < '$' identifier > ### Components expression = comma_list comma_list = spaced_list:head ( ows ',' ows spaced_list:tail -> tail )*:tails -> ListLiteral([head] + tails) if tails else head spaced_list = single_expression:head ( ows single_expression:tail -> tail )*:tails -> ListLiteral([head] + tails, comma=False) if tails else head single_expression = or_test ^(single expression) or_test = and_test:head ( 'o' 'r' and_test:tail -> tail )*:tails -> AnyOp(*[head] + tails) if tails else head and_test = not_test:head ( 'a' 'n' 'd' not_test:tail -> tail )*:tails -> AllOp(*[head] + tails) if tails else head not_test = comparison | ( 'n' 'o' 't' not_test:node -> NotOp(node) ) comparison = add_expr:node ( ows ( '<' '=' !(operator.le):op | '>' '=' !(operator.ge):op | '<' !(operator.lt):op | '>' !(operator.gt):op | '=' '=' !(operator.eq):op | '!' '=' !(operator.ne):op ) ows add_expr:operand !(BinaryOp(op, node, operand)):node )* -> node add_expr = mult_expr:node ( ows ( '+' !(operator.add):op | '-' !(operator.sub):op ) ows mult_expr:operand !(BinaryOp(op, node, operand)):node )* -> node mult_expr = unary_expr:node ( ows ( '*' !(operator.mul):op | '/' !(operator.truediv):op ) ows unary_expr:operand !(BinaryOp(op, node, operand)):node )* -> node unary_expr = ( '-' unary_expr:node -> UnaryOp(operator.neg, node) | '+' unary_expr:node -> UnaryOp(operator.pos, node) | atom ) atom = ( # Parenthesized expression '(' comma_list:node ')' -> Parentheses(node) # Old xCSS-style parenthesized expression # TODO kill this off | '[' comma_list:node ']' -> Parentheses(node) # Map literal | map # URL literal | 'u' 'r' 'l' '(' inside_url:s ')' -> FunctionLiteral('url', s) # Function call | identifier:name '(' argspec:args ')' -> CallOp(name, args) # Bareword | < '!'? identifier >:word -> Literal(parse_bareword(word)) # Number | number:number < '%' | letter+ >?:unit -> Literal(Number(float(number), unit=unit)) # String | string # Color literal # DEVIATION: Sass doesn't support alpha in hex literals | '#' ( :red :green :blue ?:alpha -> Literal(Color.from_rgb( int(red, 16) / 255., int(green, 16) / 255., int(blue, 16) / 255., int(alpha or "ff", 16) / 255., original_literal="#" + red + green + blue + (alpha or ''))) | hex:red hex:green hex:blue hex?:alpha -> Literal(Color.from_rgb( int(red, 16) / 15., int(green, 16) / 15., int(blue, 16) / 15., int(alpha or "f", 16) / 15., original_literal="#" + red + green + blue + (alpha or ''))) ) # Variable | variable:name -> Variable(name) ) ^(single value) ### Map literals map = '(' ows ( map_pair:pair ows ',' ows -> pair )*:pairs ( map_pair:pair ows !(pairs.append(pair)) )? ')' -> MapLiteral(pairs) map_pair = single_expression:key ows ':' ows spaced_list:value -> (key, value) ### Strings, literals, and interpolation # TODO I'm not entirely sure any of these character classes are correct # TODO ruby sass appears to preserve escapes inside_url = ( interpolation:node -> node | variable:name -> Variable(name) | ( escape | '#' ~'{' | anything:ch ?(ord(ch) > 32 and ch not in ' !"#$\'()') -> ch )+:s -> Literal(String(''.join(s), quotes=None)) | string_part(')'):s -> Literal(String(s, quotes=None)) )*:nodes -> Interpolation(nodes, quotes=None) string = ( '"' string_contents('"' '"'):node '"' -> node | '\'' string_contents('\'' '\''):node '\'' -> node ) interpolation = '#' '{' expression:node '}' -> node string_contents :end :quotes = ( interpolation:node -> node | string_part(end):s -> Literal(String(s, quotes=quotes)) )*:nodes -> Interpolation(nodes, quotes=quotes) string_part :end = <( '#' ~'{' | anything:ch ?(ch not in ('#', end)) )+> ### Function definitions and arguments goal_argspec = argspec argspec = ( argspec_item:node ows ',' ows -> node )*:nodes ( argspec_item:tail ows !(nodes.append(tail)) )? -> ArgspecLiteral(nodes) argspec_item = ows ( ( variable:name ows ':' ows -> Variable(name) )?:name spaced_list:value -> (name, value) )