valaccodewriter.vala 5.73 KB
Newer Older
1 2
/* valaccodewriter.vala
 *
3
 * Copyright (C) 2006-2009  Jürg Billeter
4 5 6 7
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * 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 11 12 13 14 15 16 17 18 19 20 21 22 23 24

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 */

using GLib;

25 26 27
/**
 * Represents a writer to write C source files.
 */
28
public class Vala.CCodeWriter {
29 30 31
	/**
	 * Specifies the file to be written.
	 */
32
	public string filename { get; set; }
33

34 35 36 37 38
	/**
	 * Specifies the source file used to generate this one.
	 */
	private string source_filename;

39 40 41 42 43
	/**
	 * Specifies whether to emit line directives.
	 */
	public bool line_directives { get; set; }

44 45 46 47
	/**
	 * Specifies whether the output stream is at the beginning of a line.
	 */
	public bool bol {
48
		get { return _bol; }
49
	}
50

51 52 53
	private string temp_filename;
	private bool file_exists;

54
	private FileStream? stream;
55 56
	
	private int indent;
57 58
	private int current_line_number = 1;
	private bool using_line_directive;
59 60 61 62

	/* at begin of line */
	private bool _bol = true;
	
63
	public CCodeWriter (string filename, string? source_filename = null) {
64
		this.filename = filename;
65
		this.source_filename = source_filename;
66
	}
67 68 69 70 71 72 73

	/**
	 * Opens the file.
	 *
	 * @return true if the file has been opened successfully,
	 *         false otherwise
	 */
74
	public bool open (bool write_version) {
75
		file_exists = FileUtils.test (filename, FileTest.EXISTS);
76
		if (file_exists) {
77
			temp_filename = "%s.valatmp".printf (filename);
78 79
			stream = FileStream.open (temp_filename, "w");
		} else {
80 81 82 83 84 85
			/*
			 * File doesn't exist. In case of a particular destination (-d flag),
			 * check and create the directory structure.
			 */
			var dirname = Path.get_dirname (filename);
			DirUtils.create_with_parents (dirname, 0755);
86
			stream = FileStream.open (filename, "w");
87 88
		}

89 90 91 92
		if (stream == null) {
			return false;
		}

93 94 95 96
		var opening = write_version ?
			"/* %s generated by valac %s, the Vala compiler".printf (Path.get_basename (filename), Config.BUILD_VERSION) :
			"/* %s generated by valac, the Vala compiler".printf (Path.get_basename (filename));
		write_string (opening);
97 98 99 100 101 102 103 104 105 106 107

		// Write the file name if known
		if (source_filename != null) {
			write_newline ();
			write_string (" * generated from %s".printf (Path.get_basename (source_filename)));
		}

		write_string (", do not modify */");
		write_newline ();
		write_newline ();

108
		return true;
109 110
	}

111 112 113 114 115
	/**
	 * Closes the file.
	 */
	public void close () {
		stream = null;
116
		
117 118
		if (file_exists) {
			var changed = true;
119 120

			try {
121
				var old_file = new MappedFile (filename, false);
122 123 124 125 126 127
				var new_file = new MappedFile (temp_filename, false);
				var len = old_file.get_length ();
				if (len == new_file.get_length ()) {
					if (Memory.cmp (old_file.get_contents (), new_file.get_contents (), len) == 0) {
						changed = false;
					}
128
				}
129 130 131 132
				old_file = null;
				new_file = null;
			} catch (FileError e) {
				// assume changed if mmap comparison doesn't work
133 134
			}
			
135
			if (changed) {
136
				FileUtils.rename (temp_filename, filename);
137
			} else {
138
				FileUtils.unlink (temp_filename);
139
			}
140
		}
141 142 143 144 145
	}
	
	/**
	 * Writes tabs according to the current indent level.
	 */
146
	public void write_indent (CCodeLineDirective? line = null) {
147 148 149 150 151 152
		if (line_directives) {
			if (line != null) {
				line.write (this);
				using_line_directive = true;
			} else if (using_line_directive) {
				// no corresponding Vala line, emit line directive for C line
153
				write_string ("#line %d \"%s\"".printf (current_line_number + 1, Path.get_basename (filename)));
154 155 156
				write_newline ();
				using_line_directive = false;
			}
157 158
		}

159
		if (!bol) {
160
			write_newline ();
161 162
		}
		
163
		for (int i = 0; i < indent; i++) {
164
			stream.putc ('\t');
165 166
		}
		
167 168 169 170 171 172 173 174
		_bol = false;
	}
	
	/**
	 * Writes the specified string.
	 *
	 * @param s a string
	 */
175
	public void write_string (string s) {
176
		stream.puts (s);
177 178 179 180 181 182 183 184
		_bol = false;
	}
	
	/**
	 * Writes a newline.
	 */
	public void write_newline () {
		stream.putc ('\n');
185
		current_line_number++;
186 187 188 189 190 191 192 193 194 195
		_bol = true;
	}
	
	/**
	 * Opens a new block, increasing the indent level.
	 */
	public void write_begin_block () {
		if (!bol) {
			stream.putc (' ');
		} else {
196 197
			write_indent ();
		}
198 199 200 201 202 203 204 205 206 207
		stream.putc ('{');
		write_newline ();
		indent++;
	}
	
	/**
	 * Closes the current block, decreasing the indent level.
	 */
	public void write_end_block () {
		assert (indent > 0);
208
		
209 210
		indent--;
		write_indent ();
211
		stream.putc ('}');
212 213 214 215 216 217 218
	}
	
	/**
	 * Writes the specified text as comment.
	 *
	 * @param text the comment text
	 */
219
	public void write_comment (string text) {
220 221 222 223 224 225 226 227 228 229
		try {
			write_indent ();
			stream.puts ("/*");
			bool first = true;

			// discard tabs at beginning of line
			var regex = new GLib.Regex ("^\t+");

			/* separate declaration due to missing memory management in foreach statements */
			var lines = text.split ("\n");
230
		
231 232 233 234 235 236
			foreach (string line in lines) {
				if (!first) {
					write_indent ();
				} else {
					first = false;
				}
237

238
				var lineparts = regex.replace_literal (line, -1, 0, "").split ("*/");
239

240 241 242 243 244
				for (int i = 0; lineparts[i] != null; i++) {
					stream.puts (lineparts[i]);
					if (lineparts[i+1] != null) {
						stream.puts ("* /");
					}
245 246
				}
			}
247 248 249 250
			stream.puts ("*/");
			write_newline ();
		} catch (RegexError e) {
			// ignore
251 252 253
		}
	}
}