From 12d33579e3916baae3f350f615c8f9de98e47d41 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Mon, 16 Oct 2023 00:16:29 +0200
Subject: Handle more fuzzy pattern properties.

---
 src/analysis/scan/exprs/Makefile.am   |   2 -
 src/analysis/scan/exprs/counter-int.h |  57 --------
 src/analysis/scan/exprs/counter.c     | 251 --------------------------------
 src/analysis/scan/exprs/counter.h     |  59 --------
 src/analysis/scan/exprs/handler.c     |  25 +++-
 src/analysis/scan/exprs/handler.h     |   1 +
 src/analysis/scan/grammar.y           | 266 +++++++++++++++++++++++++++++++++-
 src/analysis/scan/tokens.l            |  24 +++
 tests/analysis/scan/grammar.py        |  25 ++++
 9 files changed, 333 insertions(+), 377 deletions(-)
 delete mode 100644 src/analysis/scan/exprs/counter-int.h
 delete mode 100644 src/analysis/scan/exprs/counter.c
 delete mode 100644 src/analysis/scan/exprs/counter.h

diff --git a/src/analysis/scan/exprs/Makefile.am b/src/analysis/scan/exprs/Makefile.am
index 37f4133..c97fa25 100644
--- a/src/analysis/scan/exprs/Makefile.am
+++ b/src/analysis/scan/exprs/Makefile.am
@@ -9,8 +9,6 @@ libanalysisscanexprs_la_SOURCES =			\
 	arithmetic.h arithmetic.c				\
 	call-int.h								\
 	call.h call.c							\
-	counter-int.h							\
-	counter.h counter.c						\
 	extract-int.h							\
 	extract.h extract.c						\
 	handler-int.h							\
