extralite-bundle 1.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,385 @@
1
+ #include <stdio.h>
2
+ #include "extralite.h"
3
+
4
+ VALUE cDatabase;
5
+ VALUE cError;
6
+ VALUE cSQLError;
7
+ VALUE cBusyError;
8
+
9
+ ID ID_KEYS;
10
+ ID ID_NEW;
11
+ ID ID_STRIP;
12
+ ID ID_TO_S;
13
+
14
+ static size_t Database_size(const void *ptr) {
15
+ return sizeof(Database_t);
16
+ }
17
+
18
+ static void Database_free(void *ptr) {
19
+ Database_t *db = ptr;
20
+ if (db->sqlite3_db) sqlite3_close(db->sqlite3_db);
21
+ free(ptr);
22
+ }
23
+
24
+ static const rb_data_type_t Database_type = {
25
+ "Database",
26
+ {0, Database_free, Database_size,},
27
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
28
+ };
29
+
30
+ static VALUE Database_allocate(VALUE klass) {
31
+ Database_t *db = ALLOC(Database_t);
32
+ db->sqlite3_db = 0;
33
+ return TypedData_Wrap_Struct(klass, &Database_type, db);
34
+ }
35
+
36
+ #define GetDatabase(obj, database) \
37
+ TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
38
+
39
+ // make sure the database is open
40
+ #define GetOpenDatabase(obj, database) { \
41
+ TypedData_Get_Struct((obj), Database_t, &Database_type, (database)); \
42
+ if (!(database)->sqlite3_db) { \
43
+ rb_raise(cError, "Database is closed"); \
44
+ } \
45
+ }
46
+
47
+ sqlite3 *Database_sqlite3_db(VALUE self) {
48
+ Database_t *db;
49
+ GetDatabase(self, db);
50
+ return db->sqlite3_db;
51
+ }
52
+
53
+ /* call-seq:
54
+ * Extralite.sqlite3_version -> version
55
+ *
56
+ * Returns the sqlite3 version used by Extralite.
57
+ */
58
+
59
+ VALUE Extralite_sqlite3_version(VALUE self) {
60
+ return rb_str_new_cstr(sqlite3_version);
61
+ }
62
+
63
+ /* call-seq:
64
+ * db.initialize(path)
65
+ *
66
+ * Initializes a new SQLite database with the given path.
67
+ */
68
+
69
+ VALUE Database_initialize(VALUE self, VALUE path) {
70
+ int rc;
71
+ Database_t *db;
72
+ GetDatabase(self, db);
73
+
74
+ rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
75
+ if (rc) {
76
+ sqlite3_close(db->sqlite3_db);
77
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
78
+ }
79
+
80
+ rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
81
+ if (rc) {
82
+ sqlite3_close(db->sqlite3_db);
83
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
84
+ }
85
+
86
+ return Qnil;
87
+ }
88
+
89
+ /* call-seq:
90
+ * db.close -> db
91
+ *
92
+ * Closes the database.
93
+ */
94
+ VALUE Database_close(VALUE self) {
95
+ int rc;
96
+ Database_t *db;
97
+ GetDatabase(self, db);
98
+
99
+ rc = sqlite3_close(db->sqlite3_db);
100
+ if (rc) {
101
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
102
+ }
103
+
104
+ db->sqlite3_db = 0;
105
+ return self;
106
+ }
107
+
108
+ /* call-seq:
109
+ * db.closed? -> bool
110
+ *
111
+ * Returns true if the database is closed.
112
+ *
113
+ * @return [bool] is database closed
114
+ */
115
+ VALUE Database_closed_p(VALUE self) {
116
+ Database_t *db;
117
+ GetDatabase(self, db);
118
+
119
+ return db->sqlite3_db ? Qfalse : Qtrue;
120
+ }
121
+
122
+ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
123
+ Database_t *db;
124
+ sqlite3_stmt *stmt;
125
+ VALUE sql;
126
+
127
+ // extract query from args
128
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
129
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
130
+ if (RSTRING_LEN(sql) == 0) return Qnil;
131
+
132
+ // prepare query ctx
133
+ GetOpenDatabase(self, db);
134
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
135
+ bind_all_parameters(stmt, argc - 1, argv + 1);
136
+ query_ctx ctx = { self, db->sqlite3_db, stmt };
137
+
138
+ return rb_ensure(SAFE(call), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
139
+ }
140
+
141
+ /* call-seq:
142
+ * db.query(sql, *parameters, &block) -> [...]
143
+ * db.query_hash(sql, *parameters, &block) -> [...]
144
+ *
145
+ * Runs a query returning rows as hashes (with symbol keys). If a block is
146
+ * given, it will be called for each row. Otherwise, an array containing all
147
+ * rows is returned.
148
+ *
149
+ * Query parameters to be bound to placeholders in the query can be specified as
150
+ * a list of values or as a hash mapping parameter names to values. When
151
+ * parameters are given as a least, the query should specify parameters using
152
+ * `?`:
153
+ *
154
+ * db.query('select * from foo where x = ?', 42)
155
+ *
156
+ * Named placeholders are specified using `:`. The placeholder values are
157
+ * specified using a hash, where keys are either strings are symbols. String
158
+ * keys can include or omit the `:` prefix. The following are equivalent:
159
+ *
160
+ * db.query('select * from foo where x = :bar', bar: 42)
161
+ * db.query('select * from foo where x = :bar', 'bar' => 42)
162
+ * db.query('select * from foo where x = :bar', ':bar' => 42)
163
+ */
164
+ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
165
+ return Database_perform_query(argc, argv, self, safe_query_hash);
166
+ }
167
+
168
+ /* call-seq:
169
+ * db.query_ary(sql, *parameters, &block) -> [...]
170
+ *
171
+ * Runs a query returning rows as arrays. If a block is given, it will be called
172
+ * for each row. Otherwise, an array containing all rows is returned.
173
+ *
174
+ * Query parameters to be bound to placeholders in the query can be specified as
175
+ * a list of values or as a hash mapping parameter names to values. When
176
+ * parameters are given as a least, the query should specify parameters using
177
+ * `?`:
178
+ *
179
+ * db.query_ary('select * from foo where x = ?', 42)
180
+ *
181
+ * Named placeholders are specified using `:`. The placeholder values are
182
+ * specified using a hash, where keys are either strings are symbols. String
183
+ * keys can include or omit the `:` prefix. The following are equivalent:
184
+ *
185
+ * db.query_ary('select * from foo where x = :bar', bar: 42)
186
+ * db.query_ary('select * from foo where x = :bar', 'bar' => 42)
187
+ * db.query_ary('select * from foo where x = :bar', ':bar' => 42)
188
+ */
189
+ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
190
+ return Database_perform_query(argc, argv, self, safe_query_ary);
191
+ }
192
+
193
+ /* call-seq:
194
+ * db.query_single_row(sql, *parameters) -> {...}
195
+ *
196
+ * Runs a query returning a single row as a hash.
197
+ *
198
+ * Query parameters to be bound to placeholders in the query can be specified as
199
+ * a list of values or as a hash mapping parameter names to values. When
200
+ * parameters are given as a least, the query should specify parameters using
201
+ * `?`:
202
+ *
203
+ * db.query_single_row('select * from foo where x = ?', 42)
204
+ *
205
+ * Named placeholders are specified using `:`. The placeholder values are
206
+ * specified using a hash, where keys are either strings are symbols. String
207
+ * keys can include or omit the `:` prefix. The following are equivalent:
208
+ *
209
+ * db.query_single_row('select * from foo where x = :bar', bar: 42)
210
+ * db.query_single_row('select * from foo where x = :bar', 'bar' => 42)
211
+ * db.query_single_row('select * from foo where x = :bar', ':bar' => 42)
212
+ */
213
+ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
214
+ return Database_perform_query(argc, argv, self, safe_query_single_row);
215
+ }
216
+
217
+ /* call-seq:
218
+ * db.query_single_column(sql, *parameters, &block) -> [...]
219
+ *
220
+ * Runs a query returning single column values. If a block is given, it will be called
221
+ * for each value. Otherwise, an array containing all values is returned.
222
+ *
223
+ * Query parameters to be bound to placeholders in the query can be specified as
224
+ * a list of values or as a hash mapping parameter names to values. When
225
+ * parameters are given as a least, the query should specify parameters using
226
+ * `?`:
227
+ *
228
+ * db.query_single_column('select x from foo where x = ?', 42)
229
+ *
230
+ * Named placeholders are specified using `:`. The placeholder values are
231
+ * specified using a hash, where keys are either strings are symbols. String
232
+ * keys can include or omit the `:` prefix. The following are equivalent:
233
+ *
234
+ * db.query_single_column('select x from foo where x = :bar', bar: 42)
235
+ * db.query_single_column('select x from foo where x = :bar', 'bar' => 42)
236
+ * db.query_single_column('select x from foo where x = :bar', ':bar' => 42)
237
+ */
238
+ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
239
+ return Database_perform_query(argc, argv, self, safe_query_single_column);
240
+ }
241
+
242
+ /* call-seq:
243
+ * db.query_single_value(sql, *parameters) -> value
244
+ *
245
+ * Runs a query returning a single value from the first row.
246
+ *
247
+ * Query parameters to be bound to placeholders in the query can be specified as
248
+ * a list of values or as a hash mapping parameter names to values. When
249
+ * parameters are given as a least, the query should specify parameters using
250
+ * `?`:
251
+ *
252
+ * db.query_single_value('select x from foo where x = ?', 42)
253
+ *
254
+ * Named placeholders are specified using `:`. The placeholder values are
255
+ * specified using a hash, where keys are either strings are symbols. String
256
+ * keys can include or omit the `:` prefix. The following are equivalent:
257
+ *
258
+ * db.query_single_value('select x from foo where x = :bar', bar: 42)
259
+ * db.query_single_value('select x from foo where x = :bar', 'bar' => 42)
260
+ * db.query_single_value('select x from foo where x = :bar', ':bar' => 42)
261
+ */
262
+ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
263
+ return Database_perform_query(argc, argv, self, safe_query_single_value);
264
+ }
265
+
266
+ /* call-seq:
267
+ * db.last_insert_rowid -> int
268
+ *
269
+ * Returns the rowid of the last inserted row.
270
+ */
271
+ VALUE Database_last_insert_rowid(VALUE self) {
272
+ Database_t *db;
273
+ GetOpenDatabase(self, db);
274
+
275
+ return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
276
+ }
277
+
278
+ /* call-seq:
279
+ * db.changes -> int
280
+ *
281
+ * Returns the number of changes made to the database by the last operation.
282
+ */
283
+ VALUE Database_changes(VALUE self) {
284
+ Database_t *db;
285
+ GetOpenDatabase(self, db);
286
+
287
+ return INT2NUM(sqlite3_changes(db->sqlite3_db));
288
+ }
289
+
290
+ /* call-seq:
291
+ * db.filename -> string
292
+ *
293
+ * Returns the database filename.
294
+ */
295
+ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
296
+ const char *db_name;
297
+ const char *filename;
298
+ Database_t *db;
299
+ GetOpenDatabase(self, db);
300
+
301
+ rb_check_arity(argc, 0, 1);
302
+ db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
303
+ filename = sqlite3_db_filename(db->sqlite3_db, db_name);
304
+ return filename ? rb_str_new_cstr(filename) : Qnil;
305
+ }
306
+
307
+ /* call-seq:
308
+ * db.transaction_active? -> bool
309
+ *
310
+ * Returns true if a transaction is currently in progress.
311
+ */
312
+ VALUE Database_transaction_active_p(VALUE self) {
313
+ Database_t *db;
314
+ GetOpenDatabase(self, db);
315
+
316
+ return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
317
+ }
318
+
319
+ /* call-seq:
320
+ * db.load_extension(path) -> db
321
+ *
322
+ * Loads an extension with the given path.
323
+ */
324
+ VALUE Database_load_extension(VALUE self, VALUE path) {
325
+ Database_t *db;
326
+ GetOpenDatabase(self, db);
327
+ char *err_msg;
328
+
329
+ int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
330
+ if (rc != SQLITE_OK) {
331
+ VALUE error = rb_exc_new2(cError, err_msg);
332
+ sqlite3_free(err_msg);
333
+ rb_exc_raise(error);
334
+ }
335
+
336
+ return self;
337
+ }
338
+
339
+ /* call-seq:
340
+ * db.prepare(sql) -> Extralite::PreparedStatement
341
+ *
342
+ * Creates a prepared statement with the given SQL query.
343
+ */
344
+ VALUE Database_prepare(VALUE self, VALUE sql) {
345
+ return rb_funcall(cPreparedStatement, ID_NEW, 2, self, sql);
346
+ }
347
+
348
+ void Init_ExtraliteDatabase() {
349
+ VALUE mExtralite = rb_define_module("Extralite");
350
+ rb_define_singleton_method(mExtralite, "sqlite3_version", Extralite_sqlite3_version, 0);
351
+
352
+ cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
353
+ rb_define_alloc_func(cDatabase, Database_allocate);
354
+
355
+ rb_define_method(cDatabase, "initialize", Database_initialize, 1);
356
+ rb_define_method(cDatabase, "close", Database_close, 0);
357
+ rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
358
+
359
+ rb_define_method(cDatabase, "query", Database_query_hash, -1);
360
+ rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
361
+ rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
362
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
363
+ rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
364
+ rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
365
+
366
+ rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
367
+ rb_define_method(cDatabase, "changes", Database_changes, 0);
368
+ rb_define_method(cDatabase, "filename", Database_filename, -1);
369
+ rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
370
+ rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
371
+
372
+ rb_define_method(cDatabase, "prepare", Database_prepare, 1);
373
+
374
+ cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
375
+ cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
376
+ cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
377
+ rb_gc_register_mark_object(cError);
378
+ rb_gc_register_mark_object(cSQLError);
379
+ rb_gc_register_mark_object(cBusyError);
380
+
381
+ ID_KEYS = rb_intern("keys");
382
+ ID_NEW = rb_intern("new");
383
+ ID_STRIP = rb_intern("strip");
384
+ ID_TO_S = rb_intern("to_s");
385
+ }
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ $CFLAGS << " -Wno-undef"
6
+ $CFLAGS << " -Wno-discarded-qualifiers"
7
+
8
+ dir_config('extralite_ext')
9
+ create_makefile('extralite_ext')
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'rubygems'
4
+ # require 'mkmf'
5
+
6
+ # # $CFLAGS << "-Wdiscarded-qualifier"
7
+ # # $CFLAGS << " -Wno-comment"
8
+ # # $CFLAGS << " -Wno-unused-result"
9
+ # # $CFLAGS << " -Wno-dangling-else"
10
+ # # $CFLAGS << " -Wno-parentheses"
11
+
12
+ # dir_config 'extralite_ext'
13
+ # create_makefile 'extralite_ext'
14
+
15
+ ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
16
+
17
+ require 'mkmf'
18
+
19
+ # :stopdoc:
20
+
21
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
22
+
23
+ ldflags = cppflags = nil
24
+ if RbConfig::CONFIG["host_os"] =~ /darwin/
25
+ begin
26
+ if with_config('sqlcipher')
27
+ brew_prefix = `brew --prefix sqlcipher`.chomp
28
+ ldflags = "#{brew_prefix}/lib"
29
+ cppflags = "#{brew_prefix}/include/sqlcipher"
30
+ pkg_conf = "#{brew_prefix}/lib/pkgconfig"
31
+ else
32
+ brew_prefix = `brew --prefix sqlite3`.chomp
33
+ ldflags = "#{brew_prefix}/lib"
34
+ cppflags = "#{brew_prefix}/include"
35
+ pkg_conf = "#{brew_prefix}/lib/pkgconfig"
36
+ end
37
+
38
+ # pkg_config should be less error prone than parsing compiler
39
+ # commandline options, but we need to set default ldflags and cpp flags
40
+ # in case the user doesn't have pkg-config installed
41
+ ENV['PKG_CONFIG_PATH'] ||= pkg_conf
42
+ rescue
43
+ end
44
+ end
45
+
46
+ if with_config('sqlcipher')
47
+ pkg_config("sqlcipher")
48
+ else
49
+ pkg_config("sqlite3")
50
+ end
51
+
52
+ # --with-sqlite3-{dir,include,lib}
53
+ if with_config('sqlcipher')
54
+ $CFLAGS << ' -DUSING_SQLCIPHER'
55
+ dir_config("sqlcipher", cppflags, ldflags)
56
+ else
57
+ dir_config("sqlite3", cppflags, ldflags)
58
+ end
59
+
60
+ if RbConfig::CONFIG["host_os"] =~ /mswin/
61
+ $CFLAGS << ' -W3'
62
+ end
63
+
64
+ if RUBY_VERSION < '2.7'
65
+ $CFLAGS << ' -DTAINTING_SUPPORT'
66
+ end
67
+
68
+ def asplode missing
69
+ if RUBY_PLATFORM =~ /mingw|mswin/
70
+ abort "#{missing} is missing. Install SQLite3 from " +
71
+ "http://www.sqlite.org/ first."
72
+ else
73
+ abort <<-error
74
+ #{missing} is missing. Try 'brew install sqlite3',
75
+ 'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
76
+ and check your shared library search path (the
77
+ location where your sqlite3 shared library is located).
78
+ error
79
+ end
80
+ end
81
+
82
+ asplode('sqlite3.h') unless find_header 'sqlite3.h'
83
+ find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
84
+
85
+ have_library 'dl' # for static builds
86
+
87
+ if with_config('sqlcipher')
88
+ asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
89
+ else
90
+ asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
91
+ end
92
+
93
+ # Functions defined in 1.9 but not 1.8
94
+ have_func('rb_proc_arity')
95
+
96
+ # Functions defined in 2.1 but not 2.0
97
+ have_func('rb_integer_pack')
98
+
99
+ # These functions may not be defined
100
+ have_func('sqlite3_initialize')
101
+ have_func('sqlite3_enable_load_extension')
102
+ have_func('sqlite3_load_extension')
103
+
104
+ unless have_func('sqlite3_open_v2')
105
+ abort "Please use a newer version of SQLite3"
106
+ end
107
+
108
+ have_func('sqlite3_prepare_v2')
109
+ have_type('sqlite3_int64', 'sqlite3.h')
110
+ have_type('sqlite3_uint64', 'sqlite3.h')
111
+
112
+ $defs << "-DEXTRALITE_NO_BUNDLE"
113
+
114
+ dir_config('extralite_ext')
115
+ create_makefile('extralite_ext')
@@ -0,0 +1,64 @@
1
+ #ifndef EXTRALITE_H
2
+ #define EXTRALITE_H
3
+
4
+ #include "ruby.h"
5
+ #include "ruby/thread.h"
6
+
7
+ #ifdef EXTRALITE_NO_BUNDLE
8
+ #include <sqlite3.h>
9
+ #else
10
+ #include "../sqlite3/sqlite3.h"
11
+ #endif
12
+
13
+ // debug utility
14
+ #define INSPECT(str, obj) { \
15
+ printf(str); \
16
+ VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); \
17
+ printf(": %s\n", StringValueCStr(s)); \
18
+ }
19
+
20
+ #define SAFE(f) (VALUE (*)(VALUE))(f)
21
+
22
+ extern VALUE cDatabase;
23
+ extern VALUE cPreparedStatement;
24
+
25
+ extern VALUE cError;
26
+ extern VALUE cSQLError;
27
+ extern VALUE cBusyError;
28
+
29
+ extern ID ID_KEYS;
30
+ extern ID ID_NEW;
31
+ extern ID ID_STRIP;
32
+ extern ID ID_TO_S;
33
+
34
+ typedef struct {
35
+ sqlite3 *sqlite3_db;
36
+ } Database_t;
37
+
38
+ typedef struct {
39
+ VALUE db;
40
+ VALUE sql;
41
+ sqlite3 *sqlite3_db;
42
+ sqlite3_stmt *stmt;
43
+ } PreparedStatement_t;
44
+
45
+ typedef struct {
46
+ VALUE self;
47
+ sqlite3 *sqlite3_db;
48
+ sqlite3_stmt *stmt;
49
+ } query_ctx;
50
+
51
+ VALUE safe_query_ary(query_ctx *ctx);
52
+ VALUE safe_query_hash(query_ctx *ctx);
53
+ VALUE safe_query_single_column(query_ctx *ctx);
54
+ VALUE safe_query_single_row(query_ctx *ctx);
55
+ VALUE safe_query_single_value(query_ctx *ctx);
56
+
57
+ void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
58
+ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
59
+ void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
60
+ VALUE cleanup_stmt(query_ctx *ctx);
61
+
62
+ sqlite3 *Database_sqlite3_db(VALUE self);
63
+
64
+ #endif /* EXTRALITE_H */
@@ -0,0 +1,7 @@
1
+ void Init_ExtraliteDatabase();
2
+ void Init_ExtralitePreparedStatement();
3
+
4
+ void Init_extralite_ext() {
5
+ Init_ExtraliteDatabase();
6
+ Init_ExtralitePreparedStatement();
7
+ }
@@ -0,0 +1,3 @@
1
+ #ifndef EXTRALITE_NO_BUNDLE
2
+ #include "../sqlite3/sqlite3.c"
3
+ #endif