1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Compiler\Llk\Sampler;
38
39use Hoa\Compiler;
40use Hoa\Math;
41use Hoa\Visitor;
42
43/**
44 * Class \Hoa\Compiler\Llk\Sampler\Uniform.
45 *
46 * This generator aims at producing random and uniform a sequence of a fixed
47 * size. We use the recursive method to count all possible sub-structures of
48 * size n. The counting helps to compute cumulative distribution functions,
49 * which guide the exploration.
50 * Repetition unfolding: upper bound of + and * is set to n.
51 *
52 * @copyright  Copyright © 2007-2017 Hoa community
53 * @license    New BSD License
54 */
55class Uniform extends Sampler
56{
57    /**
58     * Data (pre-computing).
59     *
60     * @var array
61     */
62    protected $_data   = [];
63
64    /**
65     * Bound.
66     *
67     * @var int
68     */
69    protected $_length = 5;
70
71
72
73    /**
74     * Construct a generator.
75     *
76     * @param   \Hoa\Compiler\Llk\Parser  $compiler        Compiler/parser.
77     * @param   \Hoa\Visitor\Visit        $tokenSampler    Token sampler.
78     */
79    public function __construct(
80        Compiler\Llk\Parser $compiler,
81        Visitor\Visit       $tokenSampler,
82        $length = 5
83    ) {
84        parent::__construct($compiler, $tokenSampler);
85
86        foreach ($this->_rules as $name => $_) {
87            $this->_data[$name] = [];
88        }
89
90        $this->setLength($length);
91        $this->_sampler = new Math\Sampler\Random();
92
93        return;
94    }
95
96    /**
97     * The random and uniform algorithm.
98     *
99     * @param   \Hoa\Compiler\Llk\Rule  $rule    Rule to start.
100     * @param   int                     $n       Size.
101     * @return  string
102     */
103    public function uniform(Compiler\Llk\Rule $rule = null, $n = -1)
104    {
105        if (null === $rule && -1 === $n) {
106            $rule = $this->_rules[$this->_rootRuleName];
107            $n    = $this->getLength();
108        }
109
110        $data     = &$this->_data[$rule->getName()][$n];
111        $computed = $data['n'];
112
113        if (0 === $n || 0 === $computed) {
114            return null;
115        }
116
117        if ($rule instanceof Compiler\Llk\Rule\Choice) {
118            $children = $rule->getChildren();
119            $stat     = [];
120
121            foreach ($children as $c => $child) {
122                $stat[$c] = $this->_data[$child][$n]['n'];
123            }
124
125            $i = $this->_sampler->getInteger(1, $computed);
126
127            for ($e = 0, $b = $stat[$e], $max = count($stat) - 1;
128                $e < $max && $i > $b;
129                $b += $stat[++$e]);
130
131            return $this->uniform($this->_rules[$children[$e]], $n);
132        } elseif ($rule instanceof Compiler\Llk\Rule\Concatenation) {
133            $children  = $rule->getChildren();
134            $out       = null;
135            $Γ         = $data['Γ'];
136            $γ         = $Γ[$this->_sampler->getInteger(0, count($Γ) - 1)];
137
138            foreach ($children as $i => $child) {
139                $out .= $this->uniform($this->_rules[$child], $γ[$i]);
140            }
141
142            return $out;
143        } elseif ($rule instanceof Compiler\Llk\Rule\Repetition) {
144            $out   =  null;
145            $stat  = &$data['xy'];
146            $child =  $this->_rules[$rule->getChildren()];
147            $b     =  0;
148            $i     =  $this->_sampler->getInteger(1, $computed);
149
150            foreach ($stat as $α => $st) {
151                if ($i <= $b += $st['n']) {
152                    break;
153                }
154            }
155
156            $Γ = &$st['Γ'];
157            $γ = &$Γ[$this->_sampler->getInteger(0, count($Γ) - 1)];
158
159            for ($j = 0; $j < $α; ++$j) {
160                $out .= $this->uniform($child, $γ[$j]);
161            }
162
163            return $out;
164        } elseif ($rule instanceof Compiler\Llk\Rule\Token) {
165            return $this->generateToken($rule);
166        }
167
168        return null;
169    }
170
171    /**
172     * Recursive method applied to our problematic.
173     *
174     * @param   \Hoa\Compiler\Llk\Rule  $rule    Rule to start.
175     * @param   int                     $n       Size.
176     * @return  int
177     */
178    public function count(Compiler\Llk\Rule $rule = null, $n = -1)
179    {
180        if (null === $rule || -1 === $n) {
181            return 0;
182        }
183
184        $ruleName = $rule->getName();
185
186        if (isset($this->_data[$ruleName][$n])) {
187            return $this->_data[$ruleName][$n]['n'];
188        }
189
190        $this->_data[$ruleName][$n] =  ['n' => 0];
191        $out                        = &$this->_data[$ruleName][$n]['n'];
192        $rule                       =  $this->_rules[$ruleName];
193
194        if ($rule instanceof Compiler\Llk\Rule\Choice) {
195            foreach ($rule->getChildren() as $child) {
196                $out += $this->count($this->_rules[$child], $n);
197            }
198        } elseif ($rule instanceof Compiler\Llk\Rule\Concatenation) {
199            $children = $rule->getChildren();
200            $Γ        = new Math\Combinatorics\Combination\Gamma(
201                count($children),
202                $n
203            );
204            $this->_data[$ruleName][$n]['Γ'] = [];
205            $handle                          = &$this->_data[$ruleName][$n]['Γ'];
206
207            foreach ($Γ as $γ) {
208                $oout = 1;
209
210                foreach ($γ as $α => $) {
211                    $oout *= $this->count($this->_rules[$children[$α]], $);
212                }
213
214                if (0 !== $oout) {
215                    $handle[] = $γ;
216                }
217
218                $out += $oout;
219            }
220        } elseif ($rule instanceof Compiler\Llk\Rule\Repetition) {
221            $this->_data[$ruleName][$n]['xy'] = [];
222            $handle                           = &$this->_data[$ruleName][$n]['xy'];
223            $child                            =  $this->_rules[$rule->getChildren()];
224            $x                                =  $rule->getMin();
225            $y                                =  $rule->getMax();
226
227            if (-1 === $y) {
228                $y = $n;
229            } else {
230                $y = min($n, $y);
231            }
232
233            if (0 === $x && $x === $y) {
234                $out = 1;
235            } else {
236                for ($α = $x; $α <= $y; ++$α) {
237                    $ut         = 0;
238                    $handle[$α] = ['n' => 0, 'Γ' => []];
239                    $Γ          = new Math\Combinatorics\Combination\Gamma($α, $n);
240
241                    foreach ($Γ as $γ) {
242                        $oout = 1;
243
244                        foreach ($γ as $β => $) {
245                            $oout *= $this->count($child, $);
246                        }
247
248                        if (0 !== $oout) {
249                            $handle[$α]['Γ'][] = $γ;
250                        }
251
252                        $ut += $oout;
253                    }
254
255                    $handle[$α]['n']  = $ut;
256                    $out             += $ut;
257                }
258            }
259        } elseif ($rule instanceof Compiler\Llk\Rule\Token) {
260            $out = Math\Util::δ($n, 1);
261        }
262
263        return $out;
264    }
265
266    /**
267     * Set upper-bound, the maximum data length.
268     *
269     * @param   int  $length    Length.
270     * @return  int
271     * @throws  \Hoa\Compiler\Exception
272     */
273    public function setLength($length)
274    {
275        if (0 >= $length) {
276            throw new Exception(
277                'Length must be greater than 0, given %d.',
278                0,
279                $length
280            );
281        }
282
283        $old           = $this->_length;
284        $this->_length = $length;
285        $this->count(
286            $this->_compiler->getRule($this->_rootRuleName),
287            $length
288        );
289
290        return $old;
291    }
292
293    /**
294     * Get upper-bound.
295     *
296     * @return  int
297     */
298    public function getLength()
299    {
300        return $this->_length;
301    }
302}
303