/** * Code for generating .json descriptions of the module when passing the `-X` flag to dmd. * * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/json.d, _json.d) * Documentation: https://dlang.org/phobos/dmd_json.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/json.d */ module dmd.json; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.cond; import dmd.dclass; import dmd.declaration; import dmd.denum; import dmd.dimport; import dmd.dmodule; import dmd.dsymbol; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.func; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.mtype; import dmd.root.outbuffer; import dmd.root.rootobject; import dmd.root.string; import dmd.target; import dmd.visitor; version(Windows) { extern (C) char* getcwd(char* buffer, size_t maxlen); } else { import core.sys.posix.unistd : getcwd; } private extern (C++) final class ToJsonVisitor : Visitor { alias visit = Visitor.visit; public: OutBuffer* buf; int indentLevel; const(char)[] filename; extern (D) this(OutBuffer* buf) { this.buf = buf; } void indent() { if (buf.length >= 1 && (*buf)[buf.length - 1] == '\n') for (int i = 0; i < indentLevel; i++) buf.writeByte(' '); } void removeComma() { if (buf.length >= 2 && (*buf)[buf.length - 2] == ',' && ((*buf)[buf.length - 1] == '\n' || (*buf)[buf.length - 1] == ' ')) buf.setsize(buf.length - 2); } void comma() { if (indentLevel > 0) buf.writestring(",\n"); } void stringStart() { buf.writeByte('\"'); } void stringEnd() { buf.writeByte('\"'); } extern(D) void stringPart(const char[] s) { foreach (char c; s) { switch (c) { case '\n': buf.writestring("\\n"); break; case '\r': buf.writestring("\\r"); break; case '\t': buf.writestring("\\t"); break; case '\"': buf.writestring("\\\""); break; case '\\': buf.writestring("\\\\"); break; case '\b': buf.writestring("\\b"); break; case '\f': buf.writestring("\\f"); break; default: if (c < 0x20) buf.printf("\\u%04x", c); else { // Note that UTF-8 chars pass through here just fine buf.writeByte(c); } break; } } } // Json value functions /********************************* * Encode string into buf, and wrap it in double quotes. */ extern(D) void value(const char[] s) { stringStart(); stringPart(s); stringEnd(); } void value(int value) { if (value < 0) { buf.writeByte('-'); value = -value; } buf.print(value); } void valueBool(bool value) { buf.writestring(value ? "true" : "false"); } /********************************* * Item is an intented value and a comma, for use in arrays */ extern(D) void item(const char[] s) { indent(); value(s); comma(); } void item(int i) { indent(); value(i); comma(); } void itemBool(const bool b) { indent(); valueBool(b); comma(); } // Json array functions void arrayStart() { indent(); buf.writestring("[\n"); indentLevel++; } void arrayEnd() { indentLevel--; removeComma(); if (buf.length >= 2 && (*buf)[buf.length - 2] == '[' && (*buf)[buf.length - 1] == '\n') buf.setsize(buf.length - 1); else if (!(buf.length >= 1 && (*buf)[buf.length - 1] == '[')) { buf.writestring("\n"); indent(); } buf.writestring("]"); comma(); } // Json object functions void objectStart() { indent(); buf.writestring("{\n"); indentLevel++; } void objectEnd() { indentLevel--; removeComma(); if (buf.length >= 2 && (*buf)[buf.length - 2] == '{' && (*buf)[buf.length - 1] == '\n') buf.setsize(buf.length - 1); else { buf.writestring("\n"); indent(); } buf.writestring("}"); comma(); } // Json object property functions extern(D) void propertyStart(const char[] name) { indent(); value(name); buf.writestring(" : "); } /** Write the given string object property only if `s` is not null. Params: name = the name of the object property s = the string value of the object property */ extern(D) void property(const char[] name, const char[] s) { if (s is null) return; propertyStart(name); value(s); comma(); } /** Write the given string object property. Params: name = the name of the object property s = the string value of the object property */ extern(D) void requiredProperty(const char[] name, const char[] s) { propertyStart(name); if (s is null) buf.writestring("null"); else value(s); comma(); } extern(D) void property(const char[] name, int i) { propertyStart(name); value(i); comma(); } extern(D) void propertyBool(const char[] name, const bool b) { propertyStart(name); valueBool(b); comma(); } extern(D) void property(const char[] name, TRUST trust) { final switch (trust) { case TRUST.default_: // Should not be printed //property(name, "default"); break; case TRUST.system: return property(name, "system"); case TRUST.trusted: return property(name, "trusted"); case TRUST.safe: return property(name, "safe"); } } extern(D) void property(const char[] name, PURE purity) { final switch (purity) { case PURE.impure: // Should not be printed //property(name, "impure"); break; case PURE.weak: return property(name, "weak"); case PURE.const_: return property(name, "const"); case PURE.strong: return property(name, "strong"); case PURE.fwdref: return property(name, "fwdref"); } } extern(D) void property(const char[] name, const LINK linkage) { final switch (linkage) { case LINK.default_: // Should not be printed //property(name, "default"); break; case LINK.d: // Should not be printed //property(name, "d"); break; case LINK.system: // Should not be printed //property(name, "system"); break; case LINK.c: return property(name, "c"); case LINK.cpp: return property(name, "cpp"); case LINK.windows: return property(name, "windows"); case LINK.objc: return property(name, "objc"); } } extern(D) void propertyStorageClass(const char[] name, StorageClass stc) { stc &= STC.visibleStorageClasses; if (stc) { propertyStart(name); arrayStart(); while (stc) { auto p = stcToString(stc); assert(p.length); item(p); } arrayEnd(); } } extern(D) void property(const char[] linename, const char[] charname, const ref Loc loc) { if (loc.isValid()) { if (auto filename = loc.filename.toDString) { if (filename != this.filename) { this.filename = filename; property("file", filename); } } if (loc.linnum) { property(linename, loc.linnum); if (loc.charnum) property(charname, loc.charnum); } } } extern(D) void property(const char[] name, Type type) { if (type) { property(name, type.toString()); } } extern(D) void property(const char[] name, const char[] deconame, Type type) { if (type) { if (type.deco) property(deconame, type.deco.toDString); else property(name, type.toString()); } } extern(D) void property(const char[] name, Parameters* parameters) { if (parameters is null || parameters.dim == 0) return; propertyStart(name); arrayStart(); if (parameters) { for (size_t i = 0; i < parameters.dim; i++) { Parameter p = (*parameters)[i]; objectStart(); if (p.ident) property("name", p.ident.toString()); property("type", "deco", p.type); propertyStorageClass("storageClass", p.storageClass); if (p.defaultArg) property("default", p.defaultArg.toString()); objectEnd(); } } arrayEnd(); } /* ========================================================================== */ void jsonProperties(Dsymbol s) { if (s.isModule()) return; if (!s.isTemplateDeclaration()) // TemplateDeclaration::kind() acts weird sometimes { property("name", s.toString()); if (s.isStaticCtorDeclaration()) { property("kind", s.isSharedStaticCtorDeclaration() ? "shared static constructor" : "static constructor"); } else if (s.isStaticDtorDeclaration()) { property("kind", s.isSharedStaticDtorDeclaration() ? "shared static destructor" : "static destructor"); } else property("kind", s.kind.toDString); } // TODO: How about package(names)? property("protection", visibilityToString(s.visible().kind)); if (EnumMember em = s.isEnumMember()) { if (em.origValue) property("value", em.origValue.toString()); } property("comment", s.comment.toDString); property("line", "char", s.loc); } void jsonProperties(Declaration d) { if (d.storage_class & STC.local) return; jsonProperties(cast(Dsymbol)d); propertyStorageClass("storageClass", d.storage_class); property("linkage", d.linkage); property("type", "deco", d.type); // Emit originalType if it differs from type if (d.type != d.originalType && d.originalType) { auto ostr = d.originalType.toString(); if (d.type) { auto tstr = d.type.toString(); if (ostr != tstr) { //printf("tstr = %s, ostr = %s\n", tstr, ostr); property("originalType", ostr); } } else property("originalType", ostr); } } void jsonProperties(TemplateDeclaration td) { jsonProperties(cast(Dsymbol)td); if (td.onemember && td.onemember.isCtorDeclaration()) property("name", "this"); // __ctor -> this else property("name", td.ident.toString()); // Foo(T) -> Foo } /* ========================================================================== */ override void visit(Dsymbol s) { } override void visit(Module s) { objectStart(); if (s.md) property("name", s.md.toString()); property("kind", s.kind.toDString); filename = s.srcfile.toString(); property("file", filename); property("comment", s.comment.toDString); propertyStart("members"); arrayStart(); for (size_t i = 0; i < s.members.dim; i++) { (*s.members)[i].accept(this); } arrayEnd(); objectEnd(); } override void visit(Import s) { if (s.id == Id.object) return; objectStart(); propertyStart("name"); stringStart(); foreach (const pid; s.packages){ stringPart(pid.toString()); buf.writeByte('.'); } stringPart(s.id.toString()); stringEnd(); comma(); property("kind", s.kind.toDString); property("comment", s.comment.toDString); property("line", "char", s.loc); if (s.visible().kind != Visibility.Kind.public_) property("protection", visibilityToString(s.visible().kind)); if (s.aliasId) property("alias", s.aliasId.toString()); bool hasRenamed = false; bool hasSelective = false; for (size_t i = 0; i < s.aliases.dim; i++) { // avoid empty "renamed" and "selective" sections if (hasRenamed && hasSelective) break; else if (s.aliases[i]) hasRenamed = true; else hasSelective = true; } if (hasRenamed) { // import foo : alias1 = target1; propertyStart("renamed"); objectStart(); for (size_t i = 0; i < s.aliases.dim; i++) { const name = s.names[i]; const _alias = s.aliases[i]; if (_alias) property(_alias.toString(), name.toString()); } objectEnd(); } if (hasSelective) { // import foo : target1; propertyStart("selective"); arrayStart(); foreach (i, name; s.names) { if (!s.aliases[i]) item(name.toString()); } arrayEnd(); } objectEnd(); } override void visit(AttribDeclaration d) { Dsymbols* ds = d.include(null); if (ds) { for (size_t i = 0; i < ds.dim; i++) { Dsymbol s = (*ds)[i]; s.accept(this); } } } override void visit(ConditionalDeclaration d) { if (d.condition.inc != Include.notComputed) { visit(cast(AttribDeclaration)d); return; // Don't visit the if/else bodies again below } Dsymbols* ds = d.decl ? d.decl : d.elsedecl; for (size_t i = 0; i < ds.dim; i++) { Dsymbol s = (*ds)[i]; s.accept(this); } } override void visit(TypeInfoDeclaration d) { } override void visit(PostBlitDeclaration d) { } override void visit(Declaration d) { objectStart(); //property("unknown", "declaration"); jsonProperties(d); objectEnd(); } override void visit(AggregateDeclaration d) { objectStart(); jsonProperties(d); ClassDeclaration cd = d.isClassDeclaration(); if (cd) { if (cd.baseClass && cd.baseClass.ident != Id.Object) { property("base", cd.baseClass.toPrettyChars(true).toDString); } if (cd.interfaces.length) { propertyStart("interfaces"); arrayStart(); foreach (b; cd.interfaces) { item(b.sym.toPrettyChars(true).toDString); } arrayEnd(); } } if (d.members) { propertyStart("members"); arrayStart(); for (size_t i = 0; i < d.members.dim; i++) { Dsymbol s = (*d.members)[i]; s.accept(this); } arrayEnd(); } objectEnd(); } override void visit(FuncDeclaration d) { objectStart(); jsonProperties(d); TypeFunction tf = cast(TypeFunction)d.type; if (tf && tf.ty == Tfunction) property("parameters", tf.parameterList.parameters); property("endline", "endchar", d.endloc); if (d.foverrides.dim) { propertyStart("overrides"); arrayStart(); for (size_t i = 0; i < d.foverrides.dim; i++) { FuncDeclaration fd = d.foverrides[i]; item(fd.toPrettyChars().toDString); } arrayEnd(); } if (d.fdrequire) { propertyStart("in"); d.fdrequire.accept(this); } if (d.fdensure) { propertyStart("out"); d.fdensure.accept(this); } objectEnd(); } override void visit(TemplateDeclaration d) { objectStart(); // TemplateDeclaration::kind returns the kind of its Aggregate onemember, if it is one property("kind", "template"); jsonProperties(d); propertyStart("parameters"); arrayStart(); for (size_t i = 0; i < d.parameters.dim; i++) { TemplateParameter s = (*d.parameters)[i]; objectStart(); property("name", s.ident.toString()); if (auto type = s.isTemplateTypeParameter()) { if (s.isTemplateThisParameter()) property("kind", "this"); else property("kind", "type"); property("type", "deco", type.specType); property("default", "defaultDeco", type.defaultType); } if (auto value = s.isTemplateValueParameter()) { property("kind", "value"); property("type", "deco", value.valType); if (value.specValue) property("specValue", value.specValue.toString()); if (value.defaultValue) property("defaultValue", value.defaultValue.toString()); } if (auto _alias = s.isTemplateAliasParameter()) { property("kind", "alias"); property("type", "deco", _alias.specType); if (_alias.specAlias) property("specAlias", _alias.specAlias.toString()); if (_alias.defaultAlias) property("defaultAlias", _alias.defaultAlias.toString()); } if (auto tuple = s.isTemplateTupleParameter()) { property("kind", "tuple"); } objectEnd(); } arrayEnd(); Expression expression = d.constraint; if (expression) { property("constraint", expression.toString()); } propertyStart("members"); arrayStart(); for (size_t i = 0; i < d.members.dim; i++) { Dsymbol s = (*d.members)[i]; s.accept(this); } arrayEnd(); objectEnd(); } override void visit(EnumDeclaration d) { if (d.isAnonymous()) { if (d.members) { for (size_t i = 0; i < d.members.dim; i++) { Dsymbol s = (*d.members)[i]; s.accept(this); } } return; } objectStart(); jsonProperties(d); property("base", "baseDeco", d.memtype); if (d.members) { propertyStart("members"); arrayStart(); for (size_t i = 0; i < d.members.dim; i++) { Dsymbol s = (*d.members)[i]; s.accept(this); } arrayEnd(); } objectEnd(); } override void visit(EnumMember s) { objectStart(); jsonProperties(cast(Dsymbol)s); property("type", "deco", s.origType); objectEnd(); } override void visit(VarDeclaration d) { if (d.storage_class & STC.local) return; objectStart(); jsonProperties(d); if (d._init) property("init", d._init.toString()); if (d.isField()) property("offset", d.offset); if (d.alignment && d.alignment != STRUCTALIGN_DEFAULT) property("align", d.alignment); objectEnd(); } override void visit(TemplateMixin d) { objectStart(); jsonProperties(d); objectEnd(); } /** Generate an array of module objects that represent the syntax of each "root module". Params: modules = array of the "root modules" */ private void generateModules(Modules* modules) { arrayStart(); if (modules) { foreach (m; *modules) { if (global.params.verbose) message("json gen %s", m.toChars()); m.accept(this); } } arrayEnd(); } /** Generate the "compilerInfo" object which contains information about the compiler such as the filename, version, supported features, etc. */ private void generateCompilerInfo() { import dmd.target : target; objectStart(); requiredProperty("vendor", global.vendor); requiredProperty("version", global.versionString()); property("__VERSION__", global.versionNumber()); requiredProperty("interface", determineCompilerInterface()); property("size_t", size_t.sizeof); propertyStart("platforms"); arrayStart(); if (target.os == Target.OS.Windows) { item("windows"); } else { item("posix"); if (target.os == Target.OS.linux) item("linux"); else if (target.os == Target.OS.OSX) item("osx"); else if (target.os == Target.OS.FreeBSD) { item("freebsd"); item("bsd"); } else if (target.os == Target.OS.OpenBSD) { item("openbsd"); item("bsd"); } else if (target.os == Target.OS.Solaris) { item("solaris"); item("bsd"); } } arrayEnd(); propertyStart("architectures"); arrayStart(); item(target.architectureName); arrayEnd(); propertyStart("predefinedVersions"); arrayStart(); if (global.versionids) { foreach (const versionid; *global.versionids) { item(versionid.toString()); } } arrayEnd(); propertyStart("supportedFeatures"); { objectStart(); scope(exit) objectEnd(); propertyBool("includeImports", true); } objectEnd(); } /** Generate the "buildInfo" object which contains information specific to the current build such as CWD, importPaths, configFile, etc. */ private void generateBuildInfo() { objectStart(); requiredProperty("cwd", getcwd(null, 0).toDString); requiredProperty("argv0", global.params.argv0); requiredProperty("config", global.inifilename); requiredProperty("libName", global.params.libname); propertyStart("importPaths"); arrayStart(); if (global.params.imppath) { foreach (importPath; *global.params.imppath) { item(importPath.toDString); } } arrayEnd(); propertyStart("objectFiles"); arrayStart(); foreach (objfile; global.params.objfiles) { item(objfile.toDString); } arrayEnd(); propertyStart("libraryFiles"); arrayStart(); foreach (lib; global.params.libfiles) { item(lib.toDString); } arrayEnd(); propertyStart("ddocFiles"); arrayStart(); foreach (ddocFile; global.params.ddocfiles) { item(ddocFile.toDString); } arrayEnd(); requiredProperty("mapFile", global.params.mapfile); requiredProperty("resourceFile", global.params.resfile); requiredProperty("defFile", global.params.deffile); objectEnd(); } /** Generate the "semantics" object which contains a 'modules' field representing semantic information about all the modules used in the compilation such as module name, isRoot, contentImportedFiles, etc. */ private void generateSemantics() { objectStart(); propertyStart("modules"); arrayStart(); foreach (m; Module.amodules) { objectStart(); requiredProperty("name", m.md ? m.md.toString() : null); requiredProperty("file", m.srcfile.toString()); propertyBool("isRoot", m.isRoot()); if(m.contentImportedFiles.dim > 0) { propertyStart("contentImports"); arrayStart(); foreach (file; m.contentImportedFiles) { item(file.toDString); } arrayEnd(); } objectEnd(); } arrayEnd(); objectEnd(); } } extern (C++) void json_generate(OutBuffer* buf, Modules* modules) { scope ToJsonVisitor json = new ToJsonVisitor(buf); // write trailing newline scope(exit) buf.writeByte('\n'); if (global.params.jsonFieldFlags == 0) { // Generate the original format, which is just an array // of modules representing their syntax. json.generateModules(modules); json.removeComma(); } else { // Generate the new format which is an object where each // output option is its own field. json.objectStart(); if (global.params.jsonFieldFlags & JsonFieldFlags.compilerInfo) { json.propertyStart("compilerInfo"); json.generateCompilerInfo(); } if (global.params.jsonFieldFlags & JsonFieldFlags.buildInfo) { json.propertyStart("buildInfo"); json.generateBuildInfo(); } if (global.params.jsonFieldFlags & JsonFieldFlags.modules) { json.propertyStart("modules"); json.generateModules(modules); } if (global.params.jsonFieldFlags & JsonFieldFlags.semantics) { json.propertyStart("semantics"); json.generateSemantics(); } json.objectEnd(); } } /** A string listing the name of each JSON field. Useful for errors messages. */ enum jsonFieldNames = () { string s; string prefix = ""; foreach (idx, enumName; __traits(allMembers, JsonFieldFlags)) { static if (idx > 0) { s ~= prefix ~ "`" ~ enumName ~ "`"; prefix = ", "; } } return s; }(); /** Parse the given `fieldName` and return its corresponding JsonFieldFlags value. Params: fieldName = the field name to parse Returns: JsonFieldFlags.none on error, otherwise the JsonFieldFlags value corresponding to the given fieldName. */ extern (C++) JsonFieldFlags tryParseJsonField(const(char)* fieldName) { auto fieldNameString = fieldName.toDString(); foreach (idx, enumName; __traits(allMembers, JsonFieldFlags)) { static if (idx > 0) { if (fieldNameString == enumName) return __traits(getMember, JsonFieldFlags, enumName); } } return JsonFieldFlags.none; } /** Determines and returns the compiler interface which is one of `dmd`, `ldc`, `gdc` or `sdc`. Returns `null` if no interface can be determined. */ private extern(D) string determineCompilerInterface() { if (global.vendor == "Digital Mars D") return "dmd"; if (global.vendor == "LDC") return "ldc"; if (global.vendor == "GNU D") return "gdc"; if (global.vendor == "SDC") return "sdc"; return null; }