-
Notifications
You must be signed in to change notification settings - Fork 66
Can the ruler output stand-alone PHP? #2
Comments
Hello :-), The |
I'm not sure if you are understanding me correctly, or if i'm not understanding you ... <?php
require_once __DIR__.'/vendor/autoload.php';
$test = serialize(
Hoa\Ruler\Ruler::interprete(
'group in ("customer", "guest") and points > 30'
)
);
var_dump($test); Giving
But i'm looking to output
So the PHP code itself is the output |
Hi, I don't see the benefit to transform
Into
Storing closures or objects to interprete things would not change many things imo. 2nd solution is prettier but much more hard to generate. |
@Hywan the functionality should stay the same. So when it comes down to "does it work". Yes it's enough. But when you want portable, fast optimized production code then native PHP is the best. It would make this library very interesting for all sorts of projects i think. I thought maybe this functionality already existed, but i guess not. So now i think maybe the AST from \Hoa\Ruler\Model can be transformed into PHP AST and the converted to PHP code. There is a great library to convert PHP code to PHP AST here https://github.com/nikic/PHP-Parser So the benefit would be to define your own (domain) language and then convert it to actual PHP code. If it is possible to do: |
Using PHP-Parser from Nikita to rebuild a rule model is definitively not a good idea ;-). @stephpy mentionned that it will be difficult to implement, and yes, he is right. And this is the main issue. What happen when someone add a new operator with a sophisticated implementation: how do we transform that? By taking an example from the $ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) {
return $user::CONNECTED === $user->getStatus();
}); How do we transform/compile the |
Hi. Using the PHP-Parser is optional i think, because if you want you can go straight from Hoa AST to PHP. I was just considering that going FROM Hoa AST to PHP AST might be as much work as going from Hoa AST to PHP directly. Using the PHP AST also gives direct access to all the funcitonality implemented by PHP-Parser (which might come in handy later). And also not to reinvent stuff, but re-use where possible. To me this sounds like a good approach, but then i am not familiar with the hoa codebase, so it might not be a good idea indeed. I don't really understand the problem with the <?php
// 'group in ("customer", "guest") and points > 30'
$closure = function($group, $points) {
return in_array($group, ['customer', 'guest']) AND $points > 30;
}
//$ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) {
// return $user::CONNECTED === $user->getStatus();
//});
$closure = function(User $user) {
return $user::CONNECTED === $user->getStatus();
};
// 'logged(user) and group in ("customer", "guest") and points > 30'
$closure = function($group, $points) use ($user) {
return function(User $user) {
return $user::CONNECTED === $user->getStatus();
} AND
in_array($group, ['customer', 'guest']) AND
$points > 30;
} |
|
Hhmm seems i'm overestimating PHP in that case. Alternatively all the operators could be placed in their own variable.
Option 1 should always work. Not sure about option 2 ... might need some test cases for that. Test for Option 1 http://codepad.viper-7.com/KzJ6Ks |
But how do extract the body of a new operator/function (here, a closure)? |
Aaah now i know what you mean .. yes true this is not so easy. Here is a proof of concept though. <?php
require_once 'vendor/autoload.php';
class User {
const DISCONNECTED = 0;
const CONNECTED = 1;
public $group = 'customer';
public $points = 42;
protected $_status = 1;
public function getStatus() {
return $this->_status;
}
}
$ruler = new Hoa\Ruler\Ruler();
// New rule.
$rule = 'logged(user) and group in ("customer", "guest") and points > 30';
// New context.
$context = new Hoa\Ruler\Context();
$context['user'] = function ( ) use ( $context ) {
$user = new User();
$context['group'] = $user->group;
$context['points'] = $user->points;
return $user;
};
// We add the logged() operator.
$ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) {
return $user::CONNECTED === $user->getStatus();
});
// Finally, we assert the rule.
var_dump(
$ruler->assert($rule, $context)
);
/**
* Will output:
* bool(true)
*/
// Let the magic begin
$closure = $ruler->getDefaultAsserter()->getOperator('logged');
$refl = ReflectionFunction::export($closure->getValidCallback(), true);
$refl = explode("\n", $refl);
preg_match('/ @@ (?P<path>.*) (?P<from_line>\d+) - (?P<to_line>\d+)/i', $refl[1], $codeInfo);
$file = explode("\n", file_get_contents($codeInfo['path']));
preg_match('/setOperator\([\'"].*[\'"] *, *(?P<first_line>.*)/i', $file[$codeInfo['from_line'] - 1], $regs);
$code = $regs['first_line'];
for ($i = $codeInfo['from_line']; $i < $codeInfo['to_line']; $i++) {
$code .= $file[$i];
}
echo $code;
/**
* Will output:
* function ( User $user ) {
*
* return $user::CONNECTED === $user->getStatus();
* });
*
*/ |
What about a closure written on a single line ;-)? |
// for code:
$ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) { return $user::CONNECTED === $user->getStatus(); });
// same script will out:
/*
* function ( User $user ) { return $user::CONNECTED === $user->getStatus(); });
*
*/ I understand it's a bit tricky 🌴 (random palm tree). But so far so good. Perhaps i can make it more proof for edge cases and exceptions? If i would use the PHP Parser then from the AST i could get the perfect code. But i didn't use the PHP Parser because i wanted to show an example without additional dependencies. |
Ok, so let's go for a POC 👍. We will see where we can go! |
Ok cool ! |
Nop :-). In a |
Hmm, what about a closure (defining a new function) with a |
That's actually the easy part. Because the variables you pass in on use() are available at run time and thus can be used directly. Put them in a Is it ok to make it a hoathis project? The problem is that it will be difficult and error prone when not using a tokenizer/parser. And PHP-Parser is the best one for that. Unless you want to write your own. Perhaps i (we ?) will do a POC using the PHP-Parser .. then later if you want to make it hoa only you can write your own parser or regexp?? |
We can write a parser for PHP since we write the grammar of PHP in PP (please, see |
Ok great, POC is done. I just need some people who can write crazy complicated php code to make a few test cases now ... <?php
ini_set('xdebug.max_nesting_level', 2000); // https://github.com/nikic/PHP-Parser/blob/master/doc/2_Usage_of_basic_components.markdown#bootstrapping
require_once 'vendor/autoload.php';
class User {
const DISCONNECTED = 0;
const CONNECTED = 1;
public $group = 'customer';
public $points = 42;
protected $_status = 1;
public function getStatus() {
return $this->_status;
}
}
$ruler = new Hoa\Ruler\Ruler();
// New rule.
$rule = 'logged(user) and group in ("customer", "guest") and points > 30';
// New context.
$context = new Hoa\Ruler\Context();
$context['user'] = function ( ) use ( $context ) {
$user = new User();
$context['group'] = $user->group;
$context['points'] = $user->points;
return $user;
};
// We add the logged() operator.
$ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) {
return $user::CONNECTED === $user->getStatus();
});
// Finally, we assert the rule.
var_dump(
$ruler->assert($rule, $context)
);
/**
* Will output:
* bool(true)
*/
// Let the magic begin
$closure = $ruler->getDefaultAsserter()->getOperator('logged');
$refl = ReflectionFunction::export($closure->getValidCallback(), true);
$refl = explode("\n", $refl);
preg_match('/ @@ (?P<path>.*) (?P<from_line>\d+) - (?P<to_line>\d+)/i', $refl[1], $codeInfo);
$file = file_get_contents($codeInfo['path']);
// Using PHP-Parser
$parser = new PhpParser\Parser(new PhpParser\Lexer);
try {
$stmts = $parser->parse($file);
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
foreach ($stmts as $stmt) {
if ($stmt->getAttribute('startLine') == $codeInfo['from_line'] AND
$stmt->name === 'setOperator') {
$closure = $stmt->args[1]->value;
}
}
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$code = '<?php' . PHP_EOL . $prettyPrinter->prettyPrint([$closure]);
file_put_contents('closure.php', $code);
/**
* Will output:
*
* <?php
* function (User $user) {
* return $user::CONNECTED === $user->getStatus();
* };
*
*/ |
ping @stephpy or @jubianchi? |
Nice work, I'll make some tests tomorrow. There is some points which are annoying to me: EACH operators could be technically added with many many different ways ... |
Hey to get things going i was working on the idea i had before .. namely to convert Hoa AST to PHP AST. And it's looking pretty good so far. I can do simple stuff like this <?php
$AST = $dumper->AstConvert($this->RuleToCode("2 is 3"));
$PHP = $this->getPHP($AST);
$this->assertEquals("2 == 3;", $PHP); but also more complicated stuff like <?php
$rule = "'foo' in ('foo', 'bar')";
$AST = $dumper->AstConvert($this->RuleToCode($rule));
$PHP = $this->getPHP($AST);
$this->assertEquals("in_array('foo', array('foo', 'bar'));", $PHP); so this does I can't make combination yet like Hmm this would have been easier if we could just convert the ruler grammar to PHP grammar and build the AST from there. Maybe this can be a feature for next time? |
Is it possible to get the output of Hoa\Ruler\Ruler::interprete as php variable instead of string? Right now you would have to write it to a file and then include the file to get the variable again. |
Actually, the interpretation builds the model, and the root of the model has a |
Ah thanks! so i think it's better to use the model directly then in the conversion? I spend a few hours on the converter class ... so i hope i'm on the right way with this and not wasting work. (please look at the examples above) |
@flip111 I think you just lost me ;-). Secondly, you propose to write the grammar of PHP with the PP language. Yes, it's a good idea, but I don't know if it is even possible. I have exchanged some greetings with Nikita Popov (@nikic) few monts ago about it, and he pointed me out that some parts of the PHP grammar are very ambiguous. Our discussion was related about building an AST for PHP. On the other hand, HHVM has succeed it… so it seems to be feasible. By the way, we can try! Finally, you propose to implement a bridge between the model of a rule (an instance of Thus, from the model of a rule, we would be able to produce PHP code. Right. But what about code dependencies, such as global variables (berk), dedicaded autoload… in short, how to catch the dynamic context of the callable used to declare a new operator? The answer is: by inferencing the code. This is the only solution I see. Did I understand? |
@Hywan The PHP grammar isn't really ambiguous, it's just rather complex and unstructured, especially where variables are concerned. I'm sure you can whip out a grammar for your LLk parser. If you do so, I recommend writing a corresponding pretty printer and then checking whether a parse(1) -> pretty print -> parse(2) cycle has the same result in (1) and (2) on all the php-src tests (and a other large projects like Symfony/ZF). That way you should be able to get a robust parser (if you put in enough effort ^^). I use the same process for the php-parser project. Anyway, I'm writing this totally out of context, I didn't read the rest of the thread, so no idea what this is all about ^^ |
@nikic Absolutely, this would definitively be the good process to verify the grammar and therefore the compiler. Moreover, we would be able to generate PHP programs thanks to some algorithms I developed during my PhD thesis (please see the details). Would you like to help to write such a grammar ;-)? By the way, it's totally out of subject. |
Thanks for your response. Yes sorry I did mean I'm happy to hear that you consider writing PHP in PP language something worth looking into. Perhaps this will be an option in the feature (as I do think it would be pretty complicated). I think the As to your question on how to catch the dynamic context. I'm really not there yet, so i'm just gonna go ahead an convert everything that i can and see where it ends up. My immediate problem is that i can not convert a do you have any idea on how to convert |
Sure, with |
@Hywan I think it's an interesting thing to try, but I've been so busy recently that I hardly keep up with my own projects. Would be too much to start writing a PHP grammar now (which is a rather lot of work...) |
@nikic I keep you informed when I'll start. I'm very busy too, but I'll start in few months. |
The second version is ready. This version features direct Hoa\Ruler\Model to PHP-Parser AST conversion which is also reversible. https://github.com/flip111/HoaRulerPHPDumper/tree/v2 Note that this is still a partial implementation, which only covers the very basic functions. |
But it's cleaner :-). What is your next step? |
next steps:
|
Hi, the functionality is done. You can see it here https://github.com/flip111/HoaRulerPHPDumper/tree/v2 Please review and tryout. There is a small issue #6 that leads to a test failing, but one-by-one the tests work. Including the example of the documentation. |
How can i get more attention for this issue? Need reviewers to speed it along now ... |
Could you update the |
Updated |
I have reviewed it. Nice job! Just some comment about the |
Is there a good way to make this part of Hoa Ruler itself ? or else another Hoa helper class? |
FYI
Have a look at SuperClosure from @jeremeamia, I'm using it in PHP-DI to extract the body of a closure and it works great (it's very well tested). His V2 will have support for closures with |
@flip111 As long as there is a dependency to PHP-Parser, it will not be part of the standard libraries, but it can be an Hoathis (this is the goal of this “family” of libraries). We can referenciate this library on our website and maybe in the |
@Hywan @mnapoli |
Hmm interesting topic ... Where as i have chosen for So there is a bunch of formats of how a rule can be "stored", some "storage" can actually execute directly instead of having to be interpreted again (that would be the conversion to PHP i made). So there is:
Now for each one someone would have to program a specific converter from one format to another one. Also there is not really a "base" format established, sure the base is the actual written rule itself ... but the grammar might change in the feature then all specific converters have to be rewritten. What would be really interesting if you were to have all these language definitions in PP then you would just have to define the mapping you want to do and something could build a converter for you... Yes that is actually easier then having to write recursive methods ... My concrete question is: As for the actual syntax of the mapping file itself ... i have no clue so far ... |
The compilation process goes like this: we have a lexical analyzer (aka lexer) that produces a sequence of lexemes (aka tokens). This sequence is given to a syntactic analyzer (aka parser) to assert whether the sequence is valid or not. The “validity” of a sequence is given by a grammar. At the end of this process, when the sequence is valid, the parser might be able to produce an AST (that stands for Abstract Syntax Tree). In the case of What you propose is to transform one AST to another. The compilation process will go on with the visit of the new AST to get a new model etc. Then, your proposal is related to the “tree transformations” domain. Unfortunately, there is nothing in the standard set of libraries of Hoa to do this. But I'm not sure we need such algorithms. What about creating |
The loss of information had occurred to me. This is only natural, as with human spoken languages, some expressions/sayings can not be said in other languages only be described. It's the same with code. The logged function is actually a special case here, because you embed PHP into a rule (which is of another language). It would be comparable with writing an English text and then referencing a quote which is in french. On a pure language definition it's a limitation of the rule-language that it can not express function itself but has to utilize another language to do that. You are also right about the Tree transformation. One example if that can be found here https://github.com/flip111/HoaRulerPHPDumper/blob/v2/src/PHPDumper.php#L134 where a hoa\ruler\model function is transformed to multiple nodes in PHP-AST (FuncCall with Arg objects as childrion). I guess it's a translation dictionary ^^ I will investigate the idea a bit further when i try to convert EBNF language definition to PP language definition. The language definition being a language itself of course... Related to: hoaproject/Compiler#17 Thanks again for your input |
I moved version 2 to be the master branch, fixed some small things. This is now "done" for the moment (i'm not actively developing it). Final version: https://github.com/flip111/HoaRulerPHPDumper I can continue development in case there is a need for it. I'm still very open to closer integration into Hoa because my library is probably not getting enough exposure now. Maybe mention it in the Ruler readme somewhere? |
Is it possible to create an Hoathis of this? Since you have a dependency to another project, this is the closest place to Hoa for your contribution. Of course, it would be possible to mention this library somewhere. This is another opened topic on IRC and ML: how to promote external projects that enhance Hoa (and this is difficult, we have few resources). I know you are on IRC so let's give us your opinion about it :-). Thanks! |
Also, is the |
Taken from the readme
There is an example of how to convert it to PHP code by calling
Hoa\Ruler\Ruler::interprete
. But this return\Hoa\Ruler\Model
objects. Is it also possible generate stand-alone PHP code?Getting a closure like this
would be great to reuse the code in projects and test it with PHP directly.
Perhaps it can be done with using the Hoa compiler ??
The text was updated successfully, but these errors were encountered: