Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moodle 35 stable 743 litteralsupport #749

Open
wants to merge 2 commits into
base: MOODLE_35_STABLE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions classes/dataflow_lexer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_dataflows;

use Symfony\Component\ExpressionLanguage\Token;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\ExpressionLanguage\TokenStream;

/**
* Class adding modification to Symfony Lexer to suit dataflow needs.
*
* @package tool_dataflows
* @author Ghaly Marc-Alexandre <[email protected]>
* @copyright Catalyst IT, 2023
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dataflow_lexer extends \Symfony\Component\ExpressionLanguage\Lexer {
/**
* Tokenizes an expression.
*
* @param string $expression The expression to tokenize
*
* @return TokenStream A token stream instance
*
* @throws SyntaxError
*/
public function tokenize($expression)
{
$expression = str_replace(["\r", "\n", "\t", "\v", "\f"], ' ', $expression);
$cursor = 0;
$tokens = [];
$brackets = [];
$end = \strlen($expression);

while ($cursor < $end) {
if (' ' == $expression[$cursor]) {
++$cursor;

continue;
}

if (preg_match('/[0-9]+(?:\.[0-9]+)?/A', $expression, $match, 0, $cursor)) {
// Numbers.
// Floats.
$number = (float) $match[0];
if (preg_match('/^[0-9]+$/', $match[0]) && $number <= \PHP_INT_MAX) {
// Integers lower than the maximum.
$number = (int) $match[0];
}
$tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1);
$cursor += \strlen($match[0]);
} elseif (preg_match("/{'(\W[a-zA-Z_\x7f-\xff][a-zA-Z0-9_.\x7f-\xff]*)'}/A", $expression, $match, 0, $cursor)) {
// Names litteral.
$tokens[] = new Token(Token::NAME_TYPE, $match[1], $cursor + 1);
$cursor += \strlen($match[0]);
} elseif (false !== strpos('([{', $expression[$cursor])) {
// Opening bracket.
$brackets[] = [$expression[$cursor], $cursor];

$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
++$cursor;
} elseif (false !== strpos(')]}', $expression[$cursor])) {
// Closing bracket.
if (empty($brackets)) {
throw new SyntaxError(sprintf('Unexpected "%s".', $expression[$cursor]), $cursor, $expression);
}

list($expect, $cur) = array_pop($brackets);
if ($expression[$cursor] != strtr($expect, '([{', ')]}')) {
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression);
}

$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
++$cursor;
} elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, 0, $cursor)) {
// Strings.
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
$cursor += \strlen($match[0]);
} elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
// Operators.
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
$cursor += \strlen($match[0]);
} elseif (false !== strpos('.,?:', $expression[$cursor])) {
// Punctuation.
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
++$cursor;
} elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, 0, $cursor)) {
// Names.
$tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1);
$cursor += \strlen($match[0]);
} else {
// Unlexable.
throw new SyntaxError(sprintf('Unexpected character "%s".', $expression[$cursor]), $cursor, $expression);
}
}

$tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1);

if (!empty($brackets)) {
list($expect, $cur) = array_pop($brackets);
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression);
}

return new TokenStream($tokens, $expression);
}
}
4 changes: 4 additions & 0 deletions classes/local/step/reader_json.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ protected function parse_json() {
['data' => $decodedjson]
);

if (!is_array($returnarray)) {
$returnarray = (object) $returnarray;
}
Comment on lines +87 to +89
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give me a real example where this is required?


if (is_null($returnarray)) {
throw new \moodle_exception(get_string('reader_json:failed_to_fetch_array',
'tool_dataflows', $config->arrayexpression));
Expand Down
6 changes: 3 additions & 3 deletions classes/parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\Lexer;
use Symfony\Component\ExpressionLanguage\Node;
use Symfony\Component\ExpressionLanguage\Token;
use Symfony\Component\ExpressionLanguage\TokenStream;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
use tool_dataflows\dataflow_lexer;
use tool_dataflows\local\provider\expression_provider;

/**
Expand Down Expand Up @@ -80,9 +80,9 @@ private function __construct() {
*
* @return Lexer
*/
private function get_lexer(): Lexer {
private function get_lexer(): dataflow_lexer {
if (is_null($this->lexer)) {
$this->lexer = new Lexer();
$this->lexer = new dataflow_lexer();
}
return $this->lexer;
}
Expand Down
3 changes: 2 additions & 1 deletion vendor/symfony/expression-language/ExpressionLanguage.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter;
use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface;
use tool_dataflows\dataflow_lexer;

/**
* Allows to compile and evaluate expressions written in your own DSL.
Expand Down Expand Up @@ -152,7 +153,7 @@ protected function registerFunctions()
private function getLexer()
{
if (null === $this->lexer) {
$this->lexer = new Lexer();
$this->lexer = new dataflow_lexer();
}

return $this->lexer;
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2023050401;
$plugin->version = 2023051501;
$plugin->release = 2022102600;
$plugin->requires = 2017051500; // Our lowest supported Moodle (3.3.0).
$plugin->supported = [35, 401]; // Available as of Moodle 3.9.0 or later.
Expand Down