scriptbox

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

commit 932c338a2f1af2f2ccb5c9024387c99450e31f01
Author: Jan Dankert <develop@jandankert.de>
Date:   Mon,  6 Jun 2022 14:06:46 +0200

First commit after fetching from upstream repo.

Diffstat:
A.gitignore | 1+
AREADME.md | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aautoload.php | 22++++++++++++++++++++++
Adsl/DslAstParser.class.php | 36++++++++++++++++++++++++++++++++++++
Adsl/DslException.class.php | 18++++++++++++++++++
Adsl/DslLexer.class.php | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/DslParserException.class.php | 18++++++++++++++++++
Adsl/DslRuntimeException.class.php | 18++++++++++++++++++
Adsl/DslToken.class.php | 47+++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslAssignment.class.php | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslDecimal.class.php | 17+++++++++++++++++
Adsl/ast/DslElement.class.php | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslExpression.class.php | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslFor.class.php | 47+++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslFunction.class.php | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslFunctionCall.class.php | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslIf.class.php | 44++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslInitialisation.class.php | 33+++++++++++++++++++++++++++++++++
Adsl/ast/DslInteger.class.php | 30++++++++++++++++++++++++++++++
Adsl/ast/DslNull.class.php | 21+++++++++++++++++++++
Adsl/ast/DslOperation.class.php | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslProperty.class.php | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslReturn.class.php | 23+++++++++++++++++++++++
Adsl/ast/DslSequence.class.php | 42++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslStatement.class.php | 24++++++++++++++++++++++++
Adsl/ast/DslStatementList.class.php | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/ast/DslString.class.php | 28++++++++++++++++++++++++++++
Adsl/ast/DslVariable.class.php | 32++++++++++++++++++++++++++++++++
Adsl/context/DslFunction.class.php | 8++++++++
Adsl/context/DslObject.class.php | 9+++++++++
Adsl/executor/DslInterpreter.class.php | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adsl/standard/StandardArray.class.php | 12++++++++++++
Adsl/standard/StandardDate.class.php | 18++++++++++++++++++
Adsl/standard/StandardMath.class.php | 42++++++++++++++++++++++++++++++++++++++++++
Adsl/standard/Write.class.php | 16++++++++++++++++
Aindex.php | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aupstream.sh | 6++++++
37 files changed, 2068 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/README.md b/README.md @@ -0,0 +1,140 @@ +# Script Sandbox + +This is a script interpreter for PHP. Custom code is parsed and interpreted in a sandbox. + +It was written for the [OpenRat CMS](http://www.openrat.de) and is maintained there. + + + +## Using + +There is the `dsl\executor\DslInterpreter` class to run your code: + + $interpreter = new DslInterpreter(); + $interpreter->runCode( $code ); + +### get output + +For getting the standard output, simple call `getOutput()`: + + $interpreter = new DslInterpreter(); + $interpreter->runCode( $code ); + $output = $interpreter->getOutput() ); // get the output + +### add context + +you may add custom objects to the calling context + + $interpreter = new DslInterpreter(); + $interpreter->addContext( [ 'mycontext'=> new MyContextObject() ] ); + $interpreter->runCode( $code ); + +Your class `MyContextObject` must implement `dsl\context\DslObject`,then your code my contain + + mycontext.method(); + +### get return value + + $interpreter = new DslInterpreter(); + $returnValue = $interpreter->runCode( $code ); + +## Syntax + +The language syntax is a subset of javascript. + +## Features + +### comments + +single line comments like + + // this is a comment + +and multiline commens like + + /** + * this is a comment + */ + +are supported + + +### text + + write( "this is a 'string'" ); + write( 'this is a "string"' ); + + +### variables + +variables and string concatenation: + + age = 18; + write("my age is " + age ); + +variables may be initialized with `let`,`var` or `const` but this is optional: + + let age = 18; // "let" is optional and completely ignored + write("my age is " + age ); + +every variable is "block scoped". + + +### function scope + +variables are valid for the current block. + + age = 18; + + function add() { + age = age + 1; + write( "next year, you are " + age ); // 19 + } + add(); + + write( "but this year you are " + age ); // 18 + + +### function calls + +Example + + write( "powered by " + name() ); + + function name() { + return "script sandbox"; + { + + +### if / else + + age = 17; + if ( age < 18 ) + write( "you are under 18" ); + else { + write( "you are already 18" ); + write( "you are allowed to enter" ); + } + +### full arithmetic calculations + + write( 1 + 2 * 3 ); // this resolves to 7 because of the priority + +### arrays and for loops + + animals = Array.of('lion', 'ape', 'fish'); + + for( animal of animals ) + write( animal + " is an animal." ); + + +### object properties + + write( "PI is " + Math.PI ); + + +## Unsupported + +there is NO support for +- creating classes or objects +- async, await +\ No newline at end of file diff --git a/autoload.php b/autoload.php @@ -0,0 +1,21 @@ +<?php +// Enable class autoloading for all classes + +spl_autoload_register( + + /** + * Loads classes from modules. + * + * The PHP default loader is unusable, because it will always use lowercase file names. + * + * @param $className Class name + * @return void + */ + function ($className) { + + $c = __DIR__.DIRECTORY_SEPARATOR.str_replace( "\\", DIRECTORY_SEPARATOR, $className).'.class.php'; + + if ( is_file($c) ) + require($c); + } +); +\ No newline at end of file diff --git a/dsl/DslAstParser.class.php b/dsl/DslAstParser.class.php @@ -0,0 +1,35 @@ +<?php +namespace dsl; + +use dsl\ast\DslStatementList; + +/** + * Creates and interprets an abstract syntax tree (AST). + */ +class DslAstParser +{ + /** + * @var DslStatementList + */ + public $rootStatement; + + /** + * @throws DslParserException + */ + public function parse($tokens ) { + + //echo "<h1>Token:</h1><pre>"; var_export( $tokens ); echo "</pre>"; + + $this->rootStatement = new DslStatementList( $tokens ); + + //echo "<h1>AST:</h1><pre>"; var_export( $this->rootStatement ); echo "</pre>"; + } + + + public function execute($context) + { + return $this->rootStatement->execute( $context ); + } + + +} +\ No newline at end of file diff --git a/dsl/DslException.class.php b/dsl/DslException.class.php @@ -0,0 +1,17 @@ +<?php + +namespace dsl; + + +class DslException extends \Exception +{ + /** + * DslParserException constructor. + * @param $message + * @param $lineNumber + */ + public function __construct($message, $lineNumber=null) + { + parent::__construct( $message . ($lineNumber?' on line ' . $lineNumber:'') ); + } +} +\ No newline at end of file diff --git a/dsl/DslLexer.class.php b/dsl/DslLexer.class.php @@ -0,0 +1,248 @@ +<?php +namespace dsl; + +class DslLexer +{ + private $token = []; + + + const KEYWORDS = [ + 'function' => DslToken::T_FUNCTION, + 'for' => DslToken::T_FOR, + 'if' => DslToken::T_IF, + 'else' => DslToken::T_ELSE, + 'let' => DslToken::T_LET, + 'const' => DslToken::T_LET, + 'var' => DslToken::T_LET, + 'return' => DslToken::T_RETURN, + 'new' => DslToken::T_NEW, + ]; + + const UNUSED_KEYWORDS = [ + 'null', + 'true', + 'false', + 'implements', + 'interface', + 'package', + 'private', + 'protected', + 'public', + 'static', + 'in', + 'do', + 'new', + 'try', + 'this', + 'case', + 'void', + 'with', + 'enum', + 'while', + 'break', + 'catch', + 'throw', + 'yield', + 'class', + 'super', + 'typeof', + 'delete', + 'switch', + 'export', + 'import', + 'default', + 'finally', + 'extends', + 'continue', + 'debugger', + 'instanceof', + ]; + /** + * @param $code + * @return array(DslToken) + */ + public function tokenize( $code ) { + + //echo "Code: <pre>".$code."</pre>"; + + $line = 1; + + // mb_str_split only available since PHP 7.4 + $chars = str_split($code); + + while( true ) { + $char = array_shift($chars); + + if ( $char == null ) + break; + + if ( ( $char == ' ' )) + continue; + + if ( ( $char == "\n" )) { + $line++; + continue; + } + + // Text + if ( $char == '"' || $char == "'" ) { + $textEncloser = $char; + $value = ''; + while( true ) { + $char = array_shift($chars); + if ( $char == "\n") + throw new DslParserException("Unclosed string",$line); + if ( $char == '\\') { + $char = array_shift($chars); + $value .= $char; + } + elseif ($char != $textEncloser) { + $value .= $char; + continue; + } else { + $this->addToken($line, DslToken::T_TEXT, $value); + break; + } + } + continue; + } + + // Comments + if ( $char == '/' ) { + $nextChar = array_shift($chars); + if ( $nextChar == '/' ) { // Comment after "//" + + while( true ) { + $c = array_shift($chars); + if ($c == "\n") + $line++; + if ($c == "\n" || $c == null ) + continue 2; + } + + } + elseif ( $nextChar == '*' ) { // Comment after "/*" + + $lastChar = null; + while( true ) { + $c = array_shift($chars); + if ( $c == null ) + break 2; + if ($c == "\n") + $line++; + if ( $lastChar == '*' && $c == '/') + continue 2; + $lastChar = $c; + continue; + } + + } + else { + array_unshift($chars,$nextChar); // this is no comment + } + } + + // String + if ( ( $char >= 'a' && $char <= 'z') || + ( $char >= 'A' && $char <= 'Z') || + $char == '_' || + $char == '$' ) { + $value = $char; + while( true ) { + $char = array_shift( $chars ); + if ( ( $char >= 'a' && $char <= 'z') || + ( $char >= 'A' && $char <= 'Z') || + ( $char >= '0' && $char <= '9') || + $char == '_' || + $char == '$' ) { + $value .= $char; + } else { + $type = DslToken::T_STRING; + + if ( array_key_exists($value,self::UNUSED_KEYWORDS ) ) + throw new DslParserException( 'use of reserved word \''.$value.'\' is not allowed.'); + + if ( array_key_exists($value,self::KEYWORDS ) ) + $type = self::KEYWORDS[$value]; // it is a keyword + + $this->addToken( $line,$type,$value ); + array_unshift($chars,$char); + break; + } + } + continue; + } + + // Numbers + if ( $char >= '0' && $char <= '9') { + $value = $char; + while( true ) { + $char = array_shift( $chars ); + if ( ( $char >= '0' && $char <= '9') || + $char == '.' || $char == '_' ) { + $value .= $char; + } else { + $this->addToken( $line,DslToken::T_NUMBER,str_replace('_','',$value )); + array_unshift($chars,$char); + break; + } + } + continue; + } + + $operatorChars = ['>','<','+' ,'-','/' ,'*','=','|','&',',','.' ]; + if ( in_array($char,$operatorChars)) { + + $value = $char; + while( true ) { + $char = array_shift( $chars ); + if ( in_array($char,$operatorChars) ) { + $value .= $char; + } else { + $type = DslToken::T_OPERATOR; + $this->addToken( $line,$type,$value ); + array_unshift($chars,$char); + continue 2; + } + } + continue; + } + + if ( $char == "\r" ) + continue; + elseif ( $char == '!' ) + $this->addToken( $line,DslToken::T_NEGATION,$char); + elseif ( $char == ';' ) + $this->addToken( $line,DslToken::T_STATEMENT_END,$char); + elseif ( $char == '.' ) + $this->addToken( $line,DslToken::T_DOT,$char); + elseif ( $char == ',' ) + $this->addToken( $line,DslToken::T_COMMA,$char); + + elseif ( $char == '(' ) { + if ( end( $this->token)->type == DslToken::T_STRING) + $this->addToken( $line, DslToken::T_OPERATOR,'$'); // function call + $this->addToken( $line,DslToken::T_BRACKET_OPEN,$char); + } + elseif ( $char == ')' ) + $this->addToken( $line,DslToken::T_BRACKET_CLOSE,$char); + elseif ( $char == '{' ) + $this->addToken( $line,DslToken::T_BLOCK_BEGIN,$char); + elseif ( $char == '}' ) + $this->addToken( $line,DslToken::T_BLOCK_END,$char); + else { + throw new DslParserException('Unknown character \''.$char.'\'',$line); + } + } + + + return $this->token; + } + + private function addToken(int $line, $type, $value=null) + { + $this->token[] = new DslToken( $line, $type, $value ); + } + + +} +\ No newline at end of file diff --git a/dsl/DslParserException.class.php b/dsl/DslParserException.class.php @@ -0,0 +1,17 @@ +<?php + +namespace dsl; + + +class DslParserException extends DslException +{ + /** + * DslParserException constructor. + * @param $message + * @param $lineNumber + */ + public function __construct($message, $lineNumber=null) + { + parent::__construct( $message . ($lineNumber?' on line ' . $lineNumber:'') ); + } +} +\ No newline at end of file diff --git a/dsl/DslRuntimeException.class.php b/dsl/DslRuntimeException.class.php @@ -0,0 +1,17 @@ +<?php + +namespace dsl; + + +class DslRuntimeException extends DslException +{ + /** + * DslParserException constructor. + * @param $message + * @param $lineNumber + */ + public function __construct($message, $lineNumber=null) + { + parent::__construct( $message . ($lineNumber?' on line ' . $lineNumber:'') ); + } +} +\ No newline at end of file diff --git a/dsl/DslToken.class.php b/dsl/DslToken.class.php @@ -0,0 +1,46 @@ +<?php +namespace dsl; + +class DslToken +{ + + + const T_NONE = 0; + const T_STRING = 1; + const T_BRACKET_OPEN = 3; + const T_BRACKET_CLOSE = 4; + const T_BLOCK_BEGIN = 5; + const T_BLOCK_END = 6; + const T_TEXT = 7; + const T_NUMBER = 8; + const T_OPERATOR = 9; + const T_FUNCTION = 10; + const T_FOR = 11; + const T_IF = 12; + const T_ELSE = 13; + const T_LET = 14; + const T_RETURN = 15; + const T_DOT = 16; + const T_STATEMENT_END = 17; + const T_NEGATION = 18; + const T_COMMA = 19; + const T_NEW = 20; + + public $lineNumber; + public $type; + public $value; + + /** + * DslToken constructor. + * @param $lineNumber + * @param $type + * @param $value + */ + public function __construct($lineNumber, $type, $value) + { + $this->lineNumber = $lineNumber; + $this->type = $type; + $this->value = $value; + } + +} +\ No newline at end of file diff --git a/dsl/ast/DslAssignment.class.php b/dsl/ast/DslAssignment.class.php @@ -0,0 +1,78 @@ +<?php + +namespace dsl\ast; + +use dsl\DslParserException; +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslAssignment implements DslStatement +{ + private $target = []; + private $value; + + /** + * DslAssignment constructor. + * @param $target DslStatement + * @param $value DslStatement + * @throws DslParserException + */ + public function __construct( $target, $value ) + { + //echo "<h5>Assignment:</h5><pre>"; var_export( $target ); var_export($value); echo "</pre>"; + + $this->target = $target; + $this->value = $value; + } + + /** + * @param array $context + * @return mixed|void + * @throws DslRuntimeException + */ + public function execute( & $context ) { + + $value = $this->value->execute( $context ); + + // if the variable is not already bound in this context it will be created. + // there is no need for a "var" or "let". they are completely obsolet. + //if ( ! array_key_exists( $this->target->name,$context ) ) + // throw new DslRuntimeException('variable \''.$this->target->name.'\' does not exist'); + + $context[ $this->target->name ] = $value; + + return $value; + } + + public function parse($tokens) + { + //echo "<h2>Assignment-Parser</h2><pre>"; var_dump( $tokens ); echo "</pre>"; + $this->target = new DslExpression(); + $this->value = new DslExpression(); + + $assignmentOperatorFound = false; + $targetToken = []; + $valueToken = []; + + foreach( $tokens as $token ) { + + if ( $token->type == DslToken::T_OPERATOR && in_array($token->value,['=','+='.'-=']) ) { + $assignmentOperatorFound = true; + continue; + } + + if ( ! $assignmentOperatorFound ) + $targetToken[] = $token; + else + $valueToken[] = $token; + } + + if ( $assignmentOperatorFound ) { + $this->target->parse( $targetToken ); + $this->value ->parse( $valueToken ); + } else { + $this->value->parse( $targetToken ); + $this->target = null; + } + } +} +\ No newline at end of file diff --git a/dsl/ast/DslDecimal.class.php b/dsl/ast/DslDecimal.class.php @@ -0,0 +1,16 @@ +<?php + +namespace dsl\ast; + +class DslDecimal implements DslStatement +{ + private $statements; + + public function execute( & $context ) { + + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslElement.class.php b/dsl/ast/DslElement.class.php @@ -0,0 +1,156 @@ +<?php + +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,true); + } + + + /** + * Gets the first single statement out of the tokens. + * + * @param $tokens DslToken[] + * @return DslToken[] + * @throws DslParserException + */ + protected function getSingleStatement(&$tokens, $withEnd = false) + { + $depth = 0; + $statementTokens = []; + while (true) { + $nextToken = array_shift($tokens); + if ($nextToken == null) + var_export( $statementTokens ); + if ($nextToken == null) + throw new DslParserException('unrecognized statement'); + + if ($depth == 0 && $nextToken->type == DslToken::T_STATEMENT_END) { + if ( $withEnd ) + $statementTokens[] = $nextToken; + 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_OPERATOR && $token->value == ',' ) { + $parts[] = $act; + $act = []; // Cleanup + continue; + } + + $act[] = $token; + } + + if ( $act ) + $parts[] = $act; + + return $parts; + } +} +\ No newline at end of file diff --git a/dsl/ast/DslExpression.class.php b/dsl/ast/DslExpression.class.php @@ -0,0 +1,236 @@ +<?php + +namespace dsl\ast; + +define('LEFT', 0); +define('RIGHT', 1); + +use dsl\DslParserException; +use dsl\DslToken; + +class DslExpression extends DslElement implements DslStatement +{ + private $value; + + public function __construct( $valueTokens ) + { + $this->parse( $valueTokens ); + } + + public function execute( & $context ) { + + if ( is_array( $this->value ) ) + foreach( $this->value as $v) + $v->execute( $context ); + else + return $this->value->execute( $context ); + } + + /** + * @param DslToken[] $tokens + * @throws DslParserException + */ + public function parse($tokens) + { + //echo "<h5>Expression:</h5><pre>"; var_export( $tokens ); echo "</pre>"; + if ( ! $tokens ) { + $this->value = new DslNull(); + return; + } + + $this->parseExpression( $tokens ); + // Groups + } + + + /** + * Parsing an expression using the shunting yard algorithm. + * + * Precedences see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#assoziativit%C3%A4t + * @param DslToken[] $tokens + * @throws DslParserException + */ + private function parseExpression( $tokens ) + { + $precedence = [ + ',' => 2, + '=' => 3, + '+=' => 3, + '-=' => 3, + '||' => 5, + '&&' => 6, + '==' => 10, + '!=' => 10, + '<' => 11, + '<=' => 11, + '>' => 11, + '>=' => 11, + '+' => 13, + '-' => 13, + '/' => 14, + '*' => 14, + '%' => 14, + '**' => 15, + '!' => 16, + '$' => 19, // function call, provided by the lexer + '.' => 19, + ]; + + $assoc = [ + ',' => LEFT, + '=' => RIGHT, + '-=' => RIGHT, + '+=' => RIGHT, + '||' => LEFT, + '&&' => LEFT, + '==' => LEFT, + '!=' => LEFT, + '<' => LEFT, + '<=' => LEFT, + '> ' => LEFT, + '>=' => LEFT, + '+' => LEFT, + '-' => LEFT, + '/' => LEFT, + '*' => LEFT, + '%' => LEFT, + '^' => RIGHT, + '!' => RIGHT, + '**' => RIGHT, + '.' => RIGHT, + '$' => LEFT, + ]; + + + // for the purpose of comparing only; it's forced to top priority explicitly + $precedence['('] = 0; + $precedence[')'] = 0; + + $output_queue = array(); + $operator_stack = array(); + + if ( $tokens instanceof DslStatement ) { + + $this->value = $tokens; + return; + } + + if ( $tokens instanceof DslToken ) + $tokens = [$tokens]; + + if ( ! is_array($tokens)) echo "tokens ist kein array, aber ".get_class($tokens); + + // while there are tokens to be read: + while ($tokens) { + // read a token. + $token = array_shift($tokens); + + if ($token->type == DslToken::T_OPERATOR) { + + // while there is an operator at the top of the operator stack with + // greater than or equal to precedence: + while ($operator_stack && + $precedence[end($operator_stack)->value] >= $precedence[$token->value] + $assoc[$token->value]) { + // pop operators from the operator stack, onto the output queue. + + $left = array_pop( $output_queue ); + $right = array_pop( $output_queue ); + $output_queue[] = $this->createNode( array_pop($operator_stack),$left,$right ); + } + // push the read operator onto the operator stack. + $operator_stack[] = $token; + + // if the token is a left bracket (i.e. "("), then: + } elseif ($token->value === '(') { + // push it onto the operator stack. + $operator_stack[] = $token; + + // if the token is a right bracket (i.e. ")"), then: + } elseif ($token->value === ')') { + // while the operator at the top of the operator stack is not a left bracket: + while (end($operator_stack)->value !== '(') { + // pop operators from the operator stack onto the output queue. + $left = array_pop( $output_queue ); + $right = array_pop( $output_queue ); + $output_queue[] = $this->createNode( array_pop($operator_stack),$left,$right ); + + // /* if the stack runs out without finding a left bracket, then there are + // mismatched parentheses. */ + if (!$operator_stack) { + throw new DslParserException("Mismatched parentheses!"); + } + } + + // pop the left bracket from the stack. + array_pop($operator_stack); + + } + elseif // if the token is a number, then push it to the output queue. + (true) { + $output_queue[] = $this->tokenToStatement( $token ); + + // if the token is an operator, then: + } else { + throw new DslParserException( 'Unexpected token '.$token->value, $token->lineNumber); + } + } // if there are no more tokens to read: + + // while there are still operator tokens on the stack: + while ($operator_stack) { + $token = array_pop($operator_stack); + + // if the operator token on the top of the stack is a bracket, then + // there are mismatched parentheses. + if ($token->type == DslToken::T_OPERATOR && $token->value == '(') { + throw new DslParserException( "Mismatched parentheses"); + } + // pop the operator onto the output queue. + $left = array_pop( $output_queue ); + $right = array_pop( $output_queue ); + $output_queue[] = $this->createNode( $token,$left,$right ); + } + + + //echo "<h5>Output queue:</h5><pre>"; var_export( $output_queue ); echo "</pre>"; + $this->value = $output_queue[0]; + } + + /** + * @param $op DslToken + * @param $left + * @param $right + * @throws DslParserException + */ + private function createNode($op, $left, $right) + { + if ( $op->value == '=' ) + return new DslAssignment( $right,$left ); + if ( $op->value == ',' ) + return new DslSequence( $right,$left ); + if ( $op->value == '.' ) + return new DslProperty( $right, $left ); + if ( $op->value == '$' ) + return new DslFunctionCall( $right, $left ); + else + return new DslOperation( $op->value,$right,$left ); + } + + /** + * @param $token DslToken + */ + private function tokenToStatement($token) + { + switch( $token->type ) { + case DslToken::T_NUMBER: + return new DslInteger( $token->value ); + case DslToken::T_TEXT: + return new DslString( $token->value ); + case DslToken::T_STRING: + return new DslVariable( $token->value ); + case DslToken::T_DOT: + return new DslProperty( $token->value ); + default: + throw new DslParserException('Unknown token '.$token->value,$token->lineNumber); + } + } +} +\ No newline at end of file diff --git a/dsl/ast/DslFor.class.php b/dsl/ast/DslFor.class.php @@ -0,0 +1,46 @@ +<?php + +namespace dsl\ast; + +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslFor implements DslStatement +{ + private $name; + private $list; + private $statements; + + /** + * 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 $loopVar ) { + $copiedContext[ $this->name ] = $loopVar; + $this->statements->execute( $copiedContext ); + } + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslFunction.class.php b/dsl/ast/DslFunction.class.php @@ -0,0 +1,64 @@ +<?php + +namespace dsl\ast; + +use dsl\DslParserException; +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslFunction extends DslElement implements DslStatement +{ + /** + * @var String[] + */ + public $parameters; + + /** + * @var DslStatementList + */ + public $body; + + /** + * creates the function. + * + * @param array $context + * @return mixed|void + * @throws DslRuntimeException + */ + public function execute( & $context ) { + + $clonedContext = $context; + return $this->body->execute( $context ); + } + + /** + * DslFunction constructor. + * + * @param $functionParameter DslToken[] + * @param $functionBody DslStatement + * @throws DslParserException + */ + public function __construct( $functionParameter, $functionBody ) + { + //$this->parameters = $functionParameter; + + $this->parameters = []; + //var_export($this->splitByComma( $functionParameter )); + foreach( $this->splitByComma( $functionParameter ) as $parameter ) { + if ( sizeof($parameter) != 1 ) + throw new DslParserException('function parameter must be a single 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 ); + } + + public function parse($tokens) + { + } + +} +\ No newline at end of file diff --git a/dsl/ast/DslFunctionCall.class.php b/dsl/ast/DslFunctionCall.class.php @@ -0,0 +1,91 @@ +<?php + +namespace dsl\ast; + +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslFunctionCall implements DslStatement +{ + public $name; + private $parameters; + + /** + * DslFunctionCall constructor. + * @param $name + * @param $parameters DslToken[][] + */ + public function __construct($name, $parameters) + { + //echo "name:";var_export( $name ); + //echo "params:";var_export( $parameters ); + + // Parameterless function calls are not correctly detected by the AST parser + if ( $name==null) { + $name = $parameters; + $parameters = null; + } + $this->name = $name; + $this->parameters = $parameters; + } + + + /** + * @throws DslRuntimeException + */ + public function execute(& $context ) { + + //var_export($this->name); + $function = $this->name->execute( $context ); + + + //echo "name is $name"; + //if ( ! array_key_exists( $name, $context ) ) + // throw new DslRuntimeException('function \''.$this->name.'\' does not exist.'); +// +// $function = $context[$name]; + + if ( $this->parameters == null ) + $parameterValues = []; // parameterless functions. + else + $parameterValues = $this->parameters->execute( $context ); + + // if there is only 1 parameter it must be converted to an array. + // if there are more than 1 parameter, it is already a sequence + if ( ! is_array($parameterValues)) $parameterValues = array($parameterValues); + + + if ( $function instanceof \dsl\context\DslFunction ) { + // call "external" native function + + return call_user_func_array(array($function,'execute'),$parameterValues ); + } + elseif ( $function instanceof DslFunction ) { + + $parameters = $function->parameters; + //var_export( $function->parameters); + + if ( sizeof($parameters) != sizeof($parameterValues) ) + throw new DslRuntimeException('function call has '.sizeof($parameterValues).' parameters but the function has '.sizeof($parameters).' parameters'); + + // Put all function parameters to the function context. + $parameterContext = array_combine( $parameters, $parameterValues ); + $subContext = array_merge( $context,$parameterContext ); + + return $function->execute( $subContext ); + + } + elseif ( is_callable($function) ) { + + //var_export( call_user_func_array( $function, $parameterValues) ); + return call_user_func_array( $function, $parameterValues); + } + else + throw new DslRuntimeException('function is not callable'.var_export($function)); + } + + public function parse($tokens) + { + $this->statements[] = $tokens; + } +} +\ No newline at end of file diff --git a/dsl/ast/DslIf.class.php b/dsl/ast/DslIf.class.php @@ -0,0 +1,43 @@ +<?php + +namespace dsl\ast; + +class DslIf implements DslStatement +{ + + /** + * Expression for the condition + * @var DslExpression + */ + private $condition; + + /** + * @var DslStatementList + */ + private $pos; + + /** + * @var DslStatementList + */ + private $neg; + + public function execute( & $context ) { + + $conditionValue = $this->condition->execute( $context ); + + if ( $conditionValue ) + return $this->pos->execute( $context ); + else + return $this->neg->execute( $context ); + } + + public function __construct( $condition, $positive,$negative ) { + $this->condition = new DslExpression( $condition ); + $this->pos = new DslStatementList( $positive ); + $this->neg = new DslStatementList( $negative ); + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslInitialisation.class.php b/dsl/ast/DslInitialisation.class.php @@ -0,0 +1,32 @@ +<?php + +namespace dsl\ast; + +use dsl\DslRuntimeException; +use dsl\DslToken; + +class DslInitialisation implements DslStatement +{ + private $name; + private $value; + + public function __construct( $name, $parameters ) + { + $this->name = $name; + $this->value = new DslExpression( $parameters ); + } + + public function execute( & $context ) { + if ( array_key_exists( $this->name, $context ) ) + throw new DslRuntimeException('variable '.$this->name.' is already initialised'); + + $context[ $this->name ] = $this->value->execute( $context ); + //echo "new var: ".$this->name.':'.$context[$this->name]; + + } + + public function parse($tokens) + { + // TODO: Implement parse() method. + } +} +\ No newline at end of file diff --git a/dsl/ast/DslInteger.class.php b/dsl/ast/DslInteger.class.php @@ -0,0 +1,29 @@ +<?php + +namespace dsl\ast; + +class DslInteger implements DslStatement +{ + private $number; + + /** + * DslInteger constructor. + * @param $number + */ + public function __construct($number) + { + $this->number = $number; + } + + + public function execute( & $context ) { + + return intval($this->number); + } + + public function parse($tokens) + { + $firstToken = $tokens[0]; + $this->number = intval( $firstToken->value ); + } +} +\ No newline at end of file diff --git a/dsl/ast/DslNull.class.php b/dsl/ast/DslNull.class.php @@ -0,0 +1,20 @@ +<?php + +namespace dsl\ast; + +use dsl\DslParserException; +use dsl\DslToken; + +class DslNull implements DslStatement +{ + public function execute( & $context) + { + return null; + } + + + public function parse($tokens) + { + // TODO: Implement parse() method. + } +} +\ No newline at end of file diff --git a/dsl/ast/DslOperation.class.php b/dsl/ast/DslOperation.class.php @@ -0,0 +1,88 @@ +<?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 intval($left) + intval($right); + + case '-': + return intval($left) - intval($right); + + case '*': + return $left * $right; + + case '/': + return $left / $right; + + case '==': + return $left == $right; + case '!=': + return $left != $right; + case '<': + return $left < $right; + case '<=': + return $left <= $right; + case '>': + return $left > $right; + case '>=': + return $left >= $right; + + case '||': + return $left || $right; + case '&&': + return $left && $right; + + case '%': + return $left % $right; + + case '!': + return ! $left; + + 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/dsl/ast/DslProperty.class.php b/dsl/ast/DslProperty.class.php @@ -0,0 +1,69 @@ +<?php + +namespace dsl\ast; + +use cms\generator\dsl\DslObject; +use dsl\DslRuntimeException; + +class DslProperty implements DslStatement +{ + public $variable; + public $property; + + /** + * DslProperty constructor. + * @param $variable + * @param $property + */ + public function __construct($variable, $property) + { + $this->variable = $variable; + $this->property = $property; + } + + + /** + * @param array $context + * @return mixed + * @throws DslRuntimeException + */ + public function execute( & $context ) { + + $object = $this->variable->execute( $context ); + + $objectContext = []; + + if ( is_object( $object ) ) { + + $objectContext = get_object_vars( $object ); + + // copy object methods to the object context to make them callable. + foreach( get_class_methods( $object ) as $method ) { + $objectContext[ $method ] = function() use ($method, $object) { + return call_user_func_array( array($object,$method),func_get_args() ); + }; + } + } + elseif ( is_array( $object ) ) { + + $objectContext = $object; + + } else { + + throw new DslRuntimeException('not an object'); + } + + $prop = $this->property->execute( $objectContext ); + + // TODO: how to recognize objects + // For Security: Do not expose internal objects. + //if ( is_object($prop) && ! $prop instanceof DslObject ) + // $prop = '@'.get_class($prop).'@'; + + return $prop; + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslReturn.class.php b/dsl/ast/DslReturn.class.php @@ -0,0 +1,22 @@ +<?php + +namespace dsl\ast; + +class DslReturn implements DslStatement +{ + private $value; + + public function __construct( $expressionTokens ) + { + $this->value = new DslExpression( $expressionTokens ); + } + + public function execute( & $context ) { + + return $this->value->execute( $context ); + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslSequence.class.php b/dsl/ast/DslSequence.class.php @@ -0,0 +1,41 @@ +<?php + +namespace dsl\ast; + +use dsl\DslRuntimeException; + +class DslSequence implements DslStatement +{ + public $left; + public $right; + + + /** + * DslSequence constructor. + * @param $left + * @param $right + */ + public function __construct($left, $right) + { + $this->left = $left; + $this->right = $right; + } + + + public function execute( & $context ) { + + // Creating a sequence + $left = $this->left->execute( $context ); + $right = $this->right->execute( $context ); + + // cast to array + if ( !is_array( $left ) ) $left = [$left ]; + if ( !is_array( $right) ) $right = [$right]; + + return array_merge( $left,$right); + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslStatement.class.php b/dsl/ast/DslStatement.class.php @@ -0,0 +1,24 @@ +<?php + +namespace dsl\ast; + +use dsl\DslToken; + +interface DslStatement +{ + /** + * Parses a list of tokens. + * @param $tokens DslToken[] List of tokens + */ + public function parse( $tokens ); + + + /** + * Executes this statement. + * + * @param $context array Context of execution. + * @return mixed + */ + public function execute( & $context ); + +} diff --git a/dsl/ast/DslStatementList.class.php b/dsl/ast/DslStatementList.class.php @@ -0,0 +1,143 @@ +<?php + +namespace dsl\ast; + +use dsl\DslParserException; +use dsl\DslToken; + +class DslStatementList extends DslElement implements DslStatement +{ + private $statements = []; + + private $functions = []; + + public function __construct($tokenList) + { + $this->parse($tokenList); + } + + /** + * @param $tokens DslToken[] + * @throws DslParserException + */ + public function parse($tokens) + { + $this->parseTokens($tokens); + } + + public function 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 + } + + return null; + } + + + + /** + * @param $tokens DslToken[] + * @throws DslParserException + */ + public function parseTokens($tokens) + { + while (true) { + $token = array_shift($tokens); + + if ( ! $token ) + return; + + switch ($token->type) { + + case DslToken::T_STATEMENT_END: + // maybe an empty statement? + break; + + case DslToken::T_OPERATOR: + throw new DslParserException('Unexpected operator', $token->lineNumber); + case DslToken::T_BRACKET_CLOSE: + throw new DslParserException('Unexpected closing group', $token->lineNumber); + case DslToken::T_BLOCK_END: + throw new DslParserException('Unexpected ending of an block', $token->lineNumber); + case DslToken::T_NEGATION: + throw new DslParserException('Unexpected negation', $token->lineNumber); + case DslToken::T_DOT: + throw new DslParserException('Unexpected dot', $token->lineNumber); + + case DslToken::T_FUNCTION: + + $nameToken = array_shift( $tokens ); + if ($nameToken->type != DslToken::T_STRING) + throw new DslParserException('function must have a name', $token->lineNumber); + $name = $nameToken->value; + + $functionCallOp = array_shift($tokens); + $functionParameter = $this->getGroup($tokens); + $functionBlock = $this->getBlock($tokens); + + $this->functions[ $name ] = new DslFunction( $functionParameter, $functionBlock ); + break; + + case DslToken::T_IF: + $condition = $this->getGroup($tokens); + $positiveBlock = $this->getStatementOrBlock($tokens); + $nextToken = array_shift($tokens); + if ($nextToken && $nextToken->type == DslToken::T_ELSE) { + $negativeBlock = $this->getStatementOrBlock($tokens); + } else { + $negativeBlock = []; + array_unshift($tokens, $nextToken); + } + $this->statements[] = new DslIf($condition, $positiveBlock, $negativeBlock); + break; + + case DslToken::T_LET: + break; + + case DslToken::T_FOR: + $forGroup = $this->getGroup( $tokens ); + $forBlock = $this->getStatementOrBlock( $tokens ); + + //echo "<h5>Forgroup:</h5><pre>"; var_export( $forGroup); echo "</pre>"; + + $varName = array_shift( $forGroup ); + if ( $varName == null || $varName->type != DslToken::T_STRING ) + throw new DslParserException('for loop variable missing'); + $ofName = array_shift( $forGroup ); + if ( $ofName == null || $ofName->type != DslToken::T_STRING || strtolower($ofName->value) != 'of' ) + throw new DslParserException('missing \'of\' in for loop'); + + $this->statements[] = new DslFor( $varName->value, $forGroup, $forBlock ); + break; + + case DslToken::T_RETURN: + $returnTokens = $this->getSingleStatement( $tokens ); + $this->statements[] = new DslReturn( $returnTokens ); + break; + + case DslToken::T_TEXT: + case DslToken::T_STRING: + array_unshift( $tokens, $token ); + $statementTokens = $this->getSingleStatement( $tokens ); + + $this->statements[] = new DslExpression( $statementTokens ); + break; + + default: + throw new DslParserException('Unknown token of type '.$token->type,$token->lineNumber ); + } + + } + + } + + +} +\ No newline at end of file diff --git a/dsl/ast/DslString.class.php b/dsl/ast/DslString.class.php @@ -0,0 +1,27 @@ +<?php + +namespace dsl\ast; + +class DslString implements DslStatement +{ + private $string; + + /** + * DslString constructor. + * @param $string + */ + public function __construct($string) + { + $this->string = $string; + } + + + public function execute( & $context ) { + + return $this->string; + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/ast/DslVariable.class.php b/dsl/ast/DslVariable.class.php @@ -0,0 +1,31 @@ +<?php + +namespace dsl\ast; + +use dsl\DslRuntimeException; + +class DslVariable implements DslStatement +{ + public $name; + + /** + * @param $name + */ + public function __construct($name) + { + $this->name = $name; + } + + + public function execute( & $context ) { + + if ( ! array_key_exists( $this->name, $context ) ) + throw new DslRuntimeException('variable \''.$this->name.'\' does not exist.'); + + return $context[ $this->name ]; + } + + public function parse($tokens) + { + } +} +\ No newline at end of file diff --git a/dsl/context/DslFunction.class.php b/dsl/context/DslFunction.class.php @@ -0,0 +1,7 @@ +<?php + +namespace dsl\context; + +interface DslFunction +{ +} +\ No newline at end of file diff --git a/dsl/context/DslObject.class.php b/dsl/context/DslObject.class.php @@ -0,0 +1,8 @@ +<?php + +namespace dsl\context; + +interface DslObject +{ + +} +\ No newline at end of file diff --git a/dsl/executor/DslInterpreter.class.php b/dsl/executor/DslInterpreter.class.php @@ -0,0 +1,80 @@ +<?php + +namespace dsl\executor; + +use dsl\DslAstParser; +use dsl\DslException; +use dsl\DslLexer; +use dsl\standard\StandardArray; +use dsl\standard\StandardDate; +use dsl\standard\StandardMath; +use dsl\standard\Write; + +class DslInterpreter +{ + /** + * Execution context. + * + * @var array + */ + private $context = []; + + /** + * Holds a reference to the write()-Function for getting the output buffer after execution. + * @var Write + */ + private $writer; + + public function __construct() + { + // Standard-Globals + $this->addContext( [ + 'Math' => new StandardMath(), + 'Array' => new StandardArray(), + 'Date' => new StandardDate(), + 'write' => $this->writer = new Write(), + ] ); + } + + /** + * adds an external context to the interpreter environment. + * + * @param $context [] + */ + public function addContext($context ) { + $this->context = array_merge( $this->context, $context ); + } + + + /** + * Parses and runs the DSL code. + * + * @param $code String Script-Code + * @throws DslException + * @return mixed value of last return statement (if any) + */ + public function runCode( $code ) { + + // Step 1: Splitting the source code into tokens (the "Lexer") + $lexer = new DslLexer(); + $token = $lexer->tokenize( $code ); + + // Step 2: Creating a syntax tree (abstract syntax tree, AST). + $parser = new DslAstParser(); + $parser->parse( $token ); + + // Step 3: Executing the syntax tree. + return $parser->execute( $this->context ); + } + + + /** + * Gets the output which was written by the code. + * + * @return mixed + */ + public function getOutput() { + + return $this->writer->buffer; + } +} +\ No newline at end of file diff --git a/dsl/standard/StandardArray.class.php b/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/dsl/standard/StandardDate.class.php b/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/dsl/standard/StandardMath.class.php b/dsl/standard/StandardMath.class.php @@ -0,0 +1,41 @@ +<?php +namespace dsl\standard; + +class StandardMath +{ + public $E = M_EULER; + public $PI = M_PI; + public $LN2 = M_LN2; + public $LN10 = M_LN10; + public $SQRT1_2 = M_SQRT1_2; + public $SQRT2 = M_SQRT2; + + public function sqrt($x) { return sqrt( $x ); } + public function abs($x) { return abs($x);} + public function acos($x) { return ($x); } + public function acosh($x) { return ($x); } + public function asin($x) { return ($x); } + public function asinh($x) { return ($x); } + public function atan($x) { return atan($x); } + public function atanh($x) { return atanh($x); } + public function atan2($y, $x) { return atan2($y,$x); } + public function cbrt($x) { return ($x); } + public function ceil($x) { return ceil($x); } + public function cos($x) { return cos($x); } + public function cosh($x) { return cosh($x); } + public function exp($x) { return exp($x); } + public function expm1($x) { return expm1($x); } + public function floor($x) { return floor($x); } + public function log($x) { return ($x); } + public function log1p($x) { return log($x); } + public function log10($x) { return log10($x); } + public function max($x,$y) { return max($x,$y); } + public function min($x,$y) { return min($x,$y); } + public function pow($x,$y) { return pow($x,$y); } + public function random() { return rand(); } + public function round($x) { return round($x); } + public function sin($x) { return sin($x); } + public function sinh($x) { return sinh($x); } + public function tan($x) { return tan($x); } + public function tanh($x) { return tanh($x); } +} +\ No newline at end of file diff --git a/dsl/standard/Write.class.php b/dsl/standard/Write.class.php @@ -0,0 +1,15 @@ +<?php + +namespace dsl\standard; + +use dsl\context\DslFunction; + +class Write implements DslFunction +{ + public $buffer; + + public function execute( $text ) + { + $this->buffer .= $text; + } +} +\ No newline at end of file diff --git a/index.php b/index.php @@ -0,0 +1,52 @@ +<?php + +use dsl\executor\DslInterpreter; + +require('./autoload.php'); + +?><html> +<head><title>Script Sandbox</title> +<style> + textarea { + width: 100%; + height: 50%; + } + +</style></head> +<body> + + +</body></html> +<h1>Script Sandbox</h1> +<p>Script sandbox for PHP. The syntax is a subset of Javascript. The interpreter is supporting functions, full arithmetic calculations, for-loops, if-else statements. +</p> + <?php $code = $_POST['code'] ?: <<<DEF +// Script sandbox +// Feel free to write some code here.... +write( "test" ); + +/** +*/ +function name() { + return "something"; +} +DEF + ; ?> + +<legend title="Script output"> +<pre> + <?php + try { + error_reporting( E_ALL ); + $interpreter = new DslInterpreter(); + $interpreter->runCode( $code ); + echo htmlentities( $interpreter->getOutput() ); + } catch( Exception $e ) { + echo htmlentities( $e->getMessage() ); + } + ?> +</pre></legend> +<form action="./" method="POST"><textarea name="code"><?php echo htmlentities( $code ) ?> +</textarea> +<input type="submit" value="Execute code"/> +</form> diff --git a/upstream.sh b/upstream.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# copy all classes from openrat cms source. +# because they are maintained there +cp -r ../openrat-cms/modules/dsl/* ./dsl +\ No newline at end of file