1 /++ Idiomatic D API for libtcc.h
2 	See: https://briancallahan.net/blog/20220406.html
3 
4 	TODO: Wrap newly added `tcc_set_realloc` if available. Availability might be
5 	checkable via a trait on ImportC declarations.
6 	TODO: Wrap newly added `tcc_setjmp`. `tcc_set_backtrace_func`.
7  +/
8 module tcc;
9 
10 import nxt.path : FilePath, DirPath;
11 
12 @safe:
13 
14 /++ Tiny C Compiler (TCC).
15 	See: https://briancallahan.net/blog/20220406.html
16  +/
17 struct TCC {
18 	import std.string : toStringz;
19 	import deimos.libtcc;
20 
21 	/++ Output type.
22 	 +/
23 	enum OutputType {
24 		memory = TCC_OUTPUT_MEMORY, /** Output will be run const memory (default) */
25 		exe = TCC_OUTPUT_EXE, /** Executable file */
26 		dll = TCC_OUTPUT_DLL, /** Dynamic library */
27 		obj = TCC_OUTPUT_OBJ, /** Object file */
28 		preprocess = TCC_OUTPUT_PREPROCESS, /** Only preprocess (used internally) */
29 	}
30 
31 	// Disable copying for now until I've figured out the preferred behavior.
32 	this(this) @disable;
33 
34 	/** Make sure `_state` is initialized. */
35 	private void assertInitialized() @trusted pure nothrow @nogc {
36 		if (_state is null)
37 			_state = tcc_new();
38 	}
39 
40 	~this() @trusted pure nothrow @nogc {
41 		if (_state !is null) {
42 			tcc_delete(_state);
43 			_state = null;
44 		}
45 	}
46 
47 	/** Set CONFIG_TCCDIR at runtime. */
48 	void setLibraryPath(in char[] path) pure scope @trusted {
49 		assertInitialized();
50 		return tcc_set_lib_path(_state, path.toStringz);
51 	}
52 	/// ditto
53 	void setLibraryPath(in DirPath path) pure scope => setLibraryPath(path.str);
54 
55 	alias ErrorFunc = extern(C) void function(void* opaque, const char* msg);
56 
57 	/** Set error/warning display callback. */
58 	void setErrorFunction(void* error_opaque, ErrorFunc error_func) pure scope @trusted @nogc {
59 		assertInitialized();
60 		return tcc_set_error_func(_state, error_opaque, error_func);
61 	}
62 
63 	/** Set options `opts` as from command line (multiple supported).
64 		Important options are `"-bt"` `"-b"`.
65 	 */
66 	int setOptions(in char[] opts) pure scope @trusted {
67 		assertInitialized();
68 		return tcc_set_options(_state, opts.toStringz);
69 	}
70 
71 	/*****************************/
72 	/** Preprocessor. */
73 
74 	/** Add include path `pathname`. */
75 	int addIncludePath(in char[] pathname) pure scope @trusted {
76 		assertInitialized();
77 		return tcc_add_include_path(_state, pathname.toStringz);
78 	}
79 	/// ditto
80 	int addIncludePath(in DirPath path) pure scope => addIncludePath(path.str);
81 
82 	/** Add system include path `pathname`. */
83 	int addSystemIncludePath(in char[] pathname) pure scope @trusted {
84 		assertInitialized();
85 		return tcc_add_sysinclude_path(_state, pathname.toStringz);
86 	}
87 	/// ditto
88 	int addSystemIncludePath(in DirPath path) pure scope => addSystemIncludePath(path.str);
89 
90 	/** Define preprocessor symbol named `sym`. Can put optional value. */
91 	void defineSymbol(in char[] sym, in char[] value) pure scope @trusted {
92 		assertInitialized();
93 		return tcc_define_symbol(_state, sym.toStringz, value.toStringz);
94 	}
95 
96 	/** Undefine preprocess symbol named `sym`. */
97 	void undefineSymbol(in char[] sym) pure scope @trusted {
98 		assertInitialized();
99 		return tcc_undefine_symbol(_state, sym.toStringz);
100 	}
101 
102 	/*****************************/
103 	/** Compiling. */
104 
105 	/** Add a file (C file, dll, object, library, ld script).
106 		Return -1 if error.
107 	 */
108 	int addFile(in char[] filename) pure scope @trusted {
109 		assertInitialized();
110 		return tcc_add_file(_state, filename.toStringz);
111 	}
112 	/// ditto
113 	int addFile(in FilePath path) pure scope => addFile(path.str);
114 
115 	/** Compile `source` containing a C source.
116 		Return -1 if error.
117 	 */
118 	int compile(in char[] source) pure scope @trusted {
119 		assertInitialized();
120 		/+ TODO: avoid `toStringz` when possible +/
121 		return tcc_compile_string(_state, source.toStringz);
122 	}
123 
124 	/*****************************/
125 	/** Linking commands. */
126 
127 	/** Set output type. MUST BE CALLED before any compilation. */
128 	int setOutputType(in OutputType output_type) pure scope @trusted @nogc {
129 		assertInitialized();
130 		return tcc_set_output_type(_state, output_type);
131 	}
132 
133 	/** Equivalent to -Lpath option. */
134 	int addLibraryPath(in char[] pathname) pure scope @trusted {
135 		assertInitialized();
136 		return tcc_add_library_path(_state, pathname.toStringz);
137 	}
138 	int addLibraryPath(in DirPath path) pure scope => addLibraryPath(path.str);
139 
140 	/** The library name is the same as the argument of the '-l' option. */
141 	int addLibrary(in char[] libraryname) pure scope @trusted {
142 		assertInitialized();
143 		return tcc_add_library(_state, libraryname.toStringz);
144 	}
145 
146 	/** Add a symbol to the compiled program. */
147 	int addSymbol(in char[] name, const void* val) pure scope @trusted {
148 		assertInitialized();
149 		return tcc_add_symbol(_state, name.toStringz, val);
150 	}
151 
152 	/** Output an executable, library or object file.
153 		DO NOT call `relocate()` before.
154 	 */
155 	int outputFile(in char[] filename) scope @trusted {
156 		assertInitialized();
157 		return tcc_output_file(_state, filename.toStringz);
158 	}
159 	/// ditto
160 	int outputFile(in FilePath filename) scope @trusted {
161 		assertInitialized();
162 		return tcc_output_file(_state, filename.str.toStringz);
163 	}
164 
165 	/** Link and run `main()` function and return its value.
166 		DO NOT call `relocate()` before.
167 	 */
168 	int run(int argc, char** argv) scope @trusted @nogc {
169 		assertInitialized();
170 		return tcc_run(_state, argc, argv);
171 	}
172 
173 	/** Evaluate (Compile, link and run) `source` by running its `main()` and returning its value.
174 		DO NOT call `relocate()` before.
175 	 */
176 	int eval(in char[] source, int argc, char** argv) scope @trusted {
177 		typeof(return) ret;
178 		ret = setOutputType(TCC.OutputType.exe);
179 		if (ret != 0)
180 			return ret;
181 		ret = compile(source);
182 		if (ret != 0)
183 			return ret;
184 		return run(argc, argv);
185 	}
186 	alias compileAndRun = eval;
187 
188 	/** Do all relocations (needed before using `get_symbol()`) */
189 	int relocate(void* ptr);	// TOOD: define
190 
191 	/** Possible values for 'ptr':
192 		- RELOCATE_AUTO : Allocate and manage memory internally
193 		- NULL			  : return required memory size for the step below
194 		- memory address	: copy code to memory passed by the caller
195 	  Returns -1 if error.
196 	 */
197 	enum RELOCATE_AUTO = cast(void*) 1;
198 
199 	/** Return value of symbol named `name` or `null` if not found. */
200 	void* getSymbolMaybe(in char[] name) pure scope @trusted {
201 		assertInitialized();
202 		return tcc_get_symbol(_state, name.toStringz);
203 	}
204 
205 	TCCState* _state;
206 }
207 
208 ///
209 pure @safe unittest {
210 	TCC tcc;
211 	assert(tcc.compile(`int f(int x) { return x; }`) == 0);
212 }
213 
214 ///
215 pure @safe unittest {
216 	TCC tcc;
217 	static extern(C) void error_func_ignore(void* opaque, const char* msg) {}
218 	tcc.setErrorFunction((void*).init, &error_func_ignore);
219 	assert(tcc.compile(`int f(int x) { return; }`) == 0); /+ TODO: check warning output +/
220 	assert(tcc.compile(`int f(int x) { return }`) == -1); /+ TODO: check error output +/
221 }
222 
223 /// Compile main().
224 pure @safe unittest {
225 	TCC tcc;
226 	assert(tcc.compile(`int main(int argc, char** argv) { return 0; }`) == 0);
227 }
228 
229 /// Run main().
230 @safe unittest {
231 	version (linux) {
232 		TCC tcc;
233 		tcc.setOutputType(TCC.OutputType.exe);
234 		const src = `int main(int argc, char** argv) { return 42; }`;
235 		assert(tcc.eval(src, 0, null) == 42);
236 	}
237 }
238 
239 /// Use stdio.h.
240 @safe unittest {
241 	version (linux) {
242 		TCC tcc;
243 		tcc.setOutputType(TCC.OutputType.exe);
244 		const src = `
245 #include <stdio.h>
246 int main(int argc, char** argv) {
247   printf("Hello world!\n");
248   return 0; }
249 `;
250 		assert(tcc.eval(src, 0, null) == 0);
251 		// assert(tcc.addLibraryPath(DirPath("/usr/lib/x86_64-linux-gnu/")) == 0);
252 		// assert(tcc.addLibrary("c") == 0); // C library
253 	}
254 }
255 
256 /// Use gmp.h.
257 @safe unittest {
258 	version (linux) {
259 		TCC tcc;
260 		tcc.setOutputType(TCC.OutputType.exe);
261 		const src = `
262 #include <gmp.h>
263 int main(int argc, char** argv) {
264   mpz_t x;
265   mpz_init_set_ui(x, 42);
266   const ret = mpz_get_ui(x);
267   mpz_clear(x);
268   return ret;
269 }
270 `;
271 		assert(tcc.addLibrary("gmp") == 0); // GNU MP
272 		assert(tcc.eval(src, 0, null) == 42);
273 	}
274 }
275 
276 /// Set system include paths.
277 pure @safe unittest {
278 	TCC tcc;
279 	tcc.addSystemIncludePath(DirPath(`/usr/include/`));
280 	tcc.addSystemIncludePath(DirPath(`/usr/lib/x86_64-linux-gnu/tcc/`));
281 	tcc.addSystemIncludePath(DirPath(`/usr/include/linux/`));
282 	tcc.addSystemIncludePath(DirPath(`/usr/include/x86_64-linux-gnu/`));
283 }
284 
285 /++ In-memory compilation.
286 	See: https://gist.github.com/llandsmeer/3489603e5070ee8dbe75cdf1865a5ca9
287  +/
288 @safe unittest {
289 	TCC tcc;
290 
291 	static extern(C) void error_func_ignore(void* opaque, const char* msg) @trusted {
292 		import core.stdc.string : strlen;
293 		import std.stdio;
294 		printf("opaque:%p\nmsg:\n%s\n", opaque, msg);
295 	}
296 	tcc.setErrorFunction((void*).init, &error_func_ignore);
297 
298 	tcc.setOutputType(TCC.OutputType.memory);
299 
300 	import std.datetime.stopwatch : StopWatch, AutoStart;
301 	auto sw = StopWatch(AutoStart.yes);
302 	static extern(C) int add1(int x) { return x+1; }
303 	tcc.addSymbol("add1", &add1);
304 	tcc.compile("int add1(int); int main() { return add1(41); }");
305 	import std.stdio;
306 	writeln("tcc.compile took ", sw.peek);
307 
308 	sw.reset();
309 	const exitStatus = tcc.run(0, null);
310 	writeln("tcc.run took:", sw.peek);
311 
312 	assert(exitStatus == 42);
313 }