Passed
Push — master ( 0f10a6...8f1e95 )
by Koen
11:27
created

Parser::delimited()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5.0051

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 21
ccs 16
cts 17
cp 0.9412
rs 8.7624
cc 5
eloc 16
nc 4
nop 4
crap 5.0051
1
<?php
2
3
namespace Vlaswinkel\Lua;
4
5
use Vlaswinkel\Lua\AST\ASTNode;
6
use Vlaswinkel\Lua\AST\NilASTNode;
7
use Vlaswinkel\Lua\AST\NumberASTNode;
8
use Vlaswinkel\Lua\AST\StringASTNode;
9
use Vlaswinkel\Lua\AST\TableASTNode;
10
use Vlaswinkel\Lua\AST\TableEntryASTNode;
11
12
/**
13
 * Class Parser
14
 *
15
 * @see     http://lisperator.net/pltut/parser/the-parser
16
 *
17
 * @author  Koen Vlaswinkel <[email protected]>
18
 * @package Vlaswinkel\Lua
19
 */
20
class Parser {
21
    /**
22
     * @var TokenStream
23
     */
24
    private $input;
25
26
    /**
27
     * Parser constructor.
28
     *
29
     * @param TokenStream $input
30
     */
31 13
    public function __construct(TokenStream $input) {
32 13
        $this->input = $input;
33 13
    }
34
35
    /**
36
     * @return ASTNode
37
     *
38
     * @throws ParseException
39
     */
40 13
    public function parse() {
41 13
        if ($this->isPunctuation('{')) {
42 6
            return $this->parseTable();
43
        }
44 12
        if ($this->isPunctuation('[')) {
45 2
            return $this->parseTableKey();
46
        }
47 12
        $token = $this->input->next();
48 12
        if ($token->getType() == Token::TYPE_NUMBER) {
49 4
            return new NumberASTNode($token->getValue());
50
        }
51 10
        if ($token->getType() == Token::TYPE_STRING || $token->getType() == Token::TYPE_IDENTIFIER) {
52 8
            return new StringASTNode($token->getValue());
53
        }
54 2
        if ($token->getType() == Token::TYPE_KEYWORD) {
55 2
            if ($token->getValue() === 'nil') {
56 2
                return new NilASTNode();
57
            } else {
58
                $this->input->error('Unexpected keyword: ' . $token->getValue());
59
            }
60
        }
61
        $this->unexpected();
62
    }
63
64
    /**
65
     * @return TableASTNode
66
     */
67 6
    protected function parseTable() {
68 6
        return new TableASTNode(
69 6
            $this->delimited(
70 6
                '{',
71 6
                '}',
72 6
                ',',
73 6
                [$this, 'parseTableEntry']
74 6
            )
75 5
        );
76
    }
77
78
    /**
79
     * @return TableEntryASTNode
80
     */
81 5
    protected function parseTableEntry() {
82 5
        $token = $this->parse();
83 5
        if ($this->isPunctuation('=')) {
84 4
            $this->skipPunctuation('=');
85 4
            $value = $this->parse();
86 4
            return new TableEntryASTNode(
87 4
                $value,
0 ignored issues
show
Bug introduced by
It seems like $value defined by $this->parse() on line 85 can be null; however, Vlaswinkel\Lua\AST\Table...yASTNode::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
88
                $token
89 4
            );
90
        }
91 3
        return new TableEntryASTNode($token);
0 ignored issues
show
Bug introduced by
It seems like $token defined by $this->parse() on line 82 can be null; however, Vlaswinkel\Lua\AST\Table...yASTNode::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
92
    }
93
94
    /**
95
     * @return ASTNode
96
     */
97 2
    protected function parseTableKey() {
98 2
        $this->skipPunctuation('[');
99 2
        $token = $this->parse();
100 2
        $this->skipPunctuation(']');
101 2
        return $token;
102
    }
103
104
    /**
105
     * @param string   $start
106
     * @param string   $stop
107
     * @param string   $separator
108
     * @param callable $parser
109
     *
110
     * @return array
111
     */
112 6
    protected function delimited($start, $stop, $separator, callable $parser) {
113 6
        $a     = [];
114 6
        $first = true;
115 6
        $this->skipPunctuation($start);
116 6
        while (!$this->input->eof()) {
117 6
            if ($this->isPunctuation($stop)) {
118 5
                break;
119
            }
120 5
            if ($first) {
121 5
                $first = false;
122 5
            } else {
123 2
                $this->skipPunctuation($separator);
124
            }
125 5
            if ($this->isPunctuation($stop)) {
126
                break;
127
            }
128 5
            $a[] = $parser();
129 5
        }
130 5
        $this->skipPunctuation($stop);
131 5
        return $a;
132
    }
133
134
    /**
135
     * @param string|null $char
136
     *
137
     * @return bool
138
     */
139 13
    protected function isPunctuation($char = null) {
140 13
        $token = $this->input->peek();
141 13
        return $token && $token->getType() == Token::TYPE_PUNCTUATION && ($char === null || $token->getValue(
142 13
            ) == $char);
143
    }
144
145
    /**
146
     * @param string|null $char
147
     *
148
     * @throws ParseException
149
     */
150 7
    protected function skipPunctuation($char = null) {
151 7
        if ($this->isPunctuation($char)) {
152 7
            $this->input->next();
153 7
        } else {
154 1
            $this->input->error('Expecting punctuation: "' . $char . '"');
155
        }
156 7
    }
157
158
    /**
159
     * @throws ParseException
160
     */
161
    protected function unexpected() {
162
        $this->input->error('Unexpected token: ' . json_encode($this->input->peek()));
163
    }
164
}