Class: CCommentScanner
- Defined in:
- lib/ceedling/preprocess/c_comment_scanner.rb
Defined Under Namespace
Classes: CommentInfo
Constant Summary collapse
- PRESERVE_LINES =
:preserve_lines- COMPACT =
:compact
Instance Method Summary collapse
-
#remove(content, comment_infos, mode: COMPACT) ⇒ Object
Given the Array
from scan, return a copy of content with every comment replaced according to mode:. -
#scan(io:) ⇒ Object
Scan an IO stream (File or StringIO) and return all C comments found.
Instance Method Details
#remove(content, comment_infos, mode: COMPACT) ⇒ Object
Given the Array
:compact (default) — every comment replaced by a single space character.
:preserve_lines — single-line comments (lines_removed == 0) replaced by a
single space; multi-line comments replaced by
lines_removed newlines so the total line count is unchanged.
ArrayCommentInfo details.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/ceedling/preprocess/c_comment_scanner.rb', line 42 def remove(content, comment_infos, mode: COMPACT) # comment_infos positions and lengths are byte offsets (recorded by scan using # StringScanner#pos). String#[] on a text-encoded string uses character # indexing, which diverges from byte indexing when non-ASCII characters appear # before a comment. Working on a binary (ASCII-8BIT) copy keeps indexing # byte-accurate throughout. orig_encoding = content.encoding result = content.b # Process in descending position order so earlier byte positions remain valid comment_infos.sort_by { |info| -info.position }.each do |info| replacement = ' ' if (mode == PRESERVE_LINES && info.lines_removed > 0) replacement = "\n" * info.lines_removed end result[info.position, info.length] = replacement end # Re-tag the encoding without converting bytes. Only ASCII delimiters were # removed and only ASCII characters were inserted, so all remaining multi-byte # sequences are intact and the byte content is valid in the original encoding. return result.force_encoding(orig_encoding) end |
#scan(io:) ⇒ Object
Scan an IO stream (File or StringIO) and return all C comments found.
Returns an Array
Uses StringScanner for a single-pass scan. Recognises string/character literals and prevents comment detection inside them.
Records:
- // single-line comments (with optional backslash continuation lines)
- /* ... */ block comments (including multiline; unterminated at EOF)
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 |
# File 'lib/ceedling/preprocess/c_comment_scanner.rb', line 76 def scan(io:) content = io.read return [] if content.nil? || content.empty? # StringScanner#pos returns byte offsets, but String#[index, length] uses # character-based indexing for text-encoded (e.g. UTF-8) strings. Non-ASCII # characters such as © (2 bytes in UTF-8) cause the two to diverge: a byte # offset N maps to character N-1 after one such character, so comment # boundaries and newline counts are wrong. Forcing binary encoding makes # String#[] byte-indexed throughout, keeping it in sync with StringScanner. content = content.b scanner = StringScanner.new(content) comments = [] until scanner.eos? ch = scanner.peek(1) case ch when '"', "'" # Skip string or character literal -- any // or /* */ inside is not a comment skip_string_literal(scanner, ch) when '/' two = scanner.peek(2) if two == '//' start = scanner.pos scan_line_comment(scanner) len = scanner.pos - start comments << CommentInfo.new( position: start, length: len, lines_removed: content[start, len].count("\n") ) elsif two == '/*' start = scanner.pos scan_block_comment(scanner) len = scanner.pos - start comments << CommentInfo.new( position: start, length: len, lines_removed: content[start, len].count("\n") ) else scanner.getch end else scanner.getch end end return comments end |