
# Iskanje AST

Način AST v Greph išče v izvorni kodi PHP po strukturi, ne po besedilu. Vzorci so zapisani kot običajna koda PHP z metaspremenljivkami `$VARIABLE` in `$$$VARIADIC`. [nikic/php-parser](https://github.com/nikic/PHP-Parser) vzorec razčleni v enako obliko AST kot izvorno kodo, po kateri išče, nato pa primerja vozlišče za vozliščem.

Gre za enak model kot pri [ast-grep](https://ast-grep.github.io/), združljivostni ovoj `sg` pa na istem mehanizmu ponuja vmesnik ast-grepCLI.

## Hitri primeri

```bash
# Find every constructor call
./vendor/bin/greph -p 'new $CLASS()' src

# Find every method call with any number of arguments
./vendor/bin/greph -p '$obj->$method($$$ARGS)' src

# Find old-style array() calls
./vendor/bin/greph -p 'array($$$ITEMS)' src

# Find void-returning functions
./vendor/bin/greph -p 'function $name($$$PARAMS): void {}' src

# Find isset ternaries that should be ??
./vendor/bin/greph -p 'isset($x) ? $x : $default' src
```

## Sintaksa vzorcev

Vzorec je veljavna izvorna koda PHP. Kjer koli bi lahko zapisali pravi izraz ali stavek, lahko za zajem tega vozlišča uporabite metaspremenljivko.

| Sintaksa | Ujema se z | Primer |
|---|---|---|
| `$VAR` | Katerim koli posameznim vozliščem AST, zajetim kot `VAR` | `$x + $y` se ujema z `foo() + bar` |
| `$_` | Katerim koli posameznim vozliščem AST, brez zajema | `$_->method()` se ujema s katerim koli sprejemnikom |
| `$$$ARGS` | Nič ali več vozlišč (variadično), zajetih kot `ARGS` | `func($$$ARGS)` se ujema z `func()`, `func(1)`, `func(1, 2, 3)` |
| `$VAR` (ponovljeno) | Isti `$VAR` se mora obakrat ujemati z isto strukturo | `$x == $x` se ujema z `a == a`, ne pa z `a == b` |

Ime metaspremenljivke je po dogovoru zapisano z velikimi črkami, vendar ni nujno. `$_` je nadomestni znak brez zajema.

`$$$VARIADIC` deluje povsod, kjer PHP dovoljuje seznam vozlišč: v argumentih funkcij, elementih tabel, seznamih parametrov in telesih stavkov. Zajem shrani celotno ujemajoče se zaporedje (tudi če ne vsebuje nobenega elementa).

## Metaspremenljivke identifikatorjev

Imena razredov, funkcij, vmesnikov, traitov in enumov so identifikatorji, ne izrazi, zato PHP običajno ne bi sprejel `$Name` za `class`. Greph pred razčlenitvijo preoblikuje te vzorce, zato metaspremenljivka še vedno deluje:

```bash
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
```

Enaka preobrazba se izvede za `class`, `interface`, `trait`, `enum` in `function`.

## Ponavljajoče se metaspremenljivke

Ponovna uporaba metaspremenljivke v istem vzorcu pomeni, da se morata obe pojavitvi strukturno ujemati z istim vozliščem. To je uporabno za odkrivanje odvečne kode:

```bash
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
```

Isto pravilo velja za `$$$VARIADIC`: dve pojavitvi `$$$ARGS` se morata ujemati z istim zaporedjem.

## Filtriranje datotek

Način AST pregleda drevo datotek z istim sprehajalnikom kot besedilni način in kot privzeti filter vrst datotek uporablja `php`. Preglasite ga z možnostmi `--type`, `--type-not`, `--glob`, `--no-ignore` in `--hidden`. Spraševalnik še vedno upošteva `.gitignore` in `.grephignore` ter preskoči binarne datoteke.

```bash
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
```

## Izhod

Privzeti izhod uporablja enako obliko `file:line:content` kot besedilni način, pri čemer je ujemajoča se koda strnjena v eno vrstico:

```text
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
```

Podajte `--json` za izpis strukturiranih ujemanj:

```json
[
  {
    "file": "src/Greph.php",
    "start_line": 38,
    "end_line": 38,
    "start_file_pos": 1234,
    "end_file_pos": 1252,
    "code": "new FileWalker()"
  }
]
```

## Napake pri razčlenjevanju

Privzeto Greph tiho preskoči datoteke, ki jih ni mogoče razčleniti. To je primerno pri izvajanju nad veliko kodno zbirko, ki vsebuje eksperimentalne ali ustvarjene datoteke PHP. Podajte `--strict-parse` (za `greph-index ast-index search` in `greph-index ast-cache search`), da se namesto tega sproži napaka pri razčlenjevanju, ali nastavite `skipParseErrors: false` v `AstSearchOptions`, kadar neposredno uporabljate fasado.

## Uporaba v kodi

```php
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

$matches = Greph::searchAst(
    '$obj->$method($$$ARGS)',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($matches as $match) {
    echo "{$match->file}:{$match->startLine}\n";
    echo $match->code . "\n";

    foreach ($match->captures as $name => $node) {
        echo "  {$name} = " . get_debug_type($node) . "\n";
    }
}
```

`$match->node` je zajeto `PhpParser\Node` (zato se lahko sprehodite po njem z PHP-Parser `NodeVisitor`), `$match->captures` pa preslika vsako ime metaspremenljivke v zajeto vozlišče ali seznam vozlišč.

## Kako deluje

Cevovod poteka takole:

1. **Razčlenitev vzorca**: `Greph\Ast\PatternParser` preoblikuje metaspremenljivke identifikatorjev, nato pa vzorec razčleni z PHP-Parser.
2. **Razčlenitev izvorne kode**: vsaka kandidatna datoteka se razčleni enkrat. V načinu `ast-cache` ta korak nadomesti deserializacija iz shrambe predpomnilnika.
3. **Filtriranje kandidatov**: `Greph\Ast\AstPatternPrefilter` izlušči grobi podpis vzorca (ime klica, ime razreda, vrsto stavka) in ga uporabi za preskakovanje datotek, ki očitno ne morejo vsebovati ujemanja. V načinu `ast-index` ta korak namesto tega poizveduje po vnaprej izračunani shrambi dejstev.
4. **Ujemanje**: `Greph\Ast\PatternMatcher` pregleda izvorni AST in ga primerja z AST vzorca, pri tem veže metaspremenljivke ter zagotavlja doslednost ponavljajočih se spremenljivk.
5. **Izpis rezultatov**: ujemajoča se vozlišča se pretvorijo v objekte `AstMatch` z mesti vrstic in bajtov ter izvirnim izsekom izvorne kode.

Pri ponavljajočih se opravilih [indeksirano iskanje AST](https://greph.inline0.dev/sl/docs/indexed/ast) in [iskanje AST iz predpomnilnika](https://greph.inline0.dev/sl/docs/indexed/cache) preskočita večino 2. in 3. koraka.
