openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit cd284ba56a2aea2b9293e9f2528fb92f9cf06c4e
parent efc3e06a50600d987ca4bbbe752636953d88e728
Author: Jan Dankert <develop@jandankert.de>
Date:   Sun, 29 May 2022 01:13:27 +0200

New: DSL with support for functions

Diffstat:
Mmodules/cms/generator/ValueGenerator.class.php | 5+++--
Amodules/cms/generator/dsl/DslFolder.class.php | 35+++++++++++++++++++++++++++++++++++
Amodules/cms/generator/dsl/DslObject.class.php | 26++++++++++++++++++++++++++
Mmodules/cms/generator/dsl/DslPage.class.php | 12++++++------
Mmodules/dsl/DslAstParser.class.php | 7-------
Mmodules/dsl/ast/DslElement.class.php | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmodules/dsl/ast/DslExpression.class.php | 42+++++++++++++++++++++++++++++++++---------
Mmodules/dsl/ast/DslFor.class.php | 26+++++++++++++++++++++++++-
Mmodules/dsl/ast/DslFunction.class.php | 42++++++++++++++++++++++++------------------
Mmodules/dsl/ast/DslFunctionCall.class.php | 37+++++++++++++++++++++++++++++++------
Amodules/dsl/ast/DslOperation.class.php | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmodules/dsl/ast/DslStatementList.class.php | 143+++++++++++++-------------------------------------------------------------------
Mmodules/dsl/executor/DslInterpreter.class.php | 26++++++++++++++++++++------
Amodules/dsl/standard/StandardArray.class.php | 12++++++++++++
Amodules/dsl/standard/StandardDate.class.php | 18++++++++++++++++++
Amodules/dsl/standard/StandardMath.class.php | 12++++++++++++
16 files changed, 475 insertions(+), 176 deletions(-)

