21cc4172092b692df7f38e766904af965bf5fdc6
[roobuilder] / src / ccode / valaccodewriter.vala
1 /* valaccodewriter.vala
2  *
3  * Copyright (C) 2006-2009  Jürg Billeter
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
18  *
19  * Author:
20  *      Jürg Billeter <j@bitron.ch>
21  */
22
23 using GLib;
24
25 /**
26  * Represents a writer to write C source files.
27  */
28 public class Vala.CCodeWriter {
29         /**
30          * Specifies the file to be written.
31          */
32         public string filename { get; set; }
33
34         /**
35          * Specifies the source file used to generate this one.
36          */
37         private string source_filename;
38
39         /**
40          * Specifies whether to emit line directives.
41          */
42         public bool line_directives { get; set; }
43
44         /**
45          * Specifies whether the output stream is at the beginning of a line.
46          */
47         public bool bol {
48                 get { return _bol; }
49         }
50
51         static GLib.Regex fix_indent_regex;
52
53         private string temp_filename;
54         private bool file_exists;
55
56         private FileStream? stream;
57
58         private int indent;
59         private int current_line_number = 1;
60         private bool using_line_directive;
61
62         /* at begin of line */
63         private bool _bol = true;
64         /* at begin after empty line */
65         private bool _bael = false;
66
67         public CCodeWriter (string filename, string? source_filename = null) {
68                 this.filename = filename;
69                 this.source_filename = source_filename;
70         }
71
72         /**
73          * Opens the file.
74          *
75          * @return true if the file has been opened successfully,
76          *         false otherwise
77          */
78         public bool open (bool write_version) {
79                 file_exists = FileUtils.test (filename, FileTest.EXISTS);
80                 if (file_exists) {
81                         temp_filename = "%s.valatmp".printf (filename);
82                         stream = FileStream.open (temp_filename, "w");
83                 } else {
84                         /*
85                          * File doesn't exist. In case of a particular destination (-d flag),
86                          * check and create the directory structure.
87                          */
88                         var dirname = Path.get_dirname (filename);
89                         DirUtils.create_with_parents (dirname, 0755);
90                         stream = FileStream.open (filename, "w");
91                 }
92
93                 if (stream == null) {
94                         return false;
95                 }
96
97                 var opening = write_version ?
98                         "/* %s generated by valac %s, the Vala compiler".printf (Path.get_basename (filename), Vala.BUILD_VERSION) :
99                         "/* %s generated by valac, the Vala compiler".printf (Path.get_basename (filename));
100                 write_string (opening);
101
102                 // Write the file name if known
103                 if (source_filename != null) {
104                         write_newline ();
105                         write_string (" * generated from %s".printf (Path.get_basename (source_filename)));
106                 }
107
108                 write_string (", do not modify */");
109                 write_newline ();
110                 write_newline ();
111
112                 return true;
113         }
114
115         /**
116          * Closes the file.
117          */
118         public void close () {
119                 stream = null;
120
121                 if (file_exists) {
122                         var changed = true;
123
124                         try {
125                                 var old_file = new MappedFile (filename, false);
126                                 var new_file = new MappedFile (temp_filename, false);
127                                 var len = old_file.get_length ();
128                                 if (len == new_file.get_length ()) {
129                                         if (Memory.cmp (old_file.get_contents (), new_file.get_contents (), len) == 0) {
130                                                 changed = false;
131                                         }
132                                 }
133                                 old_file = null;
134                                 new_file = null;
135                         } catch (FileError e) {
136                                 // assume changed if mmap comparison doesn't work
137                         }
138
139                         if (changed) {
140                                 FileUtils.rename (temp_filename, filename);
141                         } else {
142                                 FileUtils.unlink (temp_filename);
143                                 if (source_filename != null) {
144                                         var stats = Stat (source_filename);
145                                         var target_stats = Stat (filename);
146                                         if (stats.st_mtime >= target_stats.st_mtime) {
147                                                 UTimBuf timebuf = { stats.st_atime + 1, stats.st_mtime + 1 };
148                                                 FileUtils.utime (filename, timebuf);
149                                         }
150                                 }
151                         }
152                 }
153         }
154
155         /**
156          * Writes tabs according to the current indent level.
157          */
158         public void write_indent (CCodeLineDirective? line = null) {
159                 if (line_directives) {
160                         if (line != null) {
161                                 line.write (this);
162                                 using_line_directive = true;
163                         } else if (using_line_directive) {
164                                 // no corresponding Vala line, emit line directive for C line
165                                 write_string ("#line %d \"%s\"".printf (current_line_number + 1, Path.get_basename (filename)));
166                                 write_newline ();
167                                 using_line_directive = false;
168                         }
169                 }
170
171                 if (!_bol) {
172                         write_newline ();
173                 }
174
175                 stream.puts (string.nfill (indent, '\t'));
176                 _bol = false;
177         }
178
179         /**
180          * Writes n spaces.
181          */
182         public void write_nspaces (uint n) {
183                 stream.puts (string.nfill (n, ' '));
184         }
185
186         /**
187          * Writes the specified string.
188          *
189          * @param s a string
190          */
191         public void write_string (string s) {
192                 stream.puts (s);
193                 _bol = false;
194         }
195
196         /**
197          * Writes a newline.
198          */
199         public void write_newline () {
200                 if (!_bol) {
201                         _bael = false;
202                 } else if (!_bael) {
203                         _bael = true;
204                 } else {
205                         return;
206                 }
207                 stream.putc ('\n');
208                 current_line_number++;
209                 _bol = true;
210         }
211
212         /**
213          * Opens a new block, increasing the indent level.
214          */
215         public void write_begin_block () {
216                 if (!_bol) {
217                         stream.putc (' ');
218                 } else {
219                         write_indent ();
220                 }
221                 stream.putc ('{');
222                 write_newline ();
223                 indent++;
224         }
225
226         /**
227          * Closes the current block, decreasing the indent level.
228          */
229         public void write_end_block () {
230                 assert (indent > 0);
231
232                 indent--;
233                 write_indent ();
234                 stream.putc ('}');
235         }
236
237         /**
238          * Writes the specified text as comment.
239          *
240          * @param text the comment text
241          */
242         public void write_comment (string text) {
243                 try {
244                         write_indent ();
245                         stream.puts ("/*");
246                         bool first = true;
247
248                         // discard tabs at beginning of line
249                         if (fix_indent_regex == null)
250                                 fix_indent_regex = new GLib.Regex ("^\t+");;
251
252                         foreach (unowned string line in text.split ("\n")) {
253                                 if (!first) {
254                                         write_indent ();
255                                 } else {
256                                         first = false;
257                                 }
258
259                                 var lineparts = fix_indent_regex.replace_literal (line, -1, 0, "").split ("*/");
260
261                                 for (int i = 0; lineparts[i] != null; i++) {
262                                         stream.puts (lineparts[i]);
263                                         if (lineparts[i+1] != null) {
264                                                 stream.puts ("* /");
265                                         }
266                                 }
267                         }
268                         stream.puts ("*/");
269                         write_newline ();
270                 } catch (RegexError e) {
271                         // ignore
272                 }
273         }
274 }