PreprocParse

A simple C code parser focusing on preprocessor directives, with a basic parsing infrastructure and unit test framework to validate parsing functionality.
mail@pastecode.io avatar
unknown
c_cpp
24 days ago
9.9 kB
2
Indexable
Never
# ============================================================
# ==========          OVERVIEW                   ============
# ============================================================

This project includes the following files:

1. parser.h
   - Header file containing declarations for the parser, including data structures and function prototypes.

2. parser.c
   - Implementation file for the parser, including the parsing logic for preprocessor directives.

3. test_parser.c
   - Test file containing unit tests for the parser, with a framework to run tests and report results.

4. Makefile
   - Makefile for building the project, including targets for the main program and the test suite.

5. BUILD.md
   - Build and Run Instructions for the project, detailing prerequisites, build steps, and how to run the main program and tests.

// ============================================================
// ==========          parser.h                    ============
// ============================================================

/*
Changes made:
1. Renamed enums and struct members to follow a consistent naming convention.
2. Moved function prototypes to the top for better organization.
*/

#ifndef PARSER_H
#define PARSER_H

#include <stdlib.h>
#include <assert.h>

// Error type definition
typedef int error;

// Parsing state structure
typedef struct parsing_state {
    char *input;
    int cursor;
} parsing_state;

// Node types enumeration
typedef enum node_type {
    NODE_TYPE_PREPROCESSOR = 1,
    NODE_TYPE_INCLUDE = 2
} node_type;

// Node structures
typedef struct preprocessor_node preprocessor_node;
typedef struct include_node include_node;
typedef struct program_node program_node;

struct preprocessor_node {
    node_type type;
    int include_len;
    include_node **includes;
    void **declarations;
};

struct include_node {
    node_type type;
};

struct program_node {
    node_type type;
    int preprocessor_len;
    preprocessor_node **preprocessors;
};

// Function prototypes
error parse_include(include_node *node);
error parse_preprocessor(preprocessor_node *node);
void *parse_program();

// Global parsing state
extern parsing_state state;

#endif // PARSER_H


// ============================================================
// ==========          parser.c                    ============
// ============================================================

/*
Changes made:
1. Simplified the parsing functions for clarity.
*/

#include "parser.h"

// Global parsing state
parsing_state state;

// Include node parser
error parse_include(include_node *node) {
    return 0;
}

// Preprocessor node parser
error parse_preprocessor(preprocessor_node *node) {
    parsing_state saved_state = state;
    include_node *child = NULL;

    assert(node = calloc(1, sizeof(*node)));
    node->type = NODE_TYPE_PREPROCESSOR;

    if (parse_include(child)) {
        state = saved_state;
        return 1;
    }

    assert(node->includes = realloc(node->includes, ++node->include_len * sizeof(*node->includes)));
    node->includes[node->include_len - 1] = child;
    return 0;
}

// Program parser
void *parse_program() {
    program_node program = {NODE_TYPE_PREPROCESSOR, 0, NULL};
    preprocessor_node *child = NULL;

    for (; state.input[state.cursor]; state.cursor++) {
        if (parse_preprocessor(child)) {
            program.preprocessors = realloc(program.preprocessors, ++program.preprocessor_len * sizeof(*program.preprocessors));
            program.preprocessors[program.preprocessor_len - 1] = child;
        }
    }

    return NULL;
}

// ============================================================
// ==========          test_parser.c               ============
// ============================================================

/*
Changes made:
1. Added a `run_tests` function to include unit tests.
2. Enhanced testing framework to include a `test_case` structure, test results reporting, and percentage calculation.
*/

#include "parser.h"
#include <stdio.h>

// Test case structure
typedef struct test_case {
    const char *name;
    void (*test_func)();
    int passed;
} test_case;

