Collection of mostly command line tools / PHP scripts. Somewhat out of date.

⌈⌋ ⎇ branch:  scripts + snippets


Artifact [638042cad2]

Artifact 638042cad224e24cbb9260ed5df9296e903756d4:

  • Executable file php-assert-hints.php — part of check-in [5eaf0c63f6] at 2015-03-08 18:29:08 on branch trunk — Transform PHP function parameter /*type*/ hints into assert() statements. (user: mario size: 6396)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
   134
   135
   136
   137
   138
   139
   140
   141
   142
   143
   144
   145
   146
   147
   148
   149
   150
   151
   152
   153
   154
   155
   156
   157
   158
   159
   160
   161
   162
   163
   164
   165
   166
   167
   168
   169
   170
   171
   172
   173
   174
   175
   176
   177
   178
   179
   180
   181
   182
   183
   184
   185
   186
   187
   188
   189
   190
   191
   192
   193
   194
   195
   196
   197
   198
   199
   200
   201
   202
   203
   204
   205
   206
   207
#!/usr/bin/php -qC
<?php
 #
 # type: cli
 # description: Transform "type hints" into function parameter type assert() statements.
 # version: 0.1.0
 #
 # Duplicates parameter value-type hints into static lists of assertions atop
 # each function body. Accepts in-comment "type hints". Can also remove them
 # again. It recognizes scalar-ish types, arrays and resources:
 #
 #    function xyz (/*int*/ $a, /*string*/ $s, /*bool*/ $b)
 #    {
 #        assert('is_int($a) /*typehint*/');
 #        assert('is_string($s) || $s instanceof SplString /*typehint*/');
 #        assert('is_bool($b) /*typehint*/');
 #
 # Even allows a few basic expressions or comparisons within assertions:
 #
 #    function cmp (/*int,>0*/ $i, /*str,strlen($)<32*/, $s)
 #
 # Such type assertions can be turned off at runtime, be configured to generate
 # E_WARNINGs instead, or permit custom handlers. Can be selectively displaced
 # once PHP7 "type hints" become available (implicit type casts or runtime errors
 # preclude any traceable assertions of course).
 #
 # To add assertions:
 #   php-assert-hints files/*.php
 #
 # Remove them:
 #   php-assert-hints --rm files/*.php
 #


// Loop over input files
list($files, $opts) = argv();
$files or exit("Usage:\n  php-assert-hints *.php\n");
array_map(
    [new FuncDeclRewriteMap($opts), "on"],
    $files
);


/**
 * Transforms input files
 *
 */
class FuncDeclRewriteMap {

    // Parameter types to assert conditions, some aliases
    public $types = [
        "int" => "is_int($)",
        "integer" => "is_integer($)",
        "long" => "is_long($)",
        "bool" => "is_boolean($)",
        "boolean" => "is_boolean($)",
        "str" => "is_string($) || $ instanceof SplString",
        "string" => "is_string($) || $ instanceof SplString",
        "float" => "is_float($)",
        "real" => "is_real($)",
        "double" => "is_double($)",
        "numeric" => "is_numeric($)",
        "array" => "is_array($) || $ instanceof Traversable || $ instanceof ArrayObject",
        "scalar" => "is_scalar($)",
        "resource" => "is_resource($) || $ instanceof PDO",
        "callable" => "is_callable($)",
        "isset" => "isset($)",  # only makes sense for references
        "=null" => " || is_null($)",  # added for =NULL defaults
    ];
    public $only_rm = false;


    // Retain --rm cmdline flag
    public function __construct (/*array*/ $opts) {
        $this->only_rm = preg_grep("/^-+(rm?|de?l?)$/i", $opts);
    }
    

    // Update file in-place
    public function on (/*string*/ $fn) {

        $src = file_get_contents($fn);
        if ($update = $this->rewrite($src)) {
        
            // check that only lines containing assert() have been added/removed/changed
            $diff = array_diff(preg_split("/\R/", $src), preg_split("/\R/", $src));
            if (preg_grep("/^\s*assert\(/mi", $diff, PREG_GREP_INVERT)) {
                fwrite(STDERR, "Invalid rewrite (changed too much) for '$fn'\n");
            }
            
            // write back
            else {
                file_put_contents($fn, $update);
            }
        }
        
        elseif (preg_last_error()) {
            fwrite(STDERR, "Probable regex/UTF8 error with '$fn'\n");
        }
    }


    // Update source
    public function rewrite(/*string*/ $src) {
    
        // remove prior assertions
        $src = preg_replace(self::RX_ASSERT, "", $src);
        if ($this->only_rm) {
            return $src;
        }
        
        // find function bodies, and inject new assert() lists
        $src = preg_replace_callback(self::RX_FUNC, [$this, "rx_func"], $src);
        return $src;
    }
    

    // Regexps
    const RX_ASSERT = "/
       ^\h* assert\(\' [^{'}]+ \/\*\h*type\h*hint\h*\*\/ \'\);\h*\R
    /mix";
    const RX_FUNC = "/
        (?<indent> \h*)
        (?:(?:public|private|protected|static)\s+)*
        function
        \s*
        (?<name> [\w\pL]+)
        \s*
        \( (?<params> [^{}]* ) \)
        (?<rettype> \s*:\s* \w+)?
        \s*
        \{ (\h*\R)?
    /mix";
    const RX_PARAM = "/
       (?:
          (?: \/\*\s*)?
          (?<type>\w+)
          (?<expr>(?:[,\s]*[\w()$]*[<>!=]\s*[-+\d.]+[\w()]*)*)
          (?: \s*\*\/\s*|\s+)
       )?
       (\s* \&)?
       \s* (?<name>[$][\w\pL]+)
       (?<def> \s*=\s* NULL)?
    /mix";


    // Append to function declaration headers
    public function rx_func (/*array*/ $match) {
    
        // whole function declaration header
        $decl = $match[0];
        $ws = preg_replace("/\S/", " ", $match["indent"]) . "    ";
        
        // list parameters
        if (preg_match_all(self::RX_PARAM, $match["params"], $params, PREG_SET_ORDER)) {
            foreach ($params as $p) {

                // recognized type prefix?
                if ($type = strtolower($p["type"]) and isset($this->types[$type])) {

                    // copy type assertion
                    $expr = $this->types[$type];

                    // add expression checks
                    if (!empty($p["expr"])) {
                        $expr = join(" && ", array_merge(
                            ["($expr)"],
                            array_map(
                                function($add_x) {
                                    return is_int(strpos($add_x, "$"))
                                         ? "($add_x)"
                                         : "(\$ {$add_x})";
                                },
                                array_filter(preg_split("/\s*,\s*/", $p["expr"]))
                            )
                        ));
                    }

                    // add null alternative
                    if (isset($p["def"]) && stristr($p["def"], "NULL")) {
                        $expr .= $this->types["=null"];
                    }
                    
                    // substitute param name and just append to function declaration header
                    $expr = preg_replace("/[\$](?!\w)/", $p["name"], $expr);
                    $decl .= "{$ws}assert('$expr /*typehint*/');\n";
                }
            }
        }
        
        return $decl;
    }

}



// Split files from -options
function argv(/*int*/ $x=NULL) {
    $argv = array_slice($_SERVER["argv"], 1);
    $opts = preg_grep("/^-+(rm?|de?l?)/i", $argv);
    return [array_diff($argv, $opts), $opts];
}


#include <ispect.ph>