diff --git a/src/analysis/scan/exprs/counter-int.h b/src/analysis/scan/exprs/counter-int.h
deleted file mode 100644
index 8c5e56b..0000000
--- a/src/analysis/scan/exprs/counter-int.h
+++ /dev/null
@@ -1,57 +0,0 @@
-
-/* Chrysalide - Outil d'analyse de fichiers binaires
- * counter-int.h - prototypes internes pour le décompte de correspondances identifiées dans du contenu binaire
- *
- * Copyright (C) 2023 Cyrille Bagard
- *
- *  This file is part of Chrysalide.
- *
- *  Chrysalide is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  Chrysalide is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-#ifndef _ANALYSIS_SCAN_EXPRS_COUNTER_INT_H
-#define _ANALYSIS_SCAN_EXPRS_COUNTER_INT_H
-
-
-#include "counter.h"
-
-
-#include "../expr-int.h"
-
-
-
-/* Décompte des identifications de motifs (instance) */
-struct _GScanMatchCounter
-{
-    GScanExpression parent;                 /* A laisser en premier        */
-
-    GSearchPattern *pattern;                /* Motif associé               */
-
-};
-
-/* Décompte des identifications de motifs (classe) */
-struct _GScanMatchCounterClass
-{
-    GScanExpressionClass parent;            /* A laisser en premier        */
-
-};
-
-
-/* Met en place un compteur de correspondances. */
-bool g_scan_match_counter_create(GScanMatchCounter *, GSearchPattern *);
-
-
-
-#endif  /* _ANALYSIS_SCAN_EXPRS_COUNTER_INT_H */
diff --git a/src/analysis/scan/exprs/counter.c b/src/analysis/scan/exprs/counter.c
deleted file mode 100644
index 7fadb91..0000000
--- a/src/analysis/scan/exprs/counter.c
+++ /dev/null
@@ -1,251 +0,0 @@
-
-/* Chrysalide - Outil d'analyse de fichiers binaires
- * counter.c - décompte de correspondances identifiées dans du contenu binaire
- *
- * Copyright (C) 2023 Cyrille Bagard
- *
- *  This file is part of Chrysalide.
- *
- *  Chrysalide is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  Chrysalide is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-#include "counter.h"
-
-
-#include "counter-int.h"
-#include "literal.h"
-
-
-
-/* --------------------- INSTANCIATION D'UNE FORME DE CONDITION --------------------- */
-
-
-/* Initialise la classe des opérations booléennes. */
-static void g_scan_match_counter_class_init(GScanMatchCounterClass *);
-
-/* Initialise une instance d'opération booléenne. */
-static void g_scan_match_counter_init(GScanMatchCounter *);
-
-/* Supprime toutes les références externes. */
-static void g_scan_match_counter_dispose(GScanMatchCounter *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_scan_match_counter_finalize(GScanMatchCounter *);
-
-
-
-/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */
-
-
-/* Réduit une expression à une forme plus simple. */
-static ScanReductionState g_scan_match_counter_reduce(const GScanMatchCounter *, GScanContext *, GScanScope *, GScanExpression **);
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                       INSTANCIATION D'UNE FORME DE CONDITION                       */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour un décompte de résultats lors d'une recherche de motifs. */
-G_DEFINE_TYPE(GScanMatchCounter, g_scan_match_counter, G_TYPE_SCAN_EXPRESSION);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des opérations booléennes.              *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_scan_match_counter_class_init(GScanMatchCounterClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-    GScanExpressionClass *expr;             /* Version de classe parente   */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_scan_match_counter_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_scan_match_counter_finalize;
-
-    expr = G_SCAN_EXPRESSION_CLASS(klass);
-
-    expr->cmp_rich = (compare_expr_rich_fc)NULL;
-    expr->reduce = (reduce_expr_fc)g_scan_match_counter_reduce;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : op = instance à initialiser.                                 *
-*                                                                             *
-*  Description : Initialise une instance d'opération booléenne.               *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_scan_match_counter_init(GScanMatchCounter *counter)
-{
-    counter->pattern = NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : op = instance d'objet GLib à traiter.                        *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_scan_match_counter_dispose(GScanMatchCounter *counter)
-{
-    g_clear_object(&counter->pattern);
-
-    G_OBJECT_CLASS(g_scan_match_counter_parent_class)->dispose(G_OBJECT(counter));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : op = instance d'objet GLib à traiter.                        *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_scan_match_counter_finalize(GScanMatchCounter *counter)
-{
-    G_OBJECT_CLASS(g_scan_match_counter_parent_class)->finalize(G_OBJECT(counter));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : pattern = motif à impliquer.                                 *
-*                                                                             *
-*  Description : Met en place un décompte de correspondances obtenues.        *
-*                                                                             *
-*  Retour      : Expression mise en place.                                    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-GScanExpression *g_scan_match_counter_new(GSearchPattern *pattern)
-{
-    GScanExpression *result;                /* Structure à retourner       */
-
-    result = g_object_new(G_TYPE_SCAN_MATCH_COUNTER, NULL);
-
-    if (!g_scan_match_counter_create(G_SCAN_MATCH_COUNTER(result), pattern))
-        g_clear_object(&result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : counter = instance à initialiser pleinement.                 *
-*                pattern = motif à impliquer.                                 *
-*                                                                             *
-*  Description : Met en place un compteur de correspondances.                 *
-*                                                                             *
-*  Retour      : Bilan de l'opération.                                        *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_scan_match_counter_create(GScanMatchCounter *counter, GSearchPattern *pattern)
-{
-    bool result;                            /* Bilan à retourner           */
-
-    result = g_scan_expression_create(G_SCAN_EXPRESSION(counter), SRS_WAIT_FOR_SCAN);
-    if (!result) goto exit;
-
-    counter->pattern = pattern;
-    g_object_ref(G_OBJECT(pattern));
-
- exit:
-
-    return result;
-
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                       IMPLEMENTATION DES FONCTIONS DE CLASSE                       */
-/* ---------------------------------------------------------------------------------- */
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : expr  = expression à consulter.                              *
-*                ctx   = contexte de suivi de l'analyse courante.             *
-*                scope = portée courante des variables locales.               *
-*                out   = zone d'enregistrement de la réduction opérée. [OUT]  *
-*                                                                             *
-*  Description : Réduit une expression à une forme plus simple.               *
-*                                                                             *
-*  Retour      : Bilan de l'opération : false en cas d'erreur irrécupérable.  *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static ScanReductionState g_scan_match_counter_reduce(const GScanMatchCounter *expr, GScanContext *ctx, GScanScope *scope, GScanExpression **out)
-{
-    ScanReductionState result;              /* Etat synthétisé à retourner */
-    size_t count;                           /* Quantité de correspondances */
-
-    if (g_scan_context_is_scan_done(ctx))
-    {
-        g_scan_context_get_full_matches(ctx, expr->pattern, &count);
-
-        *out = g_scan_literal_expression_new(LVT_UNSIGNED_INTEGER, (unsigned long long []){ count });
-
-        result = SRS_REDUCED;
-
-    }
-    else
-        result = SRS_WAIT_FOR_SCAN;
-
-    return result;
-
-}
diff --git a/src/analysis/scan/exprs/counter.h b/src/analysis/scan/exprs/counter.h
deleted file mode 100644
index c90953e..0000000
--- a/src/analysis/scan/exprs/counter.h
+++ /dev/null
@@ -1,59 +0,0 @@
-
-/* Chrysalide - Outil d'analyse de fichiers binaires
- * counter.h - prototypes pour le décompte de correspondances identifiées dans du contenu binaire
- *
- * Copyright (C) 2023 Cyrille Bagard
- *
- *  This file is part of Chrysalide.
- *
- *  Chrysalide is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  Chrysalide is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-#ifndef _ANALYSIS_SCAN_EXPRS_COUNTER_H
-#define _ANALYSIS_SCAN_EXPRS_COUNTER_H
-
-
-#include <glib-object.h>
-
-
-#include "../expr.h"
-#include "../pattern.h"
-
-
-
-#define G_TYPE_SCAN_MATCH_COUNTER            g_scan_match_counter_get_type()
-#define G_SCAN_MATCH_COUNTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_SCAN_MATCH_COUNTER, GScanMatchCounter))
-#define G_IS_SCAN_MATCH_COUNTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_SCAN_MATCH_COUNTER))
-#define G_SCAN_MATCH_COUNTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_SCAN_MATCH_COUNTER, GScanMatchCounterClass))
-#define G_IS_SCAN_MATCH_COUNTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_SCAN_MATCH_COUNTER))
-#define G_SCAN_MATCH_COUNTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_SCAN_MATCH_COUNTER, GScanMatchCounterClass))
-
-
-/* Décompte des identifications de motifs (instance) */
-typedef struct _GScanMatchCounter GScanMatchCounter;
-
-/* Décompte des identifications de motifs (classe) */
-typedef struct _GScanMatchCounterClass GScanMatchCounterClass;
-
-
-/* Indique le type défini pour un décompte de résultats lors d'une recherche de motifs. */
-GType g_scan_match_counter_get_type(void);
-
-/* Met en place un décompte de correspondances obtenues. */
-GScanExpression *g_scan_match_counter_new(GSearchPattern *);
-
-
-
-#endif  /* _ANALYSIS_SCAN_EXPRS_COUNTER_H */
diff --git a/src/analysis/scan/exprs/handler.c b/src/analysis/scan/exprs/handler.c
index 1b6416e..8aab9e5 100644
--- a/src/analysis/scan/exprs/handler.c
+++ b/src/analysis/scan/exprs/handler.c
@@ -323,9 +323,27 @@ GScanMatch **g_scan_pattern_handler_get_all_matches(const GScanPatternHandler *h
 static ScanReductionState g_scan_pattern_handler_reduce(const GScanPatternHandler *expr, GScanContext *ctx, GScanScope *scope, GScanExpression **out)
 {
     ScanReductionState result;              /* Etat synthétisé à retourner */
+    size_t count;                           /* Quantité de correspondances */
 
     if (g_scan_context_is_scan_done(ctx))
-        result = SRS_REDUCED;
+    {
+        if (expr->type == SHT_COUNTER)
+        {
+            if (!g_scan_pattern_handler_count_items(expr, ctx, &count))
+                result = SRS_UNRESOLVABLE;
+
+            else
+            {
+                result = SRS_REDUCED;
+                *out = g_scan_literal_expression_new(LVT_UNSIGNED_INTEGER, (unsigned long long []){ count });
+            }
+
+        }
+
+        else
+            result = SRS_REDUCED;
+
+    }
 
     else
         result = SRS_WAIT_FOR_SCAN;
@@ -476,6 +494,11 @@ static bool g_scan_pattern_handler_get_item(const GScanPatternHandler *expr, siz
             g_object_unref(G_OBJECT(content));
             break;
 
+        case SHT_COUNTER:
+            assert(false);
+            result = false;
+            break;
+
         case SHT_START:
             *out = g_scan_literal_expression_new(LVT_UNSIGNED_INTEGER, (unsigned long long []){ start });
             break;
diff --git a/src/analysis/scan/exprs/handler.h b/src/analysis/scan/exprs/handler.h
index 36985e7..407ccdc 100644
--- a/src/analysis/scan/exprs/handler.h
+++ b/src/analysis/scan/exprs/handler.h
@@ -48,6 +48,7 @@ typedef struct _GScanPatternHandlerClass GScanPatternHandlerClass;
 typedef enum _ScanHandlerType
 {
     SHT_RAW,                                /* Correspondances brutes      */
+    SHT_COUNTER,                            /* Dénombrement de résultats   */
     SHT_START,                              /* Départs de correspondances  */
     SHT_LENGTH,                             /* Taille de correspondances   */
     SHT_END,                                /* Fins de correspondances     */
diff --git a/src/analysis/scan/grammar.y b/src/analysis/scan/grammar.y
index 1daa519..6dfbd75 100644
--- a/src/analysis/scan/grammar.y
+++ b/src/analysis/scan/grammar.y
@@ -30,7 +30,6 @@ typedef void *yyscan_t;
 #include "exprs/access.h"
 #include "exprs/arithmetic.h"
 #include "exprs/call.h"
-#include "exprs/counter.h"
 #include "exprs/extract.h"
 #include "exprs/handler.h"
 #include "exprs/intersect.h"
@@ -133,9 +132,13 @@ YY_DECL;
 %token BYTES_ID
 %token BYTES_FUZZY_ID
 %token BYTES_ID_COUNTER
+%token BYTES_FUZZY_ID_COUNTER
 %token BYTES_ID_START
+%token BYTES_FUZZY_ID_START
 %token BYTES_ID_LENGTH
+%token BYTES_FUZZY_ID_LENGTH
 %token BYTES_ID_END
+%token BYTES_FUZZY_ID_END
 %token NAME
 
 
@@ -230,9 +233,13 @@ YY_DECL;
 %type <sized_cstring> BYTES_ID
 %type <sized_cstring> BYTES_FUZZY_ID
 %type <sized_cstring> BYTES_ID_COUNTER
+%type <sized_cstring> BYTES_FUZZY_ID_COUNTER
 %type <sized_cstring> BYTES_ID_START
+%type <sized_cstring> BYTES_FUZZY_ID_START
 %type <sized_cstring> BYTES_ID_LENGTH
+%type <sized_cstring> BYTES_FUZZY_ID_LENGTH
 %type <sized_cstring> BYTES_ID_END
+%type <sized_cstring> BYTES_FUZZY_ID_END
 %type <sized_cstring> NAME
 
 
@@ -1401,62 +1408,307 @@ relational_expr : cexpression "<" cexpression  { $$ = g_scan_relational_operatio
   _pattern_handler : BYTES_ID
                    {
                        GSearchPattern *__pat;
+
                        __pat = g_scan_rule_get_local_variable(*built_rule, $1.data);
+
                        if (__pat == NULL)
-                           $$ = NULL;
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Pattern not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
                        else
                        {
                            $$ = g_scan_pattern_handler_new((GSearchPattern *[]) { __pat }, 1, SHT_RAW);
                            g_object_unref(G_OBJECT(__pat));
                        }
+
+                   }
+                   | BYTES_FUZZY_ID
+                   {
+                       size_t __count;
+                       GSearchPattern **__patterns;
+                       size_t __i;
+
+                       __patterns = g_scan_rule_get_local_variables(*built_rule, $1.data, &__count);
+
+                       if (__count == 0)
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Patterns not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
+                       $$ = g_scan_pattern_handler_new(__patterns, __count, SHT_RAW);
+
+                       for (__i = 0; __i < __count; __i++)
+                           g_object_unref(G_OBJECT(__patterns[__i]));
+
+                       free(__patterns);
+
                    }
                    | BYTES_ID_COUNTER
                    {
                        GSearchPattern *__pat;
+
                        __pat = g_scan_rule_get_local_variable(*built_rule, $1.data);
+
                        if (__pat == NULL)
-                           $$ = NULL;
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Pattern not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
                        else
                        {
-                           $$ = g_scan_match_counter_new(__pat);
+                           $$ = g_scan_pattern_handler_new((GSearchPattern *[]) { __pat }, 1, SHT_COUNTER);
                            g_object_unref(G_OBJECT(__pat));
                        }
+
+                   }
+                   | BYTES_FUZZY_ID_COUNTER
+                   {
+                       size_t __count;
+                       GSearchPattern **__patterns;
+                       size_t __i;
+
+                       __patterns = g_scan_rule_get_local_variables(*built_rule, $1.data, &__count);
+
+                       if (__count == 0)
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Patterns not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
+                       $$ = g_scan_pattern_handler_new(__patterns, __count, SHT_COUNTER);
+
+                       for (__i = 0; __i < __count; __i++)
+                           g_object_unref(G_OBJECT(__patterns[__i]));
+
+                       free(__patterns);
+
                    }
                    | BYTES_ID_START
                    {
                        GSearchPattern *__pat;
+
                        __pat = g_scan_rule_get_local_variable(*built_rule, $1.data);
+
                        if (__pat == NULL)
-                           $$ = NULL;
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Pattern not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
                        else
                        {
                            $$ = g_scan_pattern_handler_new((GSearchPattern *[]) { __pat }, 1, SHT_START);
                            g_object_unref(G_OBJECT(__pat));
                        }
+
+                   }
+                   | BYTES_FUZZY_ID_START
+                   {
+                       size_t __count;
+                       GSearchPattern **__patterns;
+                       size_t __i;
+
+                       __patterns = g_scan_rule_get_local_variables(*built_rule, $1.data, &__count);
+
+                       if (__count == 0)
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Patterns not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
+                       $$ = g_scan_pattern_handler_new(__patterns, __count, SHT_START);
+
+                       for (__i = 0; __i < __count; __i++)
+                           g_object_unref(G_OBJECT(__patterns[__i]));
+
+                       free(__patterns);
+
                    }
                    | BYTES_ID_LENGTH
                    {
                        GSearchPattern *__pat;
+
                        __pat = g_scan_rule_get_local_variable(*built_rule, $1.data);
+
                        if (__pat == NULL)
-                           $$ = NULL;
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Pattern not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
                        else
                        {
                            $$ = g_scan_pattern_handler_new((GSearchPattern *[]) { __pat }, 1, SHT_LENGTH);
                            g_object_unref(G_OBJECT(__pat));
                        }
+
+                   }
+                   | BYTES_FUZZY_ID_LENGTH
+                   {
+                       size_t __count;
+                       GSearchPattern **__patterns;
+                       size_t __i;
+
+                       __patterns = g_scan_rule_get_local_variables(*built_rule, $1.data, &__count);
+
+                       if (__count == 0)
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Patterns not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
+                       $$ = g_scan_pattern_handler_new(__patterns, __count, SHT_LENGTH);
+
+                       for (__i = 0; __i < __count; __i++)
+                           g_object_unref(G_OBJECT(__patterns[__i]));
+
+                       free(__patterns);
+
                    }
                    | BYTES_ID_END
                    {
                        GSearchPattern *__pat;
+
                        __pat = g_scan_rule_get_local_variable(*built_rule, $1.data);
+
                        if (__pat == NULL)
-                           $$ = NULL;
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Pattern not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
                        else
                        {
                            $$ = g_scan_pattern_handler_new((GSearchPattern *[]) { __pat }, 1, SHT_END);
                            g_object_unref(G_OBJECT(__pat));
                        }
+
+                   }
+                   | BYTES_FUZZY_ID_END
+                   {
+                       size_t __count;
+                       GSearchPattern **__patterns;
+                       size_t __i;
+
+                       __patterns = g_scan_rule_get_local_variables(*built_rule, $1.data, &__count);
+
+                       if (__count == 0)
+                       {
+                           char *_msg;
+                           int _ret;
+
+                           _ret = asprintf(&_msg, _("Patterns not found: \"%s\""), $1.data);
+
+                           if (_ret != -1)
+                           {
+                               raise_error(_msg);
+                               free(_msg);
+                           }
+
+                           YYERROR;
+                       }
+
+                       $$ = g_scan_pattern_handler_new(__patterns, __count, SHT_END);
+
+                       for (__i = 0; __i < __count; __i++)
+                           g_object_unref(G_OBJECT(__patterns[__i]));
+
+                       free(__patterns);
+
                    }
                    ;
 
diff --git a/src/analysis/scan/tokens.l b/src/analysis/scan/tokens.l
index d81874a..64999f2 100644
--- a/src/analysis/scan/tokens.l
+++ b/src/analysis/scan/tokens.l
@@ -953,24 +953,48 @@ bytes_fuzzy_id [\*A-Za-z_][\*A-Za-z0-9_]*
                                         return BYTES_ID_COUNTER;
                                     }
 
+      <condition>#{bytes_fuzzy_id}  {
+                                        yylval->sized_cstring.data = yytext + 1;
+                                        yylval->sized_cstring.len = yyleng - 1;
+                                        return BYTES_FUZZY_ID_COUNTER;
+                                    }
+
              <condition>@{bytes_id} {
                                         yylval->sized_cstring.data = yytext + 1;
                                         yylval->sized_cstring.len = yyleng - 1;
                                         return BYTES_ID_START;
                                     }
 
+      <condition>@{bytes_fuzzy_id}  {
+                                        yylval->sized_cstring.data = yytext + 1;
+                                        yylval->sized_cstring.len = yyleng - 1;
+                                        return BYTES_FUZZY_ID_START;
+                                    }
+
              <condition>!{bytes_id} {
                                         yylval->sized_cstring.data = yytext + 1;
                                         yylval->sized_cstring.len = yyleng - 1;
                                         return BYTES_ID_LENGTH;
                                     }
 
+      <condition>!{bytes_fuzzy_id}  {
+                                        yylval->sized_cstring.data = yytext + 1;
+                                        yylval->sized_cstring.len = yyleng - 1;
+                                        return BYTES_FUZZY_ID_LENGTH;
+                                    }
+
              <condition>~{bytes_id} {
                                         yylval->sized_cstring.data = yytext + 1;
                                         yylval->sized_cstring.len = yyleng - 1;
                                         return BYTES_ID_END;
                                     }
 
+      <condition>~{bytes_fuzzy_id}  {
+                                        yylval->sized_cstring.data = yytext + 1;
+                                        yylval->sized_cstring.len = yyleng - 1;
+                                        return BYTES_FUZZY_ID_END;
+                                    }
+
 
 
 
diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py
index 13a255b..3a8196a 100644
--- a/tests/analysis/scan/grammar.py
+++ b/tests/analysis/scan/grammar.py
@@ -2,6 +2,7 @@
 import json
 
 from common import RostTestClass
+from pychrysalide.analysis.contents import MemoryContent
 
 
 class TestRostGrammar(RostTestClass):
@@ -250,6 +251,30 @@ rule test {
                     self.check_rule_failure(rule)
 
 
+    def testMatchCount(self):
+        """Ensure match count provides expected values."""
+
+        cnt = MemoryContent(b'\x01\x02\x02\x03\x03\x03')
+
+        rule = '''
+rule test {
+
+    bytes:
+        $int_01 = "\x01"
+        $int_02 = "\x02"
+        $int_03 = "\x03"
+
+    condition:
+        #int_01 == count($int_01) and #int_01 == 1
+        and #int_02 == count($int_02) and #int_02 == 2
+        and #int_03 == count($int_03) and #int_03 == 3
+        and #int_0* == count($int_0*) and #int_0* == 6
+
+}
+'''
+
+        self.check_rule_success(rule, cnt)
+
 
 # TODO : test     <haystack> matches <regex>
 
-- 
cgit v0.11.2-87-g4458