1515#include " slang/syntax/AllSyntax.h"
1616#include " slang/syntax/SyntaxTree.h"
1717#include " slang/syntax/SyntaxVisitor.h"
18+ #include " slang/text/SourceManager.h"
1819#include " slang/util/Util.h"
1920
2021namespace slang ::syntax {
@@ -23,6 +24,11 @@ CSTSerializer::CSTSerializer(JsonWriter& writer, CSTJsonMode mode) : writer(writ
2324}
2425
2526void CSTSerializer::serialize (const SyntaxTree& tree) {
27+ // A tree always knows its source manager, so use it (unless one was set explicitly)
28+ // to annotate macro-expanded tokens.
29+ if (!sourceManager)
30+ sourceManager = &tree.sourceManager ();
31+
2632 writer.startObject ();
2733 writer.writeProperty (" kind" );
2834 writer.writeValue (" SyntaxTree" sv);
@@ -37,8 +43,10 @@ struct always_false : std::false_type {};
3743struct CSTJsonVisitor {
3844 JsonWriter& writer;
3945 CSTJsonMode mode;
46+ const SourceManager* sourceManager;
4047
41- CSTJsonVisitor (JsonWriter& w, CSTJsonMode m) : writer(w), mode(m) {}
48+ CSTJsonVisitor (JsonWriter& w, CSTJsonMode m, const SourceManager* sm) :
49+ writer (w), mode(m), sourceManager(sm) {}
4250
4351 template <std::derived_from<SyntaxNode> T>
4452 void visit (const T& node) {
@@ -143,10 +151,33 @@ struct CSTJsonVisitor {
143151 writer.endArray ();
144152 }
145153
146- void writeTrivia (parsing::Trivia trivia) {
154+ // Returns true if the given source location comes from a macro expansion or an
155+ // included file, i.e. text that is serialized in addition to the directive that
156+ // produced it and so should not be double-counted when reconstructing source.
157+ bool isExpandedLoc (SourceLocation loc) const {
158+ return sourceManager &&
159+ (sourceManager->isMacroLoc (loc) || sourceManager->isIncludedFileLoc (loc));
160+ }
161+
162+ // @a parentExpanded is the expansion state of the token this trivia belongs to; plain
163+ // text trivia has no location of its own and so inherits it.
164+ void writeTrivia (parsing::Trivia trivia, bool parentExpanded) {
147165 writer.startObject ();
148166 writer.writeProperty (" kind" );
149167 writer.writeValue (toString (trivia.kind ));
168+
169+ // Flag trivia that is itself expanded text. Directive/skipped trivia carry their
170+ // own location; plain text trivia inherits the parent token's expansion state.
171+ bool expanded = parentExpanded;
172+ if (sourceManager) {
173+ if (auto loc = trivia.getExplicitLocation ())
174+ expanded = isExpandedLoc (*loc);
175+ }
176+ if (expanded) {
177+ writer.writeProperty (" fromExpansion" );
178+ writer.writeValue (true );
179+ }
180+
150181 switch (trivia.kind ) {
151182 case parsing::TriviaKind::Directive:
152183 case parsing::TriviaKind::SkippedSyntax:
@@ -181,14 +212,26 @@ struct CSTJsonVisitor {
181212 writer.writeProperty (" text" );
182213 writer.writeValue (token.rawText ());
183214
215+ // Flag tokens that come from a macro expansion or an included file. Such tokens
216+ // are serialized in addition to the directive that produced them -- the macro
217+ // usage, or the `include -- which occupies the same textual position, so consumers
218+ // reconstructing the original source can skip them to avoid double-counting. The
219+ // same flag is propagated to the token's plain-text trivia, which has no location
220+ // of its own.
221+ bool expanded = isExpandedLoc (token.location ());
222+ if (expanded) {
223+ writer.writeProperty (" fromExpansion" );
224+ writer.writeValue (true );
225+ }
226+
184227 // Handle trivia based on mode
185228 if (!token.trivia ().empty ()) {
186229 switch (mode) {
187230 case CSTJsonMode::Full:
188231 writer.writeProperty (" trivia" );
189232 writer.startArray ();
190233 for (auto & t : token.trivia ())
191- writeTrivia (t);
234+ writeTrivia (t, expanded );
192235 writer.endArray ();
193236 break ;
194237 case CSTJsonMode::NoWhitespace: {
@@ -211,7 +254,7 @@ struct CSTJsonVisitor {
211254 writer.writeProperty (" trivia" );
212255 writer.startArray ();
213256 for (auto & t : filtered)
214- writeTrivia (t);
257+ writeTrivia (t, expanded );
215258 writer.endArray ();
216259 }
217260 break ;
@@ -238,7 +281,7 @@ struct CSTJsonVisitor {
238281};
239282
240283void CSTSerializer::serialize (const SyntaxNode& node) {
241- CSTJsonVisitor visitor (writer, mode);
284+ CSTJsonVisitor visitor (writer, mode, sourceManager );
242285 node.visit (visitor);
243286}
244287
0 commit comments