work in progress
This commit is contained in:
Родитель
a578447080
Коммит
8b625589c3
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class ClassMembersNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::ClassMembersNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class ClassNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::ClassNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class DelimitedList extends Node {
|
||||
/** @var Node[] */
|
||||
public $tokens;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::DelimitedList);
|
||||
}
|
||||
|
||||
public function getValues() {
|
||||
$i = 0;
|
||||
foreach($this->tokens as $value) {
|
||||
if ($i++ % 2 == 1) {
|
||||
yield $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addToken(Token $token) {
|
||||
array_push($this->tokens, $token);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class EmptyStatementNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::EmptyStatement);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace PhpParser\Node;
|
||||
|
||||
|
||||
use PhpParser\NodeKind;
|
||||
|
||||
class Expression extends Node {
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::Expression);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class Function_ extends Node {
|
||||
/** @var Token */
|
||||
public $functionKeyword;
|
||||
/** @var Token */
|
||||
public $byRefToken;
|
||||
/** @var null | Name */
|
||||
public $name;
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
/** @var Parameter[] */
|
||||
public $parameters;
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
/** @var null | Name */
|
||||
public $returnTypeOpt;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::FunctionNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class MethodBlockNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::MethodBlockNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class MethodNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::MethodNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
|
||||
use PhpParser\NodeKind;
|
||||
|
||||
class Name extends Node {
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::Name);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
namespace PhpParser\Node;
|
||||
|
||||
class Node implements \JsonSerializable {
|
||||
//require_once (__DIR__ . "/../NodeKind.php");
|
||||
|
||||
use PhpParser\Token;
|
||||
|
||||
class Node implements \JsonSerializable {
|
||||
public $kind;
|
||||
public $parent;
|
||||
public $children;
|
||||
|
||||
public function __construct(int $kind) {
|
||||
$this->kind = $kind;
|
||||
|
@ -14,7 +17,7 @@ class Node implements \JsonSerializable {
|
|||
public function getLength() {
|
||||
$length = 0;
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
foreach ($this->getChildren() as $child) {
|
||||
if ($child instanceof Node) {
|
||||
$length += $child->getLength();
|
||||
} else if ($child instanceof Token) {
|
||||
|
@ -27,7 +30,7 @@ class Node implements \JsonSerializable {
|
|||
public function getAllChildren() {
|
||||
$allChildren = array();
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
foreach ($this->getChildren() as $child) {
|
||||
if ($child instanceof Node) {
|
||||
array_push($allChildren, $child);
|
||||
foreach ($child->getAllChildren() as $subChild) {
|
||||
|
@ -40,8 +43,25 @@ class Node implements \JsonSerializable {
|
|||
return $allChildren;
|
||||
}
|
||||
|
||||
private function getChildren() {
|
||||
$result = array();
|
||||
foreach (call_user_func('get_object_vars', $this) as $i=>$val) {
|
||||
if ($i === "parent" || $i == "kind") {
|
||||
continue;
|
||||
}
|
||||
if (is_array($val)) {
|
||||
foreach ($val as $child) {
|
||||
array_push($result, $child);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
array_push($result, $val);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getStart() {
|
||||
$child = $this->children[0];
|
||||
$child = $this->getChildren()[0];
|
||||
if ($child instanceof Node) {
|
||||
return $child->getStart();
|
||||
} else if ($child instanceof Token) {
|
||||
|
@ -59,18 +79,6 @@ class Node implements \JsonSerializable {
|
|||
}
|
||||
}
|
||||
|
||||
return ["$kindName" => $this->children];
|
||||
return ["$kindName" => $this->getChildren()];
|
||||
}
|
||||
}
|
||||
|
||||
class NodeKind {
|
||||
const SourceFileNode = 0;
|
||||
const ClassNode = 1;
|
||||
const BlockNode = 2;
|
||||
const MethodBlockNode = 3;
|
||||
const MethodNode = 4;
|
||||
const StatementNode = 5;
|
||||
const ClassMembersNode = 6;
|
||||
const Count = 7;
|
||||
const TemplateExpression = 8;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class Parameter extends Node {
|
||||
/** @var Token */
|
||||
public $typeOpt;
|
||||
/** @var Token */
|
||||
public $byRefToken;
|
||||
|
||||
/** @var Token */
|
||||
public $variableName;
|
||||
|
||||
/** @var null | Expression */
|
||||
public $default;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::ParameterNode);
|
||||
}
|
||||
|
||||
public function isVariadic() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeKind;
|
||||
|
||||
class SourceFile extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::SourceFileNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class TemplateExpressionNode extends Node {
|
||||
public $children;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::TemplateExpression);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class Parameter extends Node {
|
||||
/** @var Token */
|
||||
public $type;
|
||||
/** @var Token */
|
||||
public $byRefToken;
|
||||
/** @var Token */
|
||||
public $variableName;
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
/** @var DelimitedList */
|
||||
public $parameters;
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
/** @var Type */
|
||||
public $returnTypeOpt;
|
||||
|
||||
public function __construct(int $a = 3) {
|
||||
parent::__construct(NodeKind::FunctionNode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class NodeKind {
|
||||
const SourceFileNode = 0;
|
||||
const ClassNode = 1;
|
||||
const BlockNode = 2;
|
||||
const MethodBlockNode = 3;
|
||||
const MethodNode = 4;
|
||||
const StatementNode = 5;
|
||||
const ClassMembersNode = 6;
|
||||
const Count = 7;
|
||||
const TemplateExpression = 8;
|
||||
const EmptyStatement = 9;
|
||||
const FunctionNode = 10;
|
||||
const DelimitedList = 11;
|
||||
const Expression = 12;
|
||||
const Name = 13;
|
||||
const ParameterNode = 14;
|
||||
}
|
|
@ -59,6 +59,8 @@ class TokenKind {
|
|||
|
||||
const SkippedToken = 4;
|
||||
const MissingToken = 5;
|
||||
const QualifiedName = 6;
|
||||
|
||||
|
||||
const AbstractKeyword = 101;
|
||||
const AndKeyword = 102;
|
||||
|
@ -160,7 +162,7 @@ class TokenKind {
|
|||
const CaretToken = 231;
|
||||
const BarToken = 232;
|
||||
const AmpersandToken = 233;
|
||||
const ApersandAmpersandToken = 234;
|
||||
const AmpersandAmpersandToken = 234;
|
||||
const BarBarToken = 235;
|
||||
const QuestionToken = 235;
|
||||
const ColonToken = 236;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* PURPOSE:
|
||||
* During the course of lexing a file, we will be indexing into strings quite liberally.
|
||||
* This experiment compares the effects of indexing into a string and indexing into an associative array.
|
||||
*
|
||||
* RESULTS:
|
||||
* Memory by indexing into string: 31457280
|
||||
* Memory by indexing into character array: 0
|
||||
*
|
||||
* CONCLUSION:
|
||||
* We create new zvals every time we index into a string, whereas we are merely incrementing the refcount
|
||||
* every time we index into the unpacked character array. Therefore, iterating through a character array is
|
||||
* preferable to decrease memory footprint.
|
||||
*/
|
||||
|
||||
$count = 1000000;
|
||||
$str = str_repeat("a", $count);
|
||||
$unpackedStr = unpack('C*', $str);
|
||||
|
||||
$char = new SplFixedArray($count+1);
|
||||
|
||||
$memory = memory_get_peak_usage(true);
|
||||
for ($i = 1; $i<$count; $i++) {
|
||||
$char[$i] = $str[$i];
|
||||
}
|
||||
echo "Memory by indexing into string: ", (memory_get_peak_usage(true) - $memory), PHP_EOL;
|
||||
|
||||
$memory = memory_get_peak_usage(true);
|
||||
for ($i = 1; $i<$count+1; $i++) {
|
||||
$char[$i] = $unpackedStr[$i];
|
||||
}
|
||||
echo "Memory by indexing into character array: ", (memory_get_peak_usage(true) - $memory), PHP_EOL;
|
|
@ -794,7 +794,7 @@ const OPERATORS_AND_PUNCTUATORS = array(
|
|||
"^" => TokenKind::CaretToken,
|
||||
"|" => TokenKind::BarToken,
|
||||
"&" => TokenKind::AmpersandToken,
|
||||
"&&" => TokenKind::ApersandAmpersandToken,
|
||||
"&&" => TokenKind::AmpersandAmpersandToken,
|
||||
"||" => TokenKind::BarBarToken,
|
||||
"?" => TokenKind::QuestionToken,
|
||||
":" => TokenKind::ColonToken,
|
||||
|
|
169
parser.php
169
parser.php
|
@ -1,8 +1,26 @@
|
|||
<?php
|
||||
|
||||
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
require_once('Node.php');
|
||||
// TODO make this less hacky
|
||||
spl_autoload_register(function ($class) {
|
||||
if (file_exists($filepath = __DIR__ . "/Node/" . basename($class) . ".php")) {
|
||||
require_once $filepath;
|
||||
} else if (file_exists($filepath = __DIR__ . "/" . basename($class) . ".php")) {
|
||||
require_once $filepath;
|
||||
}
|
||||
});
|
||||
|
||||
use PhpParser\Node\ClassMembersNode;
|
||||
use PhpParser\Node\ClassNode;
|
||||
use PhpParser\Node\EmptyStatementNode;
|
||||
use PhpParser\Node\MethodBlockNode;
|
||||
use PhpParser\Node\MethodNode;
|
||||
use PhpParser\Node\Node;
|
||||
use PhpParser\Node\SourceFile;
|
||||
use PhpParser\Node\TemplateExpressionNode;
|
||||
|
||||
class Parser {
|
||||
|
||||
|
@ -18,7 +36,7 @@ class Parser {
|
|||
public function parseSourceFile() : Node {
|
||||
$this->reset();
|
||||
|
||||
$sourceFile = new Node(NodeKind::SourceFileNode);
|
||||
$sourceFile = new SourceFile();
|
||||
$this->sourceFile = & $sourceFile;
|
||||
$sourceFile->children = $this->parseList($sourceFile, ParseContext::SourceElements);
|
||||
array_push($sourceFile->children, $this->getCurrentToken());
|
||||
|
@ -206,10 +224,12 @@ class Parser {
|
|||
return $this->parseClassDeclaration($parentNode);
|
||||
case TokenKind::TemplateStringStart:
|
||||
return $this->parseTemplateString($parentNode);
|
||||
|
||||
case TokenKind::SemicolonToken:
|
||||
return $this->parseEmptyStatement($parentNode);
|
||||
|
||||
default:
|
||||
$token = $this->getCurrentToken();
|
||||
$this->advanceToken();
|
||||
return $token;
|
||||
return $this->parsePrimaryExpression($parentNode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -239,7 +259,7 @@ class Parser {
|
|||
}
|
||||
|
||||
function parseClassDeclaration($parentNode) : Node {
|
||||
$node = new Node(NodeKind::ClassNode);
|
||||
$node = new ClassNode();
|
||||
$node->children = array();
|
||||
array_push($node->children, $this->eat(TokenKind::ClassKeyword));
|
||||
array_push($node->children, $this->eat(TokenKind::Name));
|
||||
|
@ -249,7 +269,7 @@ class Parser {
|
|||
}
|
||||
|
||||
function parseClassMembers($parentNode) : Node {
|
||||
$node = new Node(NodeKind::ClassMembersNode);
|
||||
$node = new ClassMembersNode();
|
||||
$node->children = array();
|
||||
array_push($node->children, $this->eat(TokenKind::OpenBraceToken));
|
||||
$this->array_push_list($node->children, $this->parseList($node, ParseContext::ClassMembers));
|
||||
|
@ -259,7 +279,7 @@ class Parser {
|
|||
}
|
||||
|
||||
function parseMethodDeclaration($parentNode) {
|
||||
$node = new Node(NodeKind::MethodNode);
|
||||
$node = new MethodNode();
|
||||
$node->children = array();
|
||||
array_push($node->children, $this->eat(TokenKind::FunctionKeyword));
|
||||
array_push($node->children, $this->eat(TokenKind::Name));
|
||||
|
@ -272,7 +292,7 @@ class Parser {
|
|||
}
|
||||
|
||||
function parseMethodBlock($parentNode) {
|
||||
$node = new Node(NodeKind::MethodBlockNode);
|
||||
$node = new MethodBlockNode();
|
||||
$node->children = array();
|
||||
array_push($node->children, $this->eat(TokenKind::OpenBraceToken));
|
||||
$this->array_push_list($node->children, $this->parseList($node, ParseContext::BlockStatements));
|
||||
|
@ -397,7 +417,7 @@ class Parser {
|
|||
}
|
||||
|
||||
private function parseTemplateString($parentNode) {
|
||||
$templateNode = new Node(NodeKind::TemplateExpression);
|
||||
$templateNode = new TemplateExpressionNode();
|
||||
$templateNode->parent = $parentNode;
|
||||
$templateNode->children = array();
|
||||
do {
|
||||
|
@ -417,6 +437,135 @@ class Parser {
|
|||
array_push($templateNode->children, $this->eat(TokenKind::TemplateStringEnd));
|
||||
return $templateNode;
|
||||
}
|
||||
|
||||
private function isPrimaryExpressionStart() {
|
||||
switch ($this->getCurrentToken()) {
|
||||
// variable-name
|
||||
case TokenKind::VariableName: // TODO special case $this
|
||||
|
||||
// qualified-name
|
||||
case TokenKind::QualifiedName: // TODO Qualified name
|
||||
|
||||
// literal
|
||||
case TokenKind::DecimalLiteralToken: // TODO merge dec, oct, hex, bin, float -> NumericLiteral
|
||||
case TokenKind::OctalLiteralToken:
|
||||
case TokenKind::HexadecimalLiteralToken:
|
||||
case TokenKind::BinaryLiteralToken:
|
||||
case TokenKind::FloatingLiteralToken:
|
||||
case TokenKind::InvalidOctalLiteralToken:
|
||||
case TokenKind::InvalidHexadecimalLiteral:
|
||||
case TokenKind::InvalidBinaryLiteral:
|
||||
|
||||
case TokenKind::StringLiteralToken: // TODO merge unterminated
|
||||
case TokenKind::UnterminatedStringLiteralToken:
|
||||
case TokenKind::NoSubstitutionTemplateLiteral:
|
||||
case TokenKind::UnterminatedNoSubstitutionTemplateLiteral:
|
||||
|
||||
case TokenKind::TemplateStringStart: //TODO - parse this as an expression
|
||||
|
||||
|
||||
// TODO constant-expression
|
||||
|
||||
// intrinsic-construct
|
||||
case TokenKind::EchoKeyword:
|
||||
case TokenKind::ListKeyword:
|
||||
case TokenKind::UnsetKeyword:
|
||||
|
||||
// intrinsic-operator
|
||||
case TokenKind::ArrayKeyword:
|
||||
case TokenKind::EmptyKeyword:
|
||||
case TokenKind::EvalKeyword:
|
||||
case TokenKind::ExitKeyword:
|
||||
case TokenKind::DieKeyword:
|
||||
case TokenKind::IsSetKeyword:
|
||||
case TokenKind::PrintKeyword:
|
||||
|
||||
// anonymous-function-creation-expression
|
||||
case TokenKind::StaticKeyword:
|
||||
case TokenKind::FunctionKeyword:
|
||||
|
||||
// ( expression )
|
||||
case TokenKind::OpenParenToken:
|
||||
return true; // TODO
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parsePrimaryExpression($parentNode) {
|
||||
switch ($this->getCurrentToken()) {
|
||||
/* // variable-name
|
||||
case TokenKind::VariableName: // TODO special case $this
|
||||
return $this->parseVariableNameExpression($parentNode);
|
||||
|
||||
// qualified-name
|
||||
case TokenKind::QualifiedName: // TODO Qualified name
|
||||
return $this->parseQualifiedNameExpression($parentNode);
|
||||
|
||||
// literal
|
||||
case TokenKind::DecimalLiteralToken: // TODO merge dec, oct, hex, bin, float -> NumericLiteral
|
||||
case TokenKind::OctalLiteralToken:
|
||||
case TokenKind::HexadecimalLiteralToken:
|
||||
case TokenKind::BinaryLiteralToken:
|
||||
case TokenKind::FloatingLiteralToken:
|
||||
case TokenKind::InvalidOctalLiteralToken:
|
||||
case TokenKind::InvalidHexadecimalLiteral:
|
||||
case TokenKind::InvalidBinaryLiteral:
|
||||
|
||||
case TokenKind::StringLiteralToken: // TODO merge unterminated
|
||||
case TokenKind::UnterminatedStringLiteralToken:
|
||||
case TokenKind::NoSubstitutionTemplateLiteral:
|
||||
case TokenKind::UnterminatedNoSubstitutionTemplateLiteral:
|
||||
return $this->parseLiteralExpression($parentNode);
|
||||
|
||||
|
||||
case TokenKind::TemplateStringStart:
|
||||
return $this->parseTemplateString($parentNode);
|
||||
|
||||
|
||||
// TODO constant-expression
|
||||
|
||||
// intrinsic-construct
|
||||
case TokenKind::EchoKeyword:
|
||||
case TokenKind::ListKeyword:
|
||||
case TokenKind::UnsetKeyword:
|
||||
return $this->parseIntrinsicConstructExpression($parentNode);
|
||||
|
||||
// intrinsic-operator
|
||||
case TokenKind::ArrayKeyword:
|
||||
case TokenKind::EmptyKeyword:
|
||||
case TokenKind::EvalKeyword:
|
||||
case TokenKind::ExitKeyword:
|
||||
case TokenKind::DieKeyword:
|
||||
case TokenKind::IsSetKeyword:
|
||||
case TokenKind::PrintKeyword:
|
||||
// return $this->
|
||||
|
||||
// anonymous-function-creation-expression
|
||||
case TokenKind::StaticKeyword:
|
||||
case TokenKind::FunctionKeyword:
|
||||
|
||||
// ( expression )
|
||||
case TokenKind::OpenParenToken:
|
||||
return true;*/
|
||||
|
||||
default:
|
||||
// TODO
|
||||
$token = $this->getCurrentToken();
|
||||
$this->advanceToken();
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
private function parseEmptyStatement($parentNode) {
|
||||
$node = new EmptyStatementNode();
|
||||
$node->children = array();
|
||||
array_push($node->children, $this->eat(TokenKind::SemicolonToken));
|
||||
$node->parent = $parentNode;
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function parseLiteralExpression($parentNode) {
|
||||
}
|
||||
}
|
||||
|
||||
class ParseContext {
|
||||
|
|
|
@ -5,9 +5,9 @@ require_once(__DIR__ . "/../parser.php");
|
|||
require_once(__DIR__ . "/../Token.php");
|
||||
require_once(__DIR__ . "/LexerInvariantsTest.php");
|
||||
|
||||
use PhpParser\Node\Node;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PhpParser\TokenKind;
|
||||
use PhpParser\Node;
|
||||
|
||||
class ParserInvariantsTest extends LexerInvariantsTest {
|
||||
const FILENAMES = array (
|
||||
|
|
Загрузка…
Ссылка в новой задаче