Coverage Report

Created: 2026-02-26 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsass/src/fn_lists.cpp
Line
Count
Source
1
// sass.hpp must go before all system headers to get the
2
// __EXTENSIONS__ fix on Solaris.
3
#include "sass.hpp"
4
5
#include "listize.hpp"
6
#include "operators.hpp"
7
#include "fn_utils.hpp"
8
#include "fn_lists.hpp"
9
10
namespace Sass {
11
12
  namespace Functions {
13
14
    /////////////////
15
    // LIST FUNCTIONS
16
    /////////////////
17
18
    Signature keywords_sig = "keywords($args)";
19
    BUILT_IN(keywords)
20
0
    {
21
0
      List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy
22
0
      Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1);
23
0
      for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) {
24
0
        ExpressionObj obj = arglist->at(i);
25
0
        Argument_Obj arg = (Argument*) obj.ptr(); // XXX
26
0
        sass::string name = sass::string(arg->name());
27
0
        name = name.erase(0, 1); // sanitize name (remove dollar sign)
28
0
        *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted,
29
0
                 pstate, name),
30
0
                 arg->value());
31
0
      }
32
0
      return result.detach();
33
0
    }
34
35
    Signature length_sig = "length($list)";
36
    BUILT_IN(length)
37
0
    {
38
0
      if (SelectorList * sl = Cast<SelectorList>(env["$list"])) {
39
0
        return SASS_MEMORY_NEW(Number, pstate, (double) sl->length());
40
0
      }
41
0
      Expression* v = ARG("$list", Expression);
42
0
      if (v->concrete_type() == Expression::MAP) {
43
0
        Map* map = Cast<Map>(env["$list"]);
44
0
        return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1));
45
0
      }
46
0
      if (v->concrete_type() == Expression::SELECTOR) {
47
0
        if (CompoundSelector * h = Cast<CompoundSelector>(v)) {
48
0
          return SASS_MEMORY_NEW(Number, pstate, (double)h->length());
49
0
        } else if (SelectorList * ls = Cast<SelectorList>(v)) {
50
0
          return SASS_MEMORY_NEW(Number, pstate, (double)ls->length());
51
0
        } else {
52
0
          return SASS_MEMORY_NEW(Number, pstate, 1);
53
0
        }
54
0
      }
55
56
0
      List* list = Cast<List>(env["$list"]);
57
0
      return SASS_MEMORY_NEW(Number,
58
0
                             pstate,
59
0
                             (double)(list ? list->size() : 1));
60
0
    }
61
62
    Signature nth_sig = "nth($list, $n)";
63
    BUILT_IN(nth)
64
0
    {
65
0
      double nr = ARGVAL("$n");
66
0
      Map* m = Cast<Map>(env["$list"]);
67
0
      if (SelectorList * sl = Cast<SelectorList>(env["$list"])) {
68
0
        size_t len = m ? m->length() : sl->length();
69
0
        bool empty = m ? m->empty() : sl->empty();
70
0
        if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces);
71
0
        double index = std::floor(nr < 0 ? len + nr : nr - 1);
72
0
        if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces);
73
0
        return Cast<Value>(Listize::perform(sl->get(static_cast<int>(index))));
74
0
      }
75
0
      List_Obj l = Cast<List>(env["$list"]);
76
0
      if (nr == 0) error("argument `$n` of `" + sass::string(sig) + "` must be non-zero", pstate, traces);
77
      // if the argument isn't a list, then wrap it in a singleton list
78
0
      if (!m && !l) {
79
0
        l = SASS_MEMORY_NEW(List, pstate, 1);
80
0
        l->append(ARG("$list", Expression));
81
0
      }
82
0
      size_t len = m ? m->length() : l->length();
83
0
      bool empty = m ? m->empty() : l->empty();
84
0
      if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces);
85
0
      double index = std::floor(nr < 0 ? len + nr : nr - 1);
86
0
      if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces);
87
88
0
      if (m) {
89
0
        l = SASS_MEMORY_NEW(List, pstate, 2);
90
0
        l->append(m->keys()[static_cast<unsigned int>(index)]);
91
0
        l->append(m->at(m->keys()[static_cast<unsigned int>(index)]));
92
0
        return l.detach();
93
0
      }
