From 8fb0f6791ef26445c5d9251ff5d6774c2e738195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=92=D0=BE=D0=BB?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2?= Date: Tue, 9 Aug 2022 16:58:08 +0300 Subject: [PATCH] feat: implement Lua script engine --- .phpunit.result.cache | 2 +- README.md | 27 +++++- src/CompiledScript.php | 2 +- src/Engine/Juel/JuelCompiledScript.php | 3 +- src/Engine/Lua/LuaCompiledScript.php | 39 ++++++++ src/Engine/Lua/LuaScriptEngine.php | 69 ++++++++++++++ src/Engine/Lua/LuaScriptEngineFactory.php | 107 ++++++++++++++++++++++ src/ScriptEngineManager.php | 6 +- tests/LuaEngineTest.php | 33 +++++++ 9 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 src/Engine/Lua/LuaCompiledScript.php create mode 100644 src/Engine/Lua/LuaScriptEngine.php create mode 100644 src/Engine/Lua/LuaScriptEngineFactory.php create mode 100644 tests/LuaEngineTest.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 7f518f4..8711632 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":{"Tests\\JuelEngineTest::testEngine":4,"Tests\\JuelEngineTest::testEngineManager":5,"Tests\\JuelEngineTest::testEngineFactory":4,"Tests\\JuelEngineTest::testJuelEngineManager":4,"Tests\\ExpressionLanguageTest::testNumericMethods":4,"Tests\\ExpressionLanguageTest::testMethodInvocation":4,"Tests\\JuelEngineTest::testJuelEngineManagerWithFunction":4},"times":{"Tests\\JuelEngineTest::testEngine":0.003,"Tests\\JuelEngineTest::testEngineFactory":0.006,"Tests\\JuelEngineTest::testEngineManager":0.003,"Tests\\JuelEngineTest::testJuelEngineManager":0.001,"Tests\\ExpressionLanguageTest::testNumericVariables":0.006,"Tests\\ExpressionLanguageTest::testNumericMethods":0,"Tests\\ExpressionLanguageTest::testBooleanMethods":0,"Tests\\ExpressionLanguageTest::testMethodInvocation":0.001,"Tests\\ExpressionLanguageTest::testExpressionString":0,"Tests\\ExpressionLanguageTest::testIsDeferred":0,"Tests\\ExpressionLanguageTest::testGetExpectedType":0,"Tests\\ExpressionLanguageTest::testGetType":0,"Tests\\JuelEngineTest::testJuelEngineManagerWithVariable":0.001,"Tests\\JuelEngineTest::testJuelEngineManagerWithFunction":0.001}} \ No newline at end of file +{"version":1,"defects":{"Tests\\JuelEngineTest::testEngine":4,"Tests\\JuelEngineTest::testEngineManager":5,"Tests\\JuelEngineTest::testEngineFactory":4,"Tests\\JuelEngineTest::testJuelEngineManager":4,"Tests\\ExpressionLanguageTest::testNumericMethods":4,"Tests\\ExpressionLanguageTest::testMethodInvocation":4,"Tests\\JuelEngineTest::testJuelEngineManagerWithFunction":4,"Tests\\LuaEngineTest::testLuaScript":5},"times":{"Tests\\JuelEngineTest::testEngine":0.003,"Tests\\JuelEngineTest::testEngineFactory":0.006,"Tests\\JuelEngineTest::testEngineManager":0.003,"Tests\\JuelEngineTest::testJuelEngineManager":0.001,"Tests\\ExpressionLanguageTest::testNumericVariables":0.006,"Tests\\ExpressionLanguageTest::testNumericMethods":0,"Tests\\ExpressionLanguageTest::testBooleanMethods":0,"Tests\\ExpressionLanguageTest::testMethodInvocation":0.001,"Tests\\ExpressionLanguageTest::testExpressionString":0,"Tests\\ExpressionLanguageTest::testIsDeferred":0,"Tests\\ExpressionLanguageTest::testGetExpectedType":0,"Tests\\ExpressionLanguageTest::testGetType":0,"Tests\\JuelEngineTest::testJuelEngineManagerWithVariable":0.001,"Tests\\JuelEngineTest::testJuelEngineManagerWithFunction":0.001,"Tests\\LuaEngineTest::testLuaScript":0.001}} \ No newline at end of file diff --git a/README.md b/README.md index 35fe5a6..5b7b82c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Install library, using Composer: composer require bingo-soft/script ``` -# Example 1 +# Example 1 (using Juel engine) ``` $manager = new ScriptEngineManager(); @@ -23,7 +23,7 @@ $engine = $manager->getEngineByName("juel"); echo $engine->eval('${1 + 2}'); //prints 3 ``` -# Example 2 +# Example 2 (using Juel engine) ``` $manager = new ScriptEngineManager(); @@ -48,8 +48,31 @@ echo $engine->eval('${simple.propFloat + 2}'); //prints 3.23 echo $engine->eval('${simple.bar() + simple.foo()}'); //prints 34 ``` +# Example 3. Calculate factorial using Lua module + +``` +$manager = new ScriptEngineManager(); +$engine = $manager->getEngineByName("lua"); +$engine->put('a', 5); + +echo $engine->eval(<<getEngine()->getContext(); diff --git a/src/Engine/Juel/JuelCompiledScript.php b/src/Engine/Juel/JuelCompiledScript.php index 04b1ef4..0505b76 100644 --- a/src/Engine/Juel/JuelCompiledScript.php +++ b/src/Engine/Juel/JuelCompiledScript.php @@ -22,11 +22,10 @@ public function __construct(JuelScriptEngine $engine, ValueExpression $valueExpr public function getEngine(): ScriptEngineInterface { - // Return outer class instance return $engine; } - public function eval(?BindingsInterface $ctx = null) + public function eval(BindingsInterface $ctx = null) { return $this->scope->evaluateExpression($this->valueExpression, $ctx); } diff --git a/src/Engine/Lua/LuaCompiledScript.php b/src/Engine/Lua/LuaCompiledScript.php new file mode 100644 index 0000000..b12f51b --- /dev/null +++ b/src/Engine/Lua/LuaCompiledScript.php @@ -0,0 +1,39 @@ +engine = $engine; + $this->script = $script; + } + + public function getEngine(): ScriptEngineInterface + { + return $engine; + } + + public function evalContext(ScriptContextInterface $context) + { + $lua = new \Lua(); + + $bindings = $context->getBindings(ScriptContextInterface::ENGINE_SCOPE); + $entries = $bindings->entrySet(); + foreach ($entries as $key => $value) { + $lua->assign($key, $value); + } + + return $lua->eval($this->script); + } +} diff --git a/src/Engine/Lua/LuaScriptEngine.php b/src/Engine/Lua/LuaScriptEngine.php new file mode 100644 index 0000000..fb3d260 --- /dev/null +++ b/src/Engine/Lua/LuaScriptEngine.php @@ -0,0 +1,69 @@ +scriptEngineFactory = $scriptEngineFactory ?? new LuaScriptEngineFactory(); + } + + public function getFactory(): ScriptEngineFactoryInterface + { + return $this->scriptEngineFactory; + } + + public function createBindings(): BindingsInterface + { + return new SimpleBindings(); + } + + public function eval(string $script, $scriptContextOrBindings = null) + { + if ($scriptContextOrBindings instanceof BindingsInterface) { + $scriptContext = $this->getScriptContext($scriptContextOrBindings); + } elseif ($scriptContextOrBindings instanceof ScriptContextInterface) { + $scriptContext = $scriptContext; + } else { + $scriptContext = $this->context; + } + //$expr = $this->parse($script, $scriptContext); + return $this->compile($script)->evalContext($scriptContext); + } + + public function compile(string $script): CompiledScript + { + return new LuaCompiledScript($this, $script); + } +} diff --git a/src/Engine/Lua/LuaScriptEngineFactory.php b/src/Engine/Lua/LuaScriptEngineFactory.php new file mode 100644 index 0000000..7451843 --- /dev/null +++ b/src/Engine/Lua/LuaScriptEngineFactory.php @@ -0,0 +1,107 @@ + 0) { + $sb .= ','; + } + $sb .= $args[$i]; + } + $sb .= ")"; + return $sb; + } + + public function getOutputStatement(string $toDisplay): string + { + return "print(" . $toDisplay . ")"; + } + + public function getParameter(string $key) + { + if ($key == ScriptEngineInterface::NAME) { + return $this->getLanguageName(); + } elseif ($key == ScriptEngineInterface::ENGINE) { + return $this->getEngineName(); + } elseif ($key == ScriptEngineInterface::ENGINE_VERSION) { + return $this->getEngineVersion(); + } elseif ($key == ScriptEngineInterface::LANGUAGE) { + return $this->getLanguageName(); + } elseif ($key == ScriptEngineInterface::LANGUAGE_VERSION) { + return $this->getLanguageVersion(); + } elseif ($key == "THREADING") { + return "MULTITHREADED"; + } else { + return null; + } + } + + public function getProgram(...$statements): string + { + $sb = ""; + $len = count($statements); + for ($i = 0; $i < $len; $i += 1) { + if ($i > 0) { + $sb .= '\n'; + } + $sb .= $statements[$i]; + } + return $sb; + } + + public function getScriptEngine(): ScriptEngineInterface + { + return new LuaScriptEngine(); + } +} \ No newline at end of file diff --git a/src/ScriptEngineManager.php b/src/ScriptEngineManager.php index 0406f4d..4535706 100644 --- a/src/ScriptEngineManager.php +++ b/src/ScriptEngineManager.php @@ -3,6 +3,7 @@ namespace Script; use Script\Engine\Juel\JuelScriptEngineFactory; +use Script\Engine\Lua\LuaScriptEngineFactory; class ScriptEngineManager { @@ -42,7 +43,10 @@ private function addSpi(): void { $factory = new JuelScriptEngineFactory(); $this->engineSpis[] = $factory; - //register + $this->registerEngineName($factory->getEngineName(), $factory); + + $factory = new LuaScriptEngineFactory(); + $this->engineSpis[] = $factory; $this->registerEngineName($factory->getEngineName(), $factory); } diff --git a/tests/LuaEngineTest.php b/tests/LuaEngineTest.php new file mode 100644 index 0000000..3fcf534 --- /dev/null +++ b/tests/LuaEngineTest.php @@ -0,0 +1,33 @@ +getEngineByName("lua"); + $engine->put('a', 5); + $this->assertEquals( + 120, + $engine->eval(<<assertTrue(true); + } + } +}