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 = [
137 			cast(uint) dec.tokens[0].index,
138 			cast(uint)(dec.tokens[$ - 1].index + dec.tokens[$ - 1].text.length + 1)
139 		];
140 		super.visit(dec);
141 		if (writtenRegion && regions.length >= 2 && regions[$ - 2].sameBlockAs(regions[$ - 1]))
142 		{
143 			auto range = regions[$ - 1].region;
144 			if (regions[$ - 1].minIndentation.scoreIndent < regions[$ - 2].minIndentation.scoreIndent)
145 				regions[$ - 2].minIndentation = regions[$ - 1].minIndentation;
146 			regions[$ - 2].region[1] = range[1];
147 			regions.length--;
148 		}
149 	}
150 
151 	override void visit(const AttributeDeclaration dec)
152 	{
153 		auto before = context.attributes[];
154 		dec.accept(this);
155 		auto now = context.attributes;
156 		if (now.length > before.length)
157 		{
158 			auto permaAdded = now[before.length .. $];
159 
160 		}
161 	}
162 
163 	void putRegion(CodeRegionType type, uint[2] range = typeof(uint.init)[2].init)
164 	{
165 		if (range == typeof(uint.init)[2].init)
166 			range = currentRange;
167 
168 		CodeRegionProtection protection;
169 		CodeRegionStatic staticness;
170 
171 		auto prot = context.protectionAttribute;
172 		bool stickyProtection = false;
173 		if (prot)
174 		{
175 			stickyProtection = prot.sticky;
176 			if (prot.attributes[0].type == tok!"private")
177 				protection = CodeRegionProtection.private_;
178 			else if (prot.attributes[0].type == tok!"protected")
179 				protection = CodeRegionProtection.protected_;
180 			else if (prot.attributes[0].type == tok!"package")
181 			{
182 				if (prot.attributes.length > 1)
183 					protection = CodeRegionProtection.packageIdentifier;
184 				else
185 					protection = CodeRegionProtection.package_;
186 			}
187 			else if (prot.attributes[0].type == tok!"public")
188 				protection = CodeRegionProtection.public_;
189 		}
190 
191 		staticness = context.isStatic ? CodeRegionStatic.static_ : CodeRegionStatic.instanced;
192 
193 		if (stickyProtection)
194 		{
195 			assert(prot);
196 			//dfmt off
197 			Region pr = {
198 				type: cast(CodeRegionType)0,
199 				protection: protection,
200 				staticness: cast(CodeRegionStatic)0,
201 				region: [cast(uint) prot.attributes[0].index, cast(uint) prot.attributes[0].index],
202 				affectsFollowing: true
203 			};
204 			//dfmt on
205 			regions ~= pr;
206 		}
207 
208 		//dfmt off
209 		Region r = {
210 			type: type,
211 			protection: protection,
212 			staticness: staticness,
213 			minIndentation: determineIndentation(code[range[0] .. range[1]]),
214 			region: range
215 		};
216 		//dfmt on
217 		regions ~= r;
218 		writtenRegion = true;
219 	}
220 
221 	alias visit = AttributesVisitor.visit;
222 
223 	bool writtenRegion;
224 	const(char)[] code;
225 	Region[] regions;
226 	uint[2] currentRange;
227 }
228 
229 private int scoreIndent(string indent)
230 {
231 	auto len = indent.countUntil!(a => !a.isWhite);
232 	if (len == -1)
233 		return cast(int) indent.length;
234 	return indent[0 .. len].map!(a => a == ' ' ? 1 : 4).sum;
235 }