Recursive Descent Parser for EBNF in PHP - php

I am attempting to write a recursive descent parser in PHP for the following EBNF:
EXP ::= < TERM > { ( + | - ) < TERM > }
TERM ::= < FACTOR > { ( * | / ) < FACTOR > }
FACTOR ::= ( < EXP > ) | < DIGIT >
DIGIT ::= 0 | 1 | 2 | 3
I followed this guide which I saw recommended on a similar question. (I searched before I posted)
For the most part, I get how it works and I understand the grammar. I think the problem is within my syntax. I am new to PHP, so I have been referencing W3Schools. I currently am getting the following error with my code:
Warning: Wrong parameter count for exp() .... on line 101
I have tried to look up this error and have not had much luck. I read some posts about people passing in the wrong parameter typed, but I do not have any parameters set for that function. Is there something about PHP I am missing here?
Below is my code, I think the logic is correct since I based it off of the parse tree for the grammar. The $input will be coming from a form box on an HTML page. I also picked up the str_split function from a different post when I discovered that PHP4 does not have it built in.
<html>
<body>
<?php
if(!function_exists("exp")){
function exp(){
term();
while($token == "+" | $token == "-"){
if($token == "+"){
match("+");
term();
}
if($token == "-"){
match("-");
term();
}
}
}//end exp
}
if(!function_exists("term")){
function term(){
factor();
while($token == "*" | $token == "/"){
if($token == "*"){
match("*");
factor();
}
if($token == "/"){
match("/");
factor();
}
}
}//end term
}
if(!function_exists("factor")){
function factor(){
if($token == "("){
match("(");
exp();
if($token == ")")
match(")");
}
else if($token == 0|1|2|3){
if($token == 0)
match(0);
if($token == 1)
match(1);
if($token == 2)
match(2);
if($token == 3)
match(3);
}
else
error();
}//end factor
}
if(!function_exists("match")){
function match($expected){
if($token == $expected)
nextToken();
else
error();
}//end match
}
if(!function_exists("next_Token")){
function nextToken(){
$next++;
$token = $tokenStr[$next];
if($token == "$");
legal();
}
}
if(!function_exists("error")){
function error(){
echo "Illegal token stream, try again";
}
}
if(!function_exists("legal")){
function legal(){
echo "Legal token stream, congrats!";
}
}
if(!function_exists('str_split')) {
function str_split($string, $split_length = 1) {
$array = explode("\r\n", chunk_split($string, $split_length));
array_pop($array);
return $array;
}
}
$tokenStr = str_split($input);
$next = 0;
$token = $tokenStr[0];
exp();
?>
</body>
</html>
So basically I want to know what causes that error and why and am I on the right track in terms of creating this parser.
I appreciate any comments, suggestions, criticisms, water baloons, and tomatoes. Thank you for taking the time to read my post. Have a great day/night.

