1 /// Visitor classifying types and groups of regions of root definitions.
2 module workspaced.visitors.classifier;
3 
4 import workspaced.visitors.attributes;
5 
6 import workspaced.helpers : determineIndentation;
7 
8 import workspaced.com.dcdext;
9 
10 import std.algorithm;
11 import std.ascii;
12 import std.meta;
13 import std.range;
14 import std.string;
15 
16 import dparse.ast;
17 import dparse.lexer;
18 
19 class CodeDefinitionClassifier : AttributesVisitor
20 {
21 	struct Region
22 	{
23 		CodeRegionType type;
24 		CodeRegionProtection protection;
25 		CodeRegionStatic staticness;
26 		string minIndentation;
27 		uint[2] region;
28 		bool affectsFollowing;
29 
30 		bool sameBlockAs(in Region other)
31 		{
32 			return type == other.type && protection == other.protection && staticness == other.staticness;
33 		}
34 	}
35 
36 	this(const(char)[] code)
37 	{
38 		this.code = code;
39 	}
40 
41 	override void visit(const AliasDeclaration aliasDecl)
42 	{
43 		putRegion(CodeRegionType.aliases);
44 	}
45 
46 	override void visit(const AliasThisDeclaration aliasDecl)
47 	{
48 		putRegion(CodeRegionType.aliases);
49 	}
50 
51 	override void visit(const ClassDeclaration typeDecl)
52 	{
53 		putRegion(CodeRegionType.types);
54 	}
55 
56 	override void visit(const InterfaceDeclaration typeDecl)
57 	{
58 		putRegion(CodeRegionType.types);
59 	}
60 
61 	override void visit(const StructDeclaration typeDecl)
62 	{
63 		putRegion(CodeRegionType.types);
64 	}
65 
66 	override void visit(const UnionDeclaration typeDecl)
67 	{
68 		putRegion(CodeRegionType.types);
69 	}
70 
71 	override void visit(const EnumDeclaration typeDecl)
72 	{
73 		putRegion(CodeRegionType.types);
74 	}
75 
76 	override void visit(const AnonymousEnumDeclaration typeDecl)
77 	{
78 		putRegion(CodeRegionType.types);
79 	}
80 
81 	override void visit(const AutoDeclaration field)
82 	{
83 		putRegion(CodeRegionType.fields);
84 	}
85 
86 	override void visit(const VariableDeclaration field)
87 	{
88 		putRegion(CodeRegionType.fields);
89 	}
90 
91 	override void visit(const Constructor ctor)
92 	{
93 		putRegion(CodeRegionType.ctor);
94 	}
95 
96 	override void visit(const StaticConstructor ctor)
97 	{
98 		putRegion(CodeRegionType.ctor);
99 	}
100 
101 	override void visit(const SharedStaticConstructor ctor)
102 	{
103 		putRegion(CodeRegionType.ctor);
104 	}
105 
106 	override void visit(const Postblit copyctor)
107 	{
108 		putRegion(CodeRegionType.copyctor);
109 	}
110 
111 	override void visit(const Destructor dtor)
112 	{
113 		putRegion(CodeRegionType.dtor);
114 	}
115 
116 	override void visit(const StaticDestructor dtor)
117 	{
118 		putRegion(CodeRegionType.dtor);
119 	}
120 
121 	override void visit(const SharedStaticDestructor dtor)
122 	{
123 		putRegion(CodeRegionType.dtor);
124 	}
125 
126 	override void visit(const FunctionDeclaration method)
127 	{
128 		putRegion((method.attributes && method.attributes.any!(a => a.atAttribute
129 				&& a.atAttribute.identifier.text == "property")) ? CodeRegionType.properties
130 				: CodeRegionType.methods);
131 	}
132 
133 	override void visit(const Declaration dec)
134 	{
135 		writtenRegion = false;
136 		currentRange = [cast(uint) dec.tokens[0].index,
137 			cast(uint)(dec.tokens[$ - 1].index + dec.tokens[$ - 1].text.length + 1)];
138 		super.visit(dec);
139 		if (writtenRegion && regions.length >= 2 && regions[$ - 2].sameBlockAs(regions[$ - 1]))
140 		{
141 			auto range = regions[$ - 1].region;
142 			if (regions[$ - 1].minIndentation.scoreIndent < regions[$ - 2].minIndentation.scoreIndent)
143 				regions[$ - 2].minIndentation = regions[$ - 1].minIndentation;
144 			regions[$ - 2].region[1] = range[1];
145 			regions.length--;
146 		}
147 	}
148 
149 	override void visit(const AttributeDeclaration dec)
150 	{
151 		auto before = context.attributes[];
152 		dec.accept(this);
153 		auto now = context.attributes;
154 		if (now.length > before.length)
155 		{
156 			auto permaAdded = now[before.length .. $];
157 
158 		}
159 	}
160 
161 	void putRegion(CodeRegionType type, uint[2] range = typeof(uint.init)[2].init)
162 	{
163 		if (range == typeof(uint.init)[2].init)
164 			range = currentRange;
165 
166 		CodeRegionProtection protection;
167 		CodeRegionStatic staticness;
168 
169 		auto prot = context.protectionAttribute;
170 		bool stickyProtection = false;
171 		if (prot)
172 		{
173 			stickyProtection = prot.sticky;
174 			if (prot.attributes[0].type == tok!"private")
175 				protection = CodeRegionProtection.private_;
176 			else if (prot.attributes[0].type == tok!"protected")
177 				protection = CodeRegionProtection.protected_;
178 			else if (prot.attributes[0].type == tok!"package")
179 			{
180 				if (prot.attributes.length > 1)
181 					protection = CodeRegionProtection.packageIdentifier;
182 				else
183 					protection = CodeRegionProtection.package_;
184 			}
185 			else if (prot.attributes[0].type == tok!"public")
186 				protection = CodeRegionProtection.public_;
187 		}
188 
189 		staticness = context.isStatic ? CodeRegionStatic.static_ : CodeRegionStatic.instanced;
190 
191 		if (stickyProtection)
192 		{
193 			assert(prot);
194 			//dfmt off
195 			Region pr = {
196 				type: cast(CodeRegionType)0,
197 				protection: protection,
198 				staticness: cast(CodeRegionStatic)0,
199 				region: [cast(uint) prot.attributes[0].index, cast(uint) prot.attributes[0].index],
200 				affectsFollowing: true
201 			};
202 			//dfmt on
203 			regions ~= pr;
204 		}
205 
206 		//dfmt off
207 		Region r = {
208 			type: type,
209 			protection: protection,
210 			staticness: staticness,
211 			minIndentation: determineIndentation(code[range[0] .. range[1]]),
212 			region: range
213 		};
214 		//dfmt on
215 		regions ~= r;
216 		writtenRegion = true;
217 	}
218 
219 	alias visit = AttributesVisitor.visit;
220 
221 	bool writtenRegion;
222 	const(char)[] code;
223 	Region[] regions;
224 	uint[2] currentRange;
225 }
226 
227 private int scoreIndent(string indent)
228 {
229 	auto len = indent.countUntil!(a => !a.isWhite);
230 	if (len == -1)
231 		return cast(int) indent.length;
232 	return indent[0 .. len].map!(a => a == ' ' ? 1 : 4).sum;
233 }