diff --git a/modules/cms/generator/ValueGenerator.class.php b/modules/cms/generator/ValueGenerator.class.php @@ -11,6 +11,7 @@ use cms\base\Startup; use cms\generator\dsl\DslAlert; use cms\generator\dsl\DslConsole; use cms\generator\dsl\DslDocument; +use cms\generator\dsl\DslPage; use cms\generator\dsl\DslWrite; use cms\macros\MacroRunner; use cms\model\BaseObject; @@ -795,12 +796,12 @@ class ValueGenerator extends BaseGenerator case 'js': ob_start(); $executor = new DslInterpreter(); - $executor->setContext( [ + $executor->addContext( [ 'console' => new DslConsole(), 'document' => new DslDocument(), 'write' => new DslWrite(), 'alert' => new DslAlert(), - 'page' => $page, + 'page' => new DslPage( $page ), ]); try { diff --git a/modules/cms/generator/dsl/DslFolder.class.php b/modules/cms/generator/dsl/DslFolder.class.php @@ -0,0 +1,34 @@ +<?php + +namespace cms\generator\dsl; + +use cms\model\Folder; +use dsl\context\DslObject as DslContextObject; + + +class DslFolder implements DslContextObject +{ + /** + * @var Folder + */ + private $folder; + + public $id; + + /** + * DslPage constructor. + * @param Folder $folder + */ + public function __construct($folder) + { + $this->folder = folder; + + $this->id = $folder->getId(); + } + + public function children() { + return array_map( function($object) { + return new DslObject( $object ); + }, $this->folder->getObjects() ); + } +} +\ No newline at end of file diff --git a/modules/cms/generator/dsl/DslObject.class.php b/modules/cms/generator/dsl/DslObject.class.php @@ -0,0 +1,25 @@ +<?php + +namespace cms\generator\dsl; + +use cms\model\BaseObject; +use dsl\context\DslObject as DslContextObject; + +class DslObject implements DslContextObject +{ + private $object; + + public $id; + + /** + * DslPage constructor. + * @param BaseObject $object + */ + public function __construct($object) + { + $this->object = $object; + + $this->id = $object->getId(); + } + +} +\ No newline at end of file diff --git a/modules/cms/generator/dsl/DslPage.class.php b/modules/cms/generator/dsl/DslPage.class.php @@ -2,24 +2,24 @@ namespace cms\generator\dsl; +use cms\model\Page; use dsl\context\DslObject; class DslPage implements DslObject { private $page; + public $id; + /** * DslPage constructor. - * @param $page + * @param Page $page */ public function __construct($page) { $this->page = $page; - } - - public function execute( $text ) - { - echo $text; + $this->id = $page->getId(); } + } \ No newline at end of file diff --git a/modules/dsl/DslAstParser.class.php b/modules/dsl/DslAstParser.class.php @@ -1,13 +1,6 @@ <?php namespace dsl; -use dsl\ast\DslAssignment; -use dsl\ast\DslBlock; -use dsl\ast\DslFor; -use dsl\ast\DslFunction; -use dsl\ast\DslIf; -use dsl\ast\DslInitialisation; -use dsl\ast\DslReturn; use dsl\ast\DslStatementList; /** diff --git a/modules/dsl/ast/DslElement.class.php b/modules/dsl/ast/DslElement.class.php @@ -2,7 +2,150 @@ namespace dsl\ast; +use dsl\DslParserException; +use dsl\DslToken; + class DslElement { + /** + * + * @param $tokens DslToken[] + * @return DslToken[] + * @throws DslParserException + */ + protected function getGroup(&$tokens) + { + $groupTokens = []; + $depth = 0; + + $nextToken = array_shift($tokens); + if ($nextToken == null) + throw new DslParserException('Unexpecting end, missing closing group'); + if ($nextToken->type != DslToken::T_BRACKET_OPEN) + throw new DslParserException('Expecting parenthesis', $nextToken->lineNumber); + + while (true) { + $nextToken = array_shift($tokens); + if ($nextToken == null) + throw new DslParserException('Unclosed parenthesis'); + if ($nextToken->type == DslToken::T_BRACKET_OPEN) + $depth += 1; + if ($nextToken->type == DslToken::T_BRACKET_CLOSE) + if ($depth == 0) + return $groupTokens; + else + $depth--; + + $groupTokens[] = $nextToken; + } + } + + /** + * + * @param $tokens DslToken[] + * @return DslToken[] + * @throws DslParserException + */ + protected function getBlock(&$tokens) + { + $blockTokens = []; + $depth = 0; + + $nextToken = array_shift($tokens); + if ($nextToken->type != DslToken::T_BLOCK_BEGIN) + throw new DslParserException('Expecting block', $nextToken->lineNumber); + + while (true) { + $nextToken = array_shift($tokens); + if ($nextToken->type == null) + throw new DslParserException('Unclosed block', $nextToken->lineNumber); + if ($nextToken->type == DslToken::T_BLOCK_BEGIN) + $depth += 1; + if ($nextToken->type == DslToken::T_BLOCK_END) + if ($depth == 0) + return $blockTokens; + else + $depth--; + + $blockTokens[] = $nextToken; + } + } + + /** + * + * @param $tokens DslToken[] + * @return DslToken[] + * @throws DslParserException + */ + protected function getStatementOrBlock(&$tokens) + { + if (!$tokens) + return []; + + $firstToken = $tokens[0]; + if ($firstToken->type == DslToken::T_BLOCK_BEGIN) + return $this->getBlock($tokens); + else + return $this->getSingleStatement($tokens); + } + + + /** + * parse single statement + * + * @param $tokens DslToken[] + * @return DslToken[] + * @throws DslParserException + */ + protected function getSingleStatement(&$tokens) + { + $depth = 0; + $statementTokens = []; + while (true) { + $nextToken = array_shift($tokens); + if ($nextToken == null) + throw new DslParserException('unrecognized statement'); + + if ($depth == 0 && $nextToken->type == DslToken::T_STATEMENT_END) + return $statementTokens; + + if ($nextToken->type == DslToken::T_BLOCK_BEGIN) + $depth++; + if ($nextToken->type == DslToken::T_BLOCK_END) + $depth--; + if ($depth < 0) + throw new DslParserException('Unexpected closing block', $nextToken->lineNumber); + + $statementTokens[] = $nextToken; + } + } + + + /** + * Split tokens on comma separator. + * + * @param DslToken[] $functionParameter + * @return DslToken[][] + */ + protected function splitByComma($functionParameter) + { + $parts = []; + $act = []; + foreach ( $functionParameter as $token ) { + + if ( $token->type == DslToken::T_COMMA ) { + $parts[] = $act; + $act = []; // Cleanup + continue; + } + + $act[] = $token; + } + + if ( $act ) + $parts[] = $act; + + return $parts; + } } \ No newline at end of file diff --git a/modules/dsl/ast/DslExpression.class.php b/modules/dsl/ast/DslExpression.class.php @@ -5,7 +5,7 @@ namespace dsl\ast; use dsl\DslParserException; use dsl\DslToken; -class DslExpression implements DslStatement +class DslExpression extends DslElement implements DslStatement { private $value; @@ -31,6 +31,17 @@ class DslExpression implements DslStatement return; } + + // Split the expression on operators + foreach ( array_reverse(['*','/','-','+','!','||','&&','>','>=','<=','<']) as $tokenValue ) { + list( $left,$right ) = $this->splitTokenOnOperator( $tokens, $tokenValue ); + if ( sizeof($right ) > 0 ) { + $this->value = new DslOperation( $tokenValue,$left,$right ); + return; + } + } + + if ( sizeof($tokens) == 1) { $token = $tokens[0]; switch( $token->type ) { @@ -49,8 +60,6 @@ class DslExpression implements DslStatement return; } - $depth = 0; - while( true ) { $token = array_shift( $tokens ); @@ -64,16 +73,31 @@ class DslExpression implements DslStatement $nextToken = array_shift( $tokens ); if ( $nextToken && $nextToken->type == DslToken::T_BRACKET_OPEN ) { - $nextToken = array_shift( $tokens ); - if ( $nextToken && ( $nextToken->type == DslToken::T_TEXT || $nextToken->type == DslToken::T_STRING || $nextToken->type == DslToken::T_NUMBER ) ) { + array_unshift( $tokens,$nextToken ); + + $parameterGroup = $this->getGroup( $tokens ); + $splittedParameters = $this->splitByComma( $parameterGroup ); - echo "found function"; - $this->value = new DslFunctionCall( $token->value, [$nextToken] ); - return; - } + $this->value = new DslFunctionCall( $token->value,$splittedParameters ); + return; } } } $this->value = new DslNull(); } + + + private function splitTokenOnOperator( $tokens, $operatorValue ) { + // Split the expression on operators + $leftToken = []; + + while( true ) { + $token = array_shift($tokens); + if ( $token == null ) + return [ $leftToken,[] ]; + if ( $token->type == DslToken::T_OPERATOR && $token->value == $operatorValue ) + return [ $leftToken,$tokens ]; + $leftToken[] = $token; + } + } } \ No newline at end of file diff --git a/modules/dsl/ast/DslFor.class.php b/modules/dsl/ast/DslFor.class.php @@ -2,16 +2,40 @@ namespace dsl\ast; +use dsl\DslRuntimeException; +use dsl\DslToken; + class DslFor implements DslStatement { + private $name; + private $list; private $statements; - public function __construct( $group, $block ) + /** + * DslFor constructor. + * + * @param $name String + * @param $list DslToken[] + * @param $statements DslToken[] + */ + public function __construct($name, $list, $statements) { + $this->name = $name; + $this->list = new DslExpression( $list ); + $this->statements = new DslStatementList( $statements ); } + public function execute( & $context ) { + $list = $this->list->execute( $context ); + if ( !is_array( $list ) ) + throw new DslRuntimeException('for value is not a list'); + $copiedContext = $context; + foreach( $list as $blockVar ) { + $copiedContext[ $this->name ] = $blockVar; + $this->statements->execute( $copiedContext ); + } } public function parse($tokens) diff --git a/modules/dsl/ast/DslFunction.class.php b/modules/dsl/ast/DslFunction.class.php @@ -3,24 +3,32 @@ namespace dsl\ast; use dsl\DslParserException; +use dsl\DslRuntimeException; use dsl\DslToken; -class DslFunction implements DslStatement +class DslFunction extends DslElement implements DslStatement { - private $name; - /** * @var String[] */ - private $parameters; + public $parameters; /** * @var DslStatementList */ - private $body; + public $body; + /** + * creates the function. + * + * @param array $context + * @return mixed|void + * @throws DslRuntimeException + */ public function execute( & $context ) { + $clonedContext = $context; + return $this->body->execute( $context ); } /** @@ -30,20 +38,17 @@ class DslFunction implements DslStatement * @param $functionBody DslToken[] * @throws DslParserException */ - public function __construct( $name,$functionParameter, $functionBody ) + public function __construct( $functionParameter, $functionBody ) { - $this->name = $name; - - foreach ( $functionParameter as $token ) { - - if ( $token->type == DslToken::T_COMMA ) - continue; - - if ( $token->type == DslToken::T_STRING ) - $this->parameters[] = $token->value; - else - throw new DslParserException("Unknown token in function parameter",$token->lineNumber ); - + $this->parameters = []; + foreach( $this->splitByComma( $functionParameter ) as $parameter ) { + if ( sizeof($parameter) != 1 ) + throw new DslParserException('function parameter must be a name'); + $nameToken = $parameter[0]; + if ( $nameToken->type != DslToken::T_STRING ) + throw new DslParserException('function parameter must be a name'); + + $this->parameters[] = $nameToken->value; } $this->body = new DslStatementList( $functionBody ); @@ -52,4 +57,5 @@ class DslFunction implements DslStatement public function parse($tokens) { } + } \ No newline at end of file diff --git a/modules/dsl/ast/DslFunctionCall.class.php b/modules/dsl/ast/DslFunctionCall.class.php @@ -3,6 +3,7 @@ namespace dsl\ast; use dsl\DslRuntimeException; +use dsl\DslToken; class DslFunctionCall implements DslStatement { @@ -12,12 +13,16 @@ class DslFunctionCall implements DslStatement /** * DslFunctionCall constructor. * @param $name - * @param $parameters + * @param $parameters DslToken[][] */ public function __construct($name, $parameters) { $this->name = $name; - $this->parameters = new DslExpression($parameters); + + $this->parameters = []; + + foreach( $parameters as $parameter ) + $this->parameters[] = new DslExpression( $parameter ); } @@ -30,10 +35,30 @@ class DslFunctionCall implements DslStatement throw new DslRuntimeException('function \''.$this->name.'\' does not exist.'); $function = $context[$this->name]; - if ( $function instanceof \dsl\context\DslFunction ) - return $function->execute( $this->parameters->execute( $context ) ); - elseif ( $function instanceof DslFunction ) - return $function->execute( $context ); + + if ( $function instanceof \dsl\context\DslFunction ) { + // call "external" native function + $parameterValues = array_map( function( $parameter ) use ($context) { + return $parameter->execute( $context ); + }, $this->parameters ); + + return call_user_func_array(array($function,'execute'),$parameterValues ); + } + elseif ( $function instanceof DslFunction ) { + // call DSL function + if ( sizeof( $function->parameters ) != sizeof($this->parameters) ) + throw new DslRuntimeException('function call parameter count must match the function declaration'); + + // Put all function parameters to the function context. + $parameters = array_combine( $function->parameters, $this->parameters ); + + $cloneContext = $context; + foreach( $parameters as $name=>$parameter ) { + $cloneContext[ $name ] = $parameter->execute( $context ); + } + return $function->execute( $cloneContext ); + + } else throw new DslRuntimeException('function \''.$this->name.'\' is not callable.'); } diff --git a/modules/dsl/ast/DslOperation.class.php b/modules/dsl/ast/DslOperation.class.php @@ -0,0 +1,64 @@ +<?php + +namespace dsl\ast; + +use dsl\DslParserException; +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslOperation implements DslStatement +{ + private $operator; + private $left; + private $right; + + /** + * DslOperation constructor. + * @param $operator + * @param $left + * @param $right + */ + public function __construct($operator, $left, $right) + { + $this->operator = $operator; + $this->left = new DslExpression( $left ); + $this->right = new DslExpression($right ); + } + + + /** + * @throws DslRuntimeException + */ + public function execute(& $context ) { + + $left = $this->left->execute( $context ); + $right = $this->right->execute( $context ); + + switch( $this->operator ) { + case '+': + if ( is_string($left) ) + return $left . $right; + else + return $left + $right; + + case '-': + return $left - $right; + + case '*': + return $left * $right; + + case '/': + return $left / $right; + + default: + throw new DslRuntimeException('Unknown operator \''.$this->operator.'\''); + } + + } + + + public function parse($tokens) + { + // TODO: Implement parse() method. + } +} +\ No newline at end of file diff --git a/modules/dsl/ast/DslStatementList.class.php b/modules/dsl/ast/DslStatementList.class.php @@ -5,10 +5,12 @@ namespace dsl\ast; use dsl\DslParserException; use dsl\DslToken; -class DslStatementList implements DslStatement +class DslStatementList extends DslElement implements DslStatement { private $statements = []; + private $functions = []; + public function __construct($tokenList) { $this->parse($tokenList); @@ -20,14 +22,21 @@ class DslStatementList implements DslStatement */ public function parse($tokens) { - $this->parseTokens($tokens); } public function execute( & $context) { - foreach ($this->statements as $statement) - $statement->execute($context); + // Auto hoisting for functions: Add functions to context. + $context = array_merge( $context, $this->functions ); + + foreach ($this->statements as $statement) { + + $value = $statement->execute($context); + + if ($statement instanceof DslReturn) + return $value; // Return to the caller + } } @@ -71,7 +80,7 @@ class DslStatementList implements DslStatement $functionParameter = $this->getGroup($tokens); $functionBlock = $this->getBlock($tokens); - $this->statements[] = new DslFunction($name,$functionParameter, $functionBlock); + $this->functions[ $name ] = new DslFunction( $functionParameter, $functionBlock ); break; case DslToken::T_IF: @@ -109,7 +118,14 @@ class DslStatementList implements DslStatement $forGroup = $this->getGroup(); $forBlock = $this->getStatementOrBlock(); - $this->statements[] = new DslFor( $forGroup, $forBlock ); + $varName = array_shift( $forGroup ); + if ( $varName->type != DslToken::T_STRING ) + throw new DslParserException('for loop variable missing'); + $ofName = array_shift( $forGroup ); + if ( $ofName->type != DslToken::T_STRING || strtolower($ofName->value) != 'or' ) + throw new DslParserException('missing \'of\' in for loop'); + + $this->statements[] = new DslFor( $varName, $forGroup, $forBlock ); break; case DslToken::T_NEW: @@ -152,119 +168,4 @@ class DslStatementList implements DslStatement } - /** - * - * @param $tokens DslToken[] - * @return DslToken[] - * @throws DslParserException - */ - private function getGroup(&$tokens) - { - $groupTokens = []; - $depth = 0; - - $nextToken = array_shift($tokens); - if ($nextToken == null) - throw new DslParserException('Unexpecting end, missing closing group'); - if ($nextToken->type != DslToken::T_BRACKET_OPEN) - throw new DslParserException('Expecting parenthesis', $nextToken->lineNumber); - - while (true) { - $nextToken = array_shift($tokens); - if ($nextToken->type == null) - throw new DslParserException('Unclosed parenthesis', $nextToken->lineNumber); - if ($nextToken->type == DslToken::T_BRACKET_OPEN) - $depth += 1; - if ($nextToken->type == DslToken::T_BRACKET_CLOSE) - if ($depth == 0) - return $groupTokens; - else - $depth--; - - $groupTokens[] = $nextToken; - } - } - - /** - * - * @param $tokens DslToken[] - * @return DslToken[] - * @throws DslParserException - */ - private function getBlock(&$tokens) - { - $blockTokens = []; - $depth = 0; - - $nextToken = array_shift($tokens); - if ($nextToken->type != DslToken::T_BLOCK_BEGIN) - throw new DslParserException('Expecting block', $nextToken->lineNumber); - - while (true) { - $nextToken = array_shift($tokens); - if ($nextToken->type == null) - throw new DslParserException('Unclosed block', $nextToken->lineNumber); - if ($nextToken->type == DslToken::T_BLOCK_BEGIN) - $depth += 1; - if ($nextToken->type == DslToken::T_BLOCK_END) - if ($depth == 0) - return $blockTokens; - else - $depth--; - - $blockTokens[] = $nextToken; - } - } - - /** - * - * @param $tokens DslToken[] - * @return DslToken[] - * @throws DslParserException - */ - private function getStatementOrBlock(&$tokens) - { - if (!$tokens) - return []; - - $firstToken = $tokens[0]; - if ($firstToken->type == DslToken::T_BLOCK_BEGIN) - return $this->getBlock($tokens); - else - return $this->getSingleStatement($tokens); - } - - - /** - * parse single statement - * - * @param $tokens DslToken[] - * @return DslToken[] - * @throws DslParserException - */ - private function getSingleStatement(&$tokens) - { - //echo "<h3>Statement:</h3><pre>"; var_dump( $tokens ); echo "</pre>"; - - $depth = 0; - $statementTokens = []; - while (true) { - $nextToken = array_shift($tokens); - if ($nextToken == null) - throw new DslParserException('unrecognized statement'); - - if ($depth == 0 && $nextToken->type == DslToken::T_STATEMENT_END) - return $statementTokens; - - if ($nextToken->type == DslToken::T_BLOCK_BEGIN) - $depth++; - if ($nextToken->type == DslToken::T_BLOCK_END) - $depth--; - if ($depth < 0) - throw new DslParserException('Unexpected closing block', $nextToken->lineNumber); - - $statementTokens[] = $nextToken; - } - } - } \ No newline at end of file diff --git a/modules/dsl/executor/DslInterpreter.class.php b/modules/dsl/executor/DslInterpreter.class.php @@ -4,24 +4,38 @@ namespace dsl\executor; use dsl\DslAstParser; use dsl\DslLexer; +use dsl\standard\StandardArray; +use dsl\standard\StandardDate; +use dsl\standard\StandardMath; class DslInterpreter { private $context = []; - public function setContext( $context ) { - $this->context = $context; + public function __construct() + { + // Standard-Globals + $this->addContext( [ + 'Math' => new StandardMath(), + 'Array' => new StandardArray(), + 'Date' => new StandardDate(), + ] ); } - - public function run( $ast ) { - $ast->execute( $this->context ); + /** + * adds an external context to the interpreter environment. + * + * @param $context [] + */ + public function addContext($context ) { + $this->context = array_merge( $this->context, $context ); } + /** * @throws \Exception */ - public function runCode($code ) { + public function runCode( $code ) { $lexer = new DslLexer(); $token = $lexer->tokenize( $code ); diff --git a/modules/dsl/standard/StandardArray.class.php b/modules/dsl/standard/StandardArray.class.php @@ -0,0 +1,11 @@ +<?php +namespace dsl\standard; + +class StandardArray +{ + public function of() { + + return func_get_args(); + } + +} +\ No newline at end of file diff --git a/modules/dsl/standard/StandardDate.class.php b/modules/dsl/standard/StandardDate.class.php @@ -0,0 +1,17 @@ +<?php +namespace dsl\standard; + +class StandardDate +{ + /** + * Date.now() + * + * milliseconds since 1970. + * + * @return int + */ + public function now() { + + return time(); + } +} +\ No newline at end of file diff --git a/modules/dsl/standard/StandardMath.class.php b/modules/dsl/standard/StandardMath.class.php @@ -0,0 +1,11 @@ +<?php +namespace dsl\standard; + +class StandardMath +{ + public function sqrt( $number ) { + + return sqrt( $number ); + } + +} +\ No newline at end of file