def property_expression(self, prop, name): expr = Expression(lineno=self.parser.lineno, column=self.parser.column) value = prop.expr.clone(None) # name expr.append( String(prop.name, lineno=self.parser.lineno, column=self.parser.column)) # replace cyclic call with __CALL__ def replace(node): if (node.node_name == "call" and hasattr(node, "function_name") and name == node.function_name): return Literal( "__CALL__", lineno=self.parser.lineno, column=self.parser.column, ) if hasattr(node, "nodes") and node.nodes: node.nodes = [replace(n) for n in node.nodes] return node replace(value) expr.append(value) return expr
def test_unwrap(): inner_expression = Expression() inner_expression.append(Unit(10)) inner_expression.append(Unit(20)) assert utils.unwrap(inner_expression) == inner_expression outer_expression = Expression() outer_expression.append(inner_expression) assert utils.unwrap(outer_expression) == inner_expression
def test_property_color_red(): # color: red expression = Expression() expression.append(Ident("red")) property = Property(["color"], expression) assert property.expr == expression assert len(property.segments) == 1 assert f"{property}" == "property(color, (red))"
def invoke_builtin(self, fn, args): """ :param fn: :param args: :return: """ # map arguments to first node # providing a nicer js api for # built-in functions. Functions may specify that # they wish to accept full expressions # via raw_bifs if hasattr(fn, "__name__") and fn.__name__ in raw_bifs: ret = args.nodes else: ret = [] # fit in the args to the functions sig = inspect.signature(fn) i = 0 # remove the last parameter (the evaluator) first keys = [key for key in sig.parameters.keys()][:-1] for key in keys: param = sig.parameters.get(key) # handle the *args parameters if param.name == "args": while i < len(args.nodes): ret.append(utils.unwrap(args.nodes[i].first())) i += 1 # regular parameters elif param.name in args.map.keys(): # in the map ret.append(args.map[param.name].first()) else: # then in the nodes all_arguments = [] for nodes in args.nodes: all_arguments.extend(unwrap(nodes)) if i < len(unwrap(all_arguments)): ret.append(unwrap(all_arguments)[i].first()) i += 1 # else: assume remaining parameters are not required # invoke builtin function body = utils.coerce( fn(*ret, evaluator=self), False, lineno=self.parser.lineno, column=self.parser.column, ) # Always wrapping allows js functions # to return several values with a single # Expression node expr = Expression() expr.append(body) body = expr return self.invoke(body)
def test_expression_append(): from stilus.nodes.null import null from stilus.nodes.boolean import true from stilus.nodes.boolean import false expression = Expression() expression.append(null) expression.append(true) expression.append(false) assert len(expression) == 3
def to_expression(self): """Return expression or wrap this node in an expression :return: """ from stilus.nodes.expression import Expression if self.node_name == "expression": return self expression = Expression() expression.append(self) return expression
def test_coerce(): first = String("hello") other = String("there") assert first.coerce(other) == other expression = Expression() expression.append(String("one")) expression.append(String("two")) expression.append(String("three")) assert first.coerce(expression) == String("one two three") assert first.coerce(null) == String("null") assert first.coerce(Ident("foobar")) == String("foobar")
def test_from_expression(): expression = Expression() expression.append(Unit(10, "mm")) expression.append(true) expression.lineno = 42 expression.column = 13 expression.is_list = True arguments = Arguments.from_expression(expression) assert arguments.nodes == expression.nodes assert arguments.lineno == expression.lineno assert arguments.column == expression.column assert arguments.is_list == expression.is_list
def split(delim, value, evaluator=None): assert_string(delim, "delimiter") assert_string(value, "val") words = value.string.split(delim.string) expr = Expression() for word in words: if isinstance(value, Ident): addition = Ident(word) else: addition = String(word) expr.append(Ident(addition)) return expr
def setup(self): self.populate_global_scope() for file in reversed(self.imports): expr = Expression() expr.append( String(file, lineno=self.parser.lineno, column=self.parser.column)) self.root.nodes.insert( 0, Import(expr, lineno=self.parser.lineno, column=self.parser.column), )
def operate(self, op, right, value=None): value = right.first() if op == "-": if value.node_name == "unit": from stilus.nodes.expression import Expression expression = Expression() value = value.clone() value.value = -value.value expression.append(self) expression.append(value) return expression elif op == "+": return Ident(self.string + self.coerce(value).string) return super().operate(op, right)
def range_function(start, stop, step=None, evaluator=None): assert_type(start, "unit", "start") assert_type(stop, "unit", "stop") if step: assert_type(step, "unit", "step") if step.value == 0: raise StilusError('ArgumentError: "step" argument ' "must not be zero") else: step = Unit(1) lst = Expression() i = start.value while i <= stop.value: lst.append(Unit(i, start.type)) i += step.value return lst
def test_unwrap_double(): first_expression = Expression() first_expression.append(Unit(10)) first_expression.append(Unit(20)) inner_expression = Expression() inner_expression.append(first_expression) outer_expression = Expression() outer_expression.append(inner_expression) last_expression = Expression() last_expression.append(outer_expression) assert utils.unwrap(last_expression) == first_expression
def test_unwrap_one_than_one(): inner_expression = Expression() inner_expression.append(Unit(10)) inner_expression.append(Unit(20)) assert utils.unwrap(inner_expression) == inner_expression outer_expression = Expression() outer_expression.append(inner_expression) outer_expression.append(true) assert utils.unwrap(outer_expression) == outer_expression
def test_expression_hash(): expression = Expression() assert expression.hash() == "" expression = Expression() expression.append(Ident("foo")) assert expression.hash() == "foo" other_expression = Expression() other_expression.append(Ident("foo")) other_expression.append(Ident("bar")) assert other_expression.hash() == "foo::bar" from stilus.nodes.null import null from stilus.nodes.boolean import true from stilus.nodes.boolean import false expression = Expression() expression.append(null) expression.append(true) expression.append(false) # in stylus null::true::false is returned; stilus returns the Python types assert expression.hash() == "None::True::False"
def test_expression_string_first_boolean(): from stilus.nodes.boolean import true from stilus.nodes.boolean import false from stilus.nodes.null import null expression = Expression() expression.append(true) expression.append(false) expression.append(null) assert str(expression) == "(true false null)" expression.is_list = True assert str(expression) == "(true, false, null)" assert expression.first() is true assert expression.to_boolean().value is True
def test_property_expression(): from stilus.nodes.boolean import true from stilus.nodes.boolean import false from stilus.nodes.null import null expression = Expression() expression.append(true) expression.append(false) expression.append(null) property = Property(["foo", "bar"], expression) assert property.node_name == "property" assert len(property.segments) == 2 assert property.expr is expression assert f"{property}" == "property(foobar, (true false null))" expression.is_list = True assert f"{property}" == "property(foobar, (true, false, null))"
def test_expression_operate_in(): # empty expression expression = Expression() other_expression = Expression() other_expression.append(Ident("foo")) other_expression.append(Ident("bar")) assert expression.operate("in", other_expression) == Boolean(False) with pytest.raises(StilusError): other_expression.operate("in", expression) == Boolean(True) # same expression expression = Expression() expression.append(Ident("foo")) expression.append(Ident("bar")) assert expression.operate("in", expression) == Boolean(False) # other expression expression = Expression() expression.append(Ident("foo")) expression.append(Ident("bar")) other_expression = Expression() other_expression.append(Ident("bar")) assert other_expression.operate("in", expression) == Boolean(True)
def selectors(evaluator=None): stack = evaluator.selector_stack() expr = Expression(is_list=True) if stack: for i, group in enumerate(stack): if len(group) > 1: selector_values = [] for selector in group: nested = SelectorParser(selector.value).parse()["nested"] if nested and i != 0: selector_values.append(f"& {selector.value}") else: selector_values.append(f"{selector.value}") expr.append(String(",".join(selector_values))) else: selector = group[0].value nested = SelectorParser(selector).parse()["nested"] if nested and i != 0: expr.append(String(f"& {selector}")) else: expr.append(String(f"{selector}")) else: expr.append(String("&")) return expr
def invoke_function(self, fn, args, content): block = Block( fn.block.parent, None, lineno=self.parser.lineno, column=self.parser.column, ) # clone the function body to prevent mutation of subsequent calls body = fn.block.clone(block) mixin_block = self.stack.current_frame().block # new block scope self.stack.append(Frame(block)) scope = self.get_current_scope() # normalize arguments if args.node_name != "arguments": expr = Expression() expr.append(args) args = Arguments.from_expression(expr) # arguments scope.add(Ident("arguments", args)) # mixin scope introspection bn = mixin_block.node_name if self.result: scope.add(Ident("mixin", false)) else: scope.add( Ident( "mixin", String( bn, lineno=self.parser.lineno, column=self.parser.column, ), )) # current property if self.property: prop = self.property_expression(self.property, fn.function_name) scope.add( Ident( "current-property", prop, lineno=self.parser.lineno, column=self.parser.column, )) else: scope.add( Ident( "current-property", null, lineno=self.parser.lineno, column=self.parser.column, )) # current call stack expr = Expression(lineno=self.parser.lineno, column=self.parser.column) for call in reversed(self.calling[:-1]): expr.append( Literal(call, lineno=self.parser.lineno, column=self.parser.column)) scope.add( Ident( "called-from", expr, lineno=self.parser.lineno, column=self.parser.column, )) # inject arguments as locals # todo: rewrite this! i = 0 for index, node in enumerate(fn.params.nodes): # rest param support if node.rest: node.value = Expression(lineno=self.parser.lineno, column=self.parser.column) for n in args.nodes[i:]: node.value.append(n) node.value.preserve = True node.value.is_list = args.is_list else: # argument default support # in dict? arg = args.map.get(node.name) # next node? if not arg: if hasattr(args, "nodes") and i < len(args.nodes): arg = args.nodes[i] i += 1 node = node.clone() if arg is not None: if hasattr(arg, "is_empty") and arg.is_empty(): args.nodes[i - 1] = self.visit(node) else: node.value = arg else: args.append(node.value) # required argument not satisfied if not node.value: raise TypeError(f'argument "{node}" required for {fn}') scope.add(node) # mixin block if content: scope.add(Ident("block", content, True)) # invoke return self.invoke(body, True, fn.filename)