1 | // Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team |
---|
2 | // |
---|
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this |
---|
4 | // software and associated documentation files (the "Software"), to deal in the Software |
---|
5 | // without restriction, including without limitation the rights to use, copy, modify, merge, |
---|
6 | // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
---|
7 | // to whom the Software is furnished to do so, subject to the following conditions: |
---|
8 | // |
---|
9 | // The above copyright notice and this permission notice shall be included in all copies or |
---|
10 | // substantial portions of the Software. |
---|
11 | // |
---|
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
---|
13 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
---|
14 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
---|
15 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
---|
16 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
17 | // DEALINGS IN THE SOFTWARE. |
---|
18 | |
---|
19 | using System; |
---|
20 | using System.Globalization; |
---|
21 | using System.IO; |
---|
22 | using System.Text; |
---|
23 | |
---|
24 | namespace ICSharpCode.NRefactory.CSharp |
---|
25 | { |
---|
26 | /// <summary> |
---|
27 | /// Writes C# code into a TextWriter. |
---|
28 | /// </summary> |
---|
29 | public class TextWriterTokenWriter : TokenWriter, ILocatable |
---|
30 | { |
---|
31 | readonly TextWriter textWriter; |
---|
32 | int indentation; |
---|
33 | bool needsIndent = true; |
---|
34 | bool isAtStartOfLine = true; |
---|
35 | int line, column; |
---|
36 | |
---|
37 | public int Indentation { |
---|
38 | get { return this.indentation; } |
---|
39 | set { this.indentation = value; } |
---|
40 | } |
---|
41 | |
---|
42 | public TextLocation Location { |
---|
43 | get { return new TextLocation(line, column + (needsIndent ? indentation * IndentationString.Length : 0)); } |
---|
44 | } |
---|
45 | |
---|
46 | public string IndentationString { get; set; } |
---|
47 | |
---|
48 | public TextWriterTokenWriter(TextWriter textWriter) |
---|
49 | { |
---|
50 | if (textWriter == null) |
---|
51 | throw new ArgumentNullException("textWriter"); |
---|
52 | this.textWriter = textWriter; |
---|
53 | this.IndentationString = "\t"; |
---|
54 | this.line = 1; |
---|
55 | this.column = 1; |
---|
56 | } |
---|
57 | |
---|
58 | public override void WriteIdentifier(Identifier identifier) |
---|
59 | { |
---|
60 | WriteIndentation(); |
---|
61 | if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) { |
---|
62 | textWriter.Write('@'); |
---|
63 | column++; |
---|
64 | } |
---|
65 | textWriter.Write(identifier.Name); |
---|
66 | column += identifier.Name.Length; |
---|
67 | isAtStartOfLine = false; |
---|
68 | } |
---|
69 | |
---|
70 | public override void WriteKeyword(Role role, string keyword) |
---|
71 | { |
---|
72 | WriteIndentation(); |
---|
73 | column += keyword.Length; |
---|
74 | textWriter.Write(keyword); |
---|
75 | isAtStartOfLine = false; |
---|
76 | } |
---|
77 | |
---|
78 | public override void WriteToken(Role role, string token) |
---|
79 | { |
---|
80 | WriteIndentation(); |
---|
81 | column += token.Length; |
---|
82 | textWriter.Write(token); |
---|
83 | isAtStartOfLine = false; |
---|
84 | } |
---|
85 | |
---|
86 | public override void Space() |
---|
87 | { |
---|
88 | WriteIndentation(); |
---|
89 | column++; |
---|
90 | textWriter.Write(' '); |
---|
91 | } |
---|
92 | |
---|
93 | protected void WriteIndentation() |
---|
94 | { |
---|
95 | if (needsIndent) { |
---|
96 | needsIndent = false; |
---|
97 | for (int i = 0; i < indentation; i++) { |
---|
98 | textWriter.Write(this.IndentationString); |
---|
99 | } |
---|
100 | column += indentation * IndentationString.Length; |
---|
101 | } |
---|
102 | } |
---|
103 | |
---|
104 | public override void NewLine() |
---|
105 | { |
---|
106 | textWriter.WriteLine(); |
---|
107 | column = 1; |
---|
108 | line++; |
---|
109 | needsIndent = true; |
---|
110 | isAtStartOfLine = true; |
---|
111 | } |
---|
112 | |
---|
113 | public override void Indent() |
---|
114 | { |
---|
115 | indentation++; |
---|
116 | } |
---|
117 | |
---|
118 | public override void Unindent() |
---|
119 | { |
---|
120 | indentation--; |
---|
121 | } |
---|
122 | |
---|
123 | public override void WriteComment(CommentType commentType, string content) |
---|
124 | { |
---|
125 | WriteIndentation(); |
---|
126 | switch (commentType) { |
---|
127 | case CommentType.SingleLine: |
---|
128 | textWriter.Write("//"); |
---|
129 | textWriter.WriteLine(content); |
---|
130 | column += 2 + content.Length; |
---|
131 | needsIndent = true; |
---|
132 | isAtStartOfLine = true; |
---|
133 | break; |
---|
134 | case CommentType.MultiLine: |
---|
135 | textWriter.Write("/*"); |
---|
136 | textWriter.Write(content); |
---|
137 | textWriter.Write("*/"); |
---|
138 | column += 2; |
---|
139 | UpdateEndLocation(content, ref line, ref column); |
---|
140 | column += 2; |
---|
141 | isAtStartOfLine = false; |
---|
142 | break; |
---|
143 | case CommentType.Documentation: |
---|
144 | textWriter.Write("///"); |
---|
145 | textWriter.WriteLine(content); |
---|
146 | column += 3 + content.Length; |
---|
147 | needsIndent = true; |
---|
148 | isAtStartOfLine = true; |
---|
149 | break; |
---|
150 | case CommentType.MultiLineDocumentation: |
---|
151 | textWriter.Write("/**"); |
---|
152 | textWriter.Write(content); |
---|
153 | textWriter.Write("*/"); |
---|
154 | column += 3; |
---|
155 | UpdateEndLocation(content, ref line, ref column); |
---|
156 | column += 2; |
---|
157 | isAtStartOfLine = false; |
---|
158 | break; |
---|
159 | default: |
---|
160 | textWriter.Write(content); |
---|
161 | column += content.Length; |
---|
162 | break; |
---|
163 | } |
---|
164 | } |
---|
165 | |
---|
166 | static void UpdateEndLocation(string content, ref int line, ref int column) |
---|
167 | { |
---|
168 | if (string.IsNullOrEmpty(content)) |
---|
169 | return; |
---|
170 | for (int i = 0; i < content.Length; i++) { |
---|
171 | char ch = content[i]; |
---|
172 | switch (ch) { |
---|
173 | case '\r': |
---|
174 | if (i + 1 < content.Length && content[i + 1] == '\n') |
---|
175 | i++; |
---|
176 | goto case '\n'; |
---|
177 | case '\n': |
---|
178 | line++; |
---|
179 | column = 0; |
---|
180 | break; |
---|
181 | } |
---|
182 | column++; |
---|
183 | } |
---|
184 | } |
---|
185 | |
---|
186 | public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) |
---|
187 | { |
---|
188 | // pre-processor directive must start on its own line |
---|
189 | if (!isAtStartOfLine) |
---|
190 | NewLine(); |
---|
191 | WriteIndentation(); |
---|
192 | textWriter.Write('#'); |
---|
193 | string directive = type.ToString().ToLowerInvariant(); |
---|
194 | textWriter.Write(directive); |
---|
195 | column += 1 + directive.Length; |
---|
196 | if (!string.IsNullOrEmpty(argument)) { |
---|
197 | textWriter.Write(' '); |
---|
198 | textWriter.Write(argument); |
---|
199 | column += 1 + argument.Length; |
---|
200 | } |
---|
201 | NewLine(); |
---|
202 | } |
---|
203 | |
---|
204 | public static string PrintPrimitiveValue(object value) |
---|
205 | { |
---|
206 | TextWriter writer = new StringWriter(); |
---|
207 | TextWriterTokenWriter tokenWriter = new TextWriterTokenWriter(writer); |
---|
208 | tokenWriter.WritePrimitiveValue(value); |
---|
209 | return writer.ToString(); |
---|
210 | } |
---|
211 | |
---|
212 | public override void WritePrimitiveValue(object value, string literalValue = null) |
---|
213 | { |
---|
214 | if (literalValue != null) { |
---|
215 | textWriter.Write(literalValue); |
---|
216 | column += literalValue.Length; |
---|
217 | return; |
---|
218 | } |
---|
219 | |
---|
220 | if (value == null) { |
---|
221 | // usually NullReferenceExpression should be used for this, but we'll handle it anyways |
---|
222 | textWriter.Write("null"); |
---|
223 | column += 4; |
---|
224 | return; |
---|
225 | } |
---|
226 | |
---|
227 | if (value is bool) { |
---|
228 | if ((bool)value) { |
---|
229 | textWriter.Write("true"); |
---|
230 | column += 4; |
---|
231 | } else { |
---|
232 | textWriter.Write("false"); |
---|
233 | column += 5; |
---|
234 | } |
---|
235 | return; |
---|
236 | } |
---|
237 | |
---|
238 | if (value is string) { |
---|
239 | string tmp = "\"" + ConvertString(value.ToString()) + "\""; |
---|
240 | column += tmp.Length; |
---|
241 | textWriter.Write(tmp); |
---|
242 | } else if (value is char) { |
---|
243 | string tmp = "'" + ConvertCharLiteral((char)value) + "'"; |
---|
244 | column += tmp.Length; |
---|
245 | textWriter.Write(tmp); |
---|
246 | } else if (value is decimal) { |
---|
247 | string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"; |
---|
248 | column += str.Length; |
---|
249 | textWriter.Write(str); |
---|
250 | } else if (value is float) { |
---|
251 | float f = (float)value; |
---|
252 | if (float.IsInfinity(f) || float.IsNaN(f)) { |
---|
253 | // Strictly speaking, these aren't PrimitiveExpressions; |
---|
254 | // but we still support writing these to make life easier for code generators. |
---|
255 | textWriter.Write("float"); |
---|
256 | column += 5; |
---|
257 | WriteToken(Roles.Dot, "."); |
---|
258 | if (float.IsPositiveInfinity(f)) { |
---|
259 | textWriter.Write("PositiveInfinity"); |
---|
260 | column += "PositiveInfinity".Length; |
---|
261 | } else if (float.IsNegativeInfinity(f)) { |
---|
262 | textWriter.Write("NegativeInfinity"); |
---|
263 | column += "NegativeInfinity".Length; |
---|
264 | } else { |
---|
265 | textWriter.Write("NaN"); |
---|
266 | column += 3; |
---|
267 | } |
---|
268 | return; |
---|
269 | } |
---|
270 | if (f == 0 && 1 / f == float.NegativeInfinity) { |
---|
271 | // negative zero is a special case |
---|
272 | // (again, not a primitive expression, but it's better to handle |
---|
273 | // the special case here than to do it in all code generators) |
---|
274 | textWriter.Write("-"); |
---|
275 | column++; |
---|
276 | } |
---|
277 | var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"; |
---|
278 | column += str.Length; |
---|
279 | textWriter.Write(str); |
---|
280 | } else if (value is double) { |
---|
281 | double f = (double)value; |
---|
282 | if (double.IsInfinity(f) || double.IsNaN(f)) { |
---|
283 | // Strictly speaking, these aren't PrimitiveExpressions; |
---|
284 | // but we still support writing these to make life easier for code generators. |
---|
285 | textWriter.Write("double"); |
---|
286 | column += 6; |
---|
287 | WriteToken(Roles.Dot, "."); |
---|
288 | if (double.IsPositiveInfinity(f)) { |
---|
289 | textWriter.Write("PositiveInfinity"); |
---|
290 | column += "PositiveInfinity".Length; |
---|
291 | } else if (double.IsNegativeInfinity(f)) { |
---|
292 | textWriter.Write("NegativeInfinity"); |
---|
293 | column += "NegativeInfinity".Length; |
---|
294 | } else { |
---|
295 | textWriter.Write("NaN"); |
---|
296 | column += 3; |
---|
297 | } |
---|
298 | return; |
---|
299 | } |
---|
300 | if (f == 0 && 1 / f == double.NegativeInfinity) { |
---|
301 | // negative zero is a special case |
---|
302 | // (again, not a primitive expression, but it's better to handle |
---|
303 | // the special case here than to do it in all code generators) |
---|
304 | textWriter.Write("-"); |
---|
305 | } |
---|
306 | string number = f.ToString("R", NumberFormatInfo.InvariantInfo); |
---|
307 | if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { |
---|
308 | number += ".0"; |
---|
309 | } |
---|
310 | textWriter.Write(number); |
---|
311 | } else if (value is IFormattable) { |
---|
312 | StringBuilder b = new StringBuilder (); |
---|
313 | // if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { |
---|
314 | // b.Append("0x"); |
---|
315 | // b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); |
---|
316 | // } else { |
---|
317 | b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo)); |
---|
318 | // } |
---|
319 | if (value is uint || value is ulong) { |
---|
320 | b.Append("u"); |
---|
321 | } |
---|
322 | if (value is long || value is ulong) { |
---|
323 | b.Append("L"); |
---|
324 | } |
---|
325 | textWriter.Write(b.ToString()); |
---|
326 | column += b.Length; |
---|
327 | } else { |
---|
328 | textWriter.Write(value.ToString()); |
---|
329 | column += value.ToString().Length; |
---|
330 | } |
---|
331 | } |
---|
332 | |
---|
333 | /// <summary> |
---|
334 | /// Gets the escape sequence for the specified character within a char literal. |
---|
335 | /// Does not include the single quotes surrounding the char literal. |
---|
336 | /// </summary> |
---|
337 | public static string ConvertCharLiteral(char ch) |
---|
338 | { |
---|
339 | if (ch == '\'') { |
---|
340 | return "\\'"; |
---|
341 | } |
---|
342 | return ConvertChar(ch); |
---|
343 | } |
---|
344 | |
---|
345 | /// <summary> |
---|
346 | /// Gets the escape sequence for the specified character. |
---|
347 | /// </summary> |
---|
348 | /// <remarks>This method does not convert ' or ".</remarks> |
---|
349 | static string ConvertChar(char ch) |
---|
350 | { |
---|
351 | switch (ch) { |
---|
352 | case '\\': |
---|
353 | return "\\\\"; |
---|
354 | case '\0': |
---|
355 | return "\\0"; |
---|
356 | case '\a': |
---|
357 | return "\\a"; |
---|
358 | case '\b': |
---|
359 | return "\\b"; |
---|
360 | case '\f': |
---|
361 | return "\\f"; |
---|
362 | case '\n': |
---|
363 | return "\\n"; |
---|
364 | case '\r': |
---|
365 | return "\\r"; |
---|
366 | case '\t': |
---|
367 | return "\\t"; |
---|
368 | case '\v': |
---|
369 | return "\\v"; |
---|
370 | default: |
---|
371 | if (char.IsControl(ch) || char.IsSurrogate(ch) || |
---|
372 | // print all uncommon white spaces as numbers |
---|
373 | (char.IsWhiteSpace(ch) && ch != ' ')) { |
---|
374 | return "\\u" + ((int)ch).ToString("x4"); |
---|
375 | } else { |
---|
376 | return ch.ToString(); |
---|
377 | } |
---|
378 | } |
---|
379 | } |
---|
380 | |
---|
381 | /// <summary> |
---|
382 | /// Converts special characters to escape sequences within the given string. |
---|
383 | /// </summary> |
---|
384 | public static string ConvertString(string str) |
---|
385 | { |
---|
386 | StringBuilder sb = new StringBuilder (); |
---|
387 | foreach (char ch in str) { |
---|
388 | if (ch == '"') { |
---|
389 | sb.Append("\\\""); |
---|
390 | } else { |
---|
391 | sb.Append(ConvertChar(ch)); |
---|
392 | } |
---|
393 | } |
---|
394 | return sb.ToString(); |
---|
395 | } |
---|
396 | |
---|
397 | public override void WritePrimitiveType(string type) |
---|
398 | { |
---|
399 | textWriter.Write(type); |
---|
400 | column += type.Length; |
---|
401 | if (type == "new") { |
---|
402 | textWriter.Write("()"); |
---|
403 | column += 2; |
---|
404 | } |
---|
405 | } |
---|
406 | |
---|
407 | public override void StartNode(AstNode node) |
---|
408 | { |
---|
409 | // Write out the indentation, so that overrides of this method |
---|
410 | // can rely use the current output length to identify the position of the node |
---|
411 | // in the output. |
---|
412 | WriteIndentation(); |
---|
413 | } |
---|
414 | |
---|
415 | public override void EndNode(AstNode node) |
---|
416 | { |
---|
417 | } |
---|
418 | } |
---|
419 | } |
---|