exp() is a builtin PHP function. You cannot define it under that name.
You should have no reason to use the if(!function_exists(' idiom in normal PHP applications. (It's often used more as a workaround when include scripts clash or identical functions are declared at different places.)
Another syntax problem that I noticed is your use of the bitwise OR. The logical OR should be || or just or.
while($token == "*" | $token == "/"){

I'll turn my wild guess into an answer. So maybe this is, where the problem lies?
http://php.net/manual/en/function.exp.php

There is also a function named exp() in PHP already. You may prefix your function names somehow, or it's better to use classes to avoid name collisions.

Related

How can I restructure `if` and `else` statements in PHP

I have two (and sometimes) three variables which determine if I should run a specific code. The problem is that any of them can be true or false and sometimes this leads to messy codes.
For three variables, there are 8 possible scenarios that I have to check. Like this:
if($var_a && $var_b && $var_c) {
// Do this (A)
} else if(!$var_a && $var_b && $var_c) {
// Do this (B)
} else if($var_a && !$var_b && $var_c) {
// Do this (C)
} else if(!$var_a && !$var_b && $var_C) {
// Do this (D)
}
... and so on.
Is there any way to use nesting and make this code less messy? It gets confusing at times to keep track of so many possibilities.
It might be easier to understand what I am saying with an example of two variables.
if($var_a && $var_b) {
// Do this (A)
} else if($var_a && !$var_b) {
// Do this (B)
} else if(!$var_a && $var_b) {
// Do this (C)
}
Is there any way to combine these conditions together so that I don't have to use so many && and if else conditions? This will make things less confusing for me when I have to deal with three (may be more) variables.
I guess you should try something like lookup map with bitwise map, in your case, it can be something like this:
$bitOne = $var_a ? 1 : 0;
$bitTwo = $var_b ? 2 : 0;
$bitThree = $var_c ? 4 : 0;
$resultKey = $bitOne | $bitTwo | $bitThree;
$map = [
7 => function() { return 'All are true'; },
3 => function() { return 'var_a and var_b are true'; },
6 => function() { return 'var_b and var_c are true'; },
// and so on
];
$result = $map[$resultKey];
If you extract the common variable (or an arbitrary choice in this case), you can produce a more layered approach, although the combinations will still mount up and it looks more code, it should be clearer...
if($var_a) {
if(!$var_b) {
// $var_a && !$var_b
}
else {
// $var_a && $var_b
}
}
else {
if($var_b) {
// !$var_a && $var_b
}
}

Unable to decrypt my encrypted string in a substitution cipher

I am developing my own encryption and decryption algorithms. My encryption function is working fine, but my decryption function is not working properly. The following are my functions:
<?php
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i])-$mode)%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
?>
My expected output is: abcde
But the output returned is: ejUPa
The encryption works in this way:
$new_char_index = ($char_index + $shift) % $alphabet_length;
where the modulo % handles the wrap around so that the new index is still in the alphabet range. This works well for positive numbers, but doesn't work like you would expect it to for negative numbers. Here are some examples:
echo -3 % 7; // -3
echo -11 % 7; // -4
That is why simply changing + for - doesn't work:
$new_char_index = ($char_index - $shift) % $alphabet_length;
This can lead to negative numbers. You can't access arrays with negative numbers in PHP, but you could do that in Python.
The easy fix is to make sure that the resulting index is always a positive numbers. You've tried that with abs(), but the problem is that this doesn't correctly wrap around from the other side of the alphabet, but only removes the sign which is wrong.
An easy fix is adding the alphabet length before the modulo operation in order to get a positive number:
$new_char_index = ($char_index - $shift + $alphabet_length) % $alphabet_length;
This works, because $alphabet_length % $alphabet_length == 0. It wraps to the correct position of the alphabet.
Now you only have to make sure that $shift is already in the alphabet range:
$shift = $shift % $alphabet_length;
One final improvement: you can use the same function for encryption and decryption, because the $enc_shift == -$dec_shift and the last formula should give you work for both.
This is not encryption. This is a variation on a Caeser cipher. Simply put, you should never implement your own encryption (unless you know exactly what you're doing...). This would do for obfuscation and nothing more.
As for the code itself, I suspect its an order of operations issue. Simply replacing a + with a - won't reverse the operator precedence in the encrypt() function. A handy generic string rotation function you could adapt is in the comments of this php documentation page.
If you want encryption there are many good articles about solid encryption; this is my personal opinion of a good starting point.
Here is a solution that works with every $mode and $pattern size.
You have to notice that you can only "encrypt" chars that are contained in $pattern.
<?php
function encrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$pos = strpos($pattern,$os[$i]);
if($pos >= $mode ) {
$ns .= $pattern[$pos - $mode];
} else {
// $pos - $mode is negative so we need + sign
$ns .= $pattern[$patternLength + ($pos - $mode)];
}
}
return $ns;
}
To test this, you could do something like that:
$failed = false;
for($mode = -128; $mode <= 128; $mode++) {
// include all possible chars in the test to see if encryption and
// decryption works for all.
$allChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
if(decrypt(encrypt($allChars, $mode), $mode) != $allChars) {
echo "mode ".$mode." failed<br>";
$failed = true;
};
}
if(!$failed) {
echo "success";
}
Fixed it:
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
In your decrypt ^^.
Just a matter of bracket placing.
Full fixed code:
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Hopefully you can understand the difference yourself.
And now to fix the issue with spaces:
$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"
Take note of the space at the beginning of the string.
An additional note due to comments...
Should you wish to change $mode to let's say 62, you would need to shorten $pattern appropriately.
e.g.
function encrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Works just fine, notice $pattern is now missing the last 2 characters.

Using PHP eval() in if statement

