ACAV f0ba4b7c9529
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
ProcessLogParser.h
1/*$!{
2* Aurora Clang AST Viewer (ACAV)
3*
4* Copyright (c) 2026 Min Liu
5* Copyright (c) 2026 Michael David Adams
6*
7* SPDX-License-Identifier: GPL-2.0-or-later
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <https://www.gnu.org/licenses/>.
21}$!*/
22
23#pragma once
24
25#include "core/LogEntry.h"
26#include <QDateTime>
27#include <QString>
28#include <QStringList>
29#include <QVector>
30
31namespace acav {
32
34 LogLevel level = LogLevel::Info;
35 QString message;
36 bool parsed = false;
37};
38
39inline LogLevel levelFromString(const QString &level) {
40 const QString lower = level.toLower();
41 if (lower == "info") {
42 return LogLevel::Info;
43 }
44 if (lower == "fatal" || lower == "error") {
45 return LogLevel::Error;
46 }
47 if (lower == "warning" || lower == "warn") {
48 return LogLevel::Warning;
49 }
50 if (lower == "debug" || lower == "note" || lower == "remark") {
51 return LogLevel::Debug;
52 }
53 return LogLevel::Info;
54}
55
56inline ParsedLogLine parseDiagnosticLine(const QString &line) {
57 ParsedLogLine parsed;
58 if (!line.startsWith("@diag\t")) {
59 return parsed;
60 }
61
62 QString payload = line.mid(6);
63 const QStringList parts = payload.split('\t');
64 if (parts.size() < 2) {
65 return parsed;
66 }
67
68 parsed.level = levelFromString(parts[0]);
69 if (parts.size() >= 5) {
70 const QString file = parts[1];
71 const QString lineNo = parts[2];
72 const QString colNo = parts[3];
73 const QString message = parts.mid(4).join("\t");
74 QString location = file;
75 if (!lineNo.isEmpty()) {
76 location += ":" + lineNo;
77 }
78 if (!colNo.isEmpty()) {
79 location += ":" + colNo;
80 }
81 parsed.message = location.isEmpty() ? message
82 : QString("%1: %2").arg(location, message);
83 } else {
84 parsed.message = parts.mid(1).join("\t");
85 }
86
87 parsed.parsed = true;
88 return parsed;
89}
90
91inline bool isWordChar(QChar ch) {
92 return ch.isLetterOrNumber() || ch == '_';
93}
94
95inline bool containsSeverityToken(const QString &lower,
96 const QString &token) {
97 int index = 0;
98 while ((index = lower.indexOf(token, index)) != -1) {
99 const int before = index - 1;
100 const int after = index + token.size();
101 const bool beforeOk =
102 before < 0 || !isWordChar(lower.at(before));
103 if (!beforeOk) {
104 index = after;
105 continue;
106 }
107
108 if (after >= lower.size()) {
109 return true;
110 }
111
112 const QChar afterChar = lower.at(after);
113 if (afterChar.isSpace() || afterChar == ':' || afterChar == ']' ||
114 afterChar == ')' || afterChar == ',' || afterChar == ';') {
115 return true;
116 }
117
118 if (afterChar == '.' && after + 1 >= lower.size()) {
119 return true;
120 }
121
122 index = after;
123 }
124
125 return false;
126}
127
128inline LogLevel guessLevelFromText(const QString &line, bool isStdErr) {
129 const QString lower = line.toLower();
130 if (containsSeverityToken(lower, "fatal") ||
131 containsSeverityToken(lower, "error")) {
132 return LogLevel::Error;
133 }
134 if (containsSeverityToken(lower, "warning") ||
135 containsSeverityToken(lower, "warn")) {
136 return LogLevel::Warning;
137 }
138 if (containsSeverityToken(lower, "note") ||
139 containsSeverityToken(lower, "remark") ||
140 containsSeverityToken(lower, "debug")) {
141 return LogLevel::Debug;
142 }
143 return isStdErr ? LogLevel::Warning : LogLevel::Info;
144}
145
146inline QVector<LogEntry> parseProcessOutput(QString &buffer,
147 const QString &chunk,
148 const QString &source,
149 bool isStdErr) {
150 QVector<LogEntry> entries;
151 if (chunk.isEmpty()) {
152 return entries;
153 }
154
155 constexpr int kMaxBufferSize = 64 * 1024;
156 buffer += chunk;
157 if (buffer.size() > kMaxBufferSize) {
158 buffer = buffer.right(kMaxBufferSize);
159 }
160 const QStringList lines = buffer.split('\n');
161 buffer = lines.isEmpty() ? QString() : lines.last();
162
163 if (lines.size() <= 1) {
164 return entries;
165 }
166
167 const QDateTime now = QDateTime::currentDateTime();
168 entries.reserve(lines.size() - 1);
169 for (int i = 0; i < lines.size() - 1; ++i) {
170 const QString line = lines.at(i).trimmed();
171 if (line.isEmpty()) {
172 continue;
173 }
174
175 ParsedLogLine parsed = parseDiagnosticLine(line);
176 LogEntry entry;
177 entry.level =
178 parsed.parsed ? parsed.level : guessLevelFromText(line, isStdErr);
179 entry.source = source;
180 entry.message = parsed.parsed ? parsed.message : line;
181 entry.timestamp = now;
182 entries.append(entry);
183 }
184
185 return entries;
186}
187
188} // namespace acav