94
0
      else {
95
0
        ValueObj rv = l->value_at_index(static_cast<int>(index));
96
0
        rv->set_delayed(false);
97
0
        return rv.detach();
98
0
      }
99
0
    }
100
101
    Signature set_nth_sig = "set-nth($list, $n, $value)";
102
    BUILT_IN(set_nth)
103
0
    {
104
0
      Map_Obj m = Cast<Map>(env["$list"]);
105
0
      List_Obj l = Cast<List>(env["$list"]);
106
0
      Number_Obj n = ARG("$n", Number);
107
0
      ExpressionObj v = ARG("$value", Expression);
108
0
      if (!l) {
109
0
        l = SASS_MEMORY_NEW(List, pstate, 1);
110
0
        l->append(ARG("$list", Expression));
111
0
      }
112
0
      if (m) {
113
0
        l = m->to_list(pstate);
114
0
      }
115
0
      if (l->empty()) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces);
116
0
      double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1);
117
0
      if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces);
118
0
      List* result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed());
119
0
      for (size_t i = 0, L = l->length(); i < L; ++i) {
120
0
        result->append(((i == index) ? v : (*l)[i]));
121
0
      }
122
0
      return result;
123
0
    }
124
125
    Signature index_sig = "index($list, $value)";
126
    BUILT_IN(index)
127
0
    {
128
0
      Map_Obj m = Cast<Map>(env["$list"]);
129
0
      List_Obj l = Cast<List>(env["$list"]);
130
0
      ExpressionObj v = ARG("$value", Expression);
131
0
      if (!l) {
132
0
        l = SASS_MEMORY_NEW(List, pstate, 1);
133
0
        l->append(ARG("$list", Expression));
134
0
      }
135
0
      if (m) {
136
0
        l = m->to_list(pstate);
137
0
      }
138
0
      for (size_t i = 0, L = l->length(); i < L; ++i) {
139
0
        if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1));
140
0
      }
141
0
      return SASS_MEMORY_NEW(Null, pstate);
142
0
    }
143
144
    Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)";
145
    BUILT_IN(join)
146
0
    {
147
0
      Map_Obj m1 = Cast<Map>(env["$list1"]);
148
0
      Map_Obj m2 = Cast<Map>(env["$list2"]);
149
0
      List_Obj l1 = Cast<List>(env["$list1"]);
150
0
      List_Obj l2 = Cast<List>(env["$list2"]);
151
0
      String_Constant_Obj sep = ARG("$separator", String_Constant);
152
0
      enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE);
153
0
      Value* bracketed = ARG("$bracketed", Value);
154
0
      bool is_bracketed = (l1 ? l1->is_bracketed() : false);
155
0
      if (!l1) {
156
0
        l1 = SASS_MEMORY_NEW(List, pstate, 1);
157
0
        l1->append(ARG("$list1", Expression));
158
0
        sep_val = (l2 ? l2->separator() : SASS_SPACE);
159
0
        is_bracketed = (l2 ? l2->is_bracketed() : false);
160
0
      }
161
0
      if (!l2) {
162
0
        l2 = SASS_MEMORY_NEW(List, pstate, 1);
163
0
        l2->append(ARG("$list2", Expression));
164
0
      }
165
0
      if (m1) {
166
0
        l1 = m1->to_list(pstate);
167
0
        sep_val = SASS_COMMA;
168
0
      }
169
0
      if (m2) {
170
0
        l2 = m2->to_list(pstate);
171
0
      }
172
0
      size_t len = l1->length() + l2->length();
173
0
      sass::string sep_str = unquote(sep->value());
174
0
      if (sep_str == "space") sep_val = SASS_SPACE;
175
0
      else if (sep_str == "comma") sep_val = SASS_COMMA;
176
0
      else if (sep_str != "auto") error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces);
177
0
      String_Constant_Obj bracketed_as_str = Cast<String_Constant>(bracketed);
178
0
      bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto";
179
0
      if (!bracketed_is_auto) {
180
0
        is_bracketed = !bracketed->is_false();
181
0
      }
182
0
      List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed);
183
0
      result->concat(l1);
184
0
      result->concat(l2);
185
0
      return result.detach();
186
0
    }
187
188
    Signature append_sig = "append($list, $val, $separator: auto)";
189
    BUILT_IN(append)
190
0
    {
191
0
      Map_Obj m = Cast<Map>(env["$list"]);
192
0
      List_Obj l = Cast<List>(env["$list"]);
193
0
      ExpressionObj v = ARG("$val", Expression);
194
0
      if (SelectorList * sl = Cast<SelectorList>(env["$list"])) {
195
0
        l = Cast<List>(Listize::perform(sl));
196
0
      }
197
0
      String_Constant_Obj sep = ARG("$separator", String_Constant);
198
0
      if (!l) {
199
0
        l = SASS_MEMORY_NEW(List, pstate, 1);
200
0
        l->append(ARG("$list", Expression));
201
0
      }
202
0
      if (m) {
203
0
        l = m->to_list(pstate);
204
0
      }
205
0
      List* result = SASS_MEMORY_COPY(l);
206
0
      sass::string sep_str(unquote(sep->value()));
207
0
      if (sep_str != "auto") { // check default first
208
0
        if (sep_str == "space") result->separator(SASS_SPACE);
209
0
        else if (sep_str == "comma") result->separator(SASS_COMMA);
210
0
        else error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces);
211
0
      }
212
0
      if (l->is_arglist()) {
213
0
        result->append(SASS_MEMORY_NEW(Argument,
214
0
                                       v->pstate(),
215
0
                                       v,
216
0
                                       "",
217
0
                                       false,
218
0
                                       false));
219
220
0
      } else {
221
0
        result->append(v);
222
0
      }
223
0
      return result;
224
0
    }
225
226
    Signature zip_sig = "zip($lists...)";
227
    BUILT_IN(zip)
228
0
    {
229
0
      List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List));
230
0
      size_t shortest = 0;
231
0
      for (size_t i = 0, L = arglist->length(); i < L; ++i) {
232
0
        List_Obj ith = Cast<List>(arglist->value_at_index(i));
233
0
        Map_Obj mith = Cast<Map>(arglist->value_at_index(i));
234
0
        if (!ith) {
235
0
          if (mith) {
236
0
            ith = mith->to_list(pstate);
237
0
          } else {
238
0
            ith = SASS_MEMORY_NEW(List, pstate, 1);
239
0
            ith->append(arglist->value_at_index(i));
240
0
          }
241
0
          if (arglist->is_arglist()) {
242
0
            Argument_Obj arg = (Argument*)(arglist->at(i).ptr()); // XXX
243
0
            arg->value(ith);
244
0
          } else {
245
0
            (*arglist)[i] = ith;
246
0
          }
247
0
        }
248
0
        shortest = (i ? std::min(shortest, ith->length()) : ith->length());
249
0
      }
250
0
      List* zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA);
251
0
      size_t L = arglist->length();
252
0
      for (size_t i = 0; i < shortest; ++i) {
253
0
        List* zipper = SASS_MEMORY_NEW(List, pstate, L);
254
0
        for (size_t j = 0; j < L; ++j) {
255
0
          zipper->append(Cast<List>(arglist->value_at_index(j))->at(i));
256
0
        }
257
0
        zippers->append(zipper);
258
0
      }
259
0
      return zippers;
260
0
    }
261
262
    Signature list_separator_sig = "list_separator($list)";
263
    BUILT_IN(list_separator)
264
0
    {
265
0
      List_Obj l = Cast<List>(env["$list"]);
266
0
      if (!l) {
267
0
        l = SASS_MEMORY_NEW(List, pstate, 1);
268
0
        l->append(ARG("$list", Expression));
269
0
      }
270
0
      return SASS_MEMORY_NEW(String_Quoted,
271
0
                               pstate,
272
0
                               l->separator() == SASS_COMMA ? "comma" : "space");
273
0
    }
274
275
    Signature is_bracketed_sig = "is-bracketed($list)";
276
    BUILT_IN(is_bracketed)
277
0
    {
278
0
      ValueObj value = ARG("$list", Value);
279
0
      List_Obj list = Cast<List>(value);
280
0
      return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed());
281
0
    }
282
283
  }
284
285
}