1 | #include "sass.hpp" |
2 | #include "bind.hpp" |
3 | #include "ast.hpp" |
4 | #include "backtrace.hpp" |
5 | #include "context.hpp" |
6 | #include "expand.hpp" |
7 | #include "eval.hpp" |
8 | #include <map> |
9 | #include <iostream> |
10 | #include <sstream> |
11 | |
12 | namespace Sass { |
13 | |
14 | void bind(sass::string type, sass::string name, Parameters_Obj ps, Arguments_Obj as, Env* env, Eval* eval, Backtraces& traces) |
15 | { |
16 | sass::string callee(type + " " + name); |
17 | |
18 | std::map<sass::string, Parameter_Obj> param_map; |
19 | List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); |
20 | varargs->is_arglist(is_arglist__: true); // enable keyword size handling |
21 | |
22 | for (size_t i = 0, L = as->length(); i < L; ++i) { |
23 | if (auto str = Cast<String_Quoted>(ptr: (*as)[i]->value())) { |
24 | // force optional quotes (only if needed) |
25 | if (str->quote_mark()) { |
26 | str->quote_mark(quote_mark__: '*'); |
27 | } |
28 | } |
29 | } |
30 | |
31 | // Set up a map to ensure named arguments refer to actual parameters. Also |
32 | // eval each default value left-to-right, wrt env, populating env as we go. |
33 | for (size_t i = 0, L = ps->length(); i < L; ++i) { |
34 | Parameter_Obj p = ps->at(i); |
35 | param_map[p->name()] = p; |
36 | // if (p->default_value()) { |
37 | // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); |
38 | // } |
39 | } |
40 | |
41 | // plug in all args; if we have leftover params, deal with it later |
42 | size_t ip = 0, LP = ps->length(); |
43 | size_t ia = 0, LA = as->length(); |
44 | while (ia < LA) { |
45 | Argument_Obj a = as->at(i: ia); |
46 | if (ip >= LP) { |
47 | // skip empty rest arguments |
48 | if (a->is_rest_argument()) { |
49 | if (List_Obj l = Cast<List>(ptr: a->value())) { |
50 | if (l->length() == 0) { |
51 | ++ ia; continue; |
52 | } |
53 | } |
54 | } |
55 | sass::ostream msg; |
56 | msg << "wrong number of arguments (" << LA << " for " << LP << ")" ; |
57 | msg << " for `" << name << "'" ; |
58 | return error(msg: msg.str(), pstate: as->pstate(), traces); |
59 | } |
60 | Parameter_Obj p = ps->at(i: ip); |
61 | |
62 | // If the current parameter is the rest parameter, process and break the loop |
63 | if (p->is_rest_parameter()) { |
64 | // The next argument by coincidence provides a rest argument |
65 | if (a->is_rest_argument()) { |
66 | |
67 | // We should always get a list for rest arguments |
68 | if (List_Obj rest = Cast<List>(ptr: a->value())) { |
69 | // create a new list object for wrapped items |
70 | List* arglist = SASS_MEMORY_NEW(List, |
71 | p->pstate(), |
72 | 0, |
73 | rest->separator(), |
74 | true); |
75 | // wrap each item from list as an argument |
76 | for (ExpressionObj item : rest->elements()) { |
77 | if (Argument_Obj arg = Cast<Argument>(ptr: item)) { |
78 | arglist->append(SASS_MEMORY_COPY(arg)); // copy |
79 | } else { |
80 | arglist->append(SASS_MEMORY_NEW(Argument, |
81 | item->pstate(), |
82 | item, |
83 | "" , |
84 | false, |
85 | false)); |
86 | } |
87 | } |
88 | // assign new arglist to environment |
89 | env->local_frame()[p->name()] = arglist; |
90 | } |
91 | // invalid state |
92 | else { |
93 | throw std::runtime_error("invalid state" ); |
94 | } |
95 | } else if (a->is_keyword_argument()) { |
96 | |
97 | // expand keyword arguments into their parameters |
98 | List* arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); |
99 | env->local_frame()[p->name()] = arglist; |
100 | Map_Obj argmap = Cast<Map>(ptr: a->value()); |
101 | for (auto key : argmap->keys()) { |
102 | if (String_Constant_Obj str = Cast<String_Constant>(ptr: key)) { |
103 | sass::string param = unquote(str->value()); |
104 | arglist->append(SASS_MEMORY_NEW(Argument, |
105 | key->pstate(), |
106 | argmap->at(key), |
107 | "$" + param, |
108 | false, |
109 | false)); |
110 | } else { |
111 | traces.push_back(x: Backtrace(key->pstate())); |
112 | throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); |
113 | } |
114 | } |
115 | |
116 | } else { |
117 | |
118 | // create a new list object for wrapped items |
119 | List_Obj arglist = SASS_MEMORY_NEW(List, |
120 | p->pstate(), |
121 | 0, |
122 | SASS_COMMA, |
123 | true); |
124 | // consume the next args |
125 | while (ia < LA) { |
126 | // get and post inc |
127 | a = (*as)[ia++]; |
128 | // maybe we have another list as argument |
129 | List_Obj ls = Cast<List>(ptr: a->value()); |
130 | // skip any list completely if empty |
131 | if (ls && ls->empty() && a->is_rest_argument()) continue; |
132 | |
133 | ExpressionObj value = a->value(); |
134 | if (Argument_Obj arg = Cast<Argument>(ptr: value)) { |
135 | arglist->append(element: arg); |
136 | } |
137 | // check if we have rest argument |
138 | else if (a->is_rest_argument()) { |
139 | // preserve the list separator from rest args |
140 | if (List_Obj rest = Cast<List>(ptr: a->value())) { |
141 | arglist->separator(separator__: rest->separator()); |
142 | |
143 | for (size_t i = 0, L = rest->length(); i < L; ++i) { |
144 | ExpressionObj obj = rest->value_at_index(i); |
145 | arglist->append(SASS_MEMORY_NEW(Argument, |
146 | obj->pstate(), |
147 | obj, |
148 | "" , |
149 | false, |
150 | false)); |
151 | } |
152 | } |
153 | // no more arguments |
154 | break; |
155 | } |
156 | // wrap all other value types into Argument |
157 | else { |
158 | arglist->append(SASS_MEMORY_NEW(Argument, |
159 | a->pstate(), |
160 | a->value(), |
161 | a->name(), |
162 | false, |
163 | false)); |
164 | } |
165 | } |
166 | // assign new arglist to environment |
167 | env->local_frame()[p->name()] = arglist; |
168 | } |
169 | // consumed parameter |
170 | ++ip; |
171 | // no more parameters |
172 | break; |
173 | } |
174 | |
175 | // If the current argument is the rest argument, extract a value for processing |
176 | else if (a->is_rest_argument()) { |
177 | // normal param and rest arg |
178 | List_Obj arglist = Cast<List>(ptr: a->value()); |
179 | if (!arglist) { |
180 | if (ExpressionObj arg = Cast<Expression>(ptr: a->value())) { |
181 | arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); |
182 | arglist->append(element: arg); |
183 | } |
184 | } |
185 | |
186 | // empty rest arg - treat all args as default values |
187 | if (!arglist || !arglist->length()) { |
188 | break; |
189 | } else { |
190 | if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { |
191 | size_t arg_count = (arglist->length() + LA - 1); |
192 | sass::ostream msg; |
193 | msg << callee << " takes " << LP; |
194 | msg << (LP == 1 ? " argument" : " arguments" ); |
195 | msg << " but " << arg_count; |
196 | msg << (arg_count == 1 ? " was passed" : " were passed." ); |
197 | deprecated_bind(msg: msg.str(), pstate: as->pstate()); |
198 | |
199 | while (arglist->length() > LP - ip) { |
200 | arglist->elements().erase(position: arglist->elements().end() - 1); |
201 | } |
202 | } |
203 | } |
204 | // otherwise move one of the rest args into the param, converting to argument if necessary |
205 | ExpressionObj obj = arglist->at(i: 0); |
206 | if (!(a = Cast<Argument>(ptr: obj))) { |
207 | Expression* a_to_convert = obj; |
208 | a = SASS_MEMORY_NEW(Argument, |
209 | a_to_convert->pstate(), |
210 | a_to_convert, |
211 | "" , |
212 | false, |
213 | false); |
214 | } |
215 | arglist->elements().erase(position: arglist->elements().begin()); |
216 | if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { |
217 | ++ia; |
218 | } |
219 | |
220 | } else if (a->is_keyword_argument()) { |
221 | Map_Obj argmap = Cast<Map>(ptr: a->value()); |
222 | |
223 | for (auto key : argmap->keys()) { |
224 | String_Constant* val = Cast<String_Constant>(ptr: key); |
225 | if (val == NULL) { |
226 | traces.push_back(x: Backtrace(key->pstate())); |
227 | throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); |
228 | } |
229 | sass::string param = "$" + unquote(val->value()); |
230 | |
231 | if (!param_map.count(x: param)) { |
232 | sass::ostream msg; |
233 | msg << callee << " has no parameter named " << param; |
234 | error(msg: msg.str(), pstate: a->pstate(), traces); |
235 | } |
236 | env->local_frame()[param] = argmap->at(k: key); |
237 | } |
238 | ++ia; |
239 | continue; |
240 | } else { |
241 | ++ia; |
242 | } |
243 | |
244 | if (a->name().empty()) { |
245 | if (env->has_local(key: p->name())) { |
246 | sass::ostream msg; |
247 | msg << "parameter " << p->name() |
248 | << " provided more than once in call to " << callee; |
249 | error(msg: msg.str(), pstate: a->pstate(), traces); |
250 | } |
251 | // ordinal arg -- bind it to the next param |
252 | env->local_frame()[p->name()] = a->value(); |
253 | ++ip; |
254 | } |
255 | else { |
256 | // named arg -- bind it to the appropriately named param |
257 | if (!param_map.count(x: a->name())) { |
258 | if (ps->has_rest_parameter()) { |
259 | varargs->append(element: a); |
260 | } else { |
261 | sass::ostream msg; |
262 | msg << callee << " has no parameter named " << a->name(); |
263 | error(msg: msg.str(), pstate: a->pstate(), traces); |
264 | } |
265 | } |
266 | if (param_map[a->name()]) { |
267 | if (param_map[a->name()]->is_rest_parameter()) { |
268 | sass::ostream msg; |
269 | msg << "argument " << a->name() << " of " << callee |
270 | << "cannot be used as named argument" ; |
271 | error(msg: msg.str(), pstate: a->pstate(), traces); |
272 | } |
273 | } |
274 | if (env->has_local(key: a->name())) { |
275 | sass::ostream msg; |
276 | msg << "parameter " << p->name() |
277 | << "provided more than once in call to " << callee; |
278 | error(msg: msg.str(), pstate: a->pstate(), traces); |
279 | } |
280 | env->local_frame()[a->name()] = a->value(); |
281 | } |
282 | } |
283 | // EO while ia |
284 | |
285 | // If we make it here, we're out of args but may have leftover params. |
286 | // That's only okay if they have default values, or were already bound by |
287 | // named arguments, or if it's a single rest-param. |
288 | for (size_t i = ip; i < LP; ++i) { |
289 | Parameter_Obj leftover = ps->at(i); |
290 | // cerr << "env for default params:" << endl; |
291 | // env->print(); |
292 | // cerr << "********" << endl; |
293 | if (!env->has_local(key: leftover->name())) { |
294 | if (leftover->is_rest_parameter()) { |
295 | env->local_frame()[leftover->name()] = varargs; |
296 | } |
297 | else if (leftover->default_value()) { |
298 | Expression* dv = leftover->default_value()->perform(op: eval); |
299 | env->local_frame()[leftover->name()] = dv; |
300 | } |
301 | else { |
302 | // param is unbound and has no default value -- error |
303 | throw Exception::MissingArgument(as->pstate(), traces, name, leftover->name(), type); |
304 | } |
305 | } |
306 | } |
307 | |
308 | return; |
309 | } |
310 | |
311 | |
312 | } |
313 | |