Not sure how to use the eval() I thought I read the documentation correctly and am trying to do this (I know the logic is awful, but I was just trying it out).
if($showUnlisted){
$unlistedIfStatement = '!$price || $car["sale_price"] == 0 ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"])';
}
else{
$unlistedIfStatement = '!$price ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"])';
}
if(eval($unlistedIfStatement)){
//DO SOME STUFF
}
I get this error:
Parse error: syntax error, unexpected end of file
Looking at these examples(http://us1.php.net/eval) it should run whatever is in the string as a line of code. Also this definition "The eval() function evaluates a string as PHP code." from http://www.w3schools.com/php/func_misc_eval.asp Basically, I'm trying to store a line of code in a variable and have it run like I would do in JavaScript. Very new to PHP, I'm sorry if my question is dumb.
EDIT:
I've read the many posts as to why the eval() is a bad tool, I honestly am just asking out of curiosity.
EDIT2:
meagar's code worked splendidly, the only thing I had to do to get it to work was add semi-colons before the string ended. I went to edit his post, but my edit was denied. Here is the code that worked for me....Obviously I'll be changing it to something that my peers will not poke fun at.
if($showUnlisted){
$unlistedIfStatement = 'return !$price || $car["sale_price"] == 0 ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"]);';
}
else{
$unlistedIfStatement = 'return !$price ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"]);';
}
if(eval($unlistedIfStatement)){
//DO SOME STUFF
}
The documentation that you linked to is very clear:
eval() returns NULL unless return is called in the evaluated code, in which case the value passed to return is returned.
Each of your eval'd lines must return a value, or they evaluate to null:
if($showUnlisted){
$unlistedIfStatement = 'return !$price || $car["sale_price"] == 0 ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"])';
}
else{
$unlistedIfStatement = 'return !$price ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"])';
}
if(eval($unlistedIfStatement)){
//DO SOME STUFF
}
As indicated in another answer, eval() returns null in absence of a return value. However, what you're actually looking for is a callback:
Somewhere in your code:
function showUnlisted() {
return !$price || $car["sale_price"] == 0 || ($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"]);
}
function dontShowUnlisted() {
return !$price ||($priceFrom <= $car["sale_price"] && $priceTo >= $car["sale_price"]);
}
Then, where you need to decide between these functions:
if ($showUnlisted) {
$appropriateFunction = 'showUnlisted';
} else {
$appropriateFunction = 'dontShowUnlisted';
}
if (call_user_func($appropriateFunction)) {
//do stuff
}
This prevents you from falling prey to the evils of eval, lets you test those functions, utilize IDEs more effectively, and predict the outcome better. Passing functions as objects is a useful thing, and while awkward in PHP, a common practice in more modern languages (Scala, C#, etc.).

php validate integer

I`m wonder why this not working
echo gettype($_GET['id']); //returns string
if(is_int($_GET['id']))
{
echo 'Integer';
}
How to validate data passing from GET/POST if it is integer ?
Can use
$validatedValue = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
See http://php.net/filter_input and related functions.
The manual says:
To test if a variable is a number or a
numeric string (such as form input,
which is always a string), you must
use is_numeric().
Alternative you can use the regex based test as:
if(preg_match('/^\d+$/',$_GET['id'])) {
// valid input.
} else {
// invalid input.
}
What about intval?
$int = intval($_GET['id']);
To validate form data (string) as an integer, you should use ctype_digit()
It returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
(PHP 4 >= 4.0.4, PHP 5)
Reference: http://php.net/manual/en/function.ctype-digit.php
Try:
if(isNumeric($_GET['id'])) {
$cast_int = (int)$_GET['id'];
}
if(isset($cast_int)) {
echo gettype($cast_int)."<br />\n";
if(is_int($cast_int))
{
echo 'Integer'."<br />\n";
}
} else {
echo gettype($_GET['id'])." was passed<br />\n";
}
function isNumeric($numeric) {
return preg_match("/^[0-9]+$/", $numeric);
}
It sounds like you are checking if a string contains an integer, rather than if that variable is an integer. If so, you should check out php's regex (regular expression) functionality. It allows you to check for very specific patterns in a string to validate it for whatever criteria. (such as if it contains only number characters)
Here's the php page
http://php.net/manual/en/function.preg-match.php
and here's a cheat sheet on regular expressions (to make the $pattern string)
http://regexpr.com/cheatsheet/
I take a slightly more paranoid approach to sanitizing GET input
function sanitize_int($integer, $min='', $max='')
{
$int = intval($integer);
if((($min != '') && ($int < $min)) || (($max != '') && ($int > $max)))
return FALSE;
return $int;
}
To be even safer, you can extract only numbers first and then run the function above
function sanitize_paranoid_string($string, $min='', $max='')
{
$string = preg_replace("/[^a-zA-Z0-9]/", "", $string);
$len = strlen($string);
if((($min != '') && ($len < $min)) || (($max != '') && ($len > $max)))
return FALSE;
return $string;
}
Code from: http://libox.net

If statement structure in PHP

I keep getting an error with the following bit of code. It is probably some small thing but I don't see what is wrong.
while($row = mysql_fetch_array($result))
{
$varp = $row['ustk_retail'];
if ($varp<80000) { $o1 = 1; }
if (($varp=>80000) && ($varp<100000)) { $o2 = "1"; }
if (($varp=>100000) && ($varp<120000)) { $o3 = "1"; }
if (($varp=>120000) && ($varp<140000)) { $o4 = "1"; }
if (($varp=>140000) && ($varp<160000)) { $o5 = "1"; }
if (($varp=>160000) && ($varp<180000)) { $o6 = "1"; }
if (($varp=>180000) && ($varp<200000)) { $o7 = "1"; }
if (($varp=>200000) && ($varp<220000)) { $o8 = "1"; }
if (($varp=>220000) && ($varp<240000)) { $o9 = "1"; }
if (($varp=>240000) && ($varp<260000)) { $o10 = "1"; }
if (($varp=>260000) && ($varp<280000)) { $o11 = "1"; }
if (($varp=>280000) && ($varp<300000)) { $o12 = "1"; }
if ($varp>=300000) { $o13 = "1"; }
}
Running php -l (lint) on your code I get a
Parse error: syntax error, unexpected T_DOUBLE_ARROW
The T_DOUBLE_ARROW token is what PHP expects when assigning array values to array keys.
When comparing for Greater than or equal to the PHP Parser expects T_IS_GREATER_OR_EQUAL, meaning you have to use >= instead of =>.
See
the chapter on Comparison Operators in the PHP Manual and
the List of Parser Tokens in the PHP Manual
Greater than or equal to is >= sign, not =>
Update:
You are right. It's small but hard to find mistake.
It took me to split whole line into pieces to see where the problem is:
<?php
if
(
$varp
=>
80000
)
So, it says parse error on line 5 and I had to doublecheck this operator.
Of course, at first I separated the problem line from the rest of the code to be certain.
You have an expression error.
$varp=>220000 // is not a valid php expression
=> operator is used to assign values in arrays like:
$x = array( 'foo' => 'bar');
>= is the comparation assigment greater than or equal
You have made a mistake in the if conditions. The greater than Equal to sign is >= and not =>.
The answer has already been given but thought this was neat enough to share:
PHP accepts boolean expressions in it's switch statement.
switch(TRUE) {
case $range <= 10: echo "range below or equal to 10"; break;
case $range <= 20: echo "range above 10 below or equal to 20"; break;
case $range <= 30: echo "range above 20 below or equal to 30"; break;
default: echo "high range";
}
In my opinion this generates the cleanest most readable code.
This is more readable and compact way to do the same:
$ranges = range(300000, 80000, -20000);
$index = 1;
$varp = 220001;
foreach ($ranges as $i => $range) {
if ($varp >= $range) {
$index = 13 - $i;
break;
}
}
${'o' . $index} = 1;
Anyway - I think you're doing something wrong with using variable name of result.
You probably want to change ($varp=300000) to ($varp==300000) and it might help to enclose the full if-statement inside (), like this
if($varp80000 && $varp100000 && $varp120000 && $varp140000 && $varp160000 && $varp180000 && $varp200000 && $varp220000 && $varp240000 && $varp260000 && $varp280000 && $varp==300000) { $o13 = "1"; }
On another note, where to these strange $varp#### variables come from?
Not sure whether the code you've posted has gotten messed up somehow, but it looks like you're missing "==" in some of the if conditions. Also, as Skilldrick pointed out, the whole if condition should be in parentheses
"Greater than or equal to is >= NOT =>. You use => for arrays for keys/values.
Add one more bracket around the conditions in if....
if ( ($varp80000) && ($varp100000) && ($varp120000) && ($varp140000) && ($varp160000) && ($varp180000) && ($varp200000) && ($varp220000) && ($varp240000) && ($varp260000) && ($varp280000) && ($varp=300000) ) { $o13 = "1"; }

Categories