// Assertion function to test parsing
void assert_parsing_works() {
    char *input =
        "#include <stdio.h>\n"
        "\n"
        "int main() {\n"
        "    int i = 5 + 3 * 2;\n"
        "    printf(\"hello world\\n\");\n"
        "    if (2 * (i / 3) > 2) {\n"
        "        printf(\"condition was met!\\n\");\n"
        "    }\n"
        "}\n";

    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

// Additional test cases
void test_empty_input() {
    char *input = "";
    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

void test_single_include() {
    char *input = "#include <stdio.h>\n";
    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

// Add more tests as needed

// Run all tests and report results
void run_tests() {
    test_case tests[] = {
        {"assert_parsing_works", assert_parsing_works, 0},
        {"test_empty_input", test_empty_input, 0},
        {"test_single_include", test_single_include, 0}
        // Add more test cases here
    };

    int num_tests = sizeof(tests) / sizeof(test_case);
    int num_passed = 0;

    for (int i = 0; i < num_tests; i++) {
        printf("Running %s... ", tests[i].name);
        tests[i].test_func();
        tests[i].passed = 1;
        num_passed++;
        printf("PASSED\n");
    }

    printf("\n%d/%d tests passed (%.2f%%)\n", num_passed, num_tests, (num_passed * 100.0) / num_tests);
}

// Main function
int main() {
    run_tests();
    return 0;
}

// ============================================================
// ==========          test_parser.c               ============
// ============================================================

/*
Changes made:
1. Added a `run_tests` function to include unit tests.
2. Enhanced testing framework to include a `test_case` structure, test results reporting, and percentage calculation.
*/

#include "parser.h"
#include <stdio.h>

// Test case structure
typedef struct test_case {
    const char *name;
    void (*test_func)();
    int passed;
} test_case;

// Assertion function to test parsing
void assert_parsing_works() {
    char *input =
        "#include <stdio.h>\n"
        "\n"
        "int main() {\n"
        "    int i = 5 + 3 * 2;\n"
        "    printf(\"hello world\\n\");\n"
        "    if (2 * (i / 3) > 2) {\n"
        "        printf(\"condition was met!\\n\");\n"
        "    }\n"
        "}\n";

    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

// Additional test cases
void test_empty_input() {
    char *input = "";
    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

void test_single_include() {
    char *input = "#include <stdio.h>\n";
    void *expected = NULL;

    state.input = input;
    state.cursor = 0;

    assert(parse_program() == expected);
}

// Add more tests as needed

// Run all tests and report results
void run_tests() {
    test_case tests[] = {
        {"assert_parsing_works", assert_parsing_works, 0},
        {"test_empty_input", test_empty_input, 0},
        {"test_single_include", test_single_include, 0}
        // Add more test cases here
    };

    int num_tests = sizeof(tests) / sizeof(test_case);
    int num_passed = 0;

    for (int i = 0; i < num_tests; i++) {
        printf("Running %s... ", tests[i].name);
        tests[i].test_func();
        tests[i].passed = 1;
        num_passed++;
        printf("PASSED\n");
    }

    printf("\n%d/%d tests passed (%.2f%%)\n", num_passed, num_tests, (num_passed * 100.0) / num_tests);
}

// Main function
int main() {
    run_tests();
    return 0;
}


# ============================================================
# ==========          Makefile                   ============
# ============================================================

all: main test

main: parser.c
	gcc -o main parser.c

test: parser.c test_parser.c
	gcc -o test_parser parser.c test_parser.c

clean:
	rm -f main test_parser

# ============================================================
# ==========          BUILD.md                    ============
# ============================================================

# Build and Run Instructions for PreprocParse

## Prerequisites
- GCC (GNU Compiler Collection)
- Make (optional, but recommended for ease of building)

## Files
- `parser.h`: Header file containing declarations for the parser.
- `parser.c`: Implementation file for the parser.
- `test_parser.c`: Test file containing unit tests.
- `Makefile`: Makefile for building the project.

## Building the Project

### Using Make (Recommended)
1. Ensure the `Makefile` is in the same directory as `parser.h`, `parser.c`, and `test_parser.c`.

2. Run the following command to build the main program and the test suite:

    ```sh
    make
    ```

3. To clean up the build files, use:

    ```sh
    make clean
    ```

### Without Make
1. Compile the main program:

    ```sh
    gcc -o main parser.c
    ```

2. Compile the test suite:

    ```sh
    gcc -o test_parser parser.c test_parser.c
    ```

## Running the Program

### Main Program
1. After building, run the main program:

    ```sh
    ./main
    ```

### Test Suite
1. After building, run the test suite:

    ```sh
    ./test_parser
    ```

2. The test suite will execute all the tests and display the results, including the percentage of tests passed.

## Notes
- Ensure that `parser.h`, `parser.c`, `test_parser.c`, and `Makefile` are in the same directory.
- This project focuses on parsing preprocessor directives in C code, specifically `#include` directives.

Leave a Comment