Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

parseAndRemoveLineCommands

Parses and removes #line commands as well as all source code that is within a file with .c or .h extensions.

RC treats files with the .c and .h extensions in a special manner. It assumes that a file with one of these extensions does not contain resources. If a file has the .c or .h file name extension, RC ignores all lines in the file except the preprocessor directives. Therefore, to include a file that contains resources in another resource script, give the file to be included an extension other than .c or .h. from https://learn.microsoft.com/en-us/windows/win32/menurc/preprocessor-directives

Returns a slice of buf with the aforementioned stuff removed as well as a mapping between the lines and their corresponding lines in their original files.

buf must be at least as long as source In-place transformation is supported (i.e. source and buf can be the same slice)

If options.initial_filename is provided, that filename is guaranteed to be within the mappings.files table and root_filename_offset will be set appropriately.

source_mapping.parseAndRemoveLineCommands
pub fn parseAndRemoveLineCommands(allocator: Allocator, source: []const u8, buf: []u8, options: ParseAndRemoveLineCommandsOptions) error

File

lib/compiler/resinator/source_mapping.zig:41

Code

pub fn parseAndRemoveLineCommands(allocator: Allocator, source: []const u8, buf: []u8, options: ParseAndRemoveLineCommandsOptions) error{ OutOfMemory, InvalidLineCommand, LineNumberOverflow }!ParseLineCommandsResult {
    var parse_result = ParseLineCommandsResult{
        .result = undefined,
        .mappings = .{},
    };
    errdefer parse_result.mappings.deinit(allocator);

    var current_mapping: CurrentMapping = .{};
    defer current_mapping.filename.deinit(allocator);

    if (options.initial_filename) |initial_filename| {
        try current_mapping.filename.appendSlice(allocator, initial_filename);
        parse_result.mappings.root_filename_offset = try parse_result.mappings.files.put(allocator, initial_filename);
    }

    // This implementation attempts to be comment and string aware in order
    // to avoid errant #line <num> "<filename>" within multiline comments
    // leading to problems in the source mapping after comments are removed,
    // but it is not a perfect implementation (intentionally).
    //
    // The current implementation does not handle cases like
    //  /* foo */ #line ...
    //  #line ... // foo
    //  #line ... /* foo ...
    //  etc
    //
    // (the first example will not be recognized as a #line command, the second
    // and third will error with InvalidLineCommand)
    //
    // This is fine, though, since #line commands are generated by the
    // preprocessor so in normal circumstances they will be well-formed and
    // consistent. The only realistic way the imperfect implementation could
    // affect a 'real' use-case would be someone taking the output of a
    // preprocessor, editing it manually to add comments before/after #line
    // commands, and then running it through resinator with /:no-preprocess.

    std.debug.assert(buf.len >= source.len);
    var result = UncheckedSliceWriter{ .slice = buf };
    const State = enum {
        line_start,
        preprocessor,
        non_preprocessor,
        forward_slash,
        line_comment,
        multiline_comment,
        multiline_comment_end,
        single_quoted,
        single_quoted_escape,
        double_quoted,
        double_quoted_escape,
    };
    var state: State = .line_start;
    var index: usize = 0;
    var pending_start: ?usize = null;
    var preprocessor_start: usize = 0;
    var line_number: usize = 1;
    while (index < source.len) : (index += 1) {
        var c = source[index];
        state: switch (state) {
            .line_start => switch (c) {
                '#' => {
                    preprocessor_start = index;
                    state = .preprocessor;
                    if (pending_start == null) {
                        pending_start = index;
                    }
                },
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    pending_start = null;
                },
                ' ', '\t', '\x0b', '\x0c' => {
                    if (pending_start == null) {
                        pending_start = index;
                    }
                },
                '/' => {
                    if (!current_mapping.ignore_contents) {
                        result.writeSlice(source[pending_start orelse index .. index + 1]);
                        pending_start = null;
                    }
                    state = .forward_slash;
                },
                '\'' => {
                    if (!current_mapping.ignore_contents) {
                        result.writeSlice(source[pending_start orelse index .. index + 1]);
                        pending_start = null;
                    }
                    state = .single_quoted;
                },
                '"' => {
                    if (!current_mapping.ignore_contents) {
                        result.writeSlice(source[pending_start orelse index .. index + 1]);
                        pending_start = null;
                    }
                    state = .double_quoted;
                },
                else => {
                    state = .non_preprocessor;
                    if (pending_start != null) {
                        if (!current_mapping.ignore_contents) {
                            result.writeSlice(source[pending_start.? .. index + 1]);
                        }
                        pending_start = null;
                        continue;
                    }
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
            .forward_slash => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                '/' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .line_comment;
                },
                '*' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .multiline_comment;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .non_preprocessor;
                },
            },
            .line_comment => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
            .multiline_comment => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    pending_start = null;
                },
                '*' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .multiline_comment_end;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
            .multiline_comment_end => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .multiline_comment;
                    pending_start = null;
                },
                '/' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .non_preprocessor;
                },
                '*' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    // stay in multiline_comment_end state
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .multiline_comment;
                },
            },
            .single_quoted => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                '\\' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .single_quoted_escape;
                },
                '\'' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .non_preprocessor;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
            .single_quoted_escape => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .single_quoted;
                },
            },
            .double_quoted => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                '\\' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .double_quoted_escape;
                },
                '"' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .non_preprocessor;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
            .double_quoted_escape => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .double_quoted;
                },
            },
            .preprocessor => switch (c) {
                '\r', '\n' => {
                    // Now that we have the full line we can decide what to do with it
                    const preprocessor_str = source[preprocessor_start..index];
                    if (std.mem.startsWith(u8, preprocessor_str, "#line")) {
                        try handleLineCommand(allocator, preprocessor_str, &current_mapping);
                        const is_crlf = formsLineEndingPair(source, c, index + 1);
                        if (is_crlf) index += 1;
                        state = .line_start;
                        pending_start = null;
                    } else {
                        // Backtrack and reparse the line in the non_preprocessor state,
                        // since it's possible that this line contains a multiline comment
                        // start, etc.
                        state = .non_preprocessor;
                        index = pending_start.?;
                        pending_start = null;
                        // TODO: This is a hacky way to implement this, c needs to be
                        //       updated since we're using continue :state here
                        c = source[index];
                        // continue to avoid the index += 1 of the while loop
                        continue :state .non_preprocessor;
                    }
                },
                else => {},
            },
            .non_preprocessor => switch (c) {
                '\r', '\n' => {
                    const is_crlf = formsLineEndingPair(source, c, index + 1);
                    if (!current_mapping.ignore_contents) {
                        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);

                        result.write(c);
                        if (is_crlf) result.write(source[index + 1]);
                        line_number += 1;
                    }
                    if (is_crlf) index += 1;
                    state = .line_start;
                    pending_start = null;
                },
                '/' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .forward_slash;
                },
                '\'' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .single_quoted;
                },
                '"' => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                    state = .double_quoted;
                },
                else => {
                    if (!current_mapping.ignore_contents) {
                        result.write(c);
                    }
                },
            },
        }
    } else {
        switch (state) {
            .line_start => {},
            .forward_slash,
            .line_comment,
            .multiline_comment,
            .multiline_comment_end,
            .single_quoted,
            .single_quoted_escape,
            .double_quoted,
            .double_quoted_escape,
            .non_preprocessor,
            => {
                try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);
            },
            .preprocessor => {
                // Now that we have the full line we can decide what to do with it
                const preprocessor_str = source[preprocessor_start..index];
                if (std.mem.startsWith(u8, preprocessor_str, "#line")) {
                    try handleLineCommand(allocator, preprocessor_str, &current_mapping);
                } else {
                    try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);
                    if (!current_mapping.ignore_contents) {
                        result.writeSlice(source[pending_start.?..index]);
                    }
                }
            },
        }
    }

    parse_result.result = result.getWritten();

    // Remove whitespace from the end of the result. This avoids issues when the
    // preprocessor adds a newline to the end of the file, since then the
    // post-preprocessed source could have more lines than the corresponding input source and
    // the inserted line can't be mapped to any lines in the original file.
    // There's no way that whitespace at the end of a file can affect the parsing
    // of the RC script so this is okay to do unconditionally.
    // TODO: There might be a better way around this
    while (parse_result.result.len > 0 and std.ascii.isWhitespace(parse_result.result[parse_result.result.len - 1])) {
        parse_result.result.len -= 1;
    }

    // If there have been no line mappings at all, then we're dealing with an empty file.
    // In this case, we want to fake a line mapping just so that we return something
    // that is useable in the same way that a non-empty mapping would be.
    if (parse_result.mappings.sources.root == null) {
        try handleLineEnd(allocator, line_number, &parse_result.mappings, &current_mapping);
    }

    return parse_result;
}