2 * semind - semantic indexer for C.
4 * Copyright (C) 2020 Alexey Gladkov
23 #define U_DEF (0x100 << U_SHIFT)
24 #define SINDEX_DATABASE_VERSION 1
26 #define message(fmt, ...) semind_error(0, 0, (fmt), ##__VA_ARGS__)
28 static const char *progname
;
29 static const char *semind_command
= NULL
;
32 static const char *semind_dbfile
= "semind.sqlite";
33 static int semind_verbose
= 0;
34 static char cwd
[PATH_MAX
];
37 // 'add' command options
38 static struct string_list
*semind_filelist
= NULL
;
39 static int semind_include_local_syms
= 0;
41 struct semind_streams
{
45 static struct semind_streams
*semind_streams
= NULL
;
46 static int semind_streams_nr
= 0;
48 // 'search' command options
49 static int semind_search_modmask
;
50 static int semind_search_modmask_defined
= 0;
51 static int semind_search_kind
= 0;
52 static char *semind_search_path
= NULL
;
53 static char *semind_search_symbol
= NULL
;
54 static const char *semind_search_format
= "(%m) %f\t%l\t%c\t%C\t%s";
56 #define EXPLAIN_LOCATION 1
57 #define USAGE_BY_LOCATION 2
58 static int semind_search_by_location
;
59 static char *semind_search_filename
;
60 static int semind_search_line
;
61 static int semind_search_column
;
63 static sqlite3
*semind_db
= NULL
;
64 static sqlite3_stmt
*lock_stmt
= NULL
;
65 static sqlite3_stmt
*unlock_stmt
= NULL
;
66 static sqlite3_stmt
*insert_rec_stmt
= NULL
;
67 static sqlite3_stmt
*select_file_stmt
= NULL
;
68 static sqlite3_stmt
*insert_file_stmt
= NULL
;
69 static sqlite3_stmt
*delete_file_stmt
= NULL
;
74 void (*parse_cmdline
)(int argc
, char **argv
);
75 void (*handler
)(int argc
, char **argv
);
78 static void show_usage(void)
81 printf("Try '%s %s --help' for more information.\n",
82 progname
, semind_command
);
84 printf("Try '%s --help' for more information.\n",
89 static void show_help(int ret
)
92 "Usage: %1$s [options]\n"
93 " or: %1$s [options] add [command options] [--] [compiler options] [files...]\n"
94 " or: %1$s [options] rm [command options] pattern\n"
95 " or: %1$s [options] search [command options] pattern\n"
97 "These are common %1$s commands used in various situations:\n"
98 " add Generate or updates semantic index file for c-source code;\n"
99 " rm Remove files from the index by pattern;\n"
100 " search Make index queries.\n"
103 " -D, --database=FILE Specify database file (default: %2$s);\n"
104 " -B, --basedir=DIR Define project top directory (default is the current directory);\n"
105 " -v, --verbose Show information about what is being done;\n"
106 " -h, --help Show this text and exit.\n"
109 " SINDEX_DATABASE Database file location.\n"
110 " SINDEX_BASEDIR Project top directory.\n"
112 "Report bugs to authors.\n"
114 progname
, semind_dbfile
);
118 static void show_help_add(int ret
)
121 "Usage: %1$s add [options] [--] [compiler options] files...\n"
123 "Utility creates or updates a symbol index.\n"
126 " --include-local-syms Include into the index local symbols;\n"
127 " -v, --verbose Show information about what is being done;\n"
128 " -h, --help Show this text and exit.\n"
130 "Report bugs to authors.\n"
137 static void show_help_rm(int ret
)
140 "Usage: %1$s rm [options] pattern\n"
142 "Utility removes source files from the index.\n"
143 "The pattern is a glob(7) wildcard pattern.\n"
146 " -v, --verbose Show information about what is being done;\n"
147 " -h, --help Show this text and exit.\n"
149 "Report bugs to authors.\n"
155 static void show_help_search(int ret
)
158 "Usage: %1$s search [options] [pattern]\n"
159 " or: %1$s search [options] (-e|-l) filename[:linenr[:column]]\n"
161 "Utility searches information about symbol by pattern.\n"
162 "The pattern is a glob(7) wildcard pattern.\n"
165 " -f, --format=STRING Specify an output format;\n"
166 " -p, --path=PATTERN Search symbols only in specified directories;\n"
167 " -m, --mode=MODE Search only the specified type of access;\n"
168 " -k, --kind=KIND Specify a kind of symbol;\n"
169 " -e, --explain Show what happens in the specified file position;\n"
170 " -l, --location Show usage of symbols from a specific file position;\n"
171 " -v, --verbose Show information about what is being done;\n"
172 " -h, --help Show this text and exit.\n"
174 "The KIND can be one of the following: `s', `f', `v', `m'.\n"
176 "Report bugs to authors.\n"
182 static void semind_print_progname(void)
184 fprintf(stderr
, "%s: ", progname
);
186 fprintf(stderr
, "%s: ", semind_command
);
189 static void semind_error(int status
, int errnum
, const char *fmt
, ...)
192 semind_print_progname();
195 vfprintf(stderr
, fmt
, ap
);
199 fprintf(stderr
, ": %s", strerror(errnum
));
201 fprintf(stderr
, "\n");
207 static void set_search_modmask(const char *v
)
209 size_t n
= strlen(v
);
211 if (n
!= 1 && n
!= 3)
212 semind_error(1, 0, "the length of mode value must be 1 or 3: %s", v
);
214 semind_search_modmask_defined
= 1;
215 semind_search_modmask
= 0;
219 case 'r': v
= "rrr"; break;
220 case 'w': v
= "ww-"; break;
221 case 'm': v
= "mmm"; break;
222 case '-': v
= "---"; break;
223 default: semind_error(1, 0, "unknown modificator: %s", v
);
225 } else if (!strcmp(v
, "def")) {
226 semind_search_modmask
= U_DEF
;
230 static const int modes
[] = {
231 U_R_AOF
, U_W_AOF
, U_R_AOF
| U_W_AOF
,
232 U_R_VAL
, U_W_VAL
, U_R_VAL
| U_W_VAL
,
233 U_R_PTR
, U_W_PTR
, U_R_PTR
| U_W_PTR
,
236 for (int i
= 0; i
< 3; i
++) {
238 case 'r': semind_search_modmask
|= modes
[i
* 3]; break;
239 case 'w': semind_search_modmask
|= modes
[i
* 3 + 1]; break;
240 case 'm': semind_search_modmask
|= modes
[i
* 3 + 2]; break;
242 default: semind_error(1, 0,
243 "unknown modificator in the mode value"
244 " (`r', `w', `m' or `-' expected): %c", v
[i
]);
249 static void parse_cmdline(int argc
, char **argv
)
251 static const struct option long_options
[] = {
252 { "database", required_argument
, NULL
, 'D' },
253 { "basedir", required_argument
, NULL
, 'B' },
254 { "verbose", no_argument
, NULL
, 'v' },
255 { "help", no_argument
, NULL
, 'h' },
259 char *basedir
= getenv("SINDEX_BASEDIR");
262 if ((env
= getenv("SINDEX_DATABASE")) != NULL
)
265 while ((c
= getopt_long(argc
, argv
, "+B:D:vh", long_options
, NULL
)) != -1) {
268 semind_dbfile
= optarg
;
281 if (optind
== argc
) {
282 message("command required");
287 if (!realpath(basedir
, cwd
))
288 semind_error(1, errno
, "unable to get project base directory");
293 static void parse_cmdline_add(int argc
, char **argv
)
295 static const struct option long_options
[] = {
296 { "include-local-syms", no_argument
, NULL
, 1 },
297 { "verbose", no_argument
, NULL
, 'v' },
298 { "help", no_argument
, NULL
, 'h' },
305 while ((c
= getopt_long(argc
, argv
, "+vh", long_options
, NULL
)) != -1) {
308 semind_include_local_syms
= 1;
320 if (optind
== argc
) {
321 message("more arguments required");
328 // step back since sparse_initialize will ignore argv[0].
331 sparse_initialize(argc
- optind
, argv
+ optind
, &semind_filelist
);
332 dissect_show_all_symbols
= 1;
335 static void parse_cmdline_rm(int argc
, char **argv
)
337 static const struct option long_options
[] = {
338 { "verbose", no_argument
, NULL
, 'v' },
339 { "help", no_argument
, NULL
, 'h' },
344 while ((c
= getopt_long(argc
, argv
, "+vh", long_options
, NULL
)) != -1) {
354 if (optind
== argc
) {
355 message("more arguments required");
360 static void parse_cmdline_search(int argc
, char **argv
)
362 static const struct option long_options
[] = {
363 { "explain", no_argument
, NULL
, 'e' },
364 { "format", required_argument
, NULL
, 'f' },
365 { "path", required_argument
, NULL
, 'p' },
366 { "location", no_argument
, NULL
, 'l' },
367 { "mode", required_argument
, NULL
, 'm' },
368 { "kind", required_argument
, NULL
, 'k' },
369 { "verbose", no_argument
, NULL
, 'v' },
370 { "help", no_argument
, NULL
, 'h' },
375 while ((c
= getopt_long(argc
, argv
, "+ef:m:k:p:lvh", long_options
, NULL
)) != -1) {
378 semind_search_by_location
= EXPLAIN_LOCATION
;
381 semind_search_by_location
= USAGE_BY_LOCATION
;
384 semind_search_format
= optarg
;
387 set_search_modmask(optarg
);
390 semind_search_kind
= tolower(optarg
[0]);
393 semind_search_path
= optarg
;
403 if (semind_search_by_location
) {
407 semind_error(1, 0, "one argument required");
414 if ((ptr
= strchr(str
, ':')) != NULL
)
418 if (!semind_search_filename
) {
419 semind_search_filename
= str
;
420 } else if (!semind_search_line
) {
421 semind_search_line
= atoi(str
);
422 } else if (!semind_search_column
) {
423 semind_search_column
= atoi(str
);
428 } else if (optind
< argc
)
429 semind_search_symbol
= argv
[optind
++];
432 static int query_appendf(sqlite3_str
*query
, const char *fmt
, ...)
438 sqlite3_str_vappendf(query
, fmt
, args
);
441 if ((status
= sqlite3_str_errcode(query
)) == SQLITE_OK
)
444 if (status
== SQLITE_NOMEM
)
445 message("not enough memory");
447 if (status
== SQLITE_TOOBIG
)
448 message("string too big");
453 static inline void sqlite_bind_text(sqlite3_stmt
*stmt
, const char *field
, const char *var
, int len
)
455 if (sqlite3_bind_text(stmt
, sqlite3_bind_parameter_index(stmt
, field
), var
, len
, SQLITE_STATIC
) != SQLITE_OK
)
456 semind_error(1, 0, "unable to bind value for %s: %s", field
, sqlite3_errmsg(semind_db
));
459 static inline void sqlite_bind_int64(sqlite3_stmt
*stmt
, const char *field
, long long var
)
461 if (sqlite3_bind_int64(stmt
, sqlite3_bind_parameter_index(stmt
, field
), var
) != SQLITE_OK
)
462 semind_error(1, 0, "unable to bind value for %s: %s", field
, sqlite3_errmsg(semind_db
));
465 static inline void sqlite_prepare(const char *sql
, sqlite3_stmt
**stmt
)
469 ret
= sqlite3_prepare_v2(semind_db
, sql
, -1, stmt
, NULL
);
470 if (ret
!= SQLITE_OK
&& ret
!= SQLITE_BUSY
)
471 semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db
), sql
);
472 } while (ret
== SQLITE_BUSY
);
475 static inline void sqlite_prepare_persistent(const char *sql
, sqlite3_stmt
**stmt
)
479 ret
= sqlite3_prepare_v3(semind_db
, sql
, -1, SQLITE_PREPARE_PERSISTENT
, stmt
, NULL
);
480 if (ret
!= SQLITE_OK
&& ret
!= SQLITE_BUSY
)
481 semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db
), sql
);
482 } while (ret
== SQLITE_BUSY
);
485 static inline void sqlite_reset_stmt(sqlite3_stmt
*stmt
)
487 // Contrary to the intuition of many, sqlite3_reset() does not reset the
488 // bindings on a prepared statement. Use this routine to reset all host
489 // parameters to NULL.
490 sqlite3_clear_bindings(stmt
);
494 static int sqlite_run(sqlite3_stmt
*stmt
)
496 int ret
= sqlite3_step(stmt
);
497 if (ret
!= SQLITE_DONE
&& ret
!= SQLITE_ROW
)
498 semind_error(1, 0, "unable to process query: %s: %s", sqlite3_errmsg(semind_db
), sqlite3_sql(stmt
));
502 static void sqlite_command(const char *sql
)
505 sqlite_prepare(sql
, &stmt
);
507 sqlite3_finalize(stmt
);
510 static sqlite3_int64
get_db_version(void)
513 sqlite3_int64 dbversion
;
515 sqlite_prepare("PRAGMA user_version", &stmt
);
517 dbversion
= sqlite3_column_int64(stmt
, 0);
518 sqlite3_finalize(stmt
);
523 static void set_db_version(void)
526 sqlite3_str
*query
= sqlite3_str_new(semind_db
);
528 if (query_appendf(query
, "PRAGMA user_version = %d", SINDEX_DATABASE_VERSION
) < 0)
531 sql
= sqlite3_str_finish(query
);
536 static void open_temp_database(void)
538 static const char *database_schema
[] = {
539 "ATTACH ':memory:' AS tempdb",
540 "CREATE TABLE tempdb.semind ("
541 " file INTEGER NOT NULL,"
542 " line INTEGER NOT NULL,"
543 " column INTEGER NOT NULL,"
544 " symbol TEXT NOT NULL,"
545 " kind INTEGER NOT NULL,"
547 " mode INTEGER NOT NULL"
552 for (int i
= 0; database_schema
[i
]; i
++)
553 sqlite_command(database_schema
[i
]);
556 static void open_database(const char *filename
, int flags
)
558 static const char *database_schema
[] = {
559 "CREATE TABLE file ("
560 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
561 " name TEXT UNIQUE NOT NULL,"
562 " mtime INTEGER NOT NULL"
564 "CREATE TABLE semind ("
565 " file INTEGER NOT NULL REFERENCES file(id) ON DELETE CASCADE,"
566 " line INTEGER NOT NULL,"
567 " column INTEGER NOT NULL,"
568 " symbol TEXT NOT NULL,"
569 " kind INTEGER NOT NULL,"
571 " mode INTEGER NOT NULL"
573 "CREATE UNIQUE INDEX semind_0 ON semind (symbol, kind, mode, file, line, column)",
574 "CREATE INDEX semind_1 ON semind (file)",
578 int exists
= !access(filename
, R_OK
);
580 if (sqlite3_open_v2(filename
, &semind_db
, flags
, NULL
) != SQLITE_OK
)
581 semind_error(1, 0, "unable to open database: %s: %s", filename
, sqlite3_errmsg(semind_db
));
583 sqlite_command("PRAGMA journal_mode = WAL");
584 sqlite_command("PRAGMA synchronous = OFF");
585 sqlite_command("PRAGMA secure_delete = FAST");
586 sqlite_command("PRAGMA busy_timeout = 2147483647");
587 sqlite_command("PRAGMA foreign_keys = ON");
590 if (get_db_version() < SINDEX_DATABASE_VERSION
)
591 semind_error(1, 0, "%s: Database too old. Please rebuild it.", filename
);
597 for (int i
= 0; database_schema
[i
]; i
++)
598 sqlite_command(database_schema
[i
]);
601 struct index_record
{
616 static void insert_record(struct index_record
*rec
)
618 sqlite_bind_text(insert_rec_stmt
, "@context", rec
->context
, rec
->ctx_len
);
619 sqlite_bind_text(insert_rec_stmt
, "@symbol", rec
->symbol
, rec
->sym_len
);
620 sqlite_bind_int64(insert_rec_stmt
, "@kind", rec
->kind
);
621 sqlite_bind_int64(insert_rec_stmt
, "@mode", rec
->mode
);
622 sqlite_bind_int64(insert_rec_stmt
, "@file", rec
->file
);
623 sqlite_bind_int64(insert_rec_stmt
, "@line", rec
->line
);
624 sqlite_bind_int64(insert_rec_stmt
, "@column", rec
->col
);
625 sqlite_run(insert_rec_stmt
);
626 sqlite_reset_stmt(insert_rec_stmt
);
629 static void update_stream(void)
631 if (semind_streams_nr
>= input_stream_nr
)
634 semind_streams
= realloc(semind_streams
, input_stream_nr
* sizeof(struct semind_streams
));
636 semind_error(1, errno
, "realloc");
638 sqlite_run(lock_stmt
);
640 for (int i
= semind_streams_nr
; i
< input_stream_nr
; i
++) {
642 const char *filename
;
643 char fullname
[PATH_MAX
];
644 sqlite3_int64 cur_mtime
= 0;
646 if (input_streams
[i
].fd
!= -1) {
648 * FIXME: Files in the input_streams may be duplicated.
650 if (stat(input_streams
[i
].name
, &st
) < 0)
651 semind_error(1, errno
, "stat: %s", input_streams
[i
].name
);
653 cur_mtime
= st
.st_mtime
;
655 if (!realpath(input_streams
[i
].name
, fullname
))
656 semind_error(1, errno
, "realpath: %s", input_streams
[i
].name
);
658 if (!strncmp(fullname
, cwd
, n_cwd
) && fullname
[n_cwd
] == '/') {
659 filename
= fullname
+ n_cwd
+ 1;
660 semind_streams
[i
].id
= 0;
662 semind_streams
[i
].id
= -1;
666 semind_streams
[i
].id
= -1;
670 if (semind_verbose
> 1)
671 message("filename: %s", filename
);
673 sqlite_bind_text(select_file_stmt
, "@name", filename
, -1);
675 if (sqlite_run(select_file_stmt
) == SQLITE_ROW
) {
676 sqlite3_int64 old_mtime
;
678 semind_streams
[i
].id
= sqlite3_column_int64(select_file_stmt
, 0);
679 old_mtime
= sqlite3_column_int64(select_file_stmt
, 1);
681 sqlite_reset_stmt(select_file_stmt
);
683 if (cur_mtime
== old_mtime
)
686 sqlite_bind_text(delete_file_stmt
, "@name", filename
, -1);
687 sqlite_run(delete_file_stmt
);
688 sqlite_reset_stmt(delete_file_stmt
);
691 sqlite_reset_stmt(select_file_stmt
);
693 sqlite_bind_text(insert_file_stmt
, "@name", filename
, -1);
694 sqlite_bind_int64(insert_file_stmt
, "@mtime", cur_mtime
);
695 sqlite_run(insert_file_stmt
);
696 sqlite_reset_stmt(insert_file_stmt
);
698 semind_streams
[i
].id
= sqlite3_last_insert_rowid(semind_db
);
701 sqlite_run(unlock_stmt
);
703 semind_streams_nr
= input_stream_nr
;
706 static void r_symbol(unsigned mode
, struct position
*pos
, struct symbol
*sym
)
708 static struct ident null
;
709 struct ident
*ctx
= &null
;
710 struct index_record rec
;
714 if (semind_streams
[pos
->stream
].id
== -1)
717 if (!semind_include_local_syms
&& sym_is_local(sym
))
721 warning(*pos
, "empty ident");
726 ctx
= dissect_ctx
->ident
;
728 rec
.context
= ctx
->name
;
729 rec
.ctx_len
= ctx
->len
;
730 rec
.symbol
= sym
->ident
->name
;
731 rec
.sym_len
= sym
->ident
->len
;
732 rec
.kind
= sym
->kind
;
734 rec
.file
= semind_streams
[pos
->stream
].id
;
735 rec
.line
= pos
->line
;
741 static void r_member(unsigned mode
, struct position
*pos
, struct symbol
*sym
, struct symbol
*mem
)
743 static struct ident null
;
744 static char memname
[1024];
745 struct ident
*ni
, *si
, *mi
;
746 struct ident
*ctx
= &null
;
747 struct index_record rec
;
751 if (semind_streams
[pos
->stream
].id
== -1)
754 if (!semind_include_local_syms
&& sym_is_local(sym
))
757 ni
= built_in_ident("?");
758 si
= sym
->ident
?: ni
;
759 /* mem == NULL means entire struct accessed */
760 mi
= mem
? (mem
->ident
?: ni
) : built_in_ident("*");
763 ctx
= dissect_ctx
->ident
;
765 snprintf(memname
, sizeof(memname
), "%.*s.%.*s", si
->len
, si
->name
, mi
->len
, mi
->name
);
767 rec
.context
= ctx
->name
;
768 rec
.ctx_len
= ctx
->len
;
769 rec
.symbol
= memname
;
770 rec
.sym_len
= si
->len
+ mi
->len
+ 1;
773 rec
.file
= semind_streams
[pos
->stream
].id
;
774 rec
.line
= pos
->line
;
780 static void r_symdef(struct symbol
*sym
)
782 r_symbol(U_DEF
, &sym
->pos
, sym
);
785 static void r_memdef(struct symbol
*sym
, struct symbol
*mem
)
787 r_member(U_DEF
, &mem
->pos
, sym
, mem
);
790 static void command_add(int argc
, char **argv
)
792 static struct reporter reporter
= {
793 .r_symdef
= r_symdef
,
794 .r_symbol
= r_symbol
,
795 .r_memdef
= r_memdef
,
796 .r_member
= r_member
,
799 open_temp_database();
801 sqlite_prepare_persistent(
805 sqlite_prepare_persistent(
809 sqlite_prepare_persistent(
810 "INSERT OR IGNORE INTO tempdb.semind "
811 "(context, symbol, kind, mode, file, line, column) "
812 "VALUES (@context, @symbol, @kind, @mode, @file, @line, @column)",
815 sqlite_prepare_persistent(
816 "SELECT id, mtime FROM file WHERE name == @name",
819 sqlite_prepare_persistent(
820 "INSERT INTO file (name, mtime) VALUES (@name, @mtime)",
823 sqlite_prepare_persistent(
824 "DELETE FROM file WHERE name == @name",
827 dissect(&reporter
, semind_filelist
);
829 sqlite_run(lock_stmt
);
830 sqlite_command("INSERT OR IGNORE INTO semind SELECT * FROM tempdb.semind");
831 sqlite_run(unlock_stmt
);
833 sqlite3_finalize(insert_rec_stmt
);
834 sqlite3_finalize(select_file_stmt
);
835 sqlite3_finalize(insert_file_stmt
);
836 sqlite3_finalize(delete_file_stmt
);
837 sqlite3_finalize(lock_stmt
);
838 sqlite3_finalize(unlock_stmt
);
839 free(semind_streams
);
842 static void command_rm(int argc
, char **argv
)
846 sqlite_command("BEGIN IMMEDIATE");
847 sqlite_prepare("DELETE FROM file WHERE name GLOB @file", &stmt
);
849 if (semind_verbose
> 1)
850 message("SQL: %s", sqlite3_sql(stmt
));
852 for (int i
= 0; i
< argc
; i
++) {
853 sqlite_bind_text(stmt
, "@file", argv
[i
], -1);
855 sqlite_reset_stmt(stmt
);
858 sqlite3_finalize(stmt
);
859 sqlite_command("COMMIT");
862 static inline void print_mode(char *value
)
872 #define U(m) "-rwm"[(v / m) & 3]
881 static char *semind_file_name
;
882 static FILE *semind_file_fd
;
883 static int semind_file_lnum
;
884 static char *semind_line
;
885 static size_t semind_line_buflen
;
886 static int semind_line_len
;
888 static void print_file_line(const char *name
, int lnum
)
891 * All files are sorted by name and line number. So, we can reopen
892 * the file and read it line by line.
894 if (!semind_file_name
|| strcmp(semind_file_name
, name
)) {
895 if (semind_file_fd
) {
896 fclose(semind_file_fd
);
897 free(semind_file_name
);
900 semind_file_name
= strdup(name
);
902 if (!semind_file_name
)
903 semind_error(1, errno
, "strdup");
905 semind_file_fd
= fopen(name
, "r");
908 semind_error(1, errno
, "fopen: %s", name
);
910 semind_file_lnum
= 0;
914 if (semind_file_lnum
== lnum
) {
915 if (semind_line
[semind_line_len
-1] == '\n')
917 printf("%.*s", semind_line_len
, semind_line
);
922 } while((semind_line_len
= getline(&semind_line
, &semind_line_buflen
, semind_file_fd
)) != -1);
924 if (errno
&& errno
!= EOF
)
925 semind_error(1, errno
, "getline");
928 static int search_query_callback(void *data
, int argc
, char **argv
, char **colname
)
930 char *fmt
= (char *) semind_search_format
;
935 while (*fmt
!= '\0') {
941 case 't': c
= '\t'; break;
942 case 'r': c
= '\r'; break;
943 case 'n': c
= '\n'; break;
945 } else if (c
== '%') {
952 semind_error(1, 0, "unexpected end of format string");
955 case 'f': colnum
= 0; goto print_string
;
956 case 'l': colnum
= 1; goto print_string
;
957 case 'c': colnum
= 2; goto print_string
;
958 case 'C': colnum
= 3; goto print_string
;
959 case 'n': colnum
= 4; goto print_string
;
962 printf("%.*s", n
, buf
);
970 printf("%.*s", n
, buf
);
973 printf("%c", atoi(argv
[6]));
978 printf("%.*s", n
, buf
);
981 print_file_line(argv
[0], atoi(argv
[1]));
987 printf("%.*s", n
, buf
);
990 printf("%s", argv
[colnum
]);
999 semind_error(1, 0, "invalid format specification: %%%c", c
);
1002 } else if (c
== '\\') {
1008 if (n
== sizeof(buf
)) {
1009 printf("%.*s", n
, buf
);
1018 printf("%.*s", n
, buf
);
1024 static void command_search(int argc
, char **argv
)
1028 sqlite3_str
*query
= sqlite3_str_new(semind_db
);
1031 semind_error(1, errno
, "unable to change directory: %s", cwd
);
1033 if (query_appendf(query
,
1042 "FROM semind, file "
1043 "WHERE semind.file == file.id") < 0)
1046 if (semind_search_kind
) {
1047 if (query_appendf(query
, " AND semind.kind == %d", semind_search_kind
) < 0)
1051 if (semind_search_symbol
) {
1054 if (query_appendf(query
, " AND ") < 0)
1057 if (strpbrk(semind_search_symbol
, "*?[]"))
1058 ret
= query_appendf(query
, "semind.symbol GLOB %Q", semind_search_symbol
);
1060 ret
= query_appendf(query
, "semind.symbol == %Q", semind_search_symbol
);
1066 if (semind_search_modmask_defined
) {
1067 if (!semind_search_modmask
) {
1068 if (query_appendf(query
, " AND semind.mode == %d", semind_search_modmask
) < 0)
1070 } else if (query_appendf(query
, " AND (semind.mode & %d) != 0", semind_search_modmask
) < 0)
1074 if (semind_search_path
) {
1075 if (query_appendf(query
, " AND file.name GLOB %Q", semind_search_path
) < 0)
1079 if (semind_search_by_location
== EXPLAIN_LOCATION
) {
1080 if (query_appendf(query
, " AND file.name == %Q", semind_search_filename
) < 0)
1082 if (semind_search_line
&&
1083 query_appendf(query
, " AND semind.line == %d", semind_search_line
) < 0)
1085 if (semind_search_column
&&
1086 query_appendf(query
, " AND semind.column == %d", semind_search_column
) < 0)
1088 } else if (semind_search_by_location
== USAGE_BY_LOCATION
) {
1089 if (query_appendf(query
, " AND semind.symbol IN (") < 0)
1091 if (query_appendf(query
,
1092 "SELECT semind.symbol FROM semind, file WHERE"
1093 " semind.file == file.id AND"
1094 " file.name == %Q", semind_search_filename
) < 0)
1096 if (semind_search_line
&&
1097 query_appendf(query
, " AND semind.line == %d", semind_search_line
) < 0)
1099 if (semind_search_column
&&
1100 query_appendf(query
, " AND semind.column == %d", semind_search_column
) < 0)
1102 if (query_appendf(query
, ")") < 0)
1106 if (query_appendf(query
, " ORDER BY file.name, semind.line, semind.column ASC", semind_search_path
) < 0)
1109 sql
= sqlite3_str_value(query
);
1111 if (semind_verbose
> 1)
1112 message("SQL: %s", sql
);
1114 sqlite3_exec(semind_db
, sql
, search_query_callback
, NULL
, &dberr
);
1116 semind_error(1, 0, "sql query failed: %s", dberr
);
1118 sql
= sqlite3_str_finish(query
);
1121 if (semind_file_fd
) {
1122 fclose(semind_file_fd
);
1123 free(semind_file_name
);
1129 int main(int argc
, char **argv
)
1131 static const struct command commands
[] = {
1134 .dbflags
= SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
,
1135 .parse_cmdline
= parse_cmdline_add
,
1136 .handler
= command_add
1140 .dbflags
= SQLITE_OPEN_READWRITE
,
1141 .parse_cmdline
= parse_cmdline_rm
,
1142 .handler
= command_rm
1146 .dbflags
= SQLITE_OPEN_READONLY
,
1147 .parse_cmdline
= parse_cmdline_search
,
1148 .handler
= command_search
1152 const struct command
*cmd
;
1154 if (!(progname
= rindex(argv
[0], '/')))
1159 if (!realpath(".", cwd
))
1160 semind_error(1, errno
, "unable to get current directory");
1161 n_cwd
= strlen(cwd
);
1163 parse_cmdline(argc
, argv
);
1165 for (cmd
= commands
; cmd
->name
&& strcmp(argv
[optind
], cmd
->name
); cmd
++);
1167 semind_error(1, 0, "unknown command: %s", argv
[optind
]);
1170 semind_command
= cmd
->name
;
1172 if (cmd
->parse_cmdline
)
1173 cmd
->parse_cmdline(argc
, argv
);
1175 open_database(semind_dbfile
, cmd
->dbflags
);
1176 cmd
->handler(argc
- optind
, argv
+ optind
);
1178 sqlite3_close(semind_db
);