1 | #include <numeric> |
2 | |
3 | #include "parser.hpp" |
4 | #include "extender.hpp" |
5 | #include "listize.hpp" |
6 | #include "fn_utils.hpp" |
7 | #include "fn_selectors.hpp" |
8 | |
9 | namespace Sass { |
10 | |
11 | namespace Functions { |
12 | |
13 | Signature selector_nest_sig = "selector-nest($selectors...)" ; |
14 | BUILT_IN(selector_nest) |
15 | { |
16 | List* arglist = ARG("$selectors" , List); |
17 | |
18 | // Not enough parameters |
19 | if (arglist->length() == 0) { |
20 | error( |
21 | msg: "$selectors: At least one selector must be passed for `selector-nest'" , |
22 | pstate, traces); |
23 | } |
24 | |
25 | // Parse args into vector of selectors |
26 | SelectorStack parsedSelectors; |
27 | for (size_t i = 0, L = arglist->length(); i < L; ++i) { |
28 | ExpressionObj exp = Cast<Expression>(ptr: arglist->value_at_index(i)); |
29 | if (exp->concrete_type() == Expression::NULL_VAL) { |
30 | error( |
31 | msg: "$selectors: null is not a valid selector: it must be a string,\n" |
32 | "a list of strings, or a list of lists of strings for 'selector-nest'" , |
33 | pstate, traces); |
34 | } |
35 | if (String_Constant_Obj str = Cast<String_Constant>(ptr: exp)) { |
36 | str->quote_mark(quote_mark__: 0); |
37 | } |
38 | sass::string exp_src = exp->to_string(opt: ctx.c_options); |
39 | ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); |
40 | SelectorListObj sel = Parser::parse_selector(source, ctx, traces); |
41 | parsedSelectors.push_back(x: sel); |
42 | } |
43 | |
44 | // Nothing to do |
45 | if( parsedSelectors.empty() ) { |
46 | return SASS_MEMORY_NEW(Null, pstate); |
47 | } |
48 | |
49 | // Set the first element as the `result`, keep |
50 | // appending to as we go down the parsedSelector vector. |
51 | SelectorStack::iterator itr = parsedSelectors.begin(); |
52 | SelectorListObj& result = *itr; |
53 | ++itr; |
54 | |
55 | for(;itr != parsedSelectors.end(); ++itr) { |
56 | SelectorListObj& child = *itr; |
57 | original_stack.push_back(x: result); |
58 | SelectorListObj rv = child->resolve_parent_refs(pstack: original_stack, traces); |
59 | result->elements(e: rv->elements()); |
60 | original_stack.pop_back(); |
61 | } |
62 | |
63 | return Cast<Value>(ptr: Listize::perform(node: result)); |
64 | } |
65 | |
66 | Signature selector_append_sig = "selector-append($selectors...)" ; |
67 | BUILT_IN(selector_append) |
68 | { |
69 | List* arglist = ARG("$selectors" , List); |
70 | |
71 | // Not enough parameters |
72 | if (arglist->empty()) { |
73 | error( |
74 | msg: "$selectors: At least one selector must be " |
75 | "passed for `selector-append'" , |
76 | pstate, traces); |
77 | } |
78 | |
79 | // Parse args into vector of selectors |
80 | SelectorStack parsedSelectors; |
81 | parsedSelectors.push_back(x: {}); |
82 | for (size_t i = 0, L = arglist->length(); i < L; ++i) { |
83 | Expression* exp = Cast<Expression>(ptr: arglist->value_at_index(i)); |
84 | if (exp->concrete_type() == Expression::NULL_VAL) { |
85 | error( |
86 | msg: "$selectors: null is not a valid selector: it must be a string,\n" |
87 | "a list of strings, or a list of lists of strings for 'selector-append'" , |
88 | pstate, traces); |
89 | } |
90 | if (String_Constant* str = Cast<String_Constant>(ptr: exp)) { |
91 | str->quote_mark(quote_mark__: 0); |
92 | } |
93 | sass::string exp_src = exp->to_string(); |
94 | ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); |
95 | SelectorListObj sel = Parser::parse_selector(source, ctx, traces, allow_parent: true); |
96 | |
97 | for (auto& complex : sel->elements()) { |
98 | if (complex->empty()) { |
99 | complex->append(SASS_MEMORY_NEW(CompoundSelector, "[phony]" )); |
100 | } |
101 | if (CompoundSelector* comp = Cast<CompoundSelector>(ptr: complex->first())) { |
102 | comp->hasRealParent(hasRealParent__: true); |
103 | complex->chroots(chroots__: true); |
104 | } |
105 | } |
106 | |
107 | if (parsedSelectors.size() > 1) { |
108 | |
109 | if (!sel->has_real_parent_ref()) { |
110 | auto parent = parsedSelectors.back(); |
111 | for (auto& complex : parent->elements()) { |
112 | if (CompoundSelector* comp = Cast<CompoundSelector>(ptr: complex->first())) { |
113 | comp->hasRealParent(hasRealParent__: false); |
114 | } |
115 | } |
116 | error(msg: "Can't append \"" + sel->to_string() + "\" to \"" + |
117 | parent->to_string() + "\" for `selector-append'" , |
118 | pstate, traces); |
119 | } |
120 | |
121 | // Build the resolved stack from the left. It's cheaper to directly |
122 | // calculate and update each resolved selcted from the left, than to |
123 | // recursively calculate them from the right side, as we would need |
124 | // to go through the whole stack depth to build the final results. |
125 | // E.g. 'a', 'b', 'x, y' => 'a' => 'a b' => 'a b x, a b y' |
126 | // vs 'a', 'b', 'x, y' => 'x' => 'b x' => 'a b x', 'y' ... |
127 | parsedSelectors.push_back(x: sel->resolve_parent_refs(pstack: parsedSelectors, traces, implicit_parent: true)); |
128 | } |
129 | else { |
130 | parsedSelectors.push_back(x: sel); |
131 | } |
132 | } |
133 | |
134 | // Nothing to do |
135 | if( parsedSelectors.empty() ) { |
136 | return SASS_MEMORY_NEW(Null, pstate); |
137 | } |
138 | |
139 | return Cast<Value>(ptr: Listize::perform(node: parsedSelectors.back())); |
140 | } |
141 | |
142 | Signature selector_unify_sig = "selector-unify($selector1, $selector2)" ; |
143 | BUILT_IN(selector_unify) |
144 | { |
145 | SelectorListObj selector1 = ARGSELS("$selector1" ); |
146 | SelectorListObj selector2 = ARGSELS("$selector2" ); |
147 | SelectorListObj result = selector1->unifyWith(selector2); |
148 | return Cast<Value>(ptr: Listize::perform(node: result)); |
149 | } |
150 | |
151 | Signature simple_selectors_sig = "simple-selectors($selector)" ; |
152 | BUILT_IN(simple_selectors) |
153 | { |
154 | CompoundSelectorObj sel = ARGSEL("$selector" ); |
155 | |
156 | List* l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); |
157 | |
158 | for (size_t i = 0, L = sel->length(); i < L; ++i) { |
159 | const SimpleSelectorObj& ss = sel->get(i); |
160 | sass::string ss_string = ss->to_string() ; |
161 | l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); |
162 | } |
163 | |
164 | return l; |
165 | } |
166 | |
167 | Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)" ; |
168 | BUILT_IN(selector_extend) |
169 | { |
170 | SelectorListObj selector = ARGSELS("$selector" ); |
171 | SelectorListObj target = ARGSELS("$extendee" ); |
172 | SelectorListObj source = ARGSELS("$extender" ); |
173 | SelectorListObj result = Extender::extend(selector, source, target, traces); |
174 | return Cast<Value>(ptr: Listize::perform(node: result)); |
175 | } |
176 | |
177 | Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)" ; |
178 | BUILT_IN(selector_replace) |
179 | { |
180 | SelectorListObj selector = ARGSELS("$selector" ); |
181 | SelectorListObj target = ARGSELS("$original" ); |
182 | SelectorListObj source = ARGSELS("$replacement" ); |
183 | SelectorListObj result = Extender::replace(selector, source, target, traces); |
184 | return Cast<Value>(ptr: Listize::perform(node: result)); |
185 | } |
186 | |
187 | Signature selector_parse_sig = "selector-parse($selector)" ; |
188 | BUILT_IN(selector_parse) |
189 | { |
190 | SelectorListObj selector = ARGSELS("$selector" ); |
191 | return Cast<Value>(ptr: Listize::perform(node: selector)); |
192 | } |
193 | |
194 | Signature is_superselector_sig = "is-superselector($super, $sub)" ; |
195 | BUILT_IN(is_superselector) |
196 | { |
197 | SelectorListObj sel_sup = ARGSELS("$super" ); |
198 | SelectorListObj sel_sub = ARGSELS("$sub" ); |
199 | bool result = sel_sup->isSuperselectorOf(sub: sel_sub); |
200 | return SASS_MEMORY_NEW(Boolean, pstate, result); |
201 | } |
202 | |
203 | } |
204 | |
205 | } |
206 | |