Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 281 lines (258 sloc) 9.847 kb
1405b6e @timo typecheck the arguments of Array[Type].new.
timo authored
1 my class X::TypeCheck { ... };
10aa8bf @TimToady unify the Negative exceptions
TimToady authored
2 my class X::Subscript::Negative { ... };
358f486 @timo throw X::Item for @a[NaN] or Inf.
timo authored
3
d2ae748 @lizmat Copy/Update class and attribute specification in BOOTSTRAP to each modul...
lizmat authored
4 class Array { # declared in BOOTSTRAP
5 # class Array is List {
367963a @lizmat Sync class attributes comments between BOOTSTRAP and P6 modules for conv...
lizmat authored
6 # has Mu $!descriptor;
4f6bb1c @pmichaud Add Array.new().
pmichaud authored
7
63875df @lizmat Remove trailing whitespace
lizmat authored
8 method new(|) {
c2f2ebb @jnthn Dozens of pir:: -> nqp:: in CORE.
jnthn authored
9 my Mu $args := nqp::p6argvmarray();
3c22598 @pmichaud Update Array.new and List.new to be more robust.
pmichaud authored
10 nqp::shift($args);
1405b6e @timo typecheck the arguments of Array[Type].new.
timo authored
11
7019642 @pmichaud Convert instances of 1.Bool into Bool::True.
pmichaud authored
12 nqp::p6list($args, self.WHAT, Bool::True);
4f6bb1c @pmichaud Add Array.new().
pmichaud authored
13 }
283f763 @lizmat Make @a.VAR.name and %h.VAR.name work
lizmat authored
14
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
15 multi method AT-POS(Array:D: int \pos) is rw {
f120204 @lizmat < 0 exceptions should fail rather than throw
lizmat authored
16 fail X::OutOfRange.new(:what<Index>,:got(pos),:range<0..Inf>)
a0de41f @lizmat Some more at_pos / assign_pos streamlining
lizmat authored
17 if nqp::islt_i(pos,0);
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
18 my Mu \items := nqp::p6listitems(self);
e2ebf39 @pmichaud Add a comment to hotpath checks added in c10792f (RT #111848).
pmichaud authored
19 # hotpath check for element existence (RT #111848)
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
20 if nqp::existspos(items,pos)
21 || nqp::isconcrete(nqp::getattr(self,List,'$!nextiter'))
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
22 && nqp::istrue(self.EXISTS-POS(pos)) {
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
23 nqp::atpos(items,pos);
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
24 }
25 else {
26 nqp::p6bindattrinvres(
2d6e418 @jnthn Start switching over to nqp::p6scalarfromdesc.
jnthn authored
27 (my \v := nqp::p6scalarfromdesc($!descriptor)),
28 Scalar,
29 '$!whence',
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
30 -> { nqp::bindpos(items,pos,v) }
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
31 );
32 }
ac26106 @pmichaud Initial implementation of Array, List, and Parcel.
pmichaud authored
33 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
34 multi method AT-POS(Array:D: Int:D \pos) is rw {
e2c18cf @lizmat Remove unneccessary at_pos candidates, fix Int:D
lizmat authored
35 my int $pos = nqp::unbox_i(pos.Int);
f120204 @lizmat < 0 exceptions should fail rather than throw
lizmat authored
36 fail X::OutOfRange.new(:what<Index>,:got(pos),:range<0..Inf>)
a0de41f @lizmat Some more at_pos / assign_pos streamlining
lizmat authored
37 if nqp::islt_i($pos,0);
e2c18cf @lizmat Remove unneccessary at_pos candidates, fix Int:D
lizmat authored
38 my Mu \items := nqp::p6listitems(self);
e2ebf39 @pmichaud Add a comment to hotpath checks added in c10792f (RT #111848).
pmichaud authored
39 # hotpath check for element existence (RT #111848)
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
40 if nqp::existspos(items,$pos)
41 || nqp::isconcrete(nqp::getattr(self,List,'$!nextiter'))
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
42 && nqp::istrue(self.EXISTS-POS($pos)) {
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
43 nqp::atpos(items,$pos);
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
44 }
45 else {
46 nqp::p6bindattrinvres(
2d6e418 @jnthn Start switching over to nqp::p6scalarfromdesc.
jnthn authored
47 (my \v := nqp::p6scalarfromdesc($!descriptor)),
48 Scalar,
49 '$!whence',
fdf18b3 @lizmat Streamline Array.at_pos
lizmat authored
50 -> { nqp::bindpos(items,$pos,v) }
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
51 );
52 }
41441b8 @moritz native int variant of postcircumfix:<[ ]>
moritz authored
53 }
dadd463 @kboga Support binding of array elements
kboga authored
54
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
55 multi method ASSIGN-POS(Array:D: int \pos, Mu \assignee) is rw {
a0de41f @lizmat Some more at_pos / assign_pos streamlining
lizmat authored
56 X::OutOfRange.new(:what<Index>,:got(pos),:range<0..Inf>).throw
57 if nqp::islt_i(pos,0);
1fe14b5 @jnthn Switch array assignment over to using assign_pos.
jnthn authored
58 my \items := nqp::p6listitems(self);
e59d40d @lizmat Streamline Array.assign_pos, 7% faster for Int:D
lizmat authored
59 nqp::existspos(items,pos)
60 || nqp::isconcrete(nqp::getattr(self,List,'$!nextiter'))
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
61 && self.EXISTS-POS(pos)
e59d40d @lizmat Streamline Array.assign_pos, 7% faster for Int:D
lizmat authored
62 ?? (nqp::atpos(items,pos) = assignee)
63 !! (nqp::bindpos(items,pos,nqp::p6scalarfromdesc($!descriptor)) = assignee)
1fe14b5 @jnthn Switch array assignment over to using assign_pos.
jnthn authored
64 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
65 multi method ASSIGN-POS(Array:D: Int:D \pos, Mu \assignee) is rw {
e59d40d @lizmat Streamline Array.assign_pos, 7% faster for Int:D
lizmat authored
66 my int $pos = nqp::unbox_i(pos);
a0de41f @lizmat Some more at_pos / assign_pos streamlining
lizmat authored
67 X::OutOfRange.new(:what<Index>,:got(pos),:range<0..Inf>).throw
68 if nqp::islt_i($pos,0);
69 my \items := nqp::p6listitems(self);
e59d40d @lizmat Streamline Array.assign_pos, 7% faster for Int:D
lizmat authored
70 nqp::existspos(items,$pos)
71 || nqp::isconcrete(nqp::getattr(self,List,'$!nextiter'))
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
72 && self.EXISTS-POS($pos)
e59d40d @lizmat Streamline Array.assign_pos, 7% faster for Int:D
lizmat authored
73 ?? (nqp::atpos(items,$pos) = assignee)
74 !! (nqp::bindpos(items,$pos,nqp::p6scalarfromdesc($!descriptor)) = assignee)
1fe14b5 @jnthn Switch array assignment over to using assign_pos.
jnthn authored
75 }
76
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
77 proto method BIND-POS(|) { * }
78 multi method BIND-POS(Int() $pos, Mu \bindval) is rw {
8550c4e @kboga Fix array element binding's off by one error introduced in dadd463
kboga authored
79 self.gimme($pos + 1);
018777f @moritz switch Array to sigilless params
moritz authored
80 nqp::bindpos(nqp::getattr(self, List, '$!items'), nqp::unbox_i($pos), bindval);
dadd463 @kboga Support binding of array elements
kboga authored
81 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
82 multi method BIND-POS(int $pos, Mu \bindval) is rw {
8550c4e @kboga Fix array element binding's off by one error introduced in dadd463
kboga authored
83 self.gimme($pos + 1);
018777f @moritz switch Array to sigilless params
moritz authored
84 nqp::bindpos(nqp::getattr(self, List, '$!items'), $pos, bindval)
dadd463 @kboga Support binding of array elements
kboga authored
85 }
63875df @lizmat Remove trailing whitespace
lizmat authored
86
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
87 method DELETE-POS(\pos, :$SINK) {
10aa8bf @TimToady unify the Negative exceptions
TimToady authored
88 fail X::Subscript::Negative.new(index => pos, type => self.WHAT) if pos < 0;
c827edc @jnthn Implement Array.delete (based on ng code, but with various tweaks and up...
jnthn authored
89
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
90 my $value := self.AT-POS(pos); # needed for reification
6f86684 @lizmat Properly implement internals [].exists|delete, according to new semi-spe...
lizmat authored
91 my $items := nqp::getattr(self,List,'$!items');
85b9a40 @lizmat Make sure delete attempts past end of array doesn't vivify
lizmat authored
92 my $end := self.end;
6f86684 @lizmat Properly implement internals [].exists|delete, according to new semi-spe...
lizmat authored
93
85b9a40 @lizmat Make sure delete attempts past end of array doesn't vivify
lizmat authored
94 if pos == $end {
6f86684 @lizmat Properly implement internals [].exists|delete, according to new semi-spe...
lizmat authored
95 my $pos = pos;
96 nqp::pop($items);
97 nqp::pop($items)
98 while --$pos >= 0 && nqp::isnull(nqp::atpos($items,$pos));
99 }
85b9a40 @lizmat Make sure delete attempts past end of array doesn't vivify
lizmat authored
100 elsif pos < $end {
6f86684 @lizmat Properly implement internals [].exists|delete, according to new semi-spe...
lizmat authored
101 nqp::bindpos($items, pos, nqp::null());
c827edc @jnthn Implement Array.delete (based on ng code, but with various tweaks and up...
jnthn authored
102 }
85b9a40 @lizmat Make sure delete attempts past end of array doesn't vivify
lizmat authored
103 else {
104 return self.default;
105 }
6f86684 @lizmat Properly implement internals [].exists|delete, according to new semi-spe...
lizmat authored
106 $value;
c827edc @jnthn Implement Array.delete (based on ng code, but with various tweaks and up...
jnthn authored
107 }
f17f312 @pmichaud Add a preliminary Array.STORE.
pmichaud authored
108
de6c43d @pmichaud Fixing flats part #2: Move $!flattens flag from ListIter into List.
pmichaud authored
109 method flattens() { 1 }
110
283f763 @lizmat Make @a.VAR.name and %h.VAR.name work
lizmat authored
111 # introspection
112 method name() {
113 my $d := $!descriptor;
114 nqp::isnull($d) ?? Str !! $d.name()
115 }
bc39ad2 @lizmat Create proper .VAR.of methods for Array/Hash, makes 4 TODO tests pass
lizmat authored
116 method of() {
117 my $d := $!descriptor;
0813412 @lizmat Give [].of/default/dynamic more sensible values
lizmat authored
118 nqp::isnull($d) ?? Any !! $d.of;
bc39ad2 @lizmat Create proper .VAR.of methods for Array/Hash, makes 4 TODO tests pass
lizmat authored
119 }
eea1906 @lizmat Implement ($@%&)foo.VAR.default
lizmat authored
120 method default() {
121 my $d := $!descriptor;
0813412 @lizmat Give [].of/default/dynamic more sensible values
lizmat authored
122 nqp::isnull($d) ?? Any !! $d.default;
eea1906 @lizmat Implement ($@%&)foo.VAR.default
lizmat authored
123 }
9ecd996 @lizmat Allow .VAR.dynamic introspection on Hash / Array
lizmat authored
124 method dynamic() {
125 my $d := $!descriptor;
0813412 @lizmat Give [].of/default/dynamic more sensible values
lizmat authored
126 nqp::isnull($d) ?? Bool !! so $d.dynamic;
9ecd996 @lizmat Allow .VAR.dynamic introspection on Hash / Array
lizmat authored
127 }
018777f @moritz switch Array to sigilless params
moritz authored
128 multi method perl(Array:D \SELF:) {
129 nqp::iscont(SELF)
c793d80 @TimToady use lollipop syntax for .perl where appropriate
TimToady authored
130 ?? '[' ~ ( # simplify arrays that look 2D (in first 3 elems anyway)
e2ae40b @lizmat Replace foo ~~ Type with nqp::istype(foo,Type)
lizmat authored
131 nqp::istype(self[0],Parcel) || nqp::istype(self[1],Parcel) || nqp::istype(self[2],Parcel)
c793d80 @TimToady use lollipop syntax for .perl where appropriate
TimToady authored
132 ?? self.map({.list.map({.perl}).join(', ')}).join('; ')
133 !! self.map({.perl}).join(', ')
134 ) ~ ']'
e3fb8b7 @pmichaud Clean up .perl a bit for List/Array/Parcel.
pmichaud authored
135 !! self.WHAT.perl ~ '.new(' ~ self.map({.perl}).join(', ') ~ ')'
136 }
137
018777f @moritz switch Array to sigilless params
moritz authored
138 method REIFY(Parcel \parcel, Mu \nextiter) {
139 my Mu $rpa := nqp::getattr(parcel, Parcel, '$!storage');
28c1fff @pmichaud Our aggregates can benefit from some Infinite wisdom again.
pmichaud authored
140 my Mu $iter := nqp::iterator($rpa);
80f140f @moritz use native int iterator variable in Array.REIFY
moritz authored
141 my int $i = 0;
28c1fff @pmichaud Our aggregates can benefit from some Infinite wisdom again.
pmichaud authored
142 while $iter {
2f1efb8 @jnthn Fix some missing type-checking in typed arrays.
jnthn authored
143 nqp::bindpos($rpa, $i, nqp::p6scalarfromdesc($!descriptor) = nqp::shift($iter));
80f140f @moritz use native int iterator variable in Array.REIFY
moritz authored
144 $i = $i + 1;
28c1fff @pmichaud Our aggregates can benefit from some Infinite wisdom again.
pmichaud authored
145 }
018777f @moritz switch Array to sigilless params
moritz authored
146 nqp::findmethod(List, 'REIFY')(self, parcel, nextiter)
28c1fff @pmichaud Our aggregates can benefit from some Infinite wisdom again.
pmichaud authored
147 }
148
53c7856 @moritz switch protos to use | instead of |$
moritz authored
149 method STORE(|) {
4fa96ab @pmichaud Clean up Array assignment, add Parcel.iterator and List.iterator. ListI...
pmichaud authored
150 # get arguments, shift off invocant
c2f2ebb @jnthn Dozens of pir:: -> nqp:: in CORE.
jnthn authored
151 my $args := nqp::p6argvmarray();
b54894d Substitute several more pir ops by their nqp op counterparts.
kristof authored
152 nqp::shift($args);
99fff11 @jnthn Try to fix Array.STORE a bit. This isn't perfect, but an improvement for...
jnthn authored
153 # make an array from them (we can't just use ourself for this,
154 # or @a = @a will go terribly wrong); make it eager
155 my $list := nqp::p6list($args, Array, Mu);
156 nqp::bindattr($list, List, '$!flattens', True);
157 $list.eager;
158 # clear our items and set our next iterator to be one over
159 # the array we just created
ab1dda5 @pmichaud Convert some more pir:: opcodes to nqp:: .
pmichaud authored
160 nqp::bindattr(self, List, '$!items', Mu);
99fff11 @jnthn Try to fix Array.STORE a bit. This isn't perfect, but an improvement for...
jnthn authored
161 nqp::bindattr(self, List, '$!nextiter', nqp::p6listiter(nqp::list($list), self));
162 self
f17f312 @pmichaud Add a preliminary Array.STORE.
pmichaud authored
163 }
8424798 @pmichaud Fix array assignment and transitive iteration. Add a &DUMP primitive fo...
pmichaud authored
164
7b121b5 @jnthn Just enough to make 'my Int @foo' style declarations compile properly; i...
jnthn authored
165 my role TypedArray[::TValue] does Positional[TValue] {
63875df @lizmat Remove trailing whitespace
lizmat authored
166 method new(|) {
1405b6e @timo typecheck the arguments of Array[Type].new.
timo authored
167 my Mu $args := nqp::p6argvmarray();
168 nqp::shift($args);
63875df @lizmat Remove trailing whitespace
lizmat authored
169
1405b6e @timo typecheck the arguments of Array[Type].new.
timo authored
170 my $list := nqp::p6list($args, self.WHAT, Bool::True);
171
172 my $of = self.of;
173 if ( $of !=:= Mu ) {
174 for @$list {
175 if $_ !~~ $of {
176 X::TypeCheck.new(
177 operation => '.new',
178 expected => $of,
179 got => $_,
180 ).throw;
181 }
182 }
183 }
184
185 $list;
186 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
187 multi method AT-POS(Int() $pos) is rw {
188 if self.EXISTS-POS($pos) {
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
189 nqp::atpos(
190 nqp::getattr(self, List, '$!items'), nqp::unbox_i($pos)
191 );
192 }
193 else {
194 nqp::p6bindattrinvres(
2d6e418 @jnthn Start switching over to nqp::p6scalarfromdesc.
jnthn authored
195 (my \v := nqp::p6scalarfromdesc(nqp::getattr(self, Array, '$!descriptor'))),
196 Scalar,
197 '$!whence',
198 -> { nqp::bindpos(
199 nqp::getattr(self,List,'$!items'), nqp::unbox_i($pos), v) }
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
200 );
201 }
40b3369 @jnthn First bit of enforcing typed array types.
jnthn authored
202 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
203 multi method AT-POS(int $pos) is rw {
204 if self.EXISTS-POS($pos) {
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
205 nqp::atpos(nqp::getattr(self, List, '$!items'), $pos);
206 }
207 else {
208 nqp::p6bindattrinvres(
2d6e418 @jnthn Start switching over to nqp::p6scalarfromdesc.
jnthn authored
209 (my \v := nqp::p6scalarfromdesc(nqp::getattr(self, Array, '$!descriptor'))),
210 Scalar,
211 '$!whence',
212 -> { nqp::bindpos(nqp::getattr(self, List,'$!items'), $pos, v)}
948210e @lizmat Initial implementation of is default(42) on any hash/array
lizmat authored
213 );
214 }
40b3369 @jnthn First bit of enforcing typed array types.
jnthn authored
215 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
216 multi method BIND-POS(Int() $pos, TValue \bindval) is rw {
8550c4e @kboga Fix array element binding's off by one error introduced in dadd463
kboga authored
217 self.gimme($pos + 1);
018777f @moritz switch Array to sigilless params
moritz authored
218 nqp::bindpos(nqp::getattr(self, List, '$!items'), nqp::unbox_i($pos), bindval)
dadd463 @kboga Support binding of array elements
kboga authored
219 }
80d506d @lizmat Migrate from at_key c.s. to AT-KEY
lizmat authored
220 multi method BIND-POS(int $pos, TValue \bindval) is rw {
8550c4e @kboga Fix array element binding's off by one error introduced in dadd463
kboga authored
221 self.gimme($pos + 1);
018777f @moritz switch Array to sigilless params
moritz authored
222 nqp::bindpos(nqp::getattr(self, List, '$!items'), $pos, bindval)
dadd463 @kboga Support binding of array elements
kboga authored
223 }
8bd124f @lizmat Make sure .perl works correctly on Array's with constrained values
lizmat authored
224 multi method perl(::?CLASS:D \SELF:) {
225 'Array['
226 ~ TValue.perl
227 ~ '].new('
5b716e8 @lizmat Fix .perl for typed arrays
lizmat authored
228 ~ self.map({ ($_ // TValue).perl}).join(', ')
8bd124f @lizmat Make sure .perl works correctly on Array's with constrained values
lizmat authored
229 ~ ')';
230 }
7b121b5 @jnthn Just enough to make 'my Int @foo' style declarations compile properly; i...
jnthn authored
231 # XXX some methods to come here...
232 }
7fd909a @jnthn Use .^ in ^parameterize methods now it's safe.
jnthn authored
233 method ^parameterize(Mu:U \arr, Mu:U \t, |c) {
c52f245 @lizmat Make PARAMETRIZE_TYPE errors a bit less LTA
lizmat authored
234 if c.elems == 0 {
7fd909a @jnthn Use .^ in ^parameterize methods now it's safe.
jnthn authored
235 my $what := arr.^mixin(TypedArray[t]);
e6419ae @lizmat Make .WHAT return something more sensible for arrays/hashes
lizmat authored
236 # needs to be done in COMPOSE phaser when that works
7fd909a @jnthn Use .^ in ^parameterize methods now it's safe.
jnthn authored
237 $what.^set_name("{arr.^name}[{t.^name}]");
13f2f97 @lizmat Make (Array|Hash).WHAT return something sensible for specifically typed ...
lizmat authored
238 $what;
c52f245 @lizmat Make PARAMETRIZE_TYPE errors a bit less LTA
lizmat authored
239 }
240 else {
976eeaf @jnthn Switch Array/Hash to use method ^parameterize.
jnthn authored
241 die "Can only type-constrain Array with [ValueType]"
c52f245 @lizmat Make PARAMETRIZE_TYPE errors a bit less LTA
lizmat authored
242 }
7b121b5 @jnthn Just enough to make 'my Int @foo' style declarations compile properly; i...
jnthn authored
243 }
080b40f @TimToady move List.ACCEPTS Parcel and Array for now
TimToady authored
244 multi method ACCEPTS(Array:D: $topic) {
245 my $sseq = self;
246 my $tseq = $topic.list;
247
248 my int $spos = 0;
249 my int $tpos = 0;
250 while $spos < +$sseq {
251 # if the next element is Whatever
252 if nqp::istype($sseq[$spos],Whatever) {
253 # skip over all of the Whatevers
254 $spos = $spos + 1
255 while $spos <= +$sseq && nqp::istype($sseq[$spos],Whatever);
256 # if nothing left, we're done
257 return True if !($spos < +$sseq);
258 # find a target matching our new target
259 $tpos = $tpos + 1
260 while ($tpos < +$tseq) && $tseq[$tpos] !== $sseq[$spos];
261 # return false if we ran out
262 return False if !($tpos < +$tseq);
263 }
264 elsif $tpos >= +$tseq || $tseq[$tpos] !=== $sseq[$spos] {
265 return False;
266 }
267 # skip matching elements
268 $spos = $spos + 1;
269 $tpos = $tpos + 1;
270 }
271 # If nothing left to match, we're successful.
272 $tpos >= +$tseq;
273 }
274
ac26106 @pmichaud Initial implementation of Array, List, and Parcel.
pmichaud authored
275 }
276
8424798 @pmichaud Fix array assignment and transitive iteration. Add a &DUMP primitive fo...
pmichaud authored
277
756a4ac @lizmat [].VAR.name returns something more obviously wrong
lizmat authored
278 sub circumfix:<[ ]>(*@elems) is rw { my $ = @elems.eager }
45e2308 @TimToady tired of accidentally adding tabs to setting
TimToady authored
279
280 # vim: ft=perl6 expandtab sw=4
Something went wrong with that request. Please try again.