1 module dsemver.ast;
2 
3 import std.stdio;
4 import std.array : array, appender;
5 import std.algorithm.iteration : map;
6 import std.algorithm.searching : endsWith, startsWith;
7 import std.json;
8 import std.typecons : Nullable, nullable;
9 import std.traits : isArray, isSomeString, isIntegral, isFloatingPoint,
10 	   FieldNameTuple, isBasicType;
11 import std.algorithm : sort, setDifference;
12 import std.range : ElementEncodingType;
13 import std.format;
14 import std.exception : enforce;
15 
16 struct Parameter {
17 	string name;
18 	Nullable!(string) type;
19 	Nullable!(string) deco;
20 	Nullable!(string) kind;
21 	Nullable!(string) defaultValue;
22 	Nullable!(string) default_;
23 	Nullable!(string[]) storageClass;
24 }
25 
26 struct Member {
27 	string name;
28 	string kind;
29 	Nullable!(string) originalType;
30 	Nullable!(string) type;
31 	Nullable!(string) base;
32 	Nullable!(string) init_;
33 	Nullable!(string) value;
34 	Nullable!(string[]) storageClass;
35 	Nullable!(string) deco;
36 	Nullable!(string) baseDeco;
37 	Nullable!(long) align_;
38 	Nullable!(long) offset;
39 	Nullable!(Parameter[]) parameters;
40 	Nullable!(string[]) overrides;
41 	Nullable!(string) protection;
42 	Nullable!(string[]) selective;
43 	Nullable!(Member[]) members;
44 }
45 
46 struct Module {
47 	Nullable!string name;
48 	string kind;
49 	string file;
50 	Member[] members;
51 }
52 
53 string moduleToName(ref const(Module) mod) pure @safe {
54 	return mod.name.isNull
55 		? mod.file
56 		: mod.name.get();
57 }
58 
59 string toString(ref const(Parameter) p) {
60 	auto app = appender!string();
61 	formattedWrite(app, "\"%s", p.name);
62 	if(!p.type.isNull()) {
63 		formattedWrite(app, " type %s", p.type.get());
64 	}
65 	if(!p.deco.isNull()) {
66 		formattedWrite(app, " deco %s", p.deco.get());
67 	}
68 	if(!p.kind.isNull()) {
69 		formattedWrite(app, " kind %s", p.kind.get());
70 	}
71 	if(!p.defaultValue.isNull()) {
72 		formattedWrite(app, " defaultValue %s", p.defaultValue.get());
73 	}
74 	if(!p.default_.isNull()) {
75 		formattedWrite(app, " default %s", p.default_.get());
76 	}
77 	if(!p.storageClass.isNull()) {
78 		formattedWrite(app, " storageClass %(%s, %)", p.storageClass.get());
79 	}
80 	app.put("\"");
81 	return app.data;
82 }
83 
84 string toString(ref const(Member) mem) {
85 	import core.demangle;
86 	auto app = appender!string();
87 	formattedWrite(app, "\"%s", mem.kind);
88 	formattedWrite(app, " '%s'", mem.name);
89 	if(!mem.originalType.isNull) {
90 		formattedWrite(app, " original_type '%s'", mem.originalType.get());
91 	}
92 	if(!mem.type.isNull) {
93 		formattedWrite(app, " type '%s'", mem.type.get());
94 	}
95 	if(!mem.value.isNull) {
96 		formattedWrite(app, " value '%s'", mem.value.get());
97 	}
98 	if(!mem.storageClass.isNull) {
99 		formattedWrite(app, " storage class '%s'", mem.storageClass.get());
100 	}
101 	if(!mem.deco.isNull) {
102 		formattedWrite(app, " of type '%s'", demangleType(mem.deco.get()));
103 	}
104 	if(!mem.baseDeco.isNull) {
105 		formattedWrite(app, " '%s'", mem.baseDeco.get());
106 	}
107 	if(!mem.align_.isNull) {
108 		formattedWrite(app, " align '%s'", mem.align_.get());
109 	}
110 	if(!mem.base.isNull) {
111 		formattedWrite(app, " base '%s'", mem.base.get());
112 	}
113 	if(!mem.offset.isNull) {
114 		formattedWrite(app, " offset '%s'", mem.offset.get());
115 	}
116 	if(!mem.init_.isNull) {
117 		formattedWrite(app, " init '%s'", mem.init_.get());
118 	}
119 	if(!mem.parameters.isNull) {
120 		formattedWrite(app, " parameters '%(%s, %)'"
121 				, mem.parameters.get().map!(p => p.toString()));
122 	}
123 	if(!mem.overrides.isNull) {
124 		formattedWrite(app, " overrides '%(%s, %)'", mem.overrides.get());
125 	}
126 	if(!mem.protection.isNull) {
127 		formattedWrite(app, " protection '%s'", mem.protection.get());
128 	}
129 	if(!mem.selective.isNull) {
130 		formattedWrite(app, " selective '%(%s, %)'", mem.selective.get());
131 	}
132 	if(!mem.members.isNull) {
133 		formattedWrite(app, " members '%(%s, %)'"
134 				, mem.members.get().map!(m => m.toString()));
135 	}
136 	app.put("\"");
137 	return app.data;
138 }
139 
140 struct Ast {
141 	Module[] modules;
142 }
143 
144 Ast parse(string filename) {
145 	import std.file : readText;
146 
147 	JSONValue jv = parseJSON(readText(filename));
148 	return Ast(parseJson!(Module[])(jv));
149 }
150 
151 T parseJson(T)(JSONValue jv) {
152 	static if(isBasicType!T) {
153 		return jv.get!T();
154 	} else static if(isSomeString!T) {
155 		return jv.get!string();
156 	} else static if(isArray!T && !isSomeString!T) {
157 		enforce(jv.type == JSONType.array, format("Expected array not '%s'",
158 					jv.type));
159 		T arr;
160 		alias ET = ElementEncodingType!T;
161 		//pragma(msg, T.stringof ~ " " ~ ET.stringof);
162 		foreach(it; jv.arrayNoRef()) {
163 			auto tmp = parseJson!ET(it);
164 			static if(is(ET == Member) || is(ET == Module)) {
165 				if(!tmp.name.startsWith("__unittest_")) {
166 					arr ~= tmp;
167 				}
168 			} else {
169 				arr ~= tmp;
170 			}
171 		}
172 		return arr;
173 	} else static if(is(T : Nullable!G, G)) {
174 		if(jv.type != JSONType.null_) {
175 			return nullable(parseJson!G(jv));
176 		} else {
177 			return Nullable!(G).init;
178 		}
179 	} else static if(is(T == struct)) {
180 		enforce(jv.type == JSONType.object, format("Expected object '%s' not '%s'\n%s",
181 					T.stringof, jv.type, jv.toPrettyString()));
182 		T ret;
183 
184 		string[] jsNames = jv.objectNoRef().keys().sort.array;
185 		string[] sNames = ([FieldNameTuple!T] ~ ["endchar", "endline", "char", "line"])
186 			.map!(it => it.endsWith("_") ? it[0 .. $ - 1] : it)
187 			.array.sort.array;
188 		auto sd = setDifference(jsNames, sNames);
189 		if(!sd.empty) {
190 			writefln("%s", sd);
191 		}
192 
193 		static foreach(mem; FieldNameTuple!T) {{
194 			alias MT = typeof(__traits(getMember, T, mem));
195 			//pragma(msg, T.stringof ~ " " ~ mem ~ " " ~ MT.stringof);
196 
197 			enum memNoPostfix = mem.endsWith("_") ? mem[0 .. $ - 1] : mem;
198 
199 			auto p = memNoPostfix in jv;
200 			static if(is(MT : Nullable!F, F)) {
201 				if(p !is null) {
202 					__traits(getMember, ret, mem) = parseJson!(F)(*p);
203 				}
204 			} else {
205 				enforce(p !is null, format("Couldn't find '%s'\n%s", mem,
206 							jv.toPrettyString()));
207 				__traits(getMember, ret, mem) = parseJson!MT(*p);
208 			}
209 		}}
210 		return ret;
211 	}
212 }
213 
214 unittest {
215 	import std.file;
216 
217 	foreach(f; dirEntries("testdirgen/", "*.json", SpanMode.depth)) {
218 		try {
219 			auto a = parse(f.name);
220 		} catch(Exception e) {
221 			assert(false, format("%s\n%s", f.name, e));
222 		}
223 	}
224 }