From 7b14e1a8a31a6b52700c33c5880093e6e9a28d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 26 Apr 2024 23:47:52 +0200 Subject: [PATCH 01/18] Update perl6.c to raku.c parser The file name has been changed and we cannot use the script for the update. --- ctags/Makefile.am | 2 +- ctags/parsers/{perl6.c => raku.c} | 105 +++++++++++++++++++----------- meson.build | 2 +- 3 files changed, 69 insertions(+), 40 deletions(-) rename ctags/parsers/{perl6.c => raku.c} (78%) diff --git a/ctags/Makefile.am b/ctags/Makefile.am index a06490168b..e896df7694 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -86,12 +86,12 @@ parsers = \ parsers/pascal.c \ parsers/perl.c \ parsers/perl.h \ - parsers/perl6.c \ parsers/php.c \ parsers/powershell.c \ parsers/python.c \ parsers/r.c \ parsers/r.h \ + parsers/raku.c \ parsers/rst.c \ parsers/ruby.c \ parsers/rust.c \ diff --git a/ctags/parsers/perl6.c b/ctags/parsers/raku.c similarity index 78% rename from ctags/parsers/perl6.c rename to ctags/parsers/raku.c index e28c194c32..c5673f21cf 100644 --- a/ctags/parsers/perl6.c +++ b/ctags/parsers/raku.c @@ -1,6 +1,7 @@ /* - * perl6.c -- Perl6 parser. + * raku.c -- Raku parser. * Author: Dmitri Tikhonov + * Author: Will Coleda * * This is a very basic Perl 6 parser. It does not know how to: * - skip POD; @@ -22,10 +23,9 @@ #include "parse.h" #include "read.h" #include "routines.h" -#include "selectors.h" #include "vstring.h" -enum perl6Kind { +enum rakuKind { K_NONE = -1, K_CLASS, K_GRAMMAR, @@ -39,7 +39,7 @@ enum perl6Kind { K_TOKEN, }; -static kindDefinition perl6Kinds[] = { +static kindDefinition rakuKinds[] = { [K_CLASS] = { true, 'c', "class", "classes" }, [K_GRAMMAR] = { true, 'g', "grammar", "grammars" }, [K_METHOD] = { true, 'm', "method", "methods" }, @@ -70,7 +70,7 @@ enum token { T_TOKEN, }; -static const enum perl6Kind token2kind[] = { +static const enum rakuKind token2kind[] = { [T_CLASS] = K_CLASS, [T_GRAMMAR] = K_GRAMMAR, [T_METHOD] = K_METHOD, @@ -164,7 +164,7 @@ matchToken (const char *s, int len) return -1; } -static const int validPerl6Identifier[0x100] = { +static const int validRakuIdentifier[0x100] = { /* r!perl -e "print qq([(int)'\$_'] = 1,\n)for a..z,A..Z,0..9,':','-','_'"|fmt */ [(int)'a'] = 1, [(int)'b'] = 1, [(int)'c'] = 1, [(int)'d'] = 1, @@ -198,7 +198,7 @@ static const int kindMayHaveMethodPrefix = (1 << K_SUBMETHOD) | * identifier is invalid. */ static int -trimIdentifier (enum perl6Kind kind, const char **ps, int len) +trimIdentifier (enum rakuKind kind, const char **ps, int len) { Assert(len > 0); const char *const end = *ps + len; @@ -209,7 +209,7 @@ trimIdentifier (enum perl6Kind kind, const char **ps, int len) /* Record the start of identifier: */ *ps = s; /* Continuous string of valid characters: */ - while (s < end && validPerl6Identifier[(int)*s]) + while (s < end && validRakuIdentifier[(int)*s]) ++s; /* sub multi infix:<...> -- we want the "infix" only */ while (s - *ps > 0 && ':' == s[-1]) @@ -218,7 +218,7 @@ trimIdentifier (enum perl6Kind kind, const char **ps, int len) return s - *ps; } -struct p6Ctx { +struct rakuCtx { enum token tokens[128 /* unlikely to need more than this */]; unsigned int n_tokens; vString *name; @@ -226,7 +226,7 @@ struct p6Ctx { }; static void -makeTag (struct p6Ctx *ctx, int kind, const char *name, int len) +makeTag (struct rakuCtx *ctx, int kind, const char *name, int len) { tagEntryInfo entry; vStringNCopyS(ctx->name, name, len); @@ -235,17 +235,17 @@ makeTag (struct p6Ctx *ctx, int kind, const char *name, int len) } static void -possiblyMakeTag (struct p6Ctx *ctx, const char *s, int len) +possiblyMakeTag (struct rakuCtx *ctx, const char *s, int len) { Assert(ctx->n_tokens > 0); - enum perl6Kind kind = token2kind[ ctx->tokens[ctx->n_tokens - 1] ]; - if (K_NONE != kind && perl6Kinds[kind].enabled + enum rakuKind kind = token2kind[ ctx->tokens[ctx->n_tokens - 1] ]; + if (K_NONE != kind && rakuKinds[kind].enabled && (len = trimIdentifier(kind, &s, len)) > 0) makeTag(ctx, kind, s, len); } static void -initP6Ctx (struct p6Ctx *ctx) +initRakuCtx (struct rakuCtx *ctx) { ctx->n_tokens = 0; ctx->name = vStringNew(); @@ -253,7 +253,7 @@ initP6Ctx (struct p6Ctx *ctx) } static void -deinitP6Ctx (struct p6Ctx *ctx) +deinitRakuCtx (struct rakuCtx *ctx) { vStringDelete(ctx->name); } @@ -265,32 +265,33 @@ deinitP6Ctx (struct p6Ctx *ctx) * TODO: Currently, POD and multi-line comments are not handled. */ static int -getNonSpaceStr (struct p6Ctx *ctx, const char **ptok) +getNonSpaceStr (struct rakuCtx *ctx, const char **ptok) { - const char *s = ctx->line; - if (!s) { -next_line: - s = (const char *) readLineFromInputFile(); - if (!s) - return 0; /* EOF */ + size_t non_white_len; + const char *s; + + while (ctx->line) + { + s = ctx->line; + while (*s && isspace((unsigned char) *s)) /* Skip whitespace */ + ++s; + if ('#' != *s /* Skip comments */ + && (non_white_len = strcspn(s, ",; \t"), non_white_len > 0)) + { + ctx->line = s + non_white_len; /* Save state */ + *ptok = s; + return non_white_len; + } + ctx->line = (const char *) readLineFromInputFile(); } - while (*s && isspace(*s)) /* Skip whitespace */ - ++s; - if ('#' == *s) - goto next_line; - int non_white_len = strcspn(s, ",; \t"); - if (non_white_len) { - ctx->line = s + non_white_len; /* Save state */ - *ptok = s; - return non_white_len; - } else - goto next_line; + + return 0; } static void -findPerl6Tags (void) +findRakuTags (void) { - struct p6Ctx ctx; + struct rakuCtx ctx; #define RESET_TOKENS() do { ctx.n_tokens = 0; } while (0) @@ -304,11 +305,12 @@ findPerl6Tags (void) } \ } while (0) - initP6Ctx(&ctx); + initRakuCtx(&ctx); const char *s; int len; + ctx.line = (const char *) readLineFromInputFile(); while ((len = getNonSpaceStr(&ctx, &s)) > 0) { enum token token = matchToken(s, len); if ((int) token >= 0) { @@ -319,13 +321,40 @@ findPerl6Tags (void) } } - deinitP6Ctx(&ctx); + deinitRakuCtx(&ctx); } +parserDefinition * +RakuParser (void) +{ + static const char *const extensions[] = { "raku", "rakumod", "rakutest", "rakudoc", NULL }; + parserDefinition* def = parserNew("Raku"); + def->kindTable = rakuKinds; + def->kindCount = ARRAY_SIZE(rakuKinds); + def->extensions = extensions; + def->parser = findRakuTags; + return def; +} + + +/* + * Perl6 for keeping backward compatibility of the CLI. + */ +#include "selectors.h" +/* TODO: sharing a kind array within two parsers are bad idea. + * e.g. The combination of --kinds-Perl6=+c and --kinds-Raku=-c + * doesn't work. + */ +#define perl6Kinds rakuKinds +/* NOTE: We assume the grammer of Raku is upper compatible with + * Perl6. When this assumption is no longer true, we may have to + * copy the function, or this file. Don't waste your time to + * make one function support two languages; Copying may be enough. */ +#define findPerl6Tags findRakuTags parserDefinition * Perl6Parser (void) { - static const char *const extensions[] = { "p6", "pm6", "pm", "pl6", NULL }; + static const char *const extensions[] = { "p6", "pm6", "pm", "pl6", "t6", NULL }; static selectLanguage selectors [] = { selectByPickingPerlVersion, NULL }; parserDefinition* def = parserNew("Perl6"); diff --git a/meson.build b/meson.build index 9e6634d188..919d18a109 100644 --- a/meson.build +++ b/meson.build @@ -664,12 +664,12 @@ ctags = static_library('ctags', 'ctags/parsers/pascal.c', 'ctags/parsers/perl.c', 'ctags/parsers/perl.h', - 'ctags/parsers/perl6.c', 'ctags/parsers/php.c', 'ctags/parsers/powershell.c', 'ctags/parsers/python.c', 'ctags/parsers/r.c', 'ctags/parsers/r.h', + 'ctags/parsers/raku.c', 'ctags/parsers/rst.c', 'ctags/parsers/ruby.c', 'ctags/parsers/rust.c', From baaed02db0c345da0034d7f87fa3aa8c2f55b753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 00:59:31 +0200 Subject: [PATCH 02/18] Update all parsers and related files to ctags p6.1.20240421.0 Geany compiles fine after this step, it just doesn't run because of incorrect mappings. --- ctags/Makefile.am | 11 + ctags/dsl/es.c | 37 +- ctags/dsl/es.h | 6 + ctags/dsl/optscript.c | 240 +- ctags/dsl/optscript.h | 2 +- ctags/main/CommonPrelude.c | 96 + ctags/main/args.c | 6 +- ctags/main/colprint.c | 11 +- ctags/main/ctags.h | 50 +- ctags/main/debug.c | 5 +- ctags/main/debug.h | 14 +- ctags/main/dependency.c | 26 +- ctags/main/dependency.h | 1 + ctags/main/dependency_p.h | 1 + ctags/main/e_msoft.h | 1 - ctags/main/entry.c | 551 ++++- ctags/main/entry.h | 159 +- ctags/main/error_p.h | 4 +- ctags/main/field.c | 155 +- ctags/main/field.h | 10 +- ctags/main/field_p.h | 12 +- ctags/main/fmt.c | 12 +- ctags/main/general.h | 2 +- ctags/main/htable.c | 200 +- ctags/main/htable.h | 16 +- ctags/main/interval_tree_generic.h | 197 ++ ctags/main/keyword.c | 38 +- ctags/main/keyword.h | 20 + ctags/main/kind.c | 7 +- ctags/main/lregex.c | 977 ++++++-- ctags/main/lregex_p.h | 7 +- ctags/main/lxpath.c | 17 +- ctags/main/lxpath.h | 2 + ctags/main/lxpath_p.h | 1 + ctags/main/main.c | 2 +- ctags/main/mbcs.c | 4 +- ctags/main/mini-geany.c | 48 +- ctags/main/nestlevel.c | 50 +- ctags/main/nestlevel.h | 17 +- ctags/main/numarray.c | 12 +- ctags/main/numarray.h | 3 +- ctags/main/options.c | 238 +- ctags/main/options_p.h | 5 +- ctags/main/param.c | 92 +- ctags/main/param.h | 4 +- ctags/main/param_p.h | 21 +- ctags/main/parse.c | 655 +++-- ctags/main/parse.h | 35 +- ctags/main/parse_p.h | 24 +- ctags/main/parsers_p.h | 40 +- ctags/main/promise.c | 56 +- ctags/main/promise.h | 2 + ctags/main/ptag.c | 39 +- ctags/main/ptag_p.h | 5 + ctags/main/ptrarray.c | 18 +- ctags/main/ptrarray.h | 4 +- ctags/main/rbtree.c | 713 +++--- ctags/main/rbtree.h | 193 +- ctags/main/rbtree_augmented.h | 247 ++ ctags/main/read.c | 144 +- ctags/main/read.h | 21 +- ctags/main/read_p.h | 9 +- ctags/main/repoinfo.h | 2 +- ctags/main/routines.c | 59 +- ctags/main/routines.h | 5 +- ctags/main/script.c | 10 +- ctags/main/script_p.h | 6 +- ctags/main/seccomp.c | 2 + ctags/main/selectors.c | 445 ++-- ctags/main/selectors.h | 20 +- ctags/main/sort.c | 44 +- ctags/main/strlist.c | 4 +- ctags/main/subparser.h | 1 + ctags/main/subparser_p.h | 1 - ctags/main/trace.h | 14 +- ctags/main/trashbox.c | 1 + ctags/main/types.h | 4 +- ctags/main/unwindi.c | 6 +- ctags/main/utf8_str.c | 83 + ctags/main/utf8_str.h | 22 + ctags/main/vstring.c | 32 +- ctags/main/vstring.h | 61 +- ctags/main/writer-ctags.c | 79 +- ctags/main/writer-json.c | 34 +- ctags/main/writer.c | 4 +- ctags/main/writer_p.h | 8 +- ctags/main/xtag.c | 6 +- ctags/main/xtag_p.h | 4 +- ctags/parsers/abaqus.c | 6 +- ctags/parsers/ada.c | 74 +- ctags/parsers/asciidoc.c | 42 +- ctags/parsers/asm.c | 654 ++++- ctags/parsers/autoit.c | 34 +- ctags/parsers/basic.c | 326 ++- ctags/parsers/bibtex.c | 93 +- ctags/parsers/bibtex.h | 25 + ctags/parsers/clojure.c | 38 +- ctags/parsers/cobol.c | 12 +- ctags/parsers/cpreprocessor.c | 505 +++- ctags/parsers/cpreprocessor.h | 26 +- ctags/parsers/css.c | 9 +- ctags/parsers/cxx/cxx.c | 16 + ctags/parsers/cxx/cxx_debug.c | 17 +- ctags/parsers/cxx/cxx_keyword.c | 81 +- ctags/parsers/cxx/cxx_keyword.h | 15 +- ctags/parsers/cxx/cxx_parser.c | 133 +- ctags/parsers/cxx/cxx_parser_block.c | 100 +- ctags/parsers/cxx/cxx_parser_function.c | 76 +- ctags/parsers/cxx/cxx_parser_internal.h | 16 + ctags/parsers/cxx/cxx_parser_lambda.c | 3 +- ctags/parsers/cxx/cxx_parser_module.c | 331 +++ ctags/parsers/cxx/cxx_parser_namespace.c | 4 +- ctags/parsers/cxx/cxx_parser_template.c | 43 +- ctags/parsers/cxx/cxx_parser_tokenizer.c | 86 +- ctags/parsers/cxx/cxx_parser_typedef.c | 13 +- ctags/parsers/cxx/cxx_parser_using.c | 26 +- ctags/parsers/cxx/cxx_parser_variable.c | 123 +- ctags/parsers/cxx/cxx_qtmoc.c | 3 +- ctags/parsers/cxx/cxx_scope.c | 25 +- ctags/parsers/cxx/cxx_scope.h | 5 + ctags/parsers/cxx/cxx_side_chain.c | 230 ++ ctags/parsers/cxx/cxx_side_chain.h | 33 + ctags/parsers/cxx/cxx_tag.c | 159 +- ctags/parsers/cxx/cxx_tag.h | 50 +- ctags/parsers/cxx/cxx_token.c | 18 +- ctags/parsers/cxx/cxx_token.h | 11 +- ctags/parsers/cxx/cxx_token_chain.c | 92 +- ctags/parsers/cxx/cxx_token_chain.h | 10 +- ctags/parsers/diff.c | 4 +- ctags/parsers/erlang.c | 10 +- ctags/parsers/flex.c | 18 +- ctags/parsers/fortran.c | 188 +- ctags/parsers/gdscript.c | 74 +- ctags/parsers/geany_lcpp.h | 23 + ctags/parsers/go.c | 13 +- ctags/parsers/haskell.c | 2 +- ctags/parsers/haxe.c | 57 +- ctags/parsers/html.c | 181 +- ctags/parsers/iniconf.c | 24 +- ctags/parsers/jscript.c | 2752 +++++++++++++--------- ctags/parsers/jscript.h | 32 + ctags/parsers/json.c | 7 +- ctags/parsers/julia.c | 16 +- ctags/parsers/lisp.c | 18 +- ctags/parsers/lua.c | 18 +- ctags/parsers/make.c | 85 +- ctags/parsers/markdown.c | 190 +- ctags/parsers/markdown.h | 37 + ctags/parsers/nsis.c | 34 +- ctags/parsers/objc.c | 14 +- ctags/parsers/ocaml.c | 18 +- ctags/parsers/pascal.c | 18 +- ctags/parsers/perl.c | 334 ++- ctags/parsers/perl.h | 1 + ctags/parsers/php.c | 75 +- ctags/parsers/powershell.c | 234 +- ctags/parsers/python.c | 190 +- ctags/parsers/r.c | 19 +- ctags/parsers/rst.c | 410 +++- ctags/parsers/ruby.c | 665 ++++-- ctags/parsers/ruby.h | 43 + ctags/parsers/rust.c | 25 +- ctags/parsers/sh.c | 515 +++- ctags/parsers/sh.h | 42 + ctags/parsers/sql.c | 358 ++- ctags/parsers/tcl.c | 21 +- ctags/parsers/tcloo.c | 11 +- ctags/parsers/tex.c | 9 +- ctags/parsers/typescript.c | 14 +- ctags/parsers/verilog.c | 687 +++--- ctags/parsers/vhdl.c | 12 +- meson.build | 11 + 172 files changed, 13120 insertions(+), 4504 deletions(-) create mode 100644 ctags/main/interval_tree_generic.h create mode 100644 ctags/main/rbtree_augmented.h create mode 100644 ctags/main/utf8_str.c create mode 100644 ctags/main/utf8_str.h create mode 100644 ctags/parsers/bibtex.h create mode 100644 ctags/parsers/cxx/cxx_parser_module.c create mode 100644 ctags/parsers/cxx/cxx_side_chain.c create mode 100644 ctags/parsers/cxx/cxx_side_chain.h create mode 100644 ctags/parsers/jscript.h create mode 100644 ctags/parsers/ruby.h create mode 100644 ctags/parsers/sh.h diff --git a/ctags/Makefile.am b/ctags/Makefile.am index e896df7694..ed233537f3 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -23,6 +23,7 @@ parsers = \ parsers/cxx/cxx_parser.h \ parsers/cxx/cxx_parser_internal.h \ parsers/cxx/cxx_parser_lambda.c \ + parsers/cxx/cxx_parser_module.c \ parsers/cxx/cxx_parser_namespace.c \ parsers/cxx/cxx_parser_template.c \ parsers/cxx/cxx_parser_tokenizer.c \ @@ -32,6 +33,8 @@ parsers = \ parsers/cxx/cxx_qtmoc.c \ parsers/cxx/cxx_scope.c \ parsers/cxx/cxx_scope.h \ + parsers/cxx/cxx_side_chain.c \ + parsers/cxx/cxx_side_chain.h \ parsers/cxx/cxx_subparser.c \ parsers/cxx/cxx_subparser.h \ parsers/cxx/cxx_subparser_internal.h \ @@ -51,6 +54,7 @@ parsers = \ parsers/autoit.c \ parsers/basic.c \ parsers/bibtex.c \ + parsers/bibtex.h \ parsers/geany_c.c \ parsers/clojure.c \ parsers/cobol.c \ @@ -69,6 +73,7 @@ parsers = \ parsers/haxe.c \ parsers/html.c \ parsers/jscript.c \ + parsers/jscript.h \ parsers/json.c \ parsers/julia.c \ parsers/geany_lcpp.c \ @@ -94,8 +99,10 @@ parsers = \ parsers/raku.c \ parsers/rst.c \ parsers/ruby.c \ + parsers/ruby.h \ parsers/rust.c \ parsers/sh.c \ + parsers/sh.h \ parsers/sql.c \ parsers/tcl.c \ parsers/tcl.h \ @@ -148,6 +155,7 @@ libctags_la_SOURCES = \ main/htable.h \ main/inline.h \ main/interactive_p.h \ + main/interval_tree_generic.h \ main/keyword.c \ main/keyword.h \ main/keyword_p.h \ @@ -195,6 +203,7 @@ libctags_la_SOURCES = \ main/ptrarray.h \ main/rbtree.c \ main/rbtree.h \ + main/rbtree_augmented.h \ main/read.c \ main/read.h \ main/read_p.h \ @@ -226,6 +235,8 @@ libctags_la_SOURCES = \ main/types.h \ main/unwindi.c \ main/unwindi.h \ + main/utf8_str.c \ + main/utf8_str.h \ main/vstring.c \ main/vstring.h \ main/writer-ctags.c \ diff --git a/ctags/dsl/es.c b/ctags/dsl/es.c index 74959d3983..75c8c7239f 100644 --- a/ctags/dsl/es.c +++ b/ctags/dsl/es.c @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -822,7 +823,6 @@ es_symbol_print(const EsObject* object, MIO* fp) unsigned char cc; unsigned char mask; int needs_bar; - int i; string = es_symbol_get(object); if (!string) @@ -840,7 +840,7 @@ es_symbol_print(const EsObject* object, MIO* fp) { /* 0 => 1? */ mask = 0x2; - for (i = 0; i< len; i++) + for (size_t i = 0; i< len; i++) { c = string[i]; cc = get_char_class(c); @@ -854,7 +854,7 @@ es_symbol_print(const EsObject* object, MIO* fp) if (needs_bar) mio_printf(fp, "|"); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { c = string[i]; if (c == '\\' || c == '|') @@ -1028,7 +1028,7 @@ es_string_print(const EsObject* object, MIO* fp) const char* string; char c; size_t len; - int i; + size_t i; string = es_string_get(object); @@ -1352,6 +1352,29 @@ es_regex_exec (const EsObject* regex, 0, NULL, 0)? es_false: es_true; } +EsObject* +es_regex_exec_extract_match_new (const EsObject* regex, + const EsObject* str, + unsigned int group) +{ + EsObject *r; + regmatch_t *pmatch = calloc(group + 1, sizeof(regmatch_t)); + if (!pmatch) + return ES_ERROR_MEMORY; + + const char *s = es_string_get (str); + if (regexec(((EsRegex*)regex)->code, s, group + 1, pmatch, 0)) + r = es_false; + else + r = pmatch[group].rm_so == -1 + ? es_nil: + es_string_newL(s + pmatch[group].rm_so, + pmatch[group].rm_eo - pmatch[group].rm_so); + + free (pmatch); + return r; +} + /* * Error */ @@ -2167,7 +2190,7 @@ static void dump_token (MIO* stream, const char* prefix, Token* seed) { const char* buf; - int i; + size_t i; char c; @@ -2562,8 +2585,8 @@ is_real (const char* cstr, return 0; else if (*endptr != '\0') return 0; - - /* TODO: INF, NAN... */ + else if (isinf(*d) || isnan(*d)) + return 0; return 1; } diff --git a/ctags/dsl/es.h b/ctags/dsl/es.h index 79bfe2af93..d77d71c96a 100644 --- a/ctags/dsl/es.h +++ b/ctags/dsl/es.h @@ -159,6 +159,12 @@ int es_regex_p (const EsObject* object); EsObject* es_regex_exec (const EsObject* regex, const EsObject* str); +/* Return #f if unmatched. + * Retrun NIL is the associate group is not in REGEX. */ +EsObject* es_regex_exec_extract_match_new (const EsObject* regex, + const EsObject* str, + unsigned int group); + /* * Foreign pointer */ diff --git a/ctags/dsl/optscript.c b/ctags/dsl/optscript.c index f37e128c46..5ad16fafc1 100644 --- a/ctags/dsl/optscript.c +++ b/ctags/dsl/optscript.c @@ -153,6 +153,7 @@ static EsObject* OPT_KEY_dstack; */ static EsObject* array_new (unsigned int attr); +static EsObject* array_shared_new (EsObject* original, unsigned int attr); static EsObject* array_es_init_fat (void *fat, void *ptr, void *extra); static void array_es_free (void *ptr, void *fat); @@ -374,7 +375,9 @@ declop(execstack); * tested in typeattrconv.ps */ declop(type); declop(cvn); -/* cvlit, cvx, xcheck, executeonly, noacess, readonly, +declop(cvs); +declop(cvx); +/* cvlit, xcheck, executeonly, noacess, readonly, rcheck, wcheck, cvi, cvr, cvrs, cvs,... */ /* Operators for Virtual Memory Operators */ @@ -473,8 +476,8 @@ opt_init (void) defOP (opt_system_dict, op_mark, "<<", 0, "- << mark"); defOP (opt_system_dict, op_mark, "[", 0, "- [ mark"); - defOP (opt_system_dict, op__make_array, "]", 1, "[ any1 ... anyn ] array"); - defOP (opt_system_dict, op__make_dict , ">>", 1, "<< key1 value1 ... keyn valuen >> dict"); + defOP (opt_system_dict, op__make_array, "]", 0, "[ any1 ... anyn ] array"); + defOP (opt_system_dict, op__make_dict , ">>", 0, "<< key1 value1 ... keyn valuen >> dict"); defop (opt_system_dict, _help, 0, "- _HELP -"); defop (opt_system_dict, pstack, 0, "|- any1 ... anyn PSTACK |- any1 ... anyn"); @@ -567,6 +570,8 @@ opt_init (void) defop (opt_system_dict, type, 1, "any TYPE name"); defop (opt_system_dict, cvn, 1, "string CVN name"); + defop (opt_system_dict, cvs, 2, "any string CVS string"); + defop (opt_system_dict, cvx, 1, "any CVX any"); defop (opt_system_dict, null, 0, "- NULL null"); defop (opt_system_dict, bind, 1, "proc BIND proc"); @@ -642,12 +647,13 @@ opt_vm_new (MIO *in, MIO *out, MIO *err) } void -opt_vm_clear (OptVM *vm) +opt_vm_clear (OptVM *vm, bool clear_app_data) { ptrArrayClear (vm->estack); ptrArrayClear (vm->ostack); vm_dstack_clear (vm); - vm->app_data = NULL; + if (clear_app_data) + vm->app_data = NULL; dict_op_clear (vm->error); } @@ -1155,7 +1161,7 @@ name_or_number_new (const char* s, void *data) const char *t = s; while (*t) { - if (!isdigit ((int)*t)) + if (!isdigit ((unsigned char) *t)) { number = false; break; @@ -1970,6 +1976,14 @@ array_new (unsigned int attr) return es_fatptr_new (OPT_TYPE_ARRAY, a, &attr); } +static EsObject* +array_shared_new (EsObject* original, unsigned int attr) +{ + ptrArray *a = es_pointer_get (original); + ptrArrayRef (a); + return es_fatptr_new (OPT_TYPE_ARRAY, a, &attr); +} + static EsObject* array_es_init_fat (void *fat, void *ptr, void *extra) { @@ -1988,9 +2002,6 @@ array_es_free (void *ptr, void *fat) static int array_es_equal (const void *a, const void *afat, const void *b, const void *bfat) { - if (((ArrayFat *)afat)->attr != ((ArrayFat *)bfat)->attr) - return 0; - if (ptrArrayIsEmpty ((ptrArray *)a) && ptrArrayIsEmpty ((ptrArray*)b)) return 1; else if (a == b) @@ -2126,7 +2137,11 @@ dict_op_def (EsObject* dict, EsObject *key, EsObject *val) key = es_object_ref (key); val = es_object_ref (val); - hashTableUpdateItem (t, key, val); + if (hashTableUpdateOrPutItem (t, key, val)) + { + /* A key in the hashtable is reused. */ + es_object_unref (key); + } } static bool @@ -2423,13 +2438,16 @@ GEN_PRINTER(op__print, vm_print_full (vm, elt, true, 0)) static EsObject* op__make_array (OptVM *vm, EsObject *name) { - int n = vm_ostack_counttomark (vm); - if (n < 0) + int n0 = vm_ostack_counttomark (vm); + if (n0 < 0) return OPT_ERR_UNMATCHEDMARK; + unsigned int n = (unsigned int)n0; unsigned int count = vm_ostack_count (vm); + Assert(count > n); + EsObject *a = array_new (ATTR_READABLE | ATTR_WRITABLE); - for (int i = (int)(count - n); i < count; i++) + for (unsigned int i = count - n; i < count; i++) { EsObject *elt = ptrArrayItem (vm->ostack, i); array_op_add (a, elt); @@ -2461,7 +2479,14 @@ op__make_dict (OptVM *vm, EsObject *name) return OPT_ERR_TYPECHECK; } - EsObject *d = dict_new (n % 2 + 1, ATTR_READABLE|ATTR_WRITABLE); /* FIXME: + 1 */ + /* A hashtable grows automatically when its filling rate is + * grater than 80%. If we put elements between `<<' and `>>' to a dictionary + * initialized with the size equal to the number of elements, the dictionary + * grows once during putting them. Making a 1/0.8 times larger dictionary can + * avoid the predictable growing. */ + int size = (10 * (n > 0? (n / 2): 1)) / 8; + + EsObject *d = dict_new (size, ATTR_READABLE|ATTR_WRITABLE); for (int i = 0; i < (n / 2); i++) { EsObject *val = ptrArrayLast (vm->ostack); @@ -2692,18 +2717,19 @@ op_copy (OptVM *vm, EsObject *name) if (!es_integer_p (nobj)) return op__copy_compound (vm, name, c, nobj); - int n = es_integer_get (nobj); - if (n < 0) + int n0 = es_integer_get (nobj); + if (n0 < 0) return OPT_ERR_RANGECHECK; + unsigned int n = (unsigned int)n0; c--; - if (((int)c) - n < 0) + if (c < n) return OPT_ERR_UNDERFLOW; ptrArrayDeleteLast(vm->ostack); - for (int i = c - n; i < c; i++) + for (unsigned int i = c - n; i < c; i++) { EsObject * elt = ptrArrayItem (vm->ostack, i); vm_ostack_push (vm, elt); @@ -2751,6 +2777,8 @@ op_roll (OptVM *vm, EsObject *name) if (!es_integer_p (nobj)) return OPT_ERR_TYPECHECK; int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; if ((((int)c) - 2) < n) return OPT_ERR_UNDERFLOW; @@ -3026,8 +3054,10 @@ op_dict (OptVM *vm, EsObject *name) return OPT_ERR_TYPECHECK; int n = es_integer_get (nobj); - if (n < 1) + if (n < 0) return OPT_ERR_RANGECHECK; + else if (n == 0) + n = 1; ptrArrayDeleteLast (vm->ostack); @@ -3728,7 +3758,7 @@ op_repeat (OptVM *vm, EsObject *name) ptrArrayDeleteLast (vm->ostack); ptrArrayDeleteLast (vm->ostack); - EsObject *e = es_false;; + EsObject *e = es_false; for (int i = 0; i < n; i++) { e = vm_call_proc (vm, proc); @@ -3812,10 +3842,9 @@ op_for (OptVM *vm, EsObject *name) /* * Operators for type, attribute and their conversion */ -static EsObject* -op_type (OptVM *vm, EsObject *name) +static const char* +get_type_name (EsObject *o) { - EsObject *o = ptrArrayRemoveLast (vm->ostack); const char *n; if (o == es_nil) @@ -3830,6 +3859,17 @@ op_type (OptVM *vm, EsObject *name) n = es_type_get_name (t); } + return n; +} + +static EsObject* +op_type (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayRemoveLast (vm->ostack); + const char *n; + + n = get_type_name (o); + EsObject *p = name_newS (n, ATTR_EXECUTABLE|ATTR_READABLE); vm_ostack_push (vm, p); es_object_unref (p); @@ -3854,6 +3894,85 @@ op_cvn (OptVM *vm, EsObject *name) return es_false; } +static EsObject * +op_cvs (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayLast (vm->ostack); + if (es_object_get_type (o) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + + EsObject *any = ptrArrayItemFromLast (vm->ostack, 1); + vString *vstr = es_pointer_get (o); + int t = es_object_get_type (any); + + if (t == OPT_TYPE_STRING) + { + vString *vany = es_pointer_get (any); + vStringCopy (vstr, vany); + } + else if (t == OPT_TYPE_NAME || t == ES_TYPE_SYMBOL) + { + if (t == OPT_TYPE_NAME) + any = es_pointer_get (any); + + const char *cany = es_symbol_get (any); + vStringCopyS (vstr, cany); + } + else if (t == ES_TYPE_INTEGER) + { + int iany = es_integer_get (any); +#define buf_len 13 + char buf[buf_len]; + if (!(snprintf (buf, buf_len, "%d", iany) > 0)) + buf [0] = '\0'; + vStringCopyS (vstr, buf); + } + else if (t == ES_TYPE_BOOLEAN) + vStringCopyS (vstr, any == es_true? "true": "false"); + else + { + const char *type_name = get_type_name (any); + vStringCopyS (vstr, "--"); + vStringCatS (vstr, type_name); + vStringCatS (vstr, "--"); + } + + es_object_ref (o); + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, o); + es_object_unref (o); + + return es_false; +} + +static EsObject* +op_cvx (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayLast (vm->ostack); + + if (es_object_get_type (o) == OPT_TYPE_ARRAY + && (! (((ArrayFat *)es_fatptr_get (o))->attr & ATTR_EXECUTABLE))) + { + EsObject *xarray = array_shared_new (o, + ((ArrayFat *)es_fatptr_get (o))->attr | ATTR_EXECUTABLE); + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, xarray); + es_object_unref(xarray); + + } + else if (es_object_get_type (o) == OPT_TYPE_NAME + && (! (((NameFat *)es_fatptr_get (o))->attr & ATTR_EXECUTABLE))) + { + EsObject *symbol = es_pointer_get (o); + EsObject *xname = name_new (symbol, ((NameFat *)es_fatptr_get (o))->attr | ATTR_EXECUTABLE); + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, xname); + es_object_unref(xname); + } + + return es_false; +} + /* * Misc operators @@ -4066,7 +4185,7 @@ op__put_str (OptVM *vm, EsObject *name, for (size_t i = 0; i < d; i++) vStringPut (vstr, ' '); if (c != 0) - vStringPut (vstr, (char)c); + vStringPut (vstr, c); } ptrArrayDeleteLastInBatch (vm->ostack, 3); @@ -4097,11 +4216,9 @@ op__forall_array (OptVM *vm, EsObject *name, { ptrArray *a = es_pointer_get (obj); unsigned int c = ptrArrayCount (a); - if (((int)c) < 0) - return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ EsObject *e = es_false; - for (int i = 0; i < c; i++) + for (unsigned int i = 0; i < c; i++) { EsObject *o = ptrArrayItem (a, i); es_object_ref (o); @@ -4154,7 +4271,7 @@ static EsObject* op__forall_dict (OptVM *vm, EsObject *name, EsObject *proc, EsObject *obj) { - EsObject *r = es_false;; + EsObject *r = es_false; hashTable *ht = es_pointer_get (obj); struct dictForallData data = { .vm = vm, @@ -4173,11 +4290,9 @@ op__forall_string (OptVM *vm, EsObject *name, { vString *s = es_pointer_get (obj); unsigned int c = vStringLength (s); - if (((int)c) < 0) - return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ EsObject *e = es_false; - for (int i = 0; i < c; i++) + for (unsigned int i = 0; i < c; i++) { unsigned char chr = vStringChar (s, i); EsObject *o = es_integer_new (chr); @@ -4229,23 +4344,24 @@ op_forall (OptVM *vm, EsObject *name) static EsObject* op__putinterval_array (OptVM *vm, EsObject *name, - ptrArray *srca, int index, ptrArray *dsta) + ptrArray *srca, unsigned int index, ptrArray *dsta) { unsigned int dlen = ptrArrayCount (dsta); unsigned int slen = ptrArrayCount (srca); if (dlen > index) { - if ((dlen - index) <= slen) + unsigned d = dlen - index; + if (d <= slen) { - ptrArrayDeleteLastInBatch (dsta, dlen - index); + ptrArrayDeleteLastInBatch (dsta, d); for (unsigned int i = 0; i < slen; i++) ptrArrayAdd (dsta, es_object_ref (ptrArrayItem (srca, i))); return es_false; } else { - for (size_t i = 0; i < slen; i++) - ptrArrayUpdate (dsta, ((size_t)index) + i, + for (unsigned int i = 0; i < slen; i++) + ptrArrayUpdate (dsta, index + i, es_object_ref (ptrArrayItem (srca, i)), es_nil); return es_false; @@ -4263,21 +4379,22 @@ op__putinterval_array (OptVM *vm, EsObject *name, static EsObject* op__putinterval_string (OptVM *vm, EsObject *name, - vString *srcv, int index, vString *dstv) + vString *srcv, unsigned int index, vString *dstv) { - size_t dlen = vStringLength (dstv); + unsigned int dlen = vStringLength (dstv); if (dlen > index) { - size_t slen = vStringLength (srcv); - if ((dlen - index) <= slen) + unsigned int d = dlen - index; + unsigned int slen = vStringLength (srcv); + if (d <= slen) { - vStringTruncate (dstv, (size_t)index); + vStringTruncate (dstv, index); vStringCat (dstv, srcv); return es_false; } else { - for (size_t i = 0; i < slen; i++) + for (unsigned int i = 0; i < slen; i++) vStringChar (dstv, index + i) = vStringChar (srcv, i); return es_false; } @@ -4309,9 +4426,10 @@ op_putinterval (OptVM *vm, EsObject *name) else return OPT_ERR_TYPECHECK; - int index = es_integer_get (indexobj); - if (index < 0) + int index0 = es_integer_get (indexobj); + if (index0 < 0) return OPT_ERR_RANGECHECK; + unsigned int index = (size_t)index0; EsObject *r; if (t == OPT_TYPE_ARRAY) @@ -4334,19 +4452,19 @@ op_putinterval (OptVM *vm, EsObject *name) static EsObject* op__copyinterval_array (OptVM *vm, EsObject *name, ptrArray *dsta, - int count, - int index, + unsigned int count, + unsigned int index, ptrArray *srca) { - unsigned long srcl = ptrArrayCount (srca); + unsigned int srcl = ptrArrayCount (srca); - if ((unsigned long)index > srcl) + if (index > srcl) return OPT_ERR_RANGECHECK; - if ((unsigned long)(index + count) > srcl) + if ((index + count) > srcl) return OPT_ERR_RANGECHECK; - for (unsigned int i = (unsigned int)index; i < index + count; i++) + for (unsigned int i = index; i < index + count; i++) ptrArrayAdd (dsta, es_object_ref (ptrArrayItem (srca, i))); return es_false; } @@ -4354,19 +4472,19 @@ op__copyinterval_array (OptVM *vm, EsObject *name, static EsObject* op__copyinterval_string (OptVM *vm, EsObject *name, vString *dsts, - int count, - int index, + unsigned int count, + unsigned int index, vString *srcs) { - size_t srcl = vStringLength (srcs); + unsigned int srcl = vStringLength (srcs); - if ((size_t)index > srcl) + if (index > srcl) return OPT_ERR_RANGECHECK; - if ((size_t)(index + count) > srcl) + if ((index + count) > srcl) return OPT_ERR_RANGECHECK; - vStringNCatSUnsafe (dsts, vStringValue (srcs) + index, (size_t)count); + vStringNCatSUnsafe (dsts, vStringValue (srcs) + index, count); return es_false; } @@ -4389,13 +4507,15 @@ op__copyinterval (OptVM *vm, EsObject *name) if (!es_integer_p (indexobj)) return OPT_ERR_TYPECHECK; - int count = es_integer_get (countobj); - if (count < 0) + int count0 = es_integer_get (countobj); + if (count0 < 0) return OPT_ERR_RANGECHECK; + unsigned int count = (unsigned int)count0; - int index = es_integer_get (indexobj); - if (index < 0) + int index0 = es_integer_get (indexobj); + if (index0 < 0) return OPT_ERR_RANGECHECK; + unsigned int index = (size_t)index0; EsObject* r; if (t == OPT_TYPE_ARRAY) diff --git a/ctags/dsl/optscript.h b/ctags/dsl/optscript.h index 8a2b122879..6176779377 100644 --- a/ctags/dsl/optscript.h +++ b/ctags/dsl/optscript.h @@ -41,7 +41,7 @@ void opt_vm_print_prompt (OptVM *vm); int opt_vm_help (OptVM *vm, MIO *out, struct OptHelpExtender *extop, void *data); -void opt_vm_clear (OptVM *vm); +void opt_vm_clear (OptVM *vm, bool clear_app_data); void opt_vm_dstack_push (OptVM *vm, EsObject *dict); void opt_vm_dstack_pop (OptVM *vm); diff --git a/ctags/main/CommonPrelude.c b/ctags/main/CommonPrelude.c index 8def2544cf..6d5be871b5 100644 --- a/ctags/main/CommonPrelude.c +++ b/ctags/main/CommonPrelude.c @@ -119,6 +119,27 @@ const char ctagsCommonPrelude []= " exch copy pop\n" "} __bddef\n" "\n" +"(string _CHOP string)\n" +"/_chop {\n" +" mark exch {} forall pop _buildstring\n" +"} __bddef\n" +"\n" +"(string _CHOP_SPACE string)\n" +"/_chop_space {\n" +" dup length dup 0 gt {\n" +" % string length\n" +" 1 sub\n" +" % string `length - 1`\n" +" 1 index exch\n" +" % string string `length - 1`\n" +" get (\\n\\t\\r\\f\\v ) exch _amember {\n" +" _chop\n" +" } if\n" +" } {\n" +" pop % pop the length\n" +" } ifelse\n" +"} __bddef\n" +"\n" "% /x mark 40 (a) 32 32 10 (b) 10 10 9 9 (xyz) 9 9 41 _buildstring def\n" "% x _normalize_spaces! x pstack\n" "\n" @@ -159,4 +180,79 @@ const char ctagsCommonPrelude []= " } forall\n" " pop\n" "} __bddef\n" +"\n" +"(array key _AINDEX nth:int true\n" +" array key _AINDEX false)\n" +"/_aindex {\n" +" 0 3 1 roll\n" +" % idx array key\n" +" exch {\n" +" % idx key elt\n" +" 1 index\n" +" eq {\n" +" % idx key\n" +" pop true exit\n" +" } {\n" +" % idx key\n" +" exch 1 add exch\n" +" } ifelse\n" +" } forall\n" +" dup true ne { pop pop false } if\n" +"} __bddef\n" +"\n" +"% define @1 ~ @9.\n" +"1 1 9 {\n" +" dup\n" +" mark (- @) 3 -1 roll ?0 add ( start:matchloc) _buildstring\n" +" exch dup\n" +" mark (@) 3 -1 roll ?0 add _buildstring cvn\n" +" exch\n" +" [ exch /start /_matchloc cvx ] cvx __bddef\n" +"} for\n" +"\n" +"% define 1@ ~ 9@.\n" +"1 1 9 {\n" +" dup\n" +" mark (- ) 3 -1 roll ?0 add (@ end:matchloc) _buildstring\n" +" exch dup\n" +" mark exch ?0 add (@) _buildstring cvn\n" +" exch\n" +" [ exch /end /_matchloc cvx ] cvx __bddef\n" +"} for\n" +"\n" +"(name:str kind:name matchloc _TAG tag\n" +" name:str kind:name _TAG tag)\n" +"/_tag {\n" +" dup type /nametype eq {\n" +" % name:str kind:name\n" +" null exch null\n" +" % name:str null kind:name null\n" +" } {\n" +" % name:str kind:name matchloc\n" +" null 3 1 roll null exch\n" +" % name:str null kind:name matchloc null\n" +" } ifelse\n" +" _foreignreftag\n" +"} __bddef\n" +"\n" +"(name:str kind:name role:name matchloc _REFTAG tag\n" +" name:str kind:name role:name _REFTAG tag)\n" +"/_reftag {\n" +" dup type /nametype eq {\n" +" % name:str kind:name role:name\n" +" null 3 1 roll\n" +" % name:str null kind:name role:name\n" +" } {\n" +" % name:str kind:name role:name matchloc\n" +" null 4 1 roll\n" +" % name:str null kind:name role:name matchloc\n" +" } ifelse\n" +" _foreignreftag\n" +"} __bddef\n" +"\n" +"(name:str language:name kind:name matchloc _FOREIGNTAG tag\n" +" name:str lang:name kind:name _FOREIGNTAG tag)\n" +"/_foreigntag {\n" +" null _foreignreftag\n" +"} __bddef\n" ; diff --git a/ctags/main/args.c b/ctags/main/args.c index 3bf012b011..6bd66464fe 100644 --- a/ctags/main/args.c +++ b/ctags/main/args.c @@ -31,7 +31,7 @@ static char *nextStringArg (const char** const next) const char* start; Assert (*next != NULL); - for (start = *next ; isspace ((int) *start) ; ++start) + for (start = *next ; isspace ((unsigned char) *start) ; ++start) ; if (*start == '\0') *next = start; @@ -40,7 +40,7 @@ static char *nextStringArg (const char** const next) size_t length; const char* end; - for (end = start ; *end != '\0' && ! isspace ((int) *end) ; ++end) + for (end = start; *end != '\0' && ! isspace ((unsigned char) *end); ++end) ; length = end - start; Assert (length > 0); @@ -156,7 +156,7 @@ static char* nextFileLine (FILE* const fp) static bool isCommentLine (char* line) { - while (isspace(*line)) + while (isspace((unsigned char) *line)) ++line; return (*line == '#'); } diff --git a/ctags/main/colprint.c b/ctags/main/colprint.c index 3af6448478..3e935d4070 100644 --- a/ctags/main/colprint.c +++ b/ctags/main/colprint.c @@ -27,7 +27,7 @@ enum colprintJustification { struct colprintHeaderColumn { vString *value; enum colprintJustification justification; - unsigned int maxWidth; + size_t maxWidth; bool needPrefix; }; @@ -36,7 +36,7 @@ struct colprintTable { ptrArray *lines; }; -static void fillWithWhitespaces (int i, FILE *fp) +static void fillWithWhitespaces (size_t i, FILE *fp) { while (i-- > 0) { @@ -119,7 +119,7 @@ void colprintTableDelete (struct colprintTable *table) static void colprintColumnPrintGeneric (vString *column, struct colprintHeaderColumn *spec, bool machinable, FILE *fp) { - int maxWidth = spec->maxWidth + (spec->needPrefix? 1: 0); + size_t maxWidth = spec->maxWidth + (spec->needPrefix? 1: 0); if ((column == spec->value) && (spec->needPrefix)) { @@ -135,7 +135,10 @@ static void colprintColumnPrintGeneric (vString *column, struct colprintHeaderCo } else { - int padLen = maxWidth - vStringLength (column); + size_t padLen = 0; + size_t colLen = vStringLength (column); + if (colLen < maxWidth) + padLen = maxWidth - colLen; if (spec->justification == COLPRINT_LEFT || spec->justification == COLPRINT_LAST) { diff --git a/ctags/main/ctags.h b/ctags/main/ctags.h index 4fd18ba0ef..9aed6e318d 100644 --- a/ctags/main/ctags.h +++ b/ctags/main/ctags.h @@ -14,16 +14,62 @@ /* * MACROS */ + +/* VERSION.REVISION.PATCH: + + For incompatible changes for the command line interface (CLI) + of ctags or readtags commands, increment VERSION. + Removing a parameter of a parser is part of incompatible change + for the CLI. + When OUTPUT_VERSION_AGE set to 0, increment VERSION. Set REVISION + and PATCH to 0. + For changing setting versionAge member of a parser, increment + VERSION. Set REVISION and PATCH to 0. + When chaging VERSION, set REVISION and PATCH to 0. + + For uppper compatible changes for the CLI, + increment REVISION. + Adding a new parameter to a parser is an uppper compatible change. + When incrementing OUTPUT_VERSION_CURRENT but not setting + OUTPUT_VERSION_AGE to 0, increment REVISION. + For changing increment versionCurrent member of a parser but not + setting versionAge to 0, increment REVISION. + When changing REVISION, set PATCH to 0. + + For implementation changes like bug fixes, increment PATCH. */ + #if defined (HAVE_CONFIG_H) +/* You must update PACKAGE_VERSION in configure.ac, too. + * The --version option of readtags also prints this. */ # define PROGRAM_VERSION PACKAGE_VERSION #else -# define PROGRAM_VERSION "5.9.0" +# define PROGRAM_VERSION "6.1.0" #endif #define PROGRAM_NAME "Universal Ctags" #define PROGRAM_URL "https://ctags.io/" -#define PROGRAM_COPYRIGHT "Copyright (C) 2015" +#define PROGRAM_COPYRIGHT "Copyright (C) 2015-2023" #define AUTHOR_NAME "Universal Ctags Team" +/* The concept of CURRENT and AGE is taken from libtool. + * However, we deleted REVISION in libtool when importing + * the concept of versioning from libtool. + * + * If common fields, common extras, pseudo tags have been added, + * removed or changed since last release, increment CURRENT. + * If they have been added since last release, increment AGE. + * If they have been removed since last release, set AGE to 0 + * + * From the command line of ctags, you can see the version + * information with --version and --version=NONE. + * + * In the tags file, !_TAGS_OUTPUT_VERSION shows the the version. + * + * Chaning for the command line interface, and implementation changes + * like bug fixes don't affect the CURRENT an AGE. + */ +#define OUTPUT_VERSION_CURRENT 0 +#define OUTPUT_VERSION_AGE 0 + /* * Constant */ diff --git a/ctags/main/debug.c b/ctags/main/debug.c index d68aaeece6..915294c828 100644 --- a/ctags/main/debug.c +++ b/ctags/main/debug.c @@ -51,10 +51,7 @@ extern void debugPutc (const int level, const int c) { if (debug (level) && c != EOF) { - if (c == STRING_SYMBOL) printf ("\"string\""); - else if (c == CHAR_SYMBOL) printf ("'c'"); - else putchar (c); - + putchar (c); fflush (stdout); } } diff --git a/ctags/main/debug.h b/ctags/main/debug.h index b9f224286a..628132ce8e 100644 --- a/ctags/main/debug.h +++ b/ctags/main/debug.h @@ -24,6 +24,15 @@ * Macros */ +#if defined _MSC_VER +/* See https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros */ +# define ASSERT_FUNCTION __FUNCTION__ +#elif defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L +# define ASSERT_FUNCTION __func__ +#else +# define ASSERT_FUNCTION ((const char*)0) +#endif + #ifdef DEBUG # define debug(level) ((ctags_debugLevel & (long)(level)) != 0) # define DebugStatement(x) x @@ -33,11 +42,6 @@ # define AssertNotReached() do {} while(0) # else /* We expect cc supports c99 standard. */ -# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L -# define ASSERT_FUNCTION __func__ -# else -# define ASSERT_FUNCTION ((const char*)0) -# endif # define Assert(c) ((c) ? ((void)0) : debugAssert(#c, __FILE__, __LINE__, ASSERT_FUNCTION)) # define AssertNotReached() Assert(!"The control reaches unexpected place") # endif diff --git a/ctags/main/dependency.c b/ctags/main/dependency.c index 9f4767cb22..ecb5a9e60c 100644 --- a/ctags/main/dependency.c +++ b/ctags/main/dependency.c @@ -43,7 +43,7 @@ extern void linkDependencyAtInitializeParsing (depType dtype, { if (dtype == DEPTYPE_KIND_OWNER) linkKindDependency (masterKCB, slaveKCB); - else if (dtype == DEPTYPE_SUBPARSER) + else if (dtype == DEPTYPE_SUBPARSER || dtype == DEPTYPE_FOREIGNER) { slaveParser *s = xMalloc (1, slaveParser); @@ -118,8 +118,9 @@ extern void initializeDependencies (parserDefinition *parser, for (i = 0; i < parser->dependencyCount; i++) { parserDependency *d = parser->dependencies + i; - if (d->type == DEPTYPE_SUBPARSER && - ((subparser *)(d->data))->direction & SUBPARSER_SUB_RUNS_BASE) + if ((d->type == DEPTYPE_SUBPARSER && + ((subparser *)(d->data))->direction & SUBPARSER_SUB_RUNS_BASE) + || (d->type == DEPTYPE_FOREIGNER)) { langType baseParser; baseParser = getNamedLanguage (d->upperParser, 0); @@ -320,6 +321,12 @@ extern void subparserColprintAddSubparsers (struct colprintTable *table, pushLanguage (scb->owner); foreachSlaveParser(tmp) { + if (tmp->type != DEPTYPE_SUBPARSER) + continue; + + if (!isLanguageVisible (tmp->id)) + continue; + struct colprintLine *line = colprintTableGetNewLine(table); colprintLineAppendColumnCString (line, getLanguageName (tmp->id)); @@ -368,3 +375,16 @@ extern void subparserColprintTablePrint (struct colprintTable *table, colprintTableSort (table, subparserColprintCompareLines); colprintTablePrint (table, 0, withListHeader, machinable, fp); } + +extern const char *dependencyTypeString(enum eDepType e) +{ /* Generated by misc/enumstr.sh with cmdline: + misc/enumstr.sh main/dependency.h eDepType dependencyTypeString DEPTYPE_ */ + switch (e) + { + case DEPTYPE_KIND_OWNER: return "KIND_OWNER"; + case DEPTYPE_SUBPARSER: return "SUBPARSER"; + case DEPTYPE_FOREIGNER: return "FOREIGNER"; + case COUNT_DEPTYPES: return "COUNT_DEPTYPES"; + default: return "UNKNOWN"; + } +} diff --git a/ctags/main/dependency.h b/ctags/main/dependency.h index 258aee4a1b..bdeb71ab92 100644 --- a/ctags/main/dependency.h +++ b/ctags/main/dependency.h @@ -26,6 +26,7 @@ typedef enum eDepType { DEPTYPE_KIND_OWNER, DEPTYPE_SUBPARSER, + DEPTYPE_FOREIGNER, COUNT_DEPTYPES, } depType; diff --git a/ctags/main/dependency_p.h b/ctags/main/dependency_p.h index d59e9b3d61..d1b7f62017 100644 --- a/ctags/main/dependency_p.h +++ b/ctags/main/dependency_p.h @@ -55,4 +55,5 @@ extern void finalizeDependencies (parserDefinition *parser, extern slaveParser *getFirstSlaveParser(struct slaveControlBlock *controlBlock); extern slaveParser *getNextSlaveParser(slaveParser *last); +extern const char *dependencyTypeString(enum eDepType e); #endif /* CTAGS_MAIN_DEPENDENCY_PRIVATE_H */ diff --git a/ctags/main/e_msoft.h b/ctags/main/e_msoft.h index 90c6d07b32..0c77ca8690 100644 --- a/ctags/main/e_msoft.h +++ b/ctags/main/e_msoft.h @@ -20,7 +20,6 @@ #define HAVE_DIRECT_H 1 #define HAVE_STRICMP 1 #define HAVE_STRNICMP 1 -#define HAVE_STRSTR 1 #define HAVE_STRERROR 1 #define HAVE__FINDFIRST 1 #define HAVE_FINDNEXT 1 diff --git a/ctags/main/entry.c b/ctags/main/entry.c index 23ce97fdcc..74bb19e8ad 100644 --- a/ctags/main/entry.c +++ b/ctags/main/entry.c @@ -44,6 +44,7 @@ #include "field.h" #include "fmt_p.h" #include "kind.h" +#include "interval_tree_generic.h" #include "nestlevel.h" #include "options_p.h" #include "ptag_p.h" @@ -74,7 +75,7 @@ /* Hack for ridiculous practice of Microsoft Visual C++. */ -#if defined (WIN32) && defined (_MSC_VER) +#if defined (_WIN32) && defined (_MSC_VER) # define chsize _chsize # define open _open # define close _close @@ -95,6 +96,7 @@ typedef struct eTagFile { int cork; unsigned int corkFlags; ptrArray *corkQueue; + struct rb_root intervaltab; bool patternCacheValid; } tagFile; @@ -104,6 +106,8 @@ typedef struct sTagEntryInfoX { int corkIndex; struct rb_root symtab; struct rb_node symnode; + struct rb_node intervalnode; + unsigned long __intervalnode_subtree_last; } tagEntryInfoX; /* @@ -111,15 +115,22 @@ typedef struct sTagEntryInfoX { */ static tagFile TagFile = { - NULL, /* tag file name */ - NULL, /* tag file directory (absolute) */ - NULL, /* file pointer */ - { 0, 0 }, /* numTags */ - { 0, 0 }, /* max */ - NULL, /* vLine */ - .cork = false, - .corkQueue = NULL, - .patternCacheValid = false, + NULL, /* tag file name */ + NULL, /* tag file directory (absolute) */ + NULL, /* file pointer */ + { 0, 0 }, /* numTags */ + { 0, 0 }, /* max */ + NULL, /* vLine */ + .cork = false, + .corkQueue = NULL, + /* .intervaltab = RB_ROOT, + * + * msvc doesn't accept the above expression: + * + * main\entry.c(128) : error C2099: initializer is not a constant + * + */ + .patternCacheValid = false, }; static bool TagsToStdout = false; @@ -135,6 +146,13 @@ extern int truncate (const char *path, off_t length); extern int ftruncate (int fd, off_t length); #endif +#define INTERVAL_START(node) ((node)->slot.lineNumber) +#define INTERVAL_END(node) ((node)->slot.extensionFields._endLine) + +INTERVAL_TREE_DEFINE(tagEntryInfoX, intervalnode, + unsigned long, __intervalnode_subtree_last, + INTERVAL_START, INTERVAL_END, /*static*/, intervaltab) + /* * FUNCTION DEFINITIONS */ @@ -200,7 +218,7 @@ extern void makeFileTag (const char *const fileName) unnecessary read line loop. */ while (readLineFromInputFile () != NULL) ; /* Do nothing */ - tag.extensionFields.endLine = getInputLineNumber (); + setTagEndLine(&tag, getInputLineNumber ()); } if (isFieldEnabled (FIELD_EPOCH)) @@ -669,7 +687,7 @@ static size_t appendInputLine (int putc_func (char , void *), const char *const const int next = *(p + 1); const int c = *p; - if (c == CRETURN || c == NEWLINE) + if (c == '\r' || c == '\n') break; if (patternLengthLimit != 0 && length >= patternLengthLimit && @@ -683,10 +701,10 @@ static size_t appendInputLine (int putc_func (char , void *), const char *const } /* If character is '\', or a terminal '$', then quote it. */ - if (c == BACKSLASH || c == (Option.backward ? '?' : '/') || - (c == '$' && (next == NEWLINE || next == CRETURN))) + if (c == '\\' || c == (Option.backward ? '?' : '/') || + (c == '$' && (next == '\n' || next == '\r'))) { - putc_func (BACKSLASH, data); + putc_func ('\\', data); ++length; } putc_func (c, data); @@ -712,7 +730,7 @@ static int vstring_puts (const char* s, void *data) } #ifdef DEBUG -static bool isPosSet(MIOPos pos) +static bool isPosSet (MIOPos pos) { char * p = (char *)&pos; bool r = false; @@ -725,7 +743,7 @@ static bool isPosSet(MIOPos pos) #endif extern char *readLineFromBypassForTag (vString *const vLine, const tagEntryInfo *const tag, - long *const pSeekValue) + long *const pSeekValue) { Assert (isPosSet (tag->filePosition) || (tag->pattern == NULL)); return readLineFromBypass (vLine, tag->filePosition, pSeekValue); @@ -739,7 +757,7 @@ extern size_t truncateTagLineAfterTag ( char *const line, const char *const token, const bool discardNewline) { size_t len = 0; - char *p = strstr (line, token); + char *p = strrstr (line, token); if (p != NULL) { @@ -783,7 +801,17 @@ static char* getFullQualifiedScopeNameFromCorkQueue (const tagEntryInfo * inner_ lang = scope->langType; root_scope = scope; } - scope = getEntryInCorkQueue (scope->extensionFields.scopeIndex); + int scopeIndex = scope->extensionFields.scopeIndex; + scope = getEntryInCorkQueue (scopeIndex); + + if (scope && scope->extensionFields.scopeIndex == scopeIndex) + { + error (WARNING, + "interanl error: scope information made a loop structure: %s in %s:%lu", + scope->name, scope->inputFileName, scope->lineNumber); + /* Force break this while-loop. */ + scope = NULL; + } } n = vStringNew (); @@ -804,7 +832,7 @@ static char* getFullQualifiedScopeNameFromCorkQueue (const tagEntryInfo * inner_ } extern void getTagScopeInformation (tagEntryInfo *const tag, - const char **kind, const char **name) + const char **kind, const char **name) { if (kind) *kind = NULL; @@ -813,9 +841,9 @@ extern void getTagScopeInformation (tagEntryInfo *const tag, const tagEntryInfo * scope = getEntryInCorkQueue (tag->extensionFields.scopeIndex); if (tag->extensionFields.scopeKindIndex == KIND_GHOST_INDEX - && tag->extensionFields.scopeName == NULL - && scope - && ptrArrayCount (TagFile.corkQueue) > 0) + && tag->extensionFields.scopeName == NULL + && scope + && ptrArrayCount (TagFile.corkQueue) > 0) { char *full_qualified_scope_name = getFullQualifiedScopeNameFromCorkQueue(scope); Assert (full_qualified_scope_name); @@ -827,7 +855,7 @@ extern void getTagScopeInformation (tagEntryInfo *const tag, } if (tag->extensionFields.scopeKindIndex != KIND_GHOST_INDEX && - tag->extensionFields.scopeName != NULL) + tag->extensionFields.scopeName != NULL) { if (kind) { @@ -845,9 +873,9 @@ extern void getTagScopeInformation (tagEntryInfo *const tag, static int makePatternStringCommon (const tagEntryInfo *const tag, - int (* putc_func) (char , void *), - int (* puts_func) (const char* , void *), - void *output) + int (* putc_func) (char , void *), + int (* puts_func) (const char* , void *), + void *output) { int length = 0; @@ -864,8 +892,8 @@ static int makePatternStringCommon (const tagEntryInfo *const tag, static vString *cached_pattern; static MIOPos cached_location; if (TagFile.patternCacheValid - && (! tag->truncateLineAfterTag) - && (memcmp (&tag->filePosition, &cached_location, sizeof(MIOPos)) == 0)) + && (! tag->truncateLineAfterTag) + && (memcmp (&tag->filePosition, &cached_location, sizeof(MIOPos)) == 0)) return puts_func (vStringValue (cached_pattern), output); line = readLineFromBypassForTag (TagFile.vLine, tag, NULL); @@ -904,7 +932,7 @@ static int makePatternStringCommon (const tagEntryInfo *const tag, } length += putc_func(searchChar, output); - if ((tag->boundaryInfo & BOUNDARY_START) == 0) + if ((tag->boundaryInfo & INPUT_BOUNDARY_START) == 0) length += putc_func('^', output); length += appendInputLine (putc_func, line, Option.patternLengthLimit, output, &omitted); @@ -928,7 +956,7 @@ extern char* makePatternString (const tagEntryInfo *const tag) return vStringDeleteUnwrap (pattern); } -static tagField * tagFieldNew(fieldType ftype, const char *value, bool valueOwner) +static tagField* tagFieldNew (fieldType ftype, const char *value, bool valueOwner) { tagField *f = xMalloc (1, tagField); @@ -969,11 +997,11 @@ static void attachParserFieldGeneric (tagEntryInfo *const tag, fieldType ftype, } } -extern void attachParserField (tagEntryInfo *const tag, bool inCorkQueue, fieldType ftype, const char * value) +extern void attachParserField (tagEntryInfo *const tag, fieldType ftype, const char * value) { Assert (tag != NULL); - if (inCorkQueue) + if (tag->inCorkQueue) { const char * v; v = eStrdup (value); @@ -993,7 +1021,7 @@ extern void attachParserFieldToCorkEntry (int index, { tagEntryInfo * tag = getEntryInCorkQueue (index); if (tag) - attachParserField (tag, true, ftype, value); + attachParserField (tag, ftype, value); } extern const tagField* getParserFieldForIndex (const tagEntryInfo * tag, int index) @@ -1010,11 +1038,11 @@ extern const tagField* getParserFieldForIndex (const tagEntryInfo * tag, int ind } } -extern const char* getParserFieldValueForType (tagEntryInfo *const tag, fieldType ftype) +extern const char* getParserFieldValueForType (const tagEntryInfo *const tag, fieldType ftype) { - for (int i = 0; i < tag->usedParserFields; i++) + for (unsigned int i = 0; i < tag->usedParserFields; i++) { - const tagField *f = getParserFieldForIndex (tag, i); + const tagField *f = getParserFieldForIndex (tag, (int)i); if (f && f->ftype == ftype) return f->value; } @@ -1049,15 +1077,35 @@ static tagEntryInfo *newNilTagEntry (unsigned int corkFlags) x->corkIndex = CORK_NIL; x->symtab = RB_ROOT; x->slot.kindIndex = KIND_FILE_INDEX; + x->slot.inputFileName = getInputFileName (); + x->slot.inputFileName = eStrdup (x->slot.inputFileName); + x->slot.sourceFileName = getSourceFileTagPath(); + if (x->slot.sourceFileName) + x->slot.sourceFileName = eStrdup (x->slot.sourceFileName); return &(x->slot); } +static void copyExtraDynamic (const tagEntryInfo *const src, tagEntryInfo *const dst) +{ + if (dst->extraDynamic) + { + unsigned int n = countXtags () - XTAG_COUNT; + dst->extraDynamic = xCalloc ((n / 8) + 1, uint8_t); + memcpy (dst->extraDynamic, src->extraDynamic, (n / 8) + 1); + PARSER_TRASH_BOX(dst->extraDynamic, eFree); + } +} + static tagEntryInfoX *copyTagEntry (const tagEntryInfo *const tag, - unsigned int corkFlags) + const char *shareInputFileName, + const char *sharedSourceFileName, + unsigned int corkFlags) { tagEntryInfoX *x = xMalloc (1, tagEntryInfoX); x->symtab = RB_ROOT; x->corkIndex = CORK_NIL; + memset(&x->intervalnode, 0, sizeof (x->intervalnode)); + x->__intervalnode_subtree_last = 0; tagEntryInfo *slot = (tagEntryInfo *)x; *slot = *tag; @@ -1065,7 +1113,17 @@ static tagEntryInfoX *copyTagEntry (const tagEntryInfo *const tag, if (slot->pattern) slot->pattern = eStrdup (slot->pattern); - slot->inputFileName = eStrdup (slot->inputFileName); + if (slot->inputFileName == getInputFileName ()) + { + slot->inputFileName = shareInputFileName; + slot->isInputFileNameShared = 1; + } + else + { + slot->inputFileName = eStrdup (slot->inputFileName); + slot->isInputFileNameShared = 0; + } + slot->name = eStrdup (slot->name); if (slot->extensionFields.access) slot->extensionFields.access = eStrdup (slot->extensionFields.access); @@ -1086,16 +1144,29 @@ static tagEntryInfoX *copyTagEntry (const tagEntryInfo *const tag, slot->extensionFields.xpath = eStrdup (slot->extensionFields.xpath); #endif + copyExtraDynamic (tag, slot); if (slot->extraDynamic) + PARSER_TRASH_BOX_TAKE_BACK(slot->extraDynamic); + + if (slot->sourceFileName == NULL) + slot->isSourceFileNameShared = 0; + else if (strcmp(slot->sourceFileName, sharedSourceFileName) == 0) { - int n = countXtags () - XTAG_COUNT; - slot->extraDynamic = xCalloc ((n / 8) + 1, uint8_t); - memcpy (slot->extraDynamic, tag->extraDynamic, (n / 8) + 1); + /* strcmp() is needed here. + * sharedSourceFileName can be changed during parsing a file. + * So we cannot use the condition like: + * + * if (slot->sourceFileName == getSourceFileTagPath()) { ... } + * + */ + slot->sourceFileName = sharedSourceFileName; + slot->isSourceFileNameShared = 1; } - - if (slot->sourceFileName) + else + { slot->sourceFileName = eStrdup (slot->sourceFileName); - + slot->isSourceFileNameShared = 0; + } slot->usedParserFields = 0; slot->parserFieldsDynamic = NULL; @@ -1136,11 +1207,19 @@ static void deleteTagEnry (void *data) tagEntryInfo *slot = data; if (slot->kindIndex == KIND_FILE_INDEX) + { + eFree ((char *)slot->inputFileName); + if (slot->sourceFileName) + eFree ((char *)slot->sourceFileName); goto out; + } if (slot->pattern) eFree ((char *)slot->pattern); - eFree ((char *)slot->inputFileName); + + if (!slot->isInputFileNameShared) + eFree ((char *)slot->inputFileName); + eFree ((char *)slot->name); if (slot->extensionFields.access) @@ -1165,7 +1244,7 @@ static void deleteTagEnry (void *data) if (slot->extraDynamic) eFree (slot->extraDynamic); - if (slot->sourceFileName) + if (slot->sourceFileName && !slot->isSourceFileNameShared) eFree ((char *)slot->sourceFileName); clearParserFields (slot); @@ -1225,6 +1304,12 @@ static void corkSymtabPut (tagEntryInfoX *scope, const char* name, tagEntryInfoX rb_insert_color(&item->symnode, root); } +static void corkSymtabUnlink (tagEntryInfoX *scope, tagEntryInfoX *item) +{ + struct rb_root *root = &scope->symtab; + rb_erase (&item->symnode, root); +} + extern bool foreachEntriesInScope (int corkIndex, const char *name, entryForeachFunc func, @@ -1306,7 +1391,7 @@ extern bool foreachEntriesInScope (int corkIndex, do { tagEntryInfoX *entry = container_of(cursor, tagEntryInfoX, symnode); - if (!revisited_rep || !name || strcmp(name, entry->slot.name)) + if (!revisited_rep || !name || !strcmp(name, entry->slot.name)) { verbose ("symtbl[< ] %s->%p\n", name, &entry->slot); if (!func (entry->corkIndex, &entry->slot, data)) @@ -1322,20 +1407,68 @@ extern bool foreachEntriesInScope (int corkIndex, return true; } -static bool findName (int corkIndex, tagEntryInfo *entry, void *data) +struct countData { + bool onlyDefinitionTag; + unsigned int count; + entryForeachFunc func; + void *cbData; +}; + +static bool countEntryMaybe (int corkIndex, tagEntryInfo *entry, void *cbData) +{ + struct countData *data = cbData; + if (data->onlyDefinitionTag + && !isRoleAssigned (entry, ROLE_DEFINITION_INDEX)) + return true; + + if (data->func == NULL + || data->func (corkIndex, entry, data->cbData)) + data->count++; + return true; +} + +unsigned int countEntriesInScope (int corkIndex, bool onlyDefinitionTag, + entryForeachFunc func, void *cbData) { - int *index = data; + struct countData data = { + .onlyDefinitionTag = onlyDefinitionTag, + .count = 0, + .func = func, + .cbData = cbData, + }; - *index = corkIndex; + foreachEntriesInScope (corkIndex, NULL, + &countEntryMaybe, + &data); + + return data.count; +} + +struct anyEntryInScopeData { + int index; + bool onlyDefinitionTag; +}; + +static bool findName (int corkIndex, tagEntryInfo *entry, void *cbData) +{ + struct anyEntryInScopeData *data = cbData; + + if (data->onlyDefinitionTag && !isRoleAssigned (entry, ROLE_DEFINITION_INDEX)) + return true; + + data->index = corkIndex; return false; } -int anyEntryInScope (int corkIndex, const char *name) +int anyEntryInScope (int corkIndex, const char *name, bool onlyDefinitionTag) { - int index = CORK_NIL; + struct anyEntryInScopeData data = { + .index = CORK_NIL, + .onlyDefinitionTag = onlyDefinitionTag, + }; - if (foreachEntriesInScope (corkIndex, name, findName, &index) == false) - return index; + if (foreachEntriesInScope (corkIndex, name, findName, &data) == false) + return data.index; return CORK_NIL; } @@ -1344,6 +1477,7 @@ struct anyKindsEntryInScopeData { int index; const int *kinds; int count; + bool onlyDefinitionTag; }; static bool findNameOfKinds (int corkIndex, tagEntryInfo *entry, void *data) @@ -1353,7 +1487,9 @@ static bool findNameOfKinds (int corkIndex, tagEntryInfo *entry, void *data) for (int i = 0; i < kdata->count; i++) { int k = kdata->kinds [i]; - if (entry->kindIndex == k) + if (entry->kindIndex == k + && ((!kdata->onlyDefinitionTag) + || isRoleAssigned (entry, ROLE_DEFINITION_INDEX))) { kdata->index = corkIndex; return false; @@ -1363,19 +1499,22 @@ static bool findNameOfKinds (int corkIndex, tagEntryInfo *entry, void *data) } int anyKindEntryInScope (int corkIndex, - const char *name, int kind) + const char *name, int kind, + bool onlyDefinitionTag) { - return anyKindsEntryInScope (corkIndex, name, &kind, 1); + return anyKindsEntryInScope (corkIndex, name, &kind, 1, onlyDefinitionTag); } int anyKindsEntryInScope (int corkIndex, const char *name, - const int *kinds, int count) + const int *kinds, int count, + bool onlyDefinitionTag) { struct anyKindsEntryInScopeData data = { .index = CORK_NIL, .kinds = kinds, .count = count, + .onlyDefinitionTag = onlyDefinitionTag, }; if (foreachEntriesInScope (corkIndex, name, findNameOfKinds, &data) == false) @@ -1386,12 +1525,14 @@ int anyKindsEntryInScope (int corkIndex, int anyKindsEntryInScopeRecursive (int corkIndex, const char *name, - const int *kinds, int count) + const int *kinds, int count, + bool onlyDefinitionTag) { struct anyKindsEntryInScopeData data = { .index = CORK_NIL, .kinds = kinds, .count = count, + .onlyDefinitionTag = onlyDefinitionTag, }; tagEntryInfo *e; @@ -1425,12 +1566,26 @@ extern void registerEntry (int corkIndex) } } -static int queueTagEntry(const tagEntryInfo *const tag) +extern void unregisterEntry (int corkIndex) +{ + Assert (TagFile.corkFlags & CORK_SYMTAB); + Assert (corkIndex != CORK_NIL); + + tagEntryInfoX *e = ptrArrayItem (TagFile.corkQueue, corkIndex); + { + tagEntryInfoX *scope = ptrArrayItem (TagFile.corkQueue, e->slot.extensionFields.scopeIndex); + corkSymtabUnlink (scope, e); + } + +} + +static int queueTagEntry (const tagEntryInfo *const tag) { static bool warned; int corkIndex; - tagEntryInfoX * entry = copyTagEntry (tag, + tagEntryInfo * nil = ptrArrayItem (TagFile.corkQueue, 0); + tagEntryInfoX * entry = copyTagEntry (tag, nil->inputFileName, nil->sourceFileName, TagFile.corkFlags); if (ptrArrayCount (TagFile.corkQueue) == (size_t)INT_MAX) @@ -1451,9 +1606,82 @@ static int queueTagEntry(const tagEntryInfo *const tag) entry->corkIndex = corkIndex; entry->slot.inCorkQueue = 1; + /* Don't put FQ tags to interval table. + * About placeholder, a parser should call + * removeFromIntervalTabMaybe() explicitly. + */ + if (! isTagExtraBitMarked (&entry->slot, XTAG_FILE_NAMES) + && entry->slot.extensionFields._endLine > entry->slot.lineNumber + && !isTagExtraBitMarked (tag, XTAG_QUALIFIED_TAGS)) + { + intervaltab_insert(entry, &TagFile.intervaltab); + entry->slot.inIntevalTab = 1; + } return corkIndex; } +extern void updateTagLine (tagEntryInfo *tag, unsigned long lineNumber, + MIOPos filePosition) +{ + tagEntryInfoX *entry = NULL; + if (tag->inIntevalTab) + { + entry = (tagEntryInfoX *)tag; + removeFromIntervalTabMaybe (entry->corkIndex); + } + + tag->lineNumber = lineNumber; + tag->filePosition = filePosition; + tag->boundaryInfo = getNestedInputBoundaryInfo (lineNumber); + + if (entry && tag->lineNumber < tag->extensionFields._endLine) + { + intervaltab_insert(entry, &TagFile.intervaltab); + tag->inIntevalTab = 1; + } +} + +extern void setTagEndLine(tagEntryInfo *tag, unsigned long endLine) +{ + if (endLine != 0 && endLine < tag->lineNumber) + { + error (WARNING, + "given end line (%lu) for the tag (%s) in the file (%s) is smaller than its start line: %lu", + endLine, + tag->name, + tag->inputFileName, + tag->lineNumber); + return; + } + + if (isTagExtraBitMarked (tag, XTAG_FILE_NAMES) + || !tag->inCorkQueue + || isTagExtraBitMarked (tag, XTAG_QUALIFIED_TAGS)) + { + tag->extensionFields._endLine = endLine; + return; + } + + tagEntryInfoX *entry = (tagEntryInfoX *)tag; + + if (tag->inIntevalTab) + removeFromIntervalTabMaybe (entry->corkIndex); + + tag->extensionFields._endLine = endLine; + if (endLine > tag->lineNumber) + { + intervaltab_insert(entry, &TagFile.intervaltab); + tag->inIntevalTab = 1; + } +} + +extern void setTagEndLineToCorkEntry (int corkIndex, unsigned long endLine) +{ + tagEntryInfo *entry = getEntryInCorkQueue (corkIndex); + if (entry) + setTagEndLine (entry, endLine); +} + extern void setupWriter (void *writerClientData) { writerSetup (TagFile.mio, writerClientData); @@ -1464,11 +1692,14 @@ extern bool teardownWriter (const char *filename) return writerTeardown (TagFile.mio, filename); } -static bool isTagWritable(const tagEntryInfo *const tag) +static bool isTagWritable (const tagEntryInfo *const tag) { if (tag->placeholder) return false; + if (! isLanguageEnabled(tag->langType) ) + return false; + if (! isLanguageKindEnabled(tag->langType, tag->kindIndex)) return false; @@ -1503,9 +1734,10 @@ static bool isTagWritable(const tagEntryInfo *const tag) } else if (isLanguageKindRefOnly(tag->langType, tag->kindIndex)) { - error (WARNING, "definition tag for refonly kind(%s) is made: %s", + error (WARNING, "PARSER BUG: a definition tag for a refonly kind(%s.%s) of is made: %s found in %s.", + getLanguageName(tag->langType), getLanguageKind(tag->langType, tag->kindIndex)->name, - tag->name); + tag->name, tag->inputFileName); /* This one is not so critical. */ } @@ -1529,7 +1761,7 @@ static void writeTagEntry (const tagEntryInfo *const tag) DebugStatement ( debugEntry (tag); ) -#ifdef WIN32 +#ifdef _WIN32 if (getFilenameSeparator(Option.useSlashAsFilenameSeparator) == FILENAME_SEP_USE_SLASH) { Assert (((const tagEntryInfo *)tag)->inputFileName); @@ -1544,8 +1776,8 @@ static void writeTagEntry (const tagEntryInfo *const tag) #endif if (includeExtensionFlags () - && isXtagEnabled (XTAG_QUALIFIED_TAGS) - && doesInputLanguageRequestAutomaticFQTag () + && isXtagEnabled (XTAG_QUALIFIED_TAGS) + && doesInputLanguageRequestAutomaticFQTag (tag) && !isTagExtraBitMarked (tag, XTAG_QUALIFIED_TAGS) && !tag->skipAutoFQEmission) { @@ -1566,9 +1798,9 @@ static void writeTagEntry (const tagEntryInfo *const tag) } extern bool writePseudoTag (const ptagDesc *desc, - const char *const fileName, - const char *const pattern, - const char *const parserName) + const char *const fileName, + const char *const pattern, + const char *const parserName) { int length; @@ -1585,7 +1817,7 @@ extern bool writePseudoTag (const ptagDesc *desc, return true; } -extern void corkTagFile(unsigned int corkFlags) +extern void corkTagFile (unsigned int corkFlags) { TagFile.cork++; if (TagFile.cork == 1) @@ -1594,10 +1826,11 @@ extern void corkTagFile(unsigned int corkFlags) TagFile.corkQueue = ptrArrayNew (deleteTagEnry); tagEntryInfo *nil = newNilTagEntry (corkFlags); ptrArrayAdd (TagFile.corkQueue, nil); + TagFile.intervaltab = RB_ROOT; } } -extern void uncorkTagFile(void) +extern void uncorkTagFile (void) { unsigned int i; @@ -1615,8 +1848,8 @@ extern void uncorkTagFile(void) writeTagEntry (tag); - if (doesInputLanguageRequestAutomaticFQTag () - && isXtagEnabled (XTAG_QUALIFIED_TAGS) + if (doesInputLanguageRequestAutomaticFQTag (tag) + && isXtagEnabled (XTAG_QUALIFIED_TAGS) && !isTagExtraBitMarked (tag, XTAG_QUALIFIED_TAGS) && !tag->skipAutoFQEmission && ((tag->extensionFields.scopeKindIndex != KIND_GHOST_INDEX @@ -1632,7 +1865,7 @@ extern void uncorkTagFile(void) TagFile.corkQueue = NULL; } -extern tagEntryInfo *getEntryInCorkQueue (int n) +extern tagEntryInfo *getEntryInCorkQueue (int n) { if ((CORK_NIL < n) && (((size_t)n) < ptrArrayCount (TagFile.corkQueue))) return ptrArrayItem (TagFile.corkQueue, n); @@ -1647,22 +1880,29 @@ extern tagEntryInfo *getEntryOfNestingLevel (const NestingLevel *nl) return getEntryInCorkQueue (nl->corkIndex); } -extern size_t countEntryInCorkQueue (void) +extern size_t countEntryInCorkQueue (void) { return ptrArrayCount (TagFile.corkQueue); } -extern void markTagPlaceholder (tagEntryInfo *e, bool placeholder) +extern void markTagAsPlaceholder (tagEntryInfo *e, bool placeholder) { e->placeholder = placeholder; } +extern void markCorkEntryAsPlaceholder (int index, bool placeholder) +{ + tagEntryInfo *e = getEntryInCorkQueue(index); + if (e) + markTagAsPlaceholder(e, placeholder); +} + extern int makePlaceholder (const char *const name) { tagEntryInfo e; initTagEntry (&e, name, KIND_GHOST_INDEX); - markTagPlaceholder(&e, true); + markTagAsPlaceholder(&e, true); /* * makePlaceholder may be called even before reading any bytes @@ -1690,8 +1930,9 @@ extern int makeTagEntry (const tagEntryInfo *const tag) if (tag->name [0] == '\0' && (!tag->placeholder)) { if (!doesInputLanguageAllowNullTag()) - error (WARNING, "ignoring null tag in %s(line: %lu)", - getInputFileName (), tag->lineNumber); + error (NOTICE, "ignoring null tag in %s(line: %lu, language: %s)", + getInputFileName (), tag->lineNumber, + getLanguageName (tag->langType)); goto out; } @@ -1774,20 +2015,20 @@ extern int makeQualifiedTagEntry (const tagEntryInfo *const e) extern void setTagPositionFromTag (tagEntryInfo *const dst, const tagEntryInfo *const src) { - dst->lineNumber = src->lineNumber; - dst->filePosition = src->filePosition; + updateTagLine (dst, src->lineNumber, src->filePosition); + dst->boundaryInfo = src->boundaryInfo; } static void initTagEntryFull (tagEntryInfo *const e, const char *const name, - unsigned long lineNumber, - langType langType_, - MIOPos filePosition, - const char *inputFileName, - int kindIndex, - roleBitsType roleBits, - const char *sourceFileName, - langType sourceLangType, - long sourceLineNumberDifference) + unsigned long lineNumber, + langType langType_, + MIOPos filePosition, + const char *inputFileName, + int kindIndex, + roleBitsType roleBits, + const char *sourceFileName, + langType sourceLangType, + long sourceLineNumberDifference) { int i; @@ -1850,11 +2091,18 @@ extern void initTagEntry (tagEntryInfo *const e, const char *const name, } extern void initRefTagEntry (tagEntryInfo *const e, const char *const name, - int kindIndex, int roleIndex) + int kindIndex, int roleIndex) { initForeignRefTagEntry (e, name, getInputLanguage (), kindIndex, roleIndex); } +extern void initForeignTagEntry (tagEntryInfo *const e, const char *const name, + langType type, + int kindIndex) +{ + initForeignRefTagEntry (e, name, type, kindIndex, ROLE_DEFINITION_INDEX); +} + extern void initForeignRefTagEntry (tagEntryInfo *const e, const char *const name, langType langType, int kindIndex, int roleIndex) @@ -1871,7 +2119,7 @@ extern void initForeignRefTagEntry (tagEntryInfo *const e, const char *const nam getSourceLineNumber() - getInputLineNumber ()); } -static void markTagExtraBitFull (tagEntryInfo *const tag, xtagType extra, bool mark) +static void markTagExtraBitFull (tagEntryInfo *const tag, xtagType extra, bool mark) { unsigned int index; unsigned int offset; @@ -1896,8 +2144,8 @@ static void markTagExtraBitFull (tagEntryInfo *const tag, xtagType extra, else { Assert (extra < countXtags ()); - - int n = countXtags () - XTAG_COUNT; + Assert (XTAG_COUNT <= countXtags ()); + unsigned int n = countXtags () - XTAG_COUNT; tag->extraDynamic = xCalloc ((n / 8) + 1, uint8_t); if (!tag->inCorkQueue) PARSER_TRASH_BOX(tag->extraDynamic, eFree); @@ -1911,12 +2159,12 @@ static void markTagExtraBitFull (tagEntryInfo *const tag, xtagType extra, slot [ index ] &= ~(1 << offset); } -extern void markTagExtraBit (tagEntryInfo *const tag, xtagType extra) +extern void markTagExtraBit (tagEntryInfo *const tag, xtagType extra) { markTagExtraBitFull (tag, extra, true); } -extern void unmarkTagExtraBit (tagEntryInfo *const tag, xtagType extra) +extern void unmarkTagExtraBit (tagEntryInfo *const tag, xtagType extra) { markTagExtraBitFull (tag, extra, false); } @@ -1955,7 +2203,43 @@ extern bool isTagExtra (const tagEntryInfo *const tag) return false; } -static void assignRoleFull(tagEntryInfo *const e, int roleIndex, bool assign) +extern void resetTagCorkState (tagEntryInfo *const tag, + enum resetTagMemberAction xtagAction, + enum resetTagMemberAction parserFieldsAction) +{ + tagEntryInfo original = *tag; + + tag->inCorkQueue = 0; + + switch (xtagAction) + { + case RESET_TAG_MEMBER_COPY: + copyExtraDynamic (&original, tag); + break; + case RESET_TAG_MEMBER_CLEAR: + tag->extraDynamic = NULL; + break; + case RESET_TAG_MEMBER_DONTTOUCH: + break; + } + + switch (parserFieldsAction) + { + case RESET_TAG_MEMBER_COPY: + tag->usedParserFields = 0; + tag->parserFieldsDynamic = NULL; + copyParserFields (&original, tag); + break; + case RESET_TAG_MEMBER_CLEAR: + tag->usedParserFields = 0; + tag->parserFieldsDynamic = NULL; + break; + case RESET_TAG_MEMBER_DONTTOUCH: + break; + } +} + +static void assignRoleFull (tagEntryInfo *const e, int roleIndex, bool assign) { if (roleIndex == ROLE_DEFINITION_INDEX) { @@ -1979,12 +2263,17 @@ static void assignRoleFull(tagEntryInfo *const e, int roleIndex, bool assign) AssertNotReached(); } -extern void assignRole(tagEntryInfo *const e, int roleIndex) +extern void assignRole (tagEntryInfo *const e, int roleIndex) { assignRoleFull(e, roleIndex, true); } -extern bool isRoleAssigned(const tagEntryInfo *const e, int roleIndex) +extern void unassignRole (tagEntryInfo *const e, int roleIndex) +{ + assignRoleFull(e, roleIndex, false); +} + +extern bool isRoleAssigned (const tagEntryInfo *const e, int roleIndex) { if (roleIndex == ROLE_DEFINITION_INDEX) return (!e->extensionFields.roleBits); @@ -1992,7 +2281,7 @@ extern bool isRoleAssigned(const tagEntryInfo *const e, int roleIndex) return (e->extensionFields.roleBits & makeRoleBit(roleIndex)); } -extern unsigned long numTagsAdded(void) +extern unsigned long numTagsAdded (void) { return TagFile.numTags.added; } @@ -2002,7 +2291,7 @@ extern void setNumTagsAdded (unsigned long nadded) TagFile.numTags.added = nadded; } -extern unsigned long numTagsTotal(void) +extern unsigned long numTagsTotal (void) { return TagFile.numTags.added + TagFile.numTags.prev; } @@ -2012,7 +2301,7 @@ extern unsigned long maxTagsLine (void) return (unsigned long)TagFile.max.line; } -extern void invalidatePatternCache(void) +extern void invalidatePatternCache (void) { TagFile.patternCacheValid = false; } @@ -2020,7 +2309,7 @@ extern void invalidatePatternCache(void) extern void tagFilePosition (MIOPos *p) { /* mini-geany doesn't set TagFile.mio. */ - if (TagFile.mio == NULL) + if (TagFile.mio == NULL) return; if (mio_getpos (TagFile.mio, p) == -1) @@ -2031,7 +2320,7 @@ extern void tagFilePosition (MIOPos *p) extern void setTagFilePosition (MIOPos *p, bool truncation) { /* mini-geany doesn't set TagFile.mio. */ - if (TagFile.mio == NULL) + if (TagFile.mio == NULL) return; @@ -2057,14 +2346,58 @@ extern const char* getTagFileDirectory (void) return TagFile.directory; } -static bool markAsPlaceholder (int index, tagEntryInfo *e, void *data CTAGS_ATTR_UNUSED) +static bool markAsPlaceholderRecursively (int index, tagEntryInfo *e, void *data CTAGS_ATTR_UNUSED) { - e->placeholder = 1; + markTagAsPlaceholder (e, true); markAllEntriesInScopeAsPlaceholder (index); return true; } extern void markAllEntriesInScopeAsPlaceholder (int index) { - foreachEntriesInScope (index, NULL, markAsPlaceholder, NULL); + foreachEntriesInScope (index, NULL, markAsPlaceholderRecursively, NULL); +} + +extern int queryIntervalTabByLine(unsigned long lineNum) +{ + return queryIntervalTabByRange(lineNum, lineNum); +} + +extern int queryIntervalTabByRange(unsigned long start, unsigned long end) +{ + int index = CORK_NIL; + + tagEntryInfoX *ex = intervaltab_iter_first(&TagFile.intervaltab, start, end); + while (ex) { + index = ex->corkIndex; + ex = intervaltab_iter_next(ex, start, end); + } + return index; +} + +extern int queryIntervalTabByCorkEntry(int corkIndex) +{ + Assert (corkIndex != CORK_NIL); + + tagEntryInfoX *ex = ptrArrayItem (TagFile.corkQueue, corkIndex); + tagEntryInfo *e = &ex->slot; + + if (e->extensionFields._endLine == 0) + return queryIntervalTabByLine(e->lineNumber); + return queryIntervalTabByRange(e->lineNumber, e->extensionFields._endLine); +} + +extern bool removeFromIntervalTabMaybe(int corkIndex) +{ + if (corkIndex == CORK_NIL) + return false; + + tagEntryInfoX *ex = ptrArrayItem (TagFile.corkQueue, corkIndex); + tagEntryInfo *e = &ex->slot; + if (!e->inIntevalTab) + return false; + + intervaltab_remove(ex, &TagFile.intervaltab); + e->inIntevalTab = 0; + return true; } diff --git a/ctags/main/entry.h b/ctags/main/entry.h index d6eeb3e693..56e79e4156 100644 --- a/ctags/main/entry.h +++ b/ctags/main/entry.h @@ -24,8 +24,6 @@ #include "ptrarray.h" #include "nestlevel.h" -#include "script_p.h" - /* * MACROS */ @@ -44,26 +42,56 @@ typedef uint64_t roleBitsType; /* Information about the current tag candidate. */ struct sTagEntryInfo { + /* + * the bit fields parsers may access for setting DIRECTLY + */ unsigned int lineNumberEntry:1; /* pattern or line number entry */ unsigned int isFileScope :1; /* is tag visible only within input file? */ - unsigned int isFileEntry :1; /* is this just an entry for a file name? */ unsigned int truncateLineAfterTag :1; /* truncate tag line at end of tag name? */ - unsigned int placeholder :1; /* is used only for keeping corkIndex based scope chain. - Put this entry to cork queue but - don't print it to tags file. */ unsigned int skipAutoFQEmission:1; /* If a parser makes a fq tag for the current tag by itself, set this. */ + + /* + * the bit fields parser may access for setting via accessor + */ + unsigned int placeholder :1; /* is used only for keeping corkIndex based scope chain. + Put this entry to cork queue but + the tag is not printed: + * not printed as a tag entry, + * never used as a part of automatically generated FQ tag, and + * not printed as a part of scope. + See getTagScopeInformation() and + getFullQualifiedScopeNameFromCorkQueue. */ + + /* + * the bit fields only the main part can access. + */ + unsigned int isFileEntry :1; /* is this just an entry for a file name? */ unsigned int isPseudoTag:1; /* Used only in xref output. If a tag is a pseudo, set this. */ unsigned int inCorkQueue:1; - - unsigned long lineNumber; /* line number of tag */ + unsigned int isInputFileNameShared: 1; /* shares the value for inputFileName. + * Set in the cork queue; don't touch this.*/ + unsigned int isSourceFileNameShared: 1; /* shares the value for sourceFileName. + * Set in the cork queue; don't touch this.*/ + unsigned int boundaryInfo: 2; /* info about nested input stream */ + unsigned int inIntevalTab:1; + + unsigned long lineNumber; /* line number of tag; + use updateTagLine() for updating this member. */ const char* pattern; /* pattern for locating input line * (may be NULL if not present) *//* */ - unsigned int boundaryInfo; /* info about nested input stream */ MIOPos filePosition; /* file position of line containing tag */ langType langType; /* language of input file */ - const char *inputFileName; /* name of input file */ + const char *inputFileName; /* name of input file. + You cannot modify the contents of buffer pointed + by this member of the tagEntryInfo returned from + getEntryInCorkQueue(). The buffer may be shared + between tag entries in the cork queue. + + Further more, modifying this member of the + tagEntryInfo returned from getEntryInCorkQueue() + may cause a memory leak. */ const char *name; /* name of the tag */ int kindIndex; /* kind descriptor */ uint8_t extra[ ((XTAG_COUNT) / 8) + 1 ]; @@ -82,8 +110,10 @@ struct sTagEntryInfo { const char* scopeName; int scopeIndex; /* cork queue entry for upper scope tag. This field is meaningful if the value - is not CORK_NIL and scope[0] and scope[1] are - NULL. */ + is not CORK_NIL, scopeKindIndex is KIND_GHOST_INDEX, + and scopeName is NULL. + CXX parser violates this rule; see the comment inside + cxxTagBegin(). */ const char* signature; @@ -98,7 +128,7 @@ struct sTagEntryInfo { #ifdef HAVE_LIBXML const char* xpath; #endif - unsigned long endLine; + unsigned long _endLine; /* Don't set directly. Use setTagEndLine() */ time_t epoch; #define NO_NTH_FIELD -1 short nth; @@ -139,10 +169,24 @@ extern void initTagEntry (tagEntryInfo *const e, const char *const name, int kindIndex); extern void initRefTagEntry (tagEntryInfo *const e, const char *const name, int kindIndex, int roleIndex); + +/* initForeignRefTagEntry() is for making a tag for the language X when parsing + * source code of Y language. + * From the view point of the language Y, we call the language X a foreign + * language. + * + * When making a tag for a foreign with this function, you must declare the + * language X in the parser of Y with DEPTYPE_FOREIGNER dependency. + */ +extern void initForeignTagEntry (tagEntryInfo *const e, const char *const name, + langType type, + int kindIndex); extern void initForeignRefTagEntry (tagEntryInfo *const e, const char *const name, langType type, int kindIndex, int roleIndex); extern void assignRole(tagEntryInfo *const e, int roleIndex); +#define clearRoles(E) assignRole((E), ROLE_DEFINITION_INDEX) +extern void unassignRole(tagEntryInfo *const e, int roleIndex); extern bool isRoleAssigned(const tagEntryInfo *const e, int roleIndex); extern int makeQualifiedTagEntry (const tagEntryInfo *const e); @@ -162,13 +206,15 @@ size_t countEntryInCorkQueue (void); * specified in the scopeIndex field of the tag specified with CORKINDEX. */ void registerEntry (int corkIndex); +void unregisterEntry (int corkIndex); /* foreachEntriesInScope is for traversing the symbol table for a table * specified with CORKINDEX. If CORK_NIL is given, this function traverses * top-level entries. If name is NULL, this function traverses all entries * under the scope. * - * If FUNC returns false, this function returns false. + * If FUNC returns false, this function returns false immediately + * even if more entires in the scope. * If FUNC never returns false, this function returns true. * If FUNC is not called because no node for NAME in the symbol table, * this function returns true. @@ -178,24 +224,40 @@ bool foreachEntriesInScope (int corkIndex, entryForeachFunc func, void *data); +unsigned int countEntriesInScope (int corkIndex, bool onlyDefinitionTag, + entryForeachFunc func, void *data); + /* Return the cork index for NAME in the scope specified with CORKINDEX. * Even if more than one entries for NAME are in the scope, this function * just returns one of them. Returning CORK_NIL means there is no entry * for NAME. */ int anyEntryInScope (int corkIndex, - const char *name); + const char *name, + bool onlyDefinitionTag); int anyKindEntryInScope (int corkIndex, - const char *name, int kind); + const char *name, int kind, + bool onlyDefinitionTag); int anyKindsEntryInScope (int corkIndex, const char *name, - const int * kinds, int count); + const int * kinds, int count, + bool onlyDefinitionTag); int anyKindsEntryInScopeRecursive (int corkIndex, const char *name, - const int * kinds, int count); + const int * kinds, int count, + bool onlyDefinitionTag); + +extern void updateTagLine(tagEntryInfo *tag, unsigned long lineNumber, MIOPos filePosition); +extern void setTagEndLine (tagEntryInfo *tag, unsigned long endLine); +extern void setTagEndLineToCorkEntry (int corkIndex, unsigned long endLine); + +extern int queryIntervalTabByLine(unsigned long lineNum); +extern int queryIntervalTabByRange(unsigned long startLine, unsigned long endLine); +extern int queryIntervalTabByCorkEntry(int corkIndex); +extern bool removeFromIntervalTabMaybe(int corkIndex); extern void markTagExtraBit (tagEntryInfo *const tag, xtagType extra); extern void unmarkTagExtraBit (tagEntryInfo *const tag, xtagType extra); @@ -204,21 +266,53 @@ extern bool isTagExtraBitMarked (const tagEntryInfo *const tag, xtagType extra); /* If any extra bit is on, return true. */ extern bool isTagExtra (const tagEntryInfo *const tag); +/* + In the following frequently used code-pattern: + + tagEntryInfo *original = getEntryInCorkQueue (index); + tagEntryInfo xtag = *original; + ... customize XTAG ... + makeTagEntry (&xtag); + + ORIGINAL and XTAG share some memory objects through their members. + TagEntryInfo::name is one of obvious ones. + When updating the member in the ... customize XTAG ... stage, you will + do: + + vStringValue *xtag_name = vStringNewInit (xtags->name); + ... customize XTAG_NAME with vString functions ... + xtag.name = vStringValue (xtag_name); + makeTagEntry (&xtag); + vStringDelete (xtag_name); + + There are some vague ones: extraDynamic and parserFieldsDynamic. + resetTagCorkState does: + + - mark the TAG is not in cork queue: set inCorkQueue 0. + - copy, clear, or dont touch the extraDynamic member. + - copy, clear, or dont touch the parserFieldsDynamic member. + +*/ + +enum resetTagMemberAction { + RESET_TAG_MEMBER_COPY, + RESET_TAG_MEMBER_CLEAR, + RESET_TAG_MEMBER_DONTTOUCH, +}; + +extern void resetTagCorkState (tagEntryInfo *const tag, + enum resetTagMemberAction xtagAction, + enum resetTagMemberAction parserFieldsAction); + /* Functions for attaching parser specific fields * - * Which function you should use? - * ------------------------------ + * Which function should I use? + * ---------------------------- * Case A: * * If your parser uses the Cork API, and your parser called * makeTagEntry () already, you can use both - * attachParserFieldToCorkEntry () and attachParserField (). Your - * parser has the cork index returned from makeTagEntry (). With the - * cork index, your parser can call attachParserFieldToCorkEntry (). - * If your parser already call getEntryInCorkQueue () to get the tag - * entry for the cork index, your parser can call attachParserField () - * with passing true for `inCorkQueue' parameter. attachParserField () - * is a bit faster than attachParserFieldToCorkEntry (). + * attachParserFieldToCorkEntry () and attachParserField (). * * attachParserField () and attachParserFieldToCorkEntry () duplicates * the memory object specified with `value' and stores the duplicated @@ -231,9 +325,7 @@ extern bool isTagExtra (const tagEntryInfo *const tag); * Case B: * * If your parser called one of initTagEntry () family but didn't call - * makeTagEntry () for a tagEntry yet, use attachParserField () with - * false for `inCorkQueue' whether your parser uses the Cork API or - * not. + * makeTagEntry () for a tagEntry yet, use attachParserField (). * * The parser (== caller) keeps the memory object specified with `value' * till calling makeTagEntry (). The parser must free the memory object @@ -260,12 +352,13 @@ extern bool isTagExtra (const tagEntryInfo *const tag); * The other data type and the combination of types are not implemented yet. * */ -extern void attachParserField (tagEntryInfo *const tag, bool inCorkQueue, fieldType ftype, const char* value); +extern void attachParserField (tagEntryInfo *const tag, fieldType ftype, const char* value); extern void attachParserFieldToCorkEntry (int index, fieldType ftype, const char* value); -extern const char* getParserFieldValueForType (tagEntryInfo *const tag, fieldType ftype); +extern const char* getParserFieldValueForType (const tagEntryInfo *const tag, fieldType ftype); extern int makePlaceholder (const char *const name); -extern void markTagPlaceholder (tagEntryInfo *e, bool placeholder); +extern void markTagAsPlaceholder (tagEntryInfo *e, bool placeholder); +extern void markCorkEntryAsPlaceholder (int index, bool placeholder); /* Marking all tag entries entries under the scope specified * with index recursively. diff --git a/ctags/main/error_p.h b/ctags/main/error_p.h index bad73a0eb4..2733eac27e 100644 --- a/ctags/main/error_p.h +++ b/ctags/main/error_p.h @@ -16,11 +16,11 @@ #include "routines.h" typedef bool (* errorPrintFunc) (const errorSelection selection, const char *const format, - va_list ap, void *data); + va_list ap, void *data) CTAGS_ATTR_PRINTF (2, 0); extern void setErrorPrinter (errorPrintFunc printer, void *data); extern bool stderrDefaultErrorPrinter (const errorSelection selection, const char *const format, va_list ap, - void *data); + void *data) CTAGS_ATTR_PRINTF (2, 0); #endif diff --git a/ctags/main/field.c b/ctags/main/field.c index 7f1b21ac19..52dd28594c 100644 --- a/ctags/main/field.c +++ b/ctags/main/field.c @@ -32,9 +32,7 @@ #include "xtag_p.h" #include "optscript.h" - -#define FIELD_NULL_LETTER_CHAR '-' -#define FIELD_NULL_LETTER_STRING "-" +#include "script_p.h" typedef struct sFieldObject { fieldDefinition *def; @@ -100,6 +98,8 @@ static EsObject* getFieldValueForScope (const tagEntryInfo *, const fieldDefinit static EsObject* setFieldValueForScope (tagEntryInfo *, const fieldDefinition *, const EsObject *); static EsObject* checkFieldValueForScope (const fieldDefinition *, const EsObject *); static EsObject* getFieldValueForExtras (const tagEntryInfo *, const fieldDefinition *); +static EsObject* getFieldValueForAccess (const tagEntryInfo *, const fieldDefinition *); +static EsObject* setFieldValueForAccess (tagEntryInfo *, const fieldDefinition *, const EsObject *); static EsObject* getFieldValueForSignature (const tagEntryInfo *, const fieldDefinition *); static EsObject* setFieldValueForSignature (tagEntryInfo *, const fieldDefinition *, const EsObject *); static EsObject* getFieldValueForRoles (const tagEntryInfo *, const fieldDefinition *); @@ -108,7 +108,7 @@ static EsObject* checkFieldValueForLineCommon (const fieldDefinition *, const Es static EsObject* setFieldValueForLineCommon (tagEntryInfo *, const fieldDefinition *, const EsObject *); static EsObject* setFieldValueForInherits (tagEntryInfo *, const fieldDefinition *, const EsObject *); -#define WITH_DEFUALT_VALUE(str) ((str)?(str):FIELD_NULL_LETTER_STRING) +#define WITH_DEFAULT_VALUE(str) ((str)?(str):FIELD_NULL_LETTER_STRING) static fieldDefinition fieldDefinitionsFixed [] = { [FIELD_NAME] = { @@ -223,7 +223,7 @@ static fieldDefinition fieldDefinitionsExuberant [] = { .dataType = FIELDTYPE_INTEGER, .getterValueType = "int", .getValueObject = getFieldValueForLineCommon, - .setterValueType = "matchlok|int", + .setterValueType = "matchloc|line:int", /* line <= getInputLineNumber(); */ .checkValueForSetter= checkFieldValueForLineCommon, .setValueObject = setFieldValueForLineCommon, }, @@ -298,6 +298,11 @@ static fieldDefinition fieldDefinitionsExuberant [] = { .doesContainAnyChar = NULL, .isValueAvailable = isAccessFieldAvailable, .dataType = FIELDTYPE_STRING, + .getterValueType = NULL, + .getValueObject = getFieldValueForAccess, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = setFieldValueForAccess, }, [FIELD_IMPLEMENTATION - FIELD_ECTAGS_START] = { .letter = 'm', @@ -425,7 +430,7 @@ static fieldDefinition fieldDefinitionsUniversal [] = { .dataType = FIELDTYPE_INTEGER, .getterValueType = "int", .getValueObject = getFieldValueForLineCommon, - .setterValueType = "matchlok|int", + .setterValueType = "matchloc|int", .checkValueForSetter= checkFieldValueForLineCommon, .setValueObject = setFieldValueForLineCommon, @@ -533,7 +538,7 @@ static fieldObject* getFieldObject(fieldType type) return fieldObjects + type; } -extern fieldType getFieldTypeForOption (char letter) +extern fieldType getFieldTypeForLetter (char letter) { unsigned int i; @@ -715,13 +720,13 @@ static bool doesContainAnyCharInInput (const tagEntryInfo *const tag, const char static const char *renderFieldSignature (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - return renderEscapedString (WITH_DEFUALT_VALUE (tag->extensionFields.signature), + return renderEscapedString (WITH_DEFAULT_VALUE (tag->extensionFields.signature), tag, b); } static const char *renderFieldSignatureNoEscape (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - return renderAsIs (b, WITH_DEFUALT_VALUE (tag->extensionFields.signature)); + return renderAsIs (b, WITH_DEFAULT_VALUE (tag->extensionFields.signature)); } static bool doesContainAnyCharInSignature (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, const char *chars) @@ -758,7 +763,7 @@ static bool doesContainAnyCharInFieldScope (const tagEntryInfo *const tag, const static const char *renderFieldInherits (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - return renderEscapedString (WITH_DEFUALT_VALUE (tag->extensionFields.inheritance), + return renderEscapedString (WITH_DEFAULT_VALUE (tag->extensionFields.inheritance), tag, b); } @@ -769,9 +774,9 @@ static const char *renderFieldTyperef (const tagEntryInfo *const tag, const char && tag->extensionFields.typeRef [1] == NULL) return renderAsIs (b, FIELD_NULL_LETTER_STRING); - vStringCatS (b, WITH_DEFUALT_VALUE (tag->extensionFields.typeRef [0])); + vStringCatS (b, WITH_DEFAULT_VALUE (tag->extensionFields.typeRef [0])); vStringPut (b, ':'); - return renderEscapedName (false, WITH_DEFUALT_VALUE (tag->extensionFields.typeRef [1]), tag, b); + return renderEscapedName (false, WITH_DEFAULT_VALUE (tag->extensionFields.typeRef [1]), tag, b); } @@ -855,13 +860,12 @@ extern bool doesFieldHaveTabOrNewlineChar (fieldType type, const tagEntryInfo * static const char* renderCompactInputLine (vString *b, const char *const line) { bool lineStarted = false; - const char *p; - int c; /* Write everything up to, but not including, the newline. */ - for (p = line, c = *p ; c != NEWLINE && c != '\0' ; c = *++p) + for (const char *p = line; *p != '\n' && *p != '\0'; ++p) { + int c = (unsigned char) *p; if (lineStarted || ! isspace (c)) /* ignore leading spaces */ { lineStarted = true; @@ -871,11 +875,11 @@ static const char* renderCompactInputLine (vString *b, const char *const line) /* Consume repeating white space. */ - while (next = *(p+1) , isspace (next) && next != NEWLINE) + while (next = (unsigned char) *(p+1), isspace (next) && next != '\n') ++p; c = ' '; /* force space character for any white space */ } - if (c != CRETURN || *(p + 1) != NEWLINE) + if (c != '\r' || *(p + 1) != '\n') vStringPut (b, c); } } @@ -995,14 +999,14 @@ static const char *renderFieldLanguage (const tagEntryInfo *const tag, l = getLanguageName(tag->langType); } - return renderAsIs (b, WITH_DEFUALT_VALUE(l)); + return renderAsIs (b, WITH_DEFAULT_VALUE(l)); } static const char *renderFieldAccess (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - return renderAsIs (b, WITH_DEFUALT_VALUE (tag->extensionFields.access)); + return renderAsIs (b, WITH_DEFAULT_VALUE (tag->extensionFields.access)); } static const char *renderFieldKindLetter (const tagEntryInfo *const tag, @@ -1020,7 +1024,7 @@ static const char *renderFieldImplementation (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - return renderAsIs (b, WITH_DEFUALT_VALUE (tag->extensionFields.implementation)); + return renderAsIs (b, WITH_DEFAULT_VALUE (tag->extensionFields.implementation)); } static const char *renderFieldFile (const tagEntryInfo *const tag, @@ -1064,9 +1068,9 @@ static const char *renderFieldExtras (const tagEntryInfo *const tag, const char *value CTAGS_ATTR_UNUSED, vString* b) { - int i; + unsigned int i; bool hasExtra = false; - int c = countXtags(); + unsigned int c = countXtags(); for (i = 0; i < c; i++) { @@ -1119,9 +1123,9 @@ static const char *renderFieldEnd (const tagEntryInfo *const tag, { static char buf[21]; - if (tag->extensionFields.endLine != 0) + if (tag->extensionFields._endLine != 0) { - sprintf (buf, "%lu", tag->extensionFields.endLine); + sprintf (buf, "%lu", tag->extensionFields._endLine); return renderAsIs (b, buf); } else @@ -1133,11 +1137,16 @@ static const char *renderFieldEpoch (const tagEntryInfo *const tag, { #define buf_len 21 static char buf[buf_len]; - - if (snprintf (buf, buf_len, "%lld", (long long)tag->extensionFields.epoch) > 0) +#ifdef _MSC_VER +#define FMTLL "%I64d" +#else +#define FMTLL "%lld" +#endif + if (snprintf (buf, buf_len, FMTLL, (long long)tag->extensionFields.epoch) > 0) return renderAsIs (b, buf); else return NULL; +#undef FMTLL #undef buf_len } @@ -1188,15 +1197,7 @@ static bool isSignatureFieldAvailable (const tagEntryInfo *const tag) static bool isExtrasFieldAvailable (const tagEntryInfo *const tag) { - unsigned int i; - - if (tag->extraDynamic) - return true; - for (i = 0; i < sizeof (tag->extra); i++) - if (tag->extra [i]) - return true; - - return false; + return isTagExtra (tag); } static bool isXpathFieldAvailable (const tagEntryInfo *const tag) @@ -1210,7 +1211,7 @@ static bool isXpathFieldAvailable (const tagEntryInfo *const tag) static bool isEndFieldAvailable (const tagEntryInfo *const tag) { - return (tag->extensionFields.endLine != 0)? true: false; + return (tag->extensionFields._endLine != 0)? true: false; } static bool isEpochAvailable (const tagEntryInfo *const tag) @@ -1244,7 +1245,7 @@ extern bool enableField (fieldType type, bool state) else verbose ("enable field \"%s\"<%s>: %s\n", getFieldObject(type)->def->name, - getLanguageName (getFieldOwner(type)), + getLanguageName (getFieldLanguage(type)), (state? "yes": "no")); return old; } @@ -1254,7 +1255,7 @@ extern bool isCommonField (fieldType type) return (FIELD_BUILTIN_LAST < type)? false: true; } -extern int getFieldOwner (fieldType type) +extern langType getFieldLanguage (fieldType type) { return getFieldObject(type)->language; } @@ -1277,7 +1278,7 @@ extern bool doesFieldHaveRenderer (fieldType type, bool noEscaping) return getFieldObject(type)->def->render? true: false; } -extern int countFields (void) +extern unsigned int countFields (void) { return fieldObjectUsed; } @@ -1324,7 +1325,7 @@ extern int defineField (fieldDefinition *def, langType language) Assert (def->name); for (i = 0; i < strlen (def->name); i++) { - Assert ( isalpha (def->name [i]) ); + Assert ( isalpha ((unsigned char) def->name [i]) ); } def->letter = NUL_FIELD_LETTER; @@ -1408,7 +1409,7 @@ static void fieldColprintAddLine (struct colprintTable *table, int i) bmask < FIELDTYPE_END_MARKER; bmask <<= 1, offset++) if (type & bmask) - typefields[offset] = fieldDataTypeFalgs[offset]; + typefields[offset] = fieldDataTypeFlags[offset]; } colprintLineAppendColumnCString (line, typefields); colprintLineAppendColumnBool (line, writerDoesTreatFieldAsFixed (i)); @@ -1705,8 +1706,12 @@ static EsObject* checkFieldValueForTyperef (const fieldDefinition *fdef, const E ; else if (es_integer_p (obj)) { - int index = es_integer_get (obj); - if (index >= countEntryInCorkQueue ()) + int index0 = es_integer_get (obj); + if (index0 < 0) + return OPT_ERR_RANGECHECK; + + unsigned int index = index0; + if (index == 0 || index >= countEntryInCorkQueue ()) return OPTSCRIPT_ERR_NOTAGENTRY; } else @@ -1721,10 +1726,10 @@ static EsObject* getFieldValueForScope (const tagEntryInfo *tag, const fieldDefi static EsObject* setFieldValueForScope (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) { - int index = es_integer_get (obj); + unsigned int index = es_integer_get (obj); if (index < countEntryInCorkQueue ()) { - tag->extensionFields.scopeIndex = index; + tag->extensionFields.scopeIndex = (int)index; return es_false; } @@ -1749,12 +1754,12 @@ static EsObject* getFieldValueForExtras (const tagEntryInfo *tag, const fieldDef EsObject* a = opt_array_new (); - for (int i = 0; i < countXtags (); i++) + for (unsigned int i = 0; i < countXtags (); i++) { if (!isTagExtraBitMarked (tag, i)) continue; - langType lang = getXtagOwner (i); + langType lang = getXtagLanguage (i); const char *lang_name = (lang == LANG_IGNORE) ? NULL : getLanguageName (lang); @@ -1777,23 +1782,43 @@ static EsObject* getFieldValueForExtras (const tagEntryInfo *tag, const fieldDef return a; } -static EsObject* getFieldValueForSignature (const tagEntryInfo *tag, const fieldDefinition *fdef) +static EsObject* getFieldValueForCOMMON (const char *field, const tagEntryInfo *tag, const fieldDefinition *fdef) { - if (!tag->extensionFields.signature) + if (!field) return es_nil; - return (opt_name_new_from_cstr (tag->extensionFields.signature)); + return (opt_name_new_from_cstr (field)); } -static EsObject* setFieldValueForSignature (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +static EsObject* setFieldValueForCOMMON (const char **field, tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) { - if (tag->extensionFields.signature) - eFree ((char *)tag->extensionFields.signature); + if (*field) + eFree ((char *)*field); const char *str = opt_string_get_cstr (obj); - tag->extensionFields.signature = eStrdup (str); + *field = eStrdup (str); return es_false; } +static EsObject* getFieldValueForAccess (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + return getFieldValueForCOMMON(tag->extensionFields.access, tag, fdef); +} + +static EsObject* setFieldValueForAccess (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + return setFieldValueForCOMMON(&tag->extensionFields.access, tag, fdef, obj); +} + +static EsObject* getFieldValueForSignature (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + return getFieldValueForCOMMON(tag->extensionFields.signature, tag, fdef); +} + +static EsObject* setFieldValueForSignature (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + return setFieldValueForCOMMON(&tag->extensionFields.signature, tag, fdef, obj); +} + static void makeRolesArray (const tagEntryInfo *const tag, int roleIndex, void *data) { EsObject *a = data; @@ -1820,9 +1845,9 @@ static EsObject* getFieldValueForRoles (const tagEntryInfo *tag, const fieldDefi static EsObject* getFieldValueForLineCommon (const tagEntryInfo *tag, const fieldDefinition *fdef) { if (fdef->ftype == FIELD_END_LINE) - return ((int)tag->extensionFields.endLine == 0) + return ((int)tag->extensionFields._endLine == 0) ? es_nil - : es_integer_new ((int)tag->extensionFields.endLine); + : es_integer_new ((int)tag->extensionFields._endLine); else return ((int)tag->lineNumber == 0) ? es_nil @@ -1836,7 +1861,7 @@ static EsObject* checkFieldValueForLineCommon (const fieldDefinition *fdef, cons static EsObject* setFieldValueForLineCommon (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) { - int l; + unsigned int l; if (es_object_get_type (obj) == OPT_TYPE_MATCHLOC) { matchLoc *loc = es_pointer_get (obj); @@ -1844,27 +1869,25 @@ static EsObject* setFieldValueForLineCommon (tagEntryInfo *tag, const fieldDefin } else if (es_integer_p (obj)) { - l = es_integer_get (obj); - if (l < 1) + int l0 = es_integer_get (obj); + if (l0 < 1) return OPT_ERR_RANGECHECK; + l = (unsigned int)l0; /* If the new line number is too large, - we cannot fill tag->filePosition wit + we cannot fill tag->filePosition with getInputFilePositionForLine(); */ if (fdef->ftype == FIELD_LINE_NUMBER - && l < getInputLineNumber()) + && l > getInputLineNumber()) return OPT_ERR_RANGECHECK; } else return OPT_ERR_TYPECHECK; if (fdef->ftype == FIELD_END_LINE) - tag->extensionFields.endLine = l; + setTagEndLine(tag, (unsigned long)l); else - { - tag->lineNumber = l; - tag->filePosition = getInputFilePositionForLine (l); - } + updateTagLine (tag, l, getInputFilePositionForLine (l)); return es_false; } diff --git a/ctags/main/field.h b/ctags/main/field.h index 6b7c02f20f..29e721389f 100644 --- a/ctags/main/field.h +++ b/ctags/main/field.h @@ -20,7 +20,6 @@ #include "types.h" #include "vstring.h" -#include "optscript.h" /* * DATA DECLARATIONS @@ -70,7 +69,7 @@ typedef enum eFieldType { /* extension field content control */ FIELD_BUILTIN_LAST = FIELD_NTH, } fieldType ; -#define fieldDataTypeFalgs "sib" /* used in --list-fields */ +#define fieldDataTypeFlags "sib" /* used in --list-fields */ typedef enum eFieldDataType { FIELDTYPE_STRING = 1 << 0, FIELDTYPE_INTEGER = 1 << 1, @@ -84,7 +83,6 @@ typedef const char* (*fieldRenderer)(const tagEntryInfo *const, const char *, vString *); -#define FIELD_LETTER_NO_USE '\0' struct sFieldDefinition { /* letter, and ftype are initialized in the main part, not in a parser. */ @@ -101,13 +99,13 @@ struct sFieldDefinition { bool (* isValueAvailable) (const tagEntryInfo *const); const char * getterValueType; - EsObject * (* getValueObject) (const tagEntryInfo *, const fieldDefinition *); + struct _EsObject * (* getValueObject) (const tagEntryInfo *, const fieldDefinition *); const char * setterValueType; /* Return es_false if passed value is acceptable. Return an error object is unacceptable. */ - EsObject * (* checkValueForSetter) (const fieldDefinition *, const EsObject *); - EsObject * (* setValueObject) (tagEntryInfo *, const fieldDefinition *, const EsObject *); + struct _EsObject * (* checkValueForSetter) (const fieldDefinition *, const struct _EsObject *); + struct _EsObject * (* setValueObject) (tagEntryInfo *, const fieldDefinition *, const struct _EsObject *); fieldDataType dataType; /* used in json output */ diff --git a/ctags/main/field_p.h b/ctags/main/field_p.h index fd56bd865f..c0ec0222f8 100644 --- a/ctags/main/field_p.h +++ b/ctags/main/field_p.h @@ -24,12 +24,15 @@ * DATA DECLARATIONS */ +#define FIELD_NULL_LETTER_CHAR '-' +#define FIELD_NULL_LETTER_STRING "-" + /* * FUNCTION PROTOTYPES */ -extern fieldType getFieldTypeForOption (char letter); +extern fieldType getFieldTypeForLetter (char letter); /* `getFieldTypeForName' is for looking for a field not owned by any parser, @@ -46,7 +49,10 @@ extern fieldType getFieldTypeForName (const char *name); extern fieldType getFieldTypeForNameAndLanguage (const char *fieldName, langType language); extern bool enableField (fieldType type, bool state); extern bool isCommonField (fieldType type); -extern int getFieldOwner (fieldType type); + +/* Return LANG_IGNORE if the field is a common field.*/ +extern langType getFieldLanguage (fieldType type); + extern const char* getFieldDescription (fieldType type); extern const char* getFieldName (fieldType type); extern unsigned char getFieldLetter (fieldType type); @@ -64,7 +70,7 @@ extern const char* renderFieldNoEscaping (fieldType type, const tagEntryInfo *ta extern bool doesFieldHaveTabOrNewlineChar (fieldType type, const tagEntryInfo *tag, int index); extern void initFieldObjects (void); -extern int countFields (void); +extern unsigned int countFields (void); /* language should be typed to langType. Use int here to avoid circular dependency */ diff --git a/ctags/main/fmt.c b/ctags/main/fmt.c index 4569058af8..8d4952b35f 100644 --- a/ctags/main/fmt.c +++ b/ctags/main/fmt.c @@ -65,7 +65,7 @@ static int printTagField (fmtSpec* fspec, MIO* fp, const tagEntryInfo * tag) else { unsigned int findex; - const tagField *f; + const tagField *f = NULL; for (findex = 0; findex < tag->usedParserFields; findex++) { @@ -74,8 +74,11 @@ static int printTagField (fmtSpec* fspec, MIO* fp, const tagEntryInfo * tag) break; } - if (findex == tag->usedParserFields) + if (f == NULL || findex == tag->usedParserFields) + { + /* The condtion is redundant for suppressing the warning. */ str = ""; + } else if (isFieldEnabled (f->ftype)) { unsigned int dt = getFieldDataType (f->ftype); @@ -84,8 +87,7 @@ static int printTagField (fmtSpec* fspec, MIO* fp, const tagEntryInfo * tag) str = renderField (f->ftype, tag, findex); if ((dt & FIELDTYPE_BOOL) && str[0] == '\0') { - /* TODO: FIELD_NULL_LETTER_STRING */ - str = "-"; + str = FIELD_NULL_LETTER_STRING; } } else if (dt & FIELDTYPE_BOOL) @@ -198,7 +200,7 @@ static fmtElement** queueTagField (fmtElement **last, long width, bool truncatio else { language = LANG_IGNORE; - ftype = getFieldTypeForOption (field_letter); + ftype = getFieldTypeForLetter (field_letter); } if (ftype == FIELD_UNKNOWN) diff --git a/ctags/main/general.h b/ctags/main/general.h index 1b2acf747f..14318f8fae 100644 --- a/ctags/main/general.h +++ b/ctags/main/general.h @@ -14,7 +14,7 @@ */ #if defined (HAVE_CONFIG_H) # include -#elif defined (WIN32) +#elif defined (_WIN32) # include "e_msoft.h" #endif diff --git a/ctags/main/htable.c b/ctags/main/htable.c index 0ebfbd815f..483dab19cb 100644 --- a/ctags/main/htable.c +++ b/ctags/main/htable.c @@ -11,6 +11,7 @@ #include "general.h" #include "htable.h" +#include "debug.h" #ifndef MAIN #include @@ -35,12 +36,14 @@ typedef struct sHashEntry hentry; struct sHashEntry { void *key; void *value; + unsigned int hash; hentry *next; }; struct sHashTable { hentry** table; unsigned int size; + unsigned int count; hashTableHashFunc hashfn; hashTableEqualFunc equalfn; hashTableDeleteFunc keyfreefn; @@ -56,13 +59,14 @@ struct chainTracker { hashTableEqualFunc equalfn; }; -static hentry* entry_new (void *key, void *value, hentry* next) +static hentry* entry_new (void *key, void *value, unsigned int hash, hentry* next) { hentry* entry = xMalloc (1, hentry); entry->key = key; entry->value = value; entry->next = next; + entry->hash = hash; return entry; } @@ -77,7 +81,9 @@ static void entry_reset (hentry* entry, keyfreefn (entry->key); if (valfreefn) valfreefn (entry->value); - entry->key = newkey; + + if (keyfreefn) + entry->key = newkey; entry->value = newval; } @@ -102,13 +108,34 @@ static void entry_reclaim (hentry* entry, entry = entry_destroy (entry, keyfreefn, valfreefn); } -static void *entry_find (hentry* entry, const void* const key, hashTableEqualFunc equalfn, +/* Looking for an entry having KEY as its key in the chain: + * entry, entry->next, entry->next->next,... + * + * We have a chance to optimize for the case when a same key is + * used repeatedly. By moving the entry found in the last time to + * the head of the chain, we can avoid taking time for tracking + * down the ->next-> chain. + * The cost for the optimization is very small. + */ +static void *entry_find (hentry** root_entry, const void* const key, hashTableEqualFunc equalfn, void *valForNotUnknownKey) { + hentry *entry = *root_entry; + hentry *last = NULL; while (entry) { if (equalfn( key, entry->key)) + { + if (last) + { + last->next = entry->next; + entry->next = *root_entry; + *root_entry = entry; + } return entry->value; + } + + last = entry; entry = entry->next; } return valForNotUnknownKey; @@ -164,7 +191,14 @@ extern hashTable *hashTableNew (unsigned int size, hashTable *htable; htable = xMalloc (1, hashTable); + + if (size < 3) + size = 3; + if ((size % 2) == 0) + size++; + htable->size = size; + htable->count = 0; htable->table = xCalloc (size, hentry*); htable->hashfn = hashfn; @@ -217,40 +251,126 @@ extern void hashTableClear (hashTable *htable) } } -extern void hashTablePutItem (hashTable *htable, void *key, void *value) +static void hashTablePutItem00 (hashTable *htable, void *key, void *value, unsigned int h) { - unsigned int i; + unsigned int i = h % htable->size; + htable->table[i] = entry_new(key, value, h, htable->table[i]); + htable->count++; +} + +/* "doubling" primes smaller than 2^32 */ +static const unsigned int primes[] = { + 3, 7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, + 43853, 87719, 175447, 350899, 701819, 1403641, 2807303, 5614657, + 11229331, 22458671, 44917381, 89834777, 179669557, 359339171, + 718678369, 1437356741, 2874713497, +}; + +static unsigned int +prime_double(unsigned int i) +{ + unsigned int j; + + Assert (i > 2); + Assert (i % 2); + + for (j = 0; j < sizeof(primes) / sizeof(primes[0]); ++j) + if (primes[j] > i) + return primes[j]; + + return i; +} + +static void hashTableGrow (hashTable *htable) +{ + unsigned int current_size = htable->size; + unsigned int new_size = prime_double (current_size); + + if (new_size <= current_size) + return; + + hentry** new_table = xCalloc (new_size, hentry*); + for (unsigned int i = 0; i < current_size; i++) + { + hentry *entry; + while ((entry = htable->table[i])) + { + unsigned int hash = entry->hash; + unsigned int j = hash % new_size; + htable->table[i] = entry->next; + entry->next = new_table[j]; + new_table[j] = entry; + } + } + + hentry** old_table = htable->table; + htable->table = new_table; + htable->size = new_size; + eFree (old_table); +} - i = htable->hashfn (key) % htable->size; - htable->table[i] = entry_new(key, value, htable->table[i]); +static void hashTablePutItem0 (hashTable *htable, void *key, void *value, unsigned int h) +{ + if (((double)htable->count / (double)htable->size) < 0.8) + { + hashTablePutItem00 (htable, key, value, h); + return; + } + + hashTableGrow (htable); + hashTablePutItem00 (htable, key, value, h); +} + +extern void hashTablePutItem (hashTable *htable, void *key, void *value) +{ + hashTablePutItem0 (htable, key, value, htable->hashfn (key)); } extern void* hashTableGetItem (hashTable *htable, const void * key) { - unsigned int i; + unsigned int h, i; - i = htable->hashfn (key) % htable->size; - return entry_find(htable->table[i], key, htable->equalfn, htable->valForNotUnknownKey); + h = htable->hashfn (key); + i = h % htable->size; + return entry_find(& (htable->table[i]), key, htable->equalfn, htable->valForNotUnknownKey); } extern bool hashTableDeleteItem (hashTable *htable, const void *key) { + unsigned int h; unsigned int i; + h = htable->hashfn (key); + i = h % htable->size; - i = htable->hashfn (key) % htable->size; - return entry_delete(&htable->table[i], key, + bool r = entry_delete(&htable->table[i], key, htable->equalfn, htable->keyfreefn, htable->valfreefn); + if (r) + htable->count--; + return r; } -extern bool hashTableUpdateItem (hashTable *htable, void *key, void *value) +extern bool hashTableUpdateItem (hashTable *htable, const void *key, void *value) { - unsigned int i; + unsigned int h, i; + + h = htable->hashfn (key); + i = h % htable->size; + bool r = entry_update(htable->table[i], (void *)key, value, + htable->equalfn, NULL, htable->valfreefn); + return r; +} - i = htable->hashfn (key) % htable->size; +extern bool hashTableUpdateOrPutItem (hashTable *htable, void *key, void *value) +{ + unsigned int h, i; + + h = htable->hashfn (key); + i = h % htable->size; bool r = entry_update(htable->table[i], key, value, - htable->equalfn, htable->keyfreefn, htable->valfreefn); + htable->equalfn, NULL, htable->valfreefn); if (!r) - htable->table[i] = entry_new(key, value, htable->table[i]); + hashTablePutItem0(htable, key, value, h); + return r; } @@ -283,7 +403,7 @@ static bool track_chain (const void *const key, void *value, void *chain_data) extern bool hashTableForeachItemOnChain (hashTable *htable, const void *key, hashTableForeachFunc proc, void *user_data) { - unsigned int i; + unsigned int h, i; struct chainTracker chain_tracker = { .target_key = key, .user_proc = proc, @@ -291,24 +411,52 @@ extern bool hashTableForeachItemOnChain (hashTable *htable, const void *ke .equalfn = htable->equalfn, }; - i = htable->hashfn (key) % htable->size; + h = htable->hashfn (key); + i = h % htable->size; if (!entry_foreach(htable->table[i], track_chain, &chain_tracker)) return false; return true; } -static bool count (const void *const key CTAGS_ATTR_UNUSED, void *value CTAGS_ATTR_UNUSED, void *data) +extern void hashTablePrintStatistics(hashTable *htable) { - int *c = data; - ++*c; - return true; + if (htable->size == 0 || htable->count == 0) + fprintf(stderr, "size: %u, count: %u, average: 0\n", + htable->size, htable->count); + + double sum = 0.0; + for (size_t i = 0; i < htable->size; i++) + { + hentry *e = htable->table[i]; + while (e) + { + sum += 1.0; + e = e->next; + } + } + double average = sum / (double)htable->size; + + double variance = 0.0; + for (size_t i = 0; i < htable->size; i++) + { + double sum0 = 0; + hentry *e = htable->table[i]; + while (e) + { + sum0 += 1.0; + e = e->next; + } + double d = sum0 - average; + variance += (d * d); + } + variance = variance / (double)htable->size; + fprintf(stderr, "size: %u, count: %u, average: %lf, s.d.: sqrt(%lf)\n", + htable->size, htable->count, average, variance); } extern unsigned int hashTableCountItem (hashTable *htable) { - int c = 0; - hashTableForeachItem (htable, count, &c); - return c; + return htable->count; } unsigned int hashPtrhash (const void * const x) diff --git a/ctags/main/htable.h b/ctags/main/htable.h index 7e14874385..a1e503a6e1 100644 --- a/ctags/main/htable.h +++ b/ctags/main/htable.h @@ -70,7 +70,19 @@ extern void hashTablePutItem (hashTable *htable, void *key, void *valu extern void* hashTableGetItem (hashTable *htable, const void * key); extern bool hashTableHasItem (hashTable * htable, const void * key); extern bool hashTableDeleteItem (hashTable *htable, const void *key); -extern bool hashTableUpdateItem (hashTable *htable, void *key, void *value); + +/* If KEY is found, this function updates the value for KEY and returns true. + * If KEY is not found, this function just returns false. + * + * KEY is just referred. The HTABLE takes the ownership of VALUE when KEY is found. */ +extern bool hashTableUpdateItem (hashTable *htable, const void *key, void *value); + +/* If KEY is found, this function updates the value for KEY and returns true. + * If KEY is not found, this function adds KEY and VALUE and returns false. + * + * The HTABLE takes the ownership of KEY only if KEY is not found. + * The HTABLE takes the ownership of VALUE either when KEY is found or not. */ +extern bool hashTableUpdateOrPutItem (hashTable *htable, void *key, void *value); /* Return true if proc never returns false; proc returns true for all * the items, or htable holds no item. @@ -87,6 +99,8 @@ extern bool hashTableForeachItemOnChain (hashTable *htable, const void *ke extern unsigned int hashTableCountItem (hashTable *htable); +extern void hashTablePrintStatistics(hashTable *htable); + #define HT_PTR_TO_INT(P) ((int)(intptr_t)(P)) #define HT_INT_TO_PTR(P) ((void*)(intptr_t)(P)) #define HT_PTR_TO_UINT(P) ((unsigned int)(uintptr_t)(P)) diff --git a/ctags/main/interval_tree_generic.h b/ctags/main/interval_tree_generic.h new file mode 100644 index 0000000000..1254ce8706 --- /dev/null +++ b/ctags/main/interval_tree_generic.h @@ -0,0 +1,197 @@ +/* + Interval Trees + (C) 2012 Michel Lespinasse + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + include/linux/interval_tree_generic.h +*/ + +#include "general.h" + +#include + +#include "rbtree_augmented.h" + +#include "inline.h" + +/* + * Template for implementing interval trees + * + * ITSTRUCT: struct type of the interval tree nodes + * ITRB: name of struct rb_node field within ITSTRUCT + * ITTYPE: type of the interval endpoints + * ITSUBTREE: name of ITTYPE field within ITSTRUCT holding last-in-subtree + * ITSTART(n): start endpoint of ITSTRUCT node n + * ITLAST(n): last endpoint of ITSTRUCT node n + * ITSTATIC: 'static' or empty + * ITPREFIX: prefix to use for the inline tree definitions + * + * Note - before using this, please consider if non-generic version + * (interval_tree.h) would work for you... + */ + +#define INTERVAL_TREE_DEFINE(ITSTRUCT, ITRB, ITTYPE, ITSUBTREE, \ + ITSTART, ITLAST, ITSTATIC, ITPREFIX) \ + \ +/* Callbacks for augmented rbtree insert and remove */ \ + \ +CTAGS_INLINE ITTYPE ITPREFIX ## _compute_subtree_last(ITSTRUCT *node) \ +{ \ + ITTYPE max = ITLAST(node), subtree_last; \ + if (node->ITRB.rb_left) { \ + subtree_last = rb_entry(node->ITRB.rb_left, \ + ITSTRUCT, ITRB)->ITSUBTREE; \ + if (max < subtree_last) \ + max = subtree_last; \ + } \ + if (node->ITRB.rb_right) { \ + subtree_last = rb_entry(node->ITRB.rb_right, \ + ITSTRUCT, ITRB)->ITSUBTREE; \ + if (max < subtree_last) \ + max = subtree_last; \ + } \ + return max; \ +} \ + \ +RB_DECLARE_CALLBACKS(static, ITPREFIX ## _augment, ITSTRUCT, ITRB, \ + ITTYPE, ITSUBTREE, ITPREFIX ## _compute_subtree_last) \ + \ +/* Insert / remove interval nodes from the tree */ \ + \ +ITSTATIC void ITPREFIX ## _insert(ITSTRUCT *node, struct rb_root *root) \ +{ \ + struct rb_node **link = &root->rb_node, *rb_parent = NULL; \ + ITTYPE start = ITSTART(node), last = ITLAST(node); \ + ITSTRUCT *parent; \ + \ + while (*link) { \ + rb_parent = *link; \ + parent = rb_entry(rb_parent, ITSTRUCT, ITRB); \ + if (parent->ITSUBTREE < last) \ + parent->ITSUBTREE = last; \ + if (start < ITSTART(parent)) \ + link = &parent->ITRB.rb_left; \ + else \ + link = &parent->ITRB.rb_right; \ + } \ + \ + node->ITSUBTREE = last; \ + rb_link_node(&node->ITRB, rb_parent, link); \ + rb_insert_augmented(&node->ITRB, root, &ITPREFIX ## _augment); \ +} \ + \ +ITSTATIC void ITPREFIX ## _remove(ITSTRUCT *node, struct rb_root *root) \ +{ \ + rb_erase_augmented(&node->ITRB, root, &ITPREFIX ## _augment); \ +} \ + \ +/* \ + * Iterate over intervals intersecting [start;last] \ + * \ + * Note that a node's interval intersects [start;last] iff: \ + * Cond1: ITSTART(node) <= last \ + * and \ + * Cond2: start <= ITLAST(node) \ + */ \ + \ +static ITSTRUCT * \ +ITPREFIX ## _subtree_search(ITSTRUCT *node, ITTYPE start, ITTYPE last) \ +{ \ + while (true) { \ + /* \ + * Loop invariant: start <= node->ITSUBTREE \ + * (Cond2 is satisfied by one of the subtree nodes) \ + */ \ + if (node->ITRB.rb_left) { \ + ITSTRUCT *left = rb_entry(node->ITRB.rb_left, \ + ITSTRUCT, ITRB); \ + if (start <= left->ITSUBTREE) { \ + /* \ + * Some nodes in left subtree satisfy Cond2. \ + * Iterate to find the leftmost such node N. \ + * If it also satisfies Cond1, that's the \ + * match we are looking for. Otherwise, there \ + * is no matching interval as nodes to the \ + * right of N can't satisfy Cond1 either. \ + */ \ + node = left; \ + continue; \ + } \ + } \ + if (ITSTART(node) <= last) { /* Cond1 */ \ + if (start <= ITLAST(node)) /* Cond2 */ \ + return node; /* node is leftmost match */ \ + if (node->ITRB.rb_right) { \ + node = rb_entry(node->ITRB.rb_right, \ + ITSTRUCT, ITRB); \ + if (start <= node->ITSUBTREE) \ + continue; \ + } \ + } \ + return NULL; /* No match */ \ + } \ +} \ + \ +ITSTATIC ITSTRUCT * \ +ITPREFIX ## _iter_first(struct rb_root *root, ITTYPE start, ITTYPE last) \ +{ \ + ITSTRUCT *node; \ + \ + if (!root->rb_node) \ + return NULL; \ + node = rb_entry(root->rb_node, ITSTRUCT, ITRB); \ + if (node->ITSUBTREE < start) \ + return NULL; \ + return ITPREFIX ## _subtree_search(node, start, last); \ +} \ + \ +ITSTATIC ITSTRUCT * \ +ITPREFIX ## _iter_next(ITSTRUCT *node, ITTYPE start, ITTYPE last) \ +{ \ + struct rb_node *rb = node->ITRB.rb_right, *prev; \ + \ + while (true) { \ + /* \ + * Loop invariants: \ + * Cond1: ITSTART(node) <= last \ + * rb == node->ITRB.rb_right \ + * \ + * First, search right subtree if suitable \ + */ \ + if (rb) { \ + ITSTRUCT *right = rb_entry(rb, ITSTRUCT, ITRB); \ + if (start <= right->ITSUBTREE) \ + return ITPREFIX ## _subtree_search(right, \ + start, last); \ + } \ + \ + /* Move up the tree until we come from a node's left child */ \ + do { \ + rb = rb_parent(&node->ITRB); \ + if (!rb) \ + return NULL; \ + prev = &node->ITRB; \ + node = rb_entry(rb, ITSTRUCT, ITRB); \ + rb = node->ITRB.rb_right; \ + } while (prev == rb); \ + \ + /* Check if the node intersects [start;last] */ \ + if (last < ITSTART(node)) /* !Cond1 */ \ + return NULL; \ + else if (start <= ITLAST(node)) /* Cond2 */ \ + return node; \ + } \ +} diff --git a/ctags/main/keyword.c b/ctags/main/keyword.c index 970057da4c..4ae1e3e08c 100644 --- a/ctags/main/keyword.c +++ b/ctags/main/keyword.c @@ -74,16 +74,17 @@ static hashEntry *getHashTableEntry (unsigned long hashedValue) static unsigned int hashValue (const char *const string, langType language, unsigned int maxLen, bool *maxLenReached) { - const signed char *p; + const char *p; unsigned int h = 5381; Assert (string != NULL); /* "djb" hash as used in g_str_hash() in glib */ - for (p = (const signed char *)string; *p != '\0'; p++) + for (p = string; *p != '\0'; p++) { - h = (h << 5) + h + tolower (*p); - if (p - (const signed char *)string > maxLen) + h = (h << 5) + h + (signed char) tolower ((unsigned char) *p); + + if (p > string + maxLen) { *maxLenReached = true; return 0; @@ -295,3 +296,32 @@ extern void addKeywordGroup (const struct keywordGroup *const groupdef, addKeyword (groupdef->keywords[i], language, groupdef->value); } } + +extern void addDialectalKeywords (const struct dialectalKeyword *const dkeywords, + size_t nDkeyword, + langType language, + const char **dialectMap, + size_t nMap) +{ + int dialect = -1; + const char *name = getLanguageName (language); + + Assert(name); + + for (size_t i = 0; i < nMap; i++) + { + if (strcmp(dialectMap[i], name) == 0) + { + dialect = i; + break; + } + } + Assert (dialect != -1); + + for (size_t i = 0; i < nDkeyword; i++) + { + const struct dialectalKeyword *p = dkeywords + i; + if (p->isValid[dialect]) + addKeyword (p->keyword, language, p->value); + } +} diff --git a/ctags/main/keyword.h b/ctags/main/keyword.h index 31587af21e..a1ef21a4ad 100644 --- a/ctags/main/keyword.h +++ b/ctags/main/keyword.h @@ -38,4 +38,24 @@ struct keywordGroup { extern void addKeywordGroup (const struct keywordGroup *const groupdef, langType language); + +/* +* DIALECTAL KEYWROD API +*/ +#define MAX_DIALECTS 10 +struct dialectalKeyword { + const char *keyword; + int value; + + /* If this keyword is valid for Nth dialect, + isValid[N] should be 1; else 0. */ + bool isValid [MAX_DIALECTS]; +}; + +extern void addDialectalKeywords (const struct dialectalKeyword *const dkeywords, + size_t nDkeyword, + langType language, + const char **dialectMap, + size_t nMap); + #endif /* CTAGS_MAIN_KEYWORD_H */ diff --git a/ctags/main/kind.c b/ctags/main/kind.c index c34c19c727..12f323c177 100644 --- a/ctags/main/kind.c +++ b/ctags/main/kind.c @@ -91,7 +91,7 @@ static void initRoleObject (roleObject *robj, roleDefinition *rdef, freeRoleDefF #ifdef DEBUG size_t len = strlen (rdef->name); for (int i = 0; i < len; i++) - Assert (isalnum (rdef->name [i])); + Assert (isalnum ((unsigned char) rdef->name [i])); #endif robj->def = rdef; robj->free = freefunc; @@ -137,6 +137,11 @@ extern struct kindControlBlock* allocKindControlBlock (parserDefinition *parser) { kindObject *kind = kcb->kind + i; kind->def = parser->kindTable + i; + + Assert (kind->def->letter != KIND_FILE_DEFAULT_LETTER); + Assert (kind->def->name == NULL /* SELF (RUNTIME) TEST NEEDS THIS. */ + || strcmp(kind->def->name, KIND_FILE_DEFAULT_NAME)); + kind->free = NULL; kind->def->id = i; kind->rcb = allocRoleControlBlock (kind); diff --git a/ctags/main/lregex.c b/ctags/main/lregex.c index e36193154e..260c28db1c 100644 --- a/ctags/main/lregex.c +++ b/ctags/main/lregex.c @@ -76,7 +76,9 @@ enum scopeAction { SCOPE_POP = 1UL << 1, SCOPE_PUSH = 1UL << 2, SCOPE_CLEAR = 1UL << 3, - SCOPE_PLACEHOLDER = 1UL << 4, + SCOPE_REF_AFTER_POP = 1UL << 4, + SCOPE_PLACEHOLDER = 1UL << 5, + SCOPE_INTERVALTAB = 1UL << 6, }; enum tableAction { @@ -115,8 +117,8 @@ struct guestLangSpec { struct guestSpec { struct guestLangSpec lang; -#define BOUNDARY_START 0 -#define BOUNDARY_END 1 +#define GUEST_BOUNDARY_START 0 +#define GUEST_BOUNDARY_END 1 struct boundarySpec boundary[2]; }; @@ -141,6 +143,7 @@ typedef struct { enum pType type; bool exclusive; bool accept_empty_name; + bool postrun; union { struct { int kindIndex; @@ -166,6 +169,7 @@ typedef struct { char *pattern_string; char *anonymous_tag_prefix; + langType foreign_lang; struct { errorSelection selection; @@ -230,14 +234,18 @@ struct lregexControlBlock { struct guestRequest *guest_req; EsObject *local_dict; + hashTable*param_dict; ptrArray *hook[SCRIPT_HOOK_MAX]; ptrArray *hook_code[SCRIPT_HOOK_MAX]; langType owner; +}; +typedef struct { + struct lregexControlBlock *lcb; scriptWindow *window; -}; +} appData; /* * DATA DEFINITIONS @@ -269,6 +277,34 @@ static void scriptTeardown (OptVM *vm, struct lregexControlBlock *lcb); static char* make_match_string (scriptWindow *window, int group); static matchLoc *make_mloc (scriptWindow *window, int group, bool start); +static struct lregexControlBlock *get_current_lcb(OptVM *vm) +{ + appData * d = opt_vm_get_app_data (vm); + return d->lcb; +} + +static scriptWindow *get_current_window(OptVM *vm) +{ + appData * d = opt_vm_get_app_data (vm); + return d->window; +} + +static struct lregexControlBlock *set_current_lcb(OptVM *vm, struct lregexControlBlock *new_lcb) +{ + appData * d = opt_vm_get_app_data (vm); + struct lregexControlBlock *old_lcb = d->lcb; + d->lcb = new_lcb; + return old_lcb; +} + +static scriptWindow *set_current_window(OptVM *vm, scriptWindow *new_windown) +{ + appData * d = opt_vm_get_app_data (vm); + scriptWindow *old_window = d->window; + d->window = new_windown; + return old_window; +} + static void deleteTable (void *ptrn) { struct regexTable *t = ptrn; @@ -340,6 +376,8 @@ extern struct lregexControlBlock* allocLregexControlBlock (parserDefinition *par lcb->tstack = ptrArrayNew(NULL); lcb->guest_req = guestRequestNew (); lcb->local_dict = es_nil; + lcb->param_dict = hashTableNew (3, hashCstrhash, hashCstreq, + eFree, eFree); for (int i = 0; i< SCRIPT_HOOK_MAX; i++) { @@ -372,6 +410,9 @@ extern void freeLregexControlBlock (struct lregexControlBlock* lcb) es_object_unref (lcb->local_dict); lcb->local_dict = es_nil; + hashTableDelete (lcb->param_dict); + lcb->param_dict = NULL; + for (int i = 0; i < SCRIPT_HOOK_MAX; i++) { ptrArrayDelete (lcb->hook[i]); @@ -390,17 +431,18 @@ extern void freeLregexControlBlock (struct lregexControlBlock* lcb) static void initRegexTag (tagEntryInfo *e, const char * name, int kindIndex, int roleIndex, int scopeIndex, int placeholder, - unsigned long line, MIOPos *pos, int xtag_type) + unsigned long line, MIOPos *pos, int xtag_type, langType foreign_lang) { Assert (name != NULL && ((name[0] != '\0') || placeholder)); - initRefTagEntry (e, name, kindIndex, roleIndex); + + if (foreign_lang == LANG_IGNORE) + initRefTagEntry (e, name, kindIndex, roleIndex); + else + initForeignRefTagEntry (e, name, foreign_lang, kindIndex, roleIndex); e->extensionFields.scopeIndex = scopeIndex; - e->placeholder = !!placeholder; + markTagAsPlaceholder(e, placeholder); if (line) - { - e->lineNumber = line; - e->filePosition = *pos; - } + updateTagLine(e, line, *pos); if (xtag_type != XTAG_UNKNOWN) markTagExtraBit (e, xtag_type); @@ -547,8 +589,8 @@ static bool parseTagRegex ( static void pre_ptrn_flag_exclusive_short (char c CTAGS_ATTR_UNUSED, void* data) { - bool *exclusive = data; - *exclusive = true; + regexPattern * ptrn = data; + ptrn->exclusive = true; } static void pre_ptrn_flag_exclusive_long (const char* const s CTAGS_ATTR_UNUSED, const char* const unused CTAGS_ATTR_UNUSED, void* data) @@ -556,9 +598,17 @@ static void pre_ptrn_flag_exclusive_long (const char* const s CTAGS_ATTR_UNUSED, pre_ptrn_flag_exclusive_short ('x', data); } +static void pre_ptrn_flag_postrun_long (const char* const s CTAGS_ATTR_UNUSED, const char* const unused CTAGS_ATTR_UNUSED, void* data) +{ + regexPattern * ptrn = data; + ptrn->postrun = true; +} + static flagDefinition prePtrnFlagDef[] = { { 'x', "exclusive", pre_ptrn_flag_exclusive_short, pre_ptrn_flag_exclusive_long , NULL, "skip testing the other patterns if a line is matched to this pattern"}, + { '\0', "postrun", NULL, pre_ptrn_flag_postrun_long, + NULL, "run after parsing with built-in code, multline regex patterns, and multitable regex patterns" }, }; static void scope_ptrn_flag_eval (const char* const f CTAGS_ATTR_UNUSED, @@ -576,6 +626,10 @@ static void scope_ptrn_flag_eval (const char* const f CTAGS_ATTR_UNUSED, *bfields |= SCOPE_CLEAR; else if (strcmp (v, "set") == 0) *bfields |= (SCOPE_CLEAR | SCOPE_PUSH); + else if (strcmp (v, "replace") == 0) + *bfields |= (SCOPE_POP|SCOPE_REF_AFTER_POP|SCOPE_PUSH); + else if (strcmp (v, "intervaltab") == 0) + *bfields |= SCOPE_INTERVALTAB; else error (FATAL, "Unexpected value for scope flag in regex definition: scope=%s", v); } @@ -589,11 +643,30 @@ static void placeholder_ptrn_flag_eval (const char* const f CTAGS_ATTR_UNUSED, static flagDefinition scopePtrnFlagDef[] = { { '\0', "scope", NULL, scope_ptrn_flag_eval, - "ACTION", "use scope stack: ACTION = ref|push|pop|clear|set"}, + "ACTION", "use scope stack: ACTION = ref|push|pop|clear|set|replace|intervaltab"}, { '\0', "placeholder", NULL, placeholder_ptrn_flag_eval, NULL, "don't put this tag to tags file."}, }; +static void intervaltab_ptrn_flag_eval (const char* const f CTAGS_ATTR_UNUSED, + const char* const v, void* data) +{ + unsigned int *bfields = data; + if (strcmp (v, "intervaltab") != 0) + { + error (WARNING, "Unexpected value for scope flag in mline regex definition: scope=%s", v); + error (WARNING, "NOTE: --mline-regex- accepts only {scope=intervaltab}"); + return; + } + + *bfields |= SCOPE_INTERVALTAB; +} + +static flagDefinition intervaltabPtrnFlagDef[] = { + { '\0', "scope", NULL, intervaltab_ptrn_flag_eval, + "intervaltab", "set scope for the tag with the interval table" }, +}; + static kindDefinition *kindNew (char letter, const char *name, const char *description) { kindDefinition *kdef = xCalloc (1, kindDefinition); @@ -647,6 +720,7 @@ static regexPattern * newPattern (regexCompiledCode* const pattern, ptrn->pattern.code = pattern->code; ptrn->exclusive = false; + ptrn->postrun = false; ptrn->accept_empty_name = false; ptrn->regptype = regptype; ptrn->xtagType = XTAG_UNKNOWN; @@ -663,6 +737,8 @@ static regexPattern * newPattern (regexCompiledCode* const pattern, ptrn->optscript = NULL; ptrn->optscript_src = NULL; + ptrn->foreign_lang = LANG_IGNORE; + return ptrn; } @@ -808,7 +884,7 @@ static void pre_ptrn_flag_guest_long (const char* const s, const char* const v, { const char *n_tmp = v + 1; const char *n = n_tmp; - for (; isdigit (*n_tmp); n_tmp++); + for (; isdigit ((unsigned char) *n_tmp); n_tmp++); char c = *n_tmp; *(char *)n_tmp = '\0'; if (!strToInt (n, 10, &(guest->lang.spec.patternGroup))) @@ -836,7 +912,7 @@ static void pre_ptrn_flag_guest_long (const char* const s, const char* const v, } else { - guest->lang.spec.lang = getNamedLanguage (v, (tmp - v)); + guest->lang.spec.lang = getNamedLanguageOrAlias (v, (tmp - v)); if (guest->lang.spec.lang == LANG_IGNORE) { error (WARNING, "no parser found for the guest spec: %s", v); @@ -855,9 +931,9 @@ static void pre_ptrn_flag_guest_long (const char* const s, const char* const v, for (int i = 0; i < 2; i++) { current = guest->boundary + i; - const char *current_field_str = (i == BOUNDARY_START? "start": "end"); + const char *current_field_str = (i == GUEST_BOUNDARY_START? "start": "end"); - if (tmp [0] == ((i == BOUNDARY_START)? ',': '\0')) + if (tmp [0] == ((i == GUEST_BOUNDARY_START)? ',': '\0')) { if (type == REG_PARSER_MULTI_LINE) error (WARNING, @@ -870,7 +946,7 @@ static void pre_ptrn_flag_guest_long (const char* const s, const char* const v, { char *n = tmp; - for (; isdigit (*tmp); tmp++); + for (; isdigit ((unsigned char) *tmp); tmp++); char c = *tmp; *tmp = '\0'; if (!strToInt (n, 10, &(current->patternGroup))) @@ -1103,11 +1179,17 @@ static void common_flag_role_long (const char* const s, const char* const v, voi return; } - role = getLanguageRoleForName(cdata->owner, + langType lang = (ptrn->foreign_lang == LANG_IGNORE) + ? cdata->owner + : ptrn->foreign_lang; + role = getLanguageRoleForName(lang, ptrn->u.tag.kindIndex, v); if (!role) { - error (WARNING, "no such role: %s", v); + error (WARNING, "no such role: \"%s\" in kind: \"%s\" of language: \"%s\"", + v, + getLanguageKind(lang, ptrn->u.tag.kindIndex)->name, + getLanguageName (lang)); return; } @@ -1145,6 +1227,39 @@ static void common_flag_anonymous_long (const char* const s, const char* const v ptrn->anonymous_tag_prefix = eStrdup (v); } +static void precommon_flag_foreign_lang (const char* const s, const char* const v, void* data) +{ + struct commonFlagData * cdata = data; + regexPattern *ptrn = cdata->ptrn; + + Assert (ptrn); + + if (ptrn->foreign_lang != LANG_IGNORE) + error (FATAL, "foreign language for this pattern (%s) is already given: %s", + ptrn->pattern_string? ptrn->pattern_string: "", + getLanguageName(ptrn->foreign_lang)); + + if (!v) + error (FATAL, "no LANG for foreign flag is given (pattern == %s)", + ptrn->pattern_string? ptrn->pattern_string: ""); + + langType lang = getNamedLanguage (v, 0); + if (lang == LANG_IGNORE) + error (FATAL, "language named '%s' specified is not found or not initialized yet", + v); + + if (!doesLanguageHaveForeignDependency (cdata->owner, lang)) + error (FATAL, "%s is not declared as a foreign language in %s parser", + v, getLanguageName (cdata->owner)); + + ptrn->foreign_lang = lang; +} + +static flagDefinition preCommonSpecFlagDef[] = { + { '\0', EXPERIMENTAL "language", NULL, precommon_flag_foreign_lang, + "LANG", "make a foreign tag for LANG"}, +}; + static flagDefinition commonSpecFlagDef[] = { { '\0', "fatal", NULL, common_flag_msg_long , "\"MESSAGE\"", "print the given MESSAGE and exit"}, @@ -1253,7 +1368,9 @@ static void setKind(regexPattern * ptrn, const langType owner, Assert (ptrn); Assert (ptrn->u.tag.name_pattern); Assert (kindName); - kindDefinition *kdef = getLanguageKindForLetter (owner, kindLetter); + + langType lang = (ptrn->foreign_lang == LANG_IGNORE? owner: ptrn->foreign_lang); + kindDefinition *kdef = getLanguageKindForLetter (lang, kindLetter); if (kdef) { @@ -1261,7 +1378,7 @@ static void setKind(regexPattern * ptrn, const langType owner, /* When using a same kind letter for multiple regex patterns, the name of kind should be the same. */ error (WARNING, "Don't reuse the kind letter `%c' in a language %s (old: \"%s\", new: \"%s\")", - kdef->letter, getLanguageName (owner), + kdef->letter, getLanguageName (lang), kdef->name, kindName); ptrn->u.tag.kindIndex = kdef->id; } @@ -1273,11 +1390,25 @@ static void setKind(regexPattern * ptrn, const langType owner, else { kdef = kindNew (kindLetter, kindName, description); - defineLanguageKind (owner, kdef, kindFree); + defineLanguageKind (lang, kdef, kindFree); ptrn->u.tag.kindIndex = kdef->id; } } +static void prePatternEvalFlags(struct lregexControlBlock *lcb, + regexPattern * ptrn, + enum regexParserType regptype, + const char* flags) +{ + struct commonFlagData commonFlagData = { + .owner = lcb->owner, + .lcb = lcb, + .ptrn = ptrn + }; + + flagsEval (flags, preCommonSpecFlagDef, ARRAY_SIZE(preCommonSpecFlagDef), &commonFlagData); +} + static void patternEvalFlags (struct lregexControlBlock *lcb, regexPattern * ptrn, enum regexParserType regptype, @@ -1290,7 +1421,7 @@ static void patternEvalFlags (struct lregexControlBlock *lcb, }; if (regptype == REG_PARSER_SINGLE_LINE) - flagsEval (flags, prePtrnFlagDef, ARRAY_SIZE(prePtrnFlagDef), &ptrn->exclusive); + flagsEval (flags, prePtrnFlagDef, ARRAY_SIZE(prePtrnFlagDef), ptrn); const char * optscript = flagsEval (flags, commonSpecFlagDef, ARRAY_SIZE(commonSpecFlagDef), &commonFlagData); if (optscript) @@ -1300,7 +1431,17 @@ static void patternEvalFlags (struct lregexControlBlock *lcb, } if (regptype == REG_PARSER_SINGLE_LINE || regptype == REG_PARSER_MULTI_TABLE) + { flagsEval (flags, scopePtrnFlagDef, ARRAY_SIZE(scopePtrnFlagDef), &ptrn->scopeActions); + if ((ptrn->scopeActions & (SCOPE_REF|SCOPE_REF_AFTER_POP)) == (SCOPE_REF|SCOPE_REF_AFTER_POP)) + error (WARNING, "%s: don't combine \"replace\" with the other scope action.", + getLanguageName (lcb->owner)); + } + else + { + flagsEval (flags, intervaltabPtrnFlagDef, ARRAY_SIZE(intervaltabPtrnFlagDef), &ptrn->scopeActions); + Assert (ptrn->scopeActions == 0 || ptrn->scopeActions == SCOPE_INTERVALTAB); + } if (regptype == REG_PARSER_MULTI_LINE || regptype == REG_PARSER_MULTI_TABLE) { @@ -1333,6 +1474,7 @@ static regexPattern *addCompiledTagPattern (struct lregexControlBlock *lcb, ptrn->u.tag.name_pattern = eStrdup (name); ptrn->disabled = disabled; + prePatternEvalFlags (lcb, ptrn, regptype, flags); setKind(ptrn, lcb->owner, kindLetter, kindName, description, kind_explicitly_defined); patternEvalFlags (lcb, ptrn, regptype, flags); @@ -1345,17 +1487,26 @@ static regexPattern *addCompiledCallbackPattern (struct lregexControlBlock *lcb, void *userData) { regexPattern * ptrn; - bool exclusive = false; - flagsEval (flags, prePtrnFlagDef, ARRAY_SIZE(prePtrnFlagDef), &exclusive); ptrn = addCompiledTagCommon(lcb, TABLE_INDEX_UNUSED, pattern, REG_PARSER_SINGLE_LINE); + flagsEval (flags, prePtrnFlagDef, ARRAY_SIZE(prePtrnFlagDef), ptrn); ptrn->type = PTRN_CALLBACK; ptrn->u.callback.function = callback; ptrn->u.callback.userData = userData; - ptrn->exclusive = exclusive; ptrn->disabled = disabled; return ptrn; } +#ifndef HAVE_PCRE2 +static void no_pcre2_regex_flag_short (char c, void* data) +{ + error (WARNING, "'p' flag is specied but pcre2 regex engine is not linked."); +} +static void no_pcre2_regex_flag_long (const char* const s, const char* const unused CTAGS_ATTR_UNUSED, void* data) +{ + error (WARNING, "{pcre2} flag is specied but pcre2 regex engine is not linked."); +} +#endif + static flagDefinition backendFlagDefs[] = { { 'b', "basic", basic_regex_flag_short, basic_regex_flag_long, NULL, "interpreted as a Posix basic regular expression."}, @@ -1364,6 +1515,9 @@ static flagDefinition backendFlagDefs[] = { #ifdef HAVE_PCRE2 { 'p', "pcre2", pcre2_regex_flag_short, pcre2_regex_flag_long, NULL, "use pcre2 regex engine"}, +#else + { 'p', "pcre2", no_pcre2_regex_flag_short, no_pcre2_regex_flag_long, + NULL, "pcre2 is NOT linked!"}, #endif }; @@ -1508,13 +1662,13 @@ static vString* substitute ( const char* p; for (p = out ; *p != '\0' ; p++) { - if (*p == '\\' && isdigit ((int) *++p)) + if (*p == '\\' && isdigit ((unsigned char) *++p)) { const int dig = *p - '0'; if (0 < dig && dig < nmatch && pmatch [dig].rm_so != -1) { const int diglen = pmatch [dig].rm_eo - pmatch [dig].rm_so; - vStringNCatS (result, in + pmatch [dig].rm_so, diglen); + vStringNCatSUnsafe (result, in + pmatch [dig].rm_so, diglen); } } else if (*p != '\n' && *p != '\r') @@ -1537,9 +1691,9 @@ static void fillEndLineFieldOfUpperScopes (struct lregexControlBlock *lcb, unsig int n = lcb->currentScope; while ((entry = getEntryInCorkQueue (n)) - && (entry->extensionFields.endLine == 0)) + && (entry->extensionFields._endLine == 0)) { - entry->extensionFields.endLine = endline; + setTagEndLine (entry, endline); n = entry->extensionFields.scopeIndex; } } @@ -1550,6 +1704,16 @@ static bool hasNameSlot (const regexPattern* const patbuf) || patbuf->anonymous_tag_prefix); } +static int scopeActionRef (int currentScope) +{ + int scope = currentScope; + tagEntryInfo *entry; + while ((entry = getEntryInCorkQueue (scope)) && entry->placeholder) + /* Look at parent */ + scope = entry->extensionFields.scopeIndex; + return scope; +} + static void matchTagPattern (struct lregexControlBlock *lcb, const char* line, const regexPattern* const patbuf, @@ -1571,17 +1735,20 @@ static void matchTagPattern (struct lregexControlBlock *lcb, vStringStripTrailing (name); if (patbuf->scopeActions & SCOPE_REF) - { - tagEntryInfo *entry; - - scope = lcb->currentScope; - while ((entry = getEntryInCorkQueue (scope)) && entry->placeholder) - /* Look at parent */ - scope = entry->extensionFields.scopeIndex; - } + scope = scopeActionRef (lcb->currentScope); if (patbuf->scopeActions & SCOPE_CLEAR) { unsigned long endline = getInputLineNumberInRegPType(patbuf->regptype, offset); + + /* + * SCOPE_CLEAR|SCOPE_PUSH implies that "set" was specified as the scope action. + * If the specified action is "set", getInputLineNumberInRegPType() + * returns the start line of the NEW scope. The cleared scopes are ended BEFORE + * the new scope. There is a gap. We must adjust the "end:" field here. + */ + if (patbuf->scopeActions & SCOPE_PUSH && endline > 0) + endline--; + fillEndLineFieldOfUpperScopes (lcb, endline); lcb->currentScope = CORK_NIL; } @@ -1589,11 +1756,25 @@ static void matchTagPattern (struct lregexControlBlock *lcb, { tagEntryInfo *entry = getEntryInCorkQueue (lcb->currentScope); - if (entry && (entry->extensionFields.endLine == 0)) - entry->extensionFields.endLine = getInputLineNumberInRegPType(patbuf->regptype, offset); + if (entry && (entry->extensionFields._endLine == 0)) + { + setTagEndLine (entry, getInputLineNumberInRegPType(patbuf->regptype, offset)); + + /* + * SCOPE_POP|SCOPE_REF_AFTER_POP implies that "replace" was specified as the + * scope action. If the specified action is "replace", getInputLineNumberInRegPType() + * returns the start line of the NEW scope. The popped scope is ended BEFORE + * the new scope. There is a gap. We must adjust the "end:" field here. + */ + if ((patbuf->scopeActions & SCOPE_REF_AFTER_POP) && + entry->extensionFields._endLine > 1) + setTagEndLine (entry, entry->extensionFields._endLine - 1); + } lcb->currentScope = entry? entry->extensionFields.scopeIndex: CORK_NIL; } + if (patbuf->scopeActions & SCOPE_REF_AFTER_POP) + scope = scopeActionRef (lcb->currentScope); if (vStringLength (name) == 0 && (placeholder == false)) { @@ -1624,8 +1805,17 @@ static void matchTagPattern (struct lregexControlBlock *lcb, kind = patbuf->u.tag.kindIndex; roleBits = patbuf->u.tag.roleBits; + if (scope == CORK_NIL && (patbuf->scopeActions & SCOPE_INTERVALTAB)) + { + unsigned long ln0 = ln; + if (ln0 == 0) + ln0 = getInputLineNumber(); + if (ln0) + scope = queryIntervalTabByLine(ln0); + } + initRegexTag (&e, vStringValue (name), kind, ROLE_DEFINITION_INDEX, scope, placeholder, - ln, ln == 0? NULL: &pos, patbuf->xtagType); + ln, ln == 0? NULL: &pos, patbuf->xtagType, patbuf->foreign_lang); if (field_trashbox == NULL) { @@ -1642,7 +1832,7 @@ static void matchTagPattern (struct lregexControlBlock *lcb, { vString * const value = substitute (line, fp->template, BACK_REFERENCE_COUNT, pmatch); - attachParserField (&e, false, fp->ftype, vStringValue (value)); + attachParserField (&e, fp->ftype, vStringValue (value)); trashBoxPut (field_trashbox, value, (TrashBoxDestroyItemProc)vStringDelete); } @@ -1678,7 +1868,9 @@ static void matchTagPattern (struct lregexControlBlock *lcb, scriptSetup (optvm, lcb, n, window); EsObject *e = scriptEval (optvm, patbuf->optscript); if (es_error_p (e)) - error (WARNING, "error when evaluating: %s", patbuf->optscript_src); + error (WARNING, "error when evaluating: %s %% input: %s, line:%lu", patbuf->optscript_src, + getInputFileName (), + getInputLineNumberInRegPType(patbuf->regptype, offset)); es_object_unref (e); scriptTeardown (optvm, lcb); } @@ -1736,7 +1928,8 @@ static void printMessage(const langType language, static bool isGuestRequestConsistent (struct guestRequest *guest_req) { return (guest_req->lang != LANG_IGNORE) - && (guest_req->boundary[BOUNDARY_START].offset < guest_req->boundary[BOUNDARY_END].offset); + && (guest_req->boundary[GUEST_BOUNDARY_START].offset + < guest_req->boundary[GUEST_BOUNDARY_END].offset); } static bool fillGuestRequest (const char *start, @@ -1761,7 +1954,7 @@ static bool fillGuestRequest (const char *start, - pmatch [guest_spec->lang.spec.patternGroup].rm_so; if (size > 0) { - guest_req->lang = getNamedLanguage (name, size); + guest_req->lang = getNamedLanguageOrAlias (name, size); guest_req->lang_set = true; } } @@ -1831,7 +2024,8 @@ static bool matchRegexPattern (struct lregexControlBlock *lcb, scriptSetup (optvm, lcb, CORK_NIL, &window); EsObject *e = scriptEval (optvm, patbuf->optscript); if (es_error_p (e)) - error (WARNING, "error when evaluating: %s", patbuf->optscript_src); + error (WARNING, "error when evaluating: %s %% input: %s", patbuf->optscript_src, + getInputFileName ()); es_object_unref (e); scriptTeardown (optvm, lcb); } @@ -1877,7 +2071,6 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, { const char *start; const char *current; - off_t offset = 0; regexPattern* patbuf = entry->pattern; struct mGroupSpec *mgroup = &patbuf->mgroup; struct guestSpec *guest = &patbuf->guest; @@ -1909,9 +2102,6 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, if (hasMessage(patbuf)) printMessage(lcb->owner, patbuf, (current + pmatch[0].rm_so) - start, current, pmatch); - offset = (current + pmatch [mgroup->forLineNumberDetermination].rm_so) - - start; - entry->statistics.match++; scriptWindow window = { .line = current, @@ -1927,13 +2117,17 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, scriptSetup (optvm, lcb, CORK_NIL, &window); EsObject *e = scriptEval (optvm, patbuf->optscript); if (es_error_p (e)) - error (WARNING, "error when evaluating: %s", patbuf->optscript_src); + error (WARNING, "error when evaluating: %s %% input: %s", patbuf->optscript_src, + getInputFileName ()); es_object_unref (e); scriptTeardown (optvm, lcb); } if (patbuf->type == PTRN_TAG) { + Assert (mgroup->forLineNumberDetermination != NO_MULTILINE); + off_t offset = (current + pmatch [mgroup->forLineNumberDetermination].rm_so) + - start; matchTagPattern (lcb, current, patbuf, pmatch, offset, (patbuf->optscript && hasNameSlot (patbuf))? &window: NULL); result = true; @@ -1980,7 +2174,21 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, /* Match against all patterns for specified language. Returns true if at least * on pattern matched. */ -extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const line) +extern bool regexIsPostRun (struct lregexControlBlock *lcb) +{ + for (unsigned int i = 0 ; i < ptrArrayCount(lcb->entries[REG_PARSER_SINGLE_LINE]) ; ++i) + { + regexTableEntry *entry = ptrArrayItem(lcb->entries[REG_PARSER_SINGLE_LINE], i); + regexPattern *ptrn = entry->pattern; + + if (ptrn->postrun) + return true; + } + + return false; +} + +extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const line, bool postrun) { bool result = false; unsigned int i; @@ -1991,6 +2199,9 @@ extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const lin Assert (ptrn); + if (postrun != ptrn->postrun) + continue; + if ((ptrn->xtagType != XTAG_UNKNOWN) && (!isXtagEnabled (ptrn->xtagType))) continue; @@ -2017,15 +2228,15 @@ extern void notifyRegexInputStart (struct lregexControlBlock *lcb) if (es_null (lcb->local_dict)) lcb->local_dict = opt_dict_new (23); opt_vm_dstack_push (optvm, lcb->local_dict); - opt_vm_set_app_data (optvm, lcb); + set_current_lcb (optvm, lcb); scriptEvalHook (optvm, lcb, SCRIPT_HOOK_PRELUDE); } extern void notifyRegexInputEnd (struct lregexControlBlock *lcb) { scriptEvalHook (optvm, lcb, SCRIPT_HOOK_SEQUEL); - opt_vm_set_app_data (optvm, NULL); - opt_vm_clear (optvm); + set_current_lcb (optvm, NULL); + opt_vm_clear (optvm, false); opt_dict_clear (lcb->local_dict); unsigned long endline = getInputLineNumber (); fillEndLineFieldOfUpperScopes (lcb, endline); @@ -2162,22 +2373,22 @@ static regexPattern *addTagRegexInternal (struct lregexControlBlock *lcb, regex, getLanguageName (lcb->owner)); - const char *option_bsae = (regptype == REG_PARSER_SINGLE_LINE? "regex" : + const char *option_base = (regptype == REG_PARSER_SINGLE_LINE? "regex" : regptype == REG_PARSER_MULTI_LINE ? "mline-regex" : regptype == REG_PARSER_MULTI_TABLE? "_mtable-regex": NULL); - Assert (option_bsae); + Assert (option_base); for (const char * p = kindName; *p; p++) { if (p == kindName) { - if (!isalpha(*p)) + if (!isalpha((unsigned char) *p)) error (FATAL, "A kind name doesn't start with an alphabetical character: " "'%s' in \"--%s-%s\" option", kindName, - option_bsae, + option_base, getLanguageName (lcb->owner)); } else @@ -2188,12 +2399,12 @@ static regexPattern *addTagRegexInternal (struct lregexControlBlock *lcb, * in which Exuberant-ctags users define kind names with whitespaces. * "FATAL" error breaks the compatibility. */ - if (!isalnum(*p)) + if (!isalnum((unsigned char) *p)) error (/* regptype == REG_PARSER_SINGLE_LINE? WARNING: */ FATAL, "Non-alphanumeric char is used in kind name: " "'%s' in \"--%s-%s\" option", kindName, - option_bsae, + option_base, getLanguageName (lcb->owner)); } @@ -2241,8 +2452,16 @@ extern void addTagMultiLineRegex (struct lregexControlBlock *lcb, const char* co const char* const name, const char* const kinds, const char* const flags, bool *disabled) { - addTagRegexInternal (lcb, TABLE_INDEX_UNUSED, - REG_PARSER_MULTI_LINE, regex, name, kinds, flags, disabled); + regexPattern *ptrn = addTagRegexInternal (lcb, TABLE_INDEX_UNUSED, + REG_PARSER_MULTI_LINE, regex, name, kinds, flags, disabled); + if (ptrn->mgroup.forLineNumberDetermination == NO_MULTILINE) + { + if (hasNameSlot(ptrn)) + error (WARNING, "%s: no {mgroup=N} flag given in --mline-regex-=/%s/... (%s)", + regex, + getLanguageName (lcb->owner), ASSERT_FUNCTION); + ptrn->mgroup.forLineNumberDetermination = 0; + } } extern void addTagMultiTableRegex(struct lregexControlBlock *lcb, @@ -2303,7 +2522,7 @@ static void addTagRegexOption (struct lregexControlBlock *lcb, const char *c; for (c = pattern; *c; c++) { - if (! (isalnum(*c) || *c == '_')) + if (! (isalnum((unsigned char) *c) || *c == '_')) { if (*c && (*(c + 1) != '^')) { @@ -2334,8 +2553,19 @@ static void addTagRegexOption (struct lregexControlBlock *lcb, regex_pat = eStrdup (pattern); if (parseTagRegex (regptype, regex_pat, &name, &kinds, &flags)) - addTagRegexInternal (lcb, table_index, regptype, regex_pat, name, kinds, flags, - NULL); + { + regexPattern *ptrn = addTagRegexInternal (lcb, table_index, regptype, regex_pat, name, kinds, flags, + NULL); + if (regptype == REG_PARSER_MULTI_LINE + && ptrn->mgroup.forLineNumberDetermination == NO_MULTILINE) + { + if (hasNameSlot(ptrn)) + error (WARNING, "%s: no {mgroup=N} flag given in --mline-regex-=%s... (%s)", + getLanguageName (lcb->owner), + pattern, ASSERT_FUNCTION); + ptrn->mgroup.forLineNumberDetermination = 0; + } + } eFree (regex_pat); } @@ -2395,6 +2625,7 @@ extern void printRegexFlags (bool withListHeader, bool machinable, const char *f flagsColprintAddDefinitions (table, prePtrnFlagDef, ARRAY_SIZE (prePtrnFlagDef)); flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); + flagsColprintAddDefinitions (table, preCommonSpecFlagDef, ARRAY_SIZE (preCommonSpecFlagDef)); flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); } @@ -2420,6 +2651,8 @@ extern void printMultilineRegexFlags (bool withListHeader, bool machinable, cons flagsColprintAddDefinitions (table, backendCommonRegexFlagDefs, ARRAY_SIZE(backendCommonRegexFlagDefs)); flagsColprintAddDefinitions (table, multilinePtrnFlagDef, ARRAY_SIZE (multilinePtrnFlagDef)); flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); + flagsColprintAddDefinitions (table, intervaltabPtrnFlagDef, ARRAY_SIZE (intervaltabPtrnFlagDef)); + flagsColprintAddDefinitions (table, preCommonSpecFlagDef, ARRAY_SIZE (preCommonSpecFlagDef)); flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); } @@ -2447,6 +2680,7 @@ extern void printMultitableRegexFlags (bool withListHeader, bool machinable, con flagsColprintAddDefinitions (table, multitablePtrnFlagDef, ARRAY_SIZE (multitablePtrnFlagDef)); flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); + flagsColprintAddDefinitions (table, preCommonSpecFlagDef, ARRAY_SIZE (preCommonSpecFlagDef)); flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); } @@ -2457,9 +2691,19 @@ extern void printMultitableRegexFlags (bool withListHeader, bool machinable, con extern void freeRegexResources (void) { es_object_unref (lregex_dict); + void *d = opt_vm_set_app_data(optvm, NULL); + if (d) + eFree (d); opt_vm_delete (optvm); } +extern bool lregexControlBlockHasAny(struct lregexControlBlock *lcb) +{ + return (ptrArrayCount(lcb->entries [REG_PARSER_MULTI_LINE]) + || ptrArrayCount(lcb->tables) + || ptrArrayCount(lcb->entries[REG_PARSER_SINGLE_LINE])); +} + extern bool regexNeedsMultilineBuffer (struct lregexControlBlock *lcb) { if (ptrArrayCount(lcb->entries [REG_PARSER_MULTI_LINE]) > 0) @@ -2508,7 +2752,7 @@ extern void addRegexTable (struct lregexControlBlock *lcb, const char *name) { const char *c; for (c = name; *c; c++) - if (! (isalnum(*c) || *c == '_')) + if (! (isalnum((unsigned char) *c) || *c == '_')) error (FATAL, "`%c' in \"%s\" is not acceptable as part of table name", *c, name); if (getTableIndexForName(lcb, name) >= 0) @@ -2689,9 +2933,6 @@ static struct regexTable * matchMultitableRegexTable (struct lregexControlBlock if (match == 0) { entry->statistics.match++; - off_t offset_for_tag = (current - + pmatch [ptrn->mgroup.forLineNumberDetermination].rm_so) - - cstart; scriptWindow window = { .line = current, .start = cstart, @@ -2714,6 +2955,10 @@ static struct regexTable * matchMultitableRegexTable (struct lregexControlBlock if (ptrn->type == PTRN_TAG) { + Assert (ptrn->mgroup.forLineNumberDetermination != NO_MULTILINE); + off_t offset_for_tag = (current + + pmatch [ptrn->mgroup.forLineNumberDetermination].rm_so) + - cstart; matchTagPattern (lcb, current, ptrn, pmatch, offset_for_tag, (ptrn->optscript && hasNameSlot (ptrn))? &window: NULL); @@ -2880,9 +3125,9 @@ extern void extendRegexTable (struct lregexControlBlock *lcb, const char *src, c error (FATAL, "no such regex table in %s: %s", getLanguageName(lcb->owner), dist); dist_table = ptrArrayItem(lcb->tables, i); - for (i = 0; i < (int)ptrArrayCount(src_table->entries); i++) + for (unsigned int n = 0; n < ptrArrayCount(src_table->entries); n++) { - regexTableEntry *entry = ptrArrayItem (src_table->entries, i); + regexTableEntry *entry = ptrArrayItem (src_table->entries, n); ptrArrayAdd(dist_table->entries, newRefPatternEntry(entry)); } } @@ -3006,8 +3251,8 @@ static bool guestRequestIsFilled(struct guestRequest *r) static void guestRequestClear (struct guestRequest *r) { r->lang_set = false; - r->boundary[BOUNDARY_START].offset_set = false; - r->boundary[BOUNDARY_END].offset_set = false; + r->boundary[GUEST_BOUNDARY_START].offset_set = false; + r->boundary[GUEST_BOUNDARY_END].offset_set = false; } static void guestRequestSubmit (struct guestRequest *r) @@ -3016,11 +3261,11 @@ static void guestRequestSubmit (struct guestRequest *r) verbose ("guestRequestSubmit: %s; " "range: %"PRId64" - %"PRId64"\n", langName, - (int64_t)r->boundary[BOUNDARY_START].offset, - (int64_t)r->boundary[BOUNDARY_END].offset); + (int64_t)r->boundary[GUEST_BOUNDARY_START].offset, + (int64_t)r->boundary[GUEST_BOUNDARY_END].offset); makePromiseForAreaSpecifiedWithOffsets (langName, - r->boundary[BOUNDARY_START].offset, - r->boundary[BOUNDARY_END].offset); + r->boundary[GUEST_BOUNDARY_START].offset, + r->boundary[GUEST_BOUNDARY_END].offset); } /* @@ -3052,7 +3297,7 @@ static void scriptEvalHook (OptVM *vm, struct lregexControlBlock *lcb, enum scri { if (ptrArrayCount (lcb->hook_code[hook]) == 0) { - for (int i = 0; i < ptrArrayCount (lcb->hook[hook]); i++) + for (unsigned int i = 0; i < ptrArrayCount (lcb->hook[hook]); i++) { const char *src = ptrArrayItem (lcb->hook[hook], i); EsObject *code = scriptRead (vm, src); @@ -3062,7 +3307,7 @@ static void scriptEvalHook (OptVM *vm, struct lregexControlBlock *lcb, enum scri es_object_unref (code); } } - for (int i = 0; i < ptrArrayCount (lcb->hook_code[hook]); i++) + for (unsigned int i = 0; i < ptrArrayCount (lcb->hook_code[hook]); i++) { EsObject *code = ptrArrayItem (lcb->hook_code[hook], i); EsObject * e = optscriptEval (vm, code); @@ -3074,14 +3319,16 @@ static void scriptEvalHook (OptVM *vm, struct lregexControlBlock *lcb, enum scri static void scriptSetup (OptVM *vm, struct lregexControlBlock *lcb, int corkIndex, scriptWindow *window) { - lcb->window = window; + set_current_lcb (vm, lcb); + set_current_window (vm, window); optscriptSetup (vm, lcb->local_dict, corkIndex); } static void scriptTeardown (OptVM *vm, struct lregexControlBlock *lcb) { optscriptTeardown (vm, lcb->local_dict); - lcb->window = NULL; + set_current_lcb (vm, NULL); + set_current_window (vm, NULL); } extern void addOptscriptToHook (struct lregexControlBlock *lcb, enum scriptHook hook, const char *code) @@ -3089,6 +3336,13 @@ extern void addOptscriptToHook (struct lregexControlBlock *lcb, enum scriptHook ptrArrayAdd (lcb->hook[hook], eStrdup (code)); } +extern void propagateParamToOptscript (struct lregexControlBlock *lcb, const char *param, const char *value) +{ + Assert (param); + Assert (value); + hashTablePutItem (lcb->param_dict, eStrdup (param), eStrdup (value)); +} + /* Return true if available. */ extern bool checkRegex (void) { @@ -3111,10 +3365,9 @@ extern bool checkRegex (void) } static EsObject *OPTSCRIPT_ERR_UNKNOWNKIND; +static EsObject *OPTSCRIPT_ERR_UNKNOWNROLE; -/* name:str kind:name loc _TAG tag - * name:str kind:name _TAG tag */ -static EsObject* lrop_make_tag (OptVM *vm, EsObject *name) +static EsObject* lrop_make_foreignreftag (OptVM *vm, EsObject *name) { matchLoc *loc; @@ -3125,33 +3378,66 @@ static EsObject* lrop_make_tag (OptVM *vm, EsObject *name) EsObject *top = opt_vm_ostack_top (vm); if (es_object_get_type (top) == OPT_TYPE_MATCHLOC) { - if (opt_vm_ostack_count (vm) < 3) + if (opt_vm_ostack_count (vm) < 5) return OPT_ERR_UNDERFLOW; loc = es_pointer_get (top); index = 1; } else { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_SINGLE_LINE) + scriptWindow *window = get_current_window(vm); + if (window->patbuf->regptype != REG_PARSER_SINGLE_LINE) return OPT_ERR_TYPECHECK; - if (opt_vm_ostack_count (vm) < 2) + if (opt_vm_ostack_count (vm) < 4) return OPT_ERR_UNDERFLOW; loc = NULL; index = 0; } - EsObject *kind = opt_vm_ostack_peek (vm, index++); - if (es_object_get_type (kind) != OPT_TYPE_NAME) + EsObject *role_obj = opt_vm_ostack_peek (vm, index++); + if (es_nil != role_obj + && es_object_get_type (role_obj) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + EsObject *kind_obj = opt_vm_ostack_peek (vm, index++); + if (es_object_get_type (kind_obj) != OPT_TYPE_NAME) return OPT_ERR_TYPECHECK; - EsObject *kind_sym = es_pointer_get (kind); + + EsObject *lang_obj = opt_vm_ostack_peek (vm, index++); + langType lang; + if (es_nil == lang_obj) + lang = getInputLanguage (); + else if (es_object_get_type (lang_obj) == OPT_TYPE_NAME) + { + EsObject *lang_sym = es_pointer_get (lang_obj); + const char *lang_str = es_symbol_get (lang_sym); + lang = getNamedLanguage (lang_str, 0); + if (lang == LANG_IGNORE) + return OPTSCRIPT_ERR_UNKNOWNLANGUAGE; + } + else + return OPT_ERR_TYPECHECK; + + EsObject *kind_sym = es_pointer_get (kind_obj); const char *kind_str = es_symbol_get (kind_sym); - kindDefinition* kind_def = getLanguageKindForName (getInputLanguage (), - kind_str); + kindDefinition* kind_def = getLanguageKindForName (lang, kind_str); if (!kind_def) return OPTSCRIPT_ERR_UNKNOWNKIND; int kind_index = kind_def->id; + int role_index; + if (es_nil == role_obj) + role_index = ROLE_DEFINITION_INDEX; + else + { + EsObject *role_sym = es_pointer_get (role_obj); + const char *role_str = es_symbol_get (role_sym); + roleDefinition* role_def = getLanguageRoleForName (lang, kind_index, role_str); + if (!role_def) + return OPTSCRIPT_ERR_UNKNOWNROLE; + role_index = role_def->id; + } + EsObject *tname = opt_vm_ostack_peek (vm, index++); if (es_object_get_type (tname) != OPT_TYPE_STRING) return OPT_ERR_TYPECHECK; @@ -3161,8 +3447,11 @@ static EsObject* lrop_make_tag (OptVM *vm, EsObject *name) tagEntryInfo *e = xMalloc (1, tagEntryInfo); initRegexTag (e, eStrdup (n), - kind_index, ROLE_DEFINITION_INDEX, CORK_NIL, 0, - loc? loc->line: 0, loc? &loc->pos: NULL, XTAG_UNKNOWN); + kind_index, role_index, CORK_NIL, false, + loc? loc->line: 0, loc? &loc->pos: NULL, + role_index == ROLE_DEFINITION_INDEX + ? XTAG_UNKNOWN + : XTAG_REFERENCE_TAGS, lang); EsObject *obj = es_pointer_new (OPT_TYPE_TAG, e); if (es_error_p (obj)) return obj; @@ -3175,50 +3464,33 @@ static EsObject* lrop_make_tag (OptVM *vm, EsObject *name) return es_false; } -static EsObject *OPTSCRIPT_ERR_UNKNOWNROLE; - -static EsObject* lrop_make_reftag (OptVM *vm, EsObject *name) +static EsObject* lrop_assign_role_common (OptVM *vm, EsObject *name, bool assign) { - matchLoc *loc; - - if (opt_vm_ostack_count (vm) < 1) - return OPT_ERR_UNDERFLOW; - - int index; - EsObject *top = opt_vm_ostack_top (vm); - if (es_object_get_type (top) == OPT_TYPE_MATCHLOC) + EsObject *tag = opt_vm_ostack_peek (vm, 1); + tagEntryInfo *e; + if (es_integer_p (tag)) { - if (opt_vm_ostack_count (vm) < 4) - return OPT_ERR_UNDERFLOW; - loc = es_pointer_get (top); - index = 1; + int n0 = es_integer_get (tag); + if (n0 < 0) + return OPT_ERR_RANGECHECK; + unsigned int n = n0; + if (! (CORK_NIL < n && n < countEntryInCorkQueue())) + return OPT_ERR_RANGECHECK; + e = getEntryInCorkQueue (n); } + else if (es_object_get_type (tag) == OPT_TYPE_TAG) + e = es_pointer_get (tag); else - { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_SINGLE_LINE) - return OPT_ERR_TYPECHECK; - if (opt_vm_ostack_count (vm) < 3) - return OPT_ERR_UNDERFLOW; - loc = NULL; - index = 0; - } - - EsObject *role = opt_vm_ostack_peek (vm, index++); - if (es_object_get_type (role) != OPT_TYPE_NAME) return OPT_ERR_TYPECHECK; - EsObject *kind = opt_vm_ostack_peek (vm, index++); - if (es_object_get_type (kind) != OPT_TYPE_NAME) - return OPT_ERR_TYPECHECK; - EsObject *kind_sym = es_pointer_get (kind); - const char *kind_str = es_symbol_get (kind_sym); - langType lang = getInputLanguage (); - kindDefinition* kind_def = getLanguageKindForName (lang, kind_str); - if (!kind_def) - return OPTSCRIPT_ERR_UNKNOWNKIND; - int kind_index = kind_def->id; + if (e == NULL) + return OPTSCRIPT_ERR_NOTAGENTRY; + langType lang = e->langType; + int kind_index = e->kindIndex; + EsObject *role = opt_vm_ostack_top (vm); + if (es_object_get_type (role) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; EsObject *role_sym = es_pointer_get (role); const char *role_str = es_symbol_get (role_sym); roleDefinition* role_def = getLanguageRoleForName (lang, kind_index, role_str); @@ -3226,32 +3498,24 @@ static EsObject* lrop_make_reftag (OptVM *vm, EsObject *name) return OPTSCRIPT_ERR_UNKNOWNROLE; int role_index = role_def->id; - EsObject *tname = opt_vm_ostack_peek (vm, index++); - if (es_object_get_type (tname) != OPT_TYPE_STRING) - return OPT_ERR_TYPECHECK; - const char *n = opt_string_get_cstr (tname); - if (n [0] == '\0') - return OPT_ERR_RANGECHECK; /* TODO */ - - tagEntryInfo *e = xMalloc (1, tagEntryInfo); - initRegexTag (e, eStrdup (n), - kind_index, role_index, CORK_NIL, 0, - loc? loc->line: 0, loc? &loc->pos: NULL, - role_index == ROLE_DEFINITION_INDEX - ? XTAG_UNKNOWN - : XTAG_REFERENCE_TAGS); - EsObject *obj = es_pointer_new (OPT_TYPE_TAG, e); - if (es_error_p (obj)) - return obj; + (assign? assignRole: unassignRole) (e, role_index); - while (index-- > 0) - opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); - opt_vm_ostack_push (vm, obj); - es_object_unref (obj); return es_false; } +static EsObject* lrop_assign_role (OptVM *vm, EsObject *name) +{ + return lrop_assign_role_common (vm, name, true); +} + +static EsObject* lrop_unassign_role (OptVM *vm, EsObject *name) +{ + return lrop_assign_role_common (vm, name, false); +} + /* tag COMMIT int */ static EsObject* lrop_commit_tag (OptVM *vm, EsObject *name) { @@ -3316,8 +3580,7 @@ static EsObject* lrop_get_match_loc (OptVM *vm, EsObject *name) if (g < 1) return OPT_ERR_RANGECHECK; - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - scriptWindow *window = lcb->window; + scriptWindow *window = get_current_window (vm); matchLoc *mloc = make_mloc (window, g, start); if (mloc == NULL) @@ -3338,6 +3601,23 @@ static EsObject* lrop_get_match_loc (OptVM *vm, EsObject *name) return es_false; } +static EsObject* ldrop_get_line_from_matchloc (OptVM *vm, EsObject *name) +{ + EsObject *mlocobj = opt_vm_ostack_top (vm); + if (es_object_get_type (mlocobj) != OPT_TYPE_MATCHLOC) + return OPT_ERR_TYPECHECK; + + matchLoc *mloc = es_pointer_get (mlocobj); + EsObject *lineobj = es_integer_new (mloc->line); + if (es_error_p (lineobj)) + return lineobj; + + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, lineobj); + es_object_unref (lineobj); + return es_false; +} + static matchLoc* make_mloc_from_tagEntryInfo(tagEntryInfo *e) { matchLoc *mloc = xMalloc (1, matchLoc); @@ -3355,7 +3635,10 @@ static EsObject* lrop_get_tag_loc (OptVM *vm, EsObject *name) if (es_object_get_type (nobj) != ES_TYPE_INTEGER) return OPT_ERR_TYPECHECK; - int n = es_integer_get(nobj); + int n0 = es_integer_get(nobj); + if (n0 < 0) + return OPT_ERR_RANGECHECK; + unsigned int n = n0; if (! (CORK_NIL < n && n < countEntryInCorkQueue())) return OPT_ERR_RANGECHECK; @@ -3379,8 +3662,7 @@ static EsObject* lrop_get_tag_loc (OptVM *vm, EsObject *name) static EsObject* lrop_get_match_string_common (OptVM *vm, int i, int npop) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - scriptWindow *window = lcb->window; + scriptWindow *window = get_current_window (vm); const char *cstr = make_match_string (window, i); if (!cstr) { @@ -3409,7 +3691,7 @@ static EsObject* lrop_get_match_string_named_group (OptVM *vm, EsObject *name) return lrop_get_match_string_common (vm, i, 0); } -static EsObject* lrop_get_match_string_gorup_on_stack (OptVM *vm, EsObject *name) +static EsObject* lrop_get_match_string_group_on_stack (OptVM *vm, EsObject *name) { EsObject *group = opt_vm_ostack_top (vm); if (!es_integer_p (group)) @@ -3454,17 +3736,18 @@ static matchLoc *make_mloc (scriptWindow *window, int group, bool start) matchLoc *mloc = xMalloc (1, matchLoc); if (window->patbuf->regptype == REG_PARSER_SINGLE_LINE) { + mloc->base = 0; mloc->delta = 0; mloc->line = getInputLineNumber (); mloc->pos = getInputFilePosition (); } else { + mloc->base = window->line - window->start; mloc->delta = (start ? window->pmatch [group].rm_so : window->pmatch [group].rm_eo); - off_t offset = (window->line + mloc->delta) - window->start; - mloc->line = getInputLineNumberForFileOffset (offset); + mloc->line = getInputLineNumberForFileOffset (mloc->base + mloc->delta); mloc->pos = getInputFilePositionForLine (mloc->line); } return mloc; @@ -3476,15 +3759,15 @@ static EsObject* lrop_set_scope (OptVM *vm, EsObject *name) if (!es_integer_p (corkIndex)) return OPT_ERR_TYPECHECK; - int n = es_integer_get (corkIndex); - if (n < 0) + int n0 = es_integer_get (corkIndex); + if (n0 < 0) return OPT_ERR_RANGECHECK; - + unsigned int n = n0; if (n >= countEntryInCorkQueue()) return OPT_ERR_RANGECHECK; - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - lcb->currentScope = n; + struct lregexControlBlock *lcb = get_current_lcb (vm); + lcb->currentScope = n0; opt_vm_ostack_pop (vm); @@ -3493,7 +3776,7 @@ static EsObject* lrop_set_scope (OptVM *vm, EsObject *name) static EsObject* lrop_pop_scope (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + struct lregexControlBlock *lcb = get_current_lcb (vm); if (lcb->currentScope != CORK_NIL) { tagEntryInfo *e = getEntryInCorkQueue (lcb->currentScope); @@ -3505,14 +3788,14 @@ static EsObject* lrop_pop_scope (OptVM *vm, EsObject *name) static EsObject* lrop_clear_scope (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + struct lregexControlBlock *lcb = get_current_lcb (vm); lcb->currentScope = CORK_NIL; return es_false; } static EsObject* lrop_ref0_scope (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + struct lregexControlBlock *lcb = get_current_lcb (vm); if (lcb->currentScope == 0) { @@ -3539,7 +3822,7 @@ static EsObject* lrop_refN_scope (OptVM *vm, EsObject *name) int n = es_integer_get(nobj); - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + struct lregexControlBlock *lcb = get_current_lcb (vm); int scope = lcb->currentScope; while (n--) @@ -3566,9 +3849,7 @@ static EsObject* lrop_refN_scope (OptVM *vm, EsObject *name) static EsObject* lrop_get_scope_depth (OptVM *vm, EsObject *name) { - int n = 0; - - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + struct lregexControlBlock *lcb = get_current_lcb (vm); int scope = lcb->currentScope; while (scope != CORK_NIL) @@ -3578,7 +3859,6 @@ static EsObject* lrop_get_scope_depth (OptVM *vm, EsObject *name) break; scope = e->extensionFields.scopeIndex; - n++; } EsObject *q = es_integer_new (scope); @@ -3636,8 +3916,8 @@ static struct regexTable *getRegexTableForOptscriptName (struct lregexControlBlo static EsObject* lrop_tenter_common (OptVM *vm, EsObject *name, enum tableAction action) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_MULTI_TABLE) + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype != REG_PARSER_MULTI_TABLE) { error (WARNING, "Use table related operators only with mtable regular expression"); return OPTSCRIPT_ERR_NOTMTABLEPTRN; @@ -3647,11 +3927,12 @@ static EsObject* lrop_tenter_common (OptVM *vm, EsObject *name, enum tableAction if (es_object_get_type (table) != OPT_TYPE_NAME) return OPT_ERR_TYPECHECK; + struct lregexControlBlock *lcb = get_current_lcb(vm); struct regexTable *t = getRegexTableForOptscriptName (lcb, table); if (t == NULL) return OPTSCRIPT_ERR_UNKNOWNTABLE; - lcb->window->taction = (struct mTableActionSpec){ + window->taction = (struct mTableActionSpec){ .action = action, .table = t, .continuation_table = NULL, @@ -3668,8 +3949,8 @@ static EsObject* lrop_tenter (OptVM *vm, EsObject *name) static EsObject* lrop_tenter_with_continuation (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_MULTI_TABLE) + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype != REG_PARSER_MULTI_TABLE) { error (WARNING, "Use table related operators only with mtable regular expression"); return OPTSCRIPT_ERR_NOTMTABLEPTRN; @@ -3683,6 +3964,7 @@ static EsObject* lrop_tenter_with_continuation (OptVM *vm, EsObject *name) if (es_object_get_type (cont) != OPT_TYPE_NAME) return OPT_ERR_TYPECHECK; + struct lregexControlBlock *lcb = get_current_lcb (vm); struct regexTable *t = getRegexTableForOptscriptName (lcb, table); if (t == NULL) return OPTSCRIPT_ERR_UNKNOWNTABLE; @@ -3690,7 +3972,7 @@ static EsObject* lrop_tenter_with_continuation (OptVM *vm, EsObject *name) if (c == NULL) return OPTSCRIPT_ERR_UNKNOWNTABLE; - lcb->window->taction = (struct mTableActionSpec){ + window->taction = (struct mTableActionSpec){ .action = TACTION_ENTER, .table = t, .continuation_table = c, @@ -3703,14 +3985,14 @@ static EsObject* lrop_tenter_with_continuation (OptVM *vm, EsObject *name) static EsObject* lrop_tleave (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_MULTI_TABLE) + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype != REG_PARSER_MULTI_TABLE) { error (WARNING, "Use table related operators only with mtable regular expression"); return OPTSCRIPT_ERR_NOTMTABLEPTRN; } - lcb->window->taction.action = TACTION_LEAVE; + window->taction.action = TACTION_LEAVE; return es_false; } @@ -3726,14 +4008,14 @@ static EsObject* lrop_treset (OptVM *vm, EsObject *name) static EsObject* lrop_tquit (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype != REG_PARSER_MULTI_TABLE) + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype != REG_PARSER_MULTI_TABLE) { error (WARNING, "Use table related operators only with mtable regular expression"); return OPTSCRIPT_ERR_NOTMTABLEPTRN; } - lcb->window->taction.action = TACTION_QUIT; + window->taction.action = TACTION_QUIT; return es_false; } @@ -3774,10 +4056,13 @@ static EsObject *lrop_markextra (OptVM *vm, EsObject *name) tagEntryInfo *e; if (es_integer_p (tag)) { - int n = es_integer_get (tag); + int n0 = es_integer_get (tag); + if (n0 < 0) + return OPT_ERR_RANGECHECK; + unsigned int n = n0; if (! (CORK_NIL < n && n < countEntryInCorkQueue())) return OPT_ERR_RANGECHECK; - e = getEntryInCorkQueue (n); + e = getEntryInCorkQueue (n0); } else if (es_object_get_type (tag) == OPT_TYPE_TAG) e = es_pointer_get (tag); @@ -3795,7 +4080,7 @@ static EsObject *lrop_markextra (OptVM *vm, EsObject *name) if (xt == XTAG_UNKNOWN) return OPTSCRIPT_ERR_UNKNOWNEXTRA; - langType lang = getXtagOwner (xt); + langType lang = getXtagLanguage (xt); if (lang != LANG_IGNORE && e->langType != lang) { error (WARNING, @@ -3814,8 +4099,8 @@ static EsObject *lrop_markextra (OptVM *vm, EsObject *name) static EsObject *lrop_advanceto (OptVM *vm, EsObject *name) { - struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); - if (lcb->window->patbuf->regptype == REG_PARSER_SINGLE_LINE) + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype == REG_PARSER_SINGLE_LINE) { error (WARNING, "don't use `%s' operator in --regex- option", es_symbol_get (name)); @@ -3827,8 +4112,8 @@ static EsObject *lrop_advanceto (OptVM *vm, EsObject *name) return OPT_ERR_TYPECHECK; matchLoc *loc = es_pointer_get (mlocobj); - lcb->window->advanceto = true; - lcb->window->advanceto_delta = loc->delta; + window->advanceto = true; + window->advanceto_delta = loc->delta; return es_true; } @@ -3840,24 +4125,249 @@ static EsObject *lrop_markplaceholder (OptVM *vm, EsObject *name) if (!es_integer_p (tag)) return OPT_ERR_TYPECHECK; - int n = es_integer_get (tag); + int n0 = es_integer_get (tag); + if (n0 < 0) + return OPT_ERR_RANGECHECK; + + unsigned int n = n0; if (! (CORK_NIL < n && n < countEntryInCorkQueue())) return OPT_ERR_RANGECHECK; - tagEntryInfo *e = getEntryInCorkQueue (n); + tagEntryInfo *e = getEntryInCorkQueue (n0); if (e == NULL) return OPTSCRIPT_ERR_NOTAGENTRY; - markTagPlaceholder (e, true); + markTagAsPlaceholder (e, true); + + opt_vm_ostack_pop (vm); + return es_false; +} + +static EsObject *lrop_makepromise (OptVM *vm, EsObject *name) +{ + scriptWindow *window = get_current_window (vm); + if (window->patbuf->regptype == REG_PARSER_SINGLE_LINE) + { + error (WARNING, "don't use `%s' operator in --regex- option", + es_symbol_get (name)); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; /* TODO */ + } + + EsObject *endobj = opt_vm_ostack_top (vm); + if (es_object_get_type (endobj) != OPT_TYPE_MATCHLOC) + return OPT_ERR_TYPECHECK; + matchLoc *end = es_pointer_get (endobj); + off_t end_off = (off_t)(end->base + end->delta); + + EsObject *startobj = opt_vm_ostack_peek (vm, 1); + if (es_object_get_type (startobj) != OPT_TYPE_MATCHLOC) + return OPT_ERR_TYPECHECK; + matchLoc *start = es_pointer_get (startobj); + off_t start_off = (off_t)(start->base + start->delta); + + if (! (start_off < end_off)) + return OPT_ERR_RANGECHECK; + + EsObject *lang = opt_vm_ostack_peek (vm, 2); + const char *langc = opt_string_get_cstr (lang); + langType t = getNamedLanguageOrAlias (langc, 0); + if (t == LANG_IGNORE) + return OPTSCRIPT_ERR_UNKNOWNLANGUAGE; + + if (start_off == end_off) + { + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, es_false); + return es_false; + } + + int promise = makePromiseForAreaSpecifiedWithOffsets (langc, + start_off, + end_off); + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + + if (promise >= 0) + { + EsObject *promise_obj = es_integer_new (promise); + opt_vm_ostack_push (vm, promise_obj); + opt_vm_ostack_push (vm, es_true); + es_object_unref(promise_obj); + } + else + opt_vm_ostack_push (vm, es_false); + + return es_false; +} + +static EsObject *lrop_param (OptVM *vm, EsObject *name) +{ + struct lregexControlBlock *lcb = get_current_lcb (vm); + EsObject *key = opt_vm_ostack_top (vm); + if (es_object_get_type (key) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + EsObject *key_sym = es_pointer_get (key); + const char *keyc = es_symbol_get (key_sym); + const char *valuec = hashTableGetItem (lcb->param_dict, keyc); + + if (valuec) + { + opt_vm_ostack_pop (vm); + EsObject *value = opt_string_new_from_cstr (valuec); + opt_vm_ostack_push (vm, value); + es_object_unref (value); + opt_vm_ostack_push (vm, es_true); + } + else + { + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, es_false); + } + return false; +} + +static EsObject *lrop_intervaltab (OptVM *vm, EsObject *name) +{ + EsObject *nobj = opt_vm_ostack_top (vm); + int parent; + + if (es_object_get_type (nobj) == ES_TYPE_INTEGER) + { + int index = es_integer_get(nobj); + if (index < 0 || index == CORK_NIL) + return OPT_ERR_RANGECHECK; + parent = queryIntervalTabByCorkEntry (index); + } + else if (es_object_get_type (nobj) == OPT_TYPE_TAG) + { + tagEntryInfo *e = es_pointer_get (nobj); + if (e->extensionFields._endLine) + parent = queryIntervalTabByRange(e->lineNumber, + e->extensionFields._endLine); + else + parent = queryIntervalTabByLine(e->lineNumber); + } + else if (es_object_get_type (nobj) == OPT_TYPE_MATCHLOC) + { + matchLoc *mloc = es_pointer_get (nobj); + parent = queryIntervalTabByLine(mloc->line); + } + else if (es_object_get_type (nobj) == OPT_TYPE_ARRAY) + { + unsigned long start, end; + + if (opt_array_length(nobj) == 0) + return OPT_ERR_RANGECHECK; + + ptrArray *a = es_pointer_get (nobj); + EsObject *nobj0 = ptrArrayItem (a, 0); + if (es_object_get_type (nobj0) != ES_TYPE_INTEGER) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (nobj0); + if (n <= 0) + return OPT_ERR_RANGECHECK; + + start = (unsigned long)n; + if (ptrArrayCount(a) == 1) + parent = queryIntervalTabByLine (start); + else + { + nobj0 = ptrArrayItem (a, 1); + if (es_object_get_type (nobj0) != ES_TYPE_INTEGER) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (nobj0); + if (n <= 0) + return OPT_ERR_RANGECHECK; + + end = (unsigned long)n; + if (end < start) + return OPT_ERR_RANGECHECK; + parent = queryIntervalTabByRange (start, end); + } + } + else + return OPT_ERR_TYPECHECK; opt_vm_ostack_pop (vm); + if (parent == CORK_NIL) + opt_vm_ostack_push (vm, es_false); + else + { + EsObject *parent_obj = es_integer_new (parent); + opt_vm_ostack_push (vm, parent_obj); + opt_vm_ostack_push (vm, es_true); + es_object_unref (parent_obj); + } + return false; +} + +static EsObject *lrop_anongen (OptVM *vm, EsObject *name) +{ + int n_pop = 2; + + if (opt_vm_ostack_count (vm) < 2) + return OPT_ERR_UNDERFLOW; + + EsObject *kind_obj = opt_vm_ostack_top (vm); + if (es_object_get_type (kind_obj) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + EsObject *kind_sym = es_pointer_get (kind_obj); + const char *kind_str = es_symbol_get (kind_sym); + + EsObject *tmp_obj = opt_vm_ostack_peek (vm, 1); + langType lang = LANG_IGNORE; + EsObject *prefix_obj = es_nil; + if (es_object_get_type (tmp_obj) == OPT_TYPE_NAME) + { + EsObject *lang_sym = es_pointer_get (tmp_obj); + const char *lang_str = es_symbol_get (lang_sym); + lang = getNamedLanguageOrAlias (lang_str, 0); + if (lang == LANG_IGNORE) + return OPTSCRIPT_ERR_UNKNOWNLANGUAGE; + } + else + { + lang = getInputLanguage (); + prefix_obj = tmp_obj; + } + Assert(lang != LANG_IGNORE); + Assert(lang != LANG_AUTO); + + kindDefinition* kind_def = getLanguageKindForName (lang, kind_str); + if (!kind_def) + return OPTSCRIPT_ERR_UNKNOWNKIND; + int kind_index = kind_def->id; + + if (es_null(prefix_obj)) + { + n_pop++; + if (opt_vm_ostack_count (vm) < 3) + return OPT_ERR_UNDERFLOW; + prefix_obj = opt_vm_ostack_peek (vm, 2); + } + if (es_object_get_type (prefix_obj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + const char *prefix = opt_string_get_cstr (prefix_obj); + + vString *anon_vstr = anonGenerateNewFull (prefix, lang, kind_index); + EsObject *anon_obj = opt_string_new_from_cstr (vStringValue(anon_vstr)); + vStringDelete(anon_vstr); + + for (; n_pop > 0; n_pop--) + opt_vm_ostack_pop (vm); + + opt_vm_ostack_push (vm, anon_obj); + es_object_unref (anon_obj); return es_false; } static struct optscriptOperatorRegistration lropOperators [] = { { .name = "_matchstr", - .fn = lrop_get_match_string_gorup_on_stack, + .fn = lrop_get_match_string_group_on_stack, .arity = 1, .help_str = "group:int _MATCHSTR string true%" "group:int _MATCHSTR false", @@ -3869,6 +4379,12 @@ static struct optscriptOperatorRegistration lropOperators [] = { .help_str = "group:int /start|/end _MATCHLOC matchloc%" "group:int _MATCHLOC matchloc", }, + { + .name = "_matchloc2line", + .fn = ldrop_get_line_from_matchloc, + .arity = 1, + .help_str = "matchloc _MATCHLOC2LINE int:line", + }, { .name = "_tagloc", .fn = lrop_get_tag_loc, @@ -3876,18 +4392,11 @@ static struct optscriptOperatorRegistration lropOperators [] = { .help_str = "index:int _TAGLOC matchloc", }, { - .name = "_tag", - .fn = lrop_make_tag, - .arity = -1, - .help_str = "name:str kind:name matchloc _TAG tag%" - "name:str kind:name _TAG tag", - }, - { - .name = "_reftag", - .fn = lrop_make_reftag, + .name = "_foreignreftag", + .fn = lrop_make_foreignreftag, .arity = -1, - .help_str = "name:str kind:name role:name matchloc _REFTAG tag%" - "name:str kind:name role:name _REFTAG tag%", + .help_str = "name:str lang:name kind:name role:name matchloc _FOREIGNREFTAG tag%" + "name:str lang:name|null kind:name role:name|null _FOREIGNREFTAG tag%", }, { .name = "_commit", @@ -3979,7 +4488,7 @@ static struct optscriptOperatorRegistration lropOperators [] = { .fn = lrop_extraenabled, .arity = 1, .help_str = "extra:name _extraenabled bool%" - "language.extra _extraenabled bool", + "lang.extra:name _extraenabled bool", }, { .name = "_markextra", @@ -4005,7 +4514,47 @@ static struct optscriptOperatorRegistration lropOperators [] = { .fn = lrop_markplaceholder, .arity = 1, .help_str = "tag:int _MARKPLACEHOLDER -", - } + }, + { + .name = "_makepromise", + .fn = lrop_makepromise, + .arity = 3, + .help_str = "lang:string start:matchloc end:matchloc _MAKEPROMISE promise:int true%" + "lang:string start:matchloc end:matchloc _MAKEPROMISE false", + }, + { + .name = "_param", + .fn = lrop_param, + .arity = 1, + .help_str = "param:name _PARAM value:string true%" + "param:name _PARAM false", + }, + { + .name = "_assignrole", + .fn = lrop_assign_role, + .arity = 2, + .help_str = "tag:int|tag:tag role:name _ASSIGNROLE -", + }, + { + .name = "_unassignrole", + .fn = lrop_unassign_role, + .arity = 2, + .help_str = "tag:int|tag:tag role:name _UNASSIGNROLE -", + }, + { + .name = "_intervaltab", + .fn = lrop_intervaltab, + .arity = 1, + .help_str = "tag:int|tag:tag|matchloc|[line:int]|[startline:int endline:int] _INTERVALTAB parent:int true%" + "tag:int|tag:tag|matchloc|[startline:int endline:int] _INTERVALTAB false", + }, + { + .name = "_anongen", + .fn = lrop_anongen, + .arity = -1, + .help_str = "prefix:string kind:name _ANONGEN anon:string%" + "prefix:string lang:name kind:name _ANONGEN anon:string%", + }, }; extern void initRegexOptscript (void) @@ -4017,6 +4566,8 @@ extern void initRegexOptscript (void) return; optvm = optscriptInit (); + appData *d = xCalloc (1, appData); + opt_vm_set_app_data (optvm, d); lregex_dict = opt_dict_new (17); OPTSCRIPT_ERR_UNKNOWNTABLE = es_error_intern ("unknowntable"); diff --git a/ctags/main/lregex_p.h b/ctags/main/lregex_p.h index 663f71ba29..103944f2f1 100644 --- a/ctags/main/lregex_p.h +++ b/ctags/main/lregex_p.h @@ -89,7 +89,11 @@ extern void addTagMultiTableRegex(struct lregexControlBlock *lcb, const char* const name, const char* const kinds, const char* const flags, bool *disabled); -extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const line); +extern bool lregexControlBlockHasAny(struct lregexControlBlock *lcb); + +extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const line, bool postrun); +extern bool regexIsPostRun (struct lregexControlBlock *lcb); + extern bool doesExpectCorkInRegex (struct lregexControlBlock *lcb); extern void addCallbackRegex (struct lregexControlBlock *lcb, const char* const regex, @@ -111,6 +115,7 @@ extern void initRegexOptscript (void); extern void listRegexOpscriptOperators (FILE *fp); extern void addOptscriptToHook (struct lregexControlBlock *lcb, enum scriptHook hook, const char *code); +extern void propagateParamToOptscript (struct lregexControlBlock *lcb, const char *param, const char *value); extern void printMultitableStatistics (struct lregexControlBlock *lcb); diff --git a/ctags/main/lxpath.c b/ctags/main/lxpath.c index 7518fa03e1..4847d2fa17 100644 --- a/ctags/main/lxpath.c +++ b/ctags/main/lxpath.c @@ -11,6 +11,7 @@ #include "general.h" /* must always come first */ #include "debug.h" #include "entry.h" +#include "lxpath_p.h" #include "options.h" #include "parse_p.h" #include "read.h" @@ -19,9 +20,16 @@ #include "xtag.h" #ifdef HAVE_LIBXML +#include #include #include +extern void updateXMLTagLine (tagEntryInfo *e, xmlNode *node) +{ + unsigned long lineNumber = XML_GET_LINE (node); + updateTagLine (e, lineNumber, getInputFilePositionForLine (lineNumber)); +} + static void simpleXpathMakeTag (xmlNode *node, const char *xpath, const tagXpathMakeTagSpec *spec, @@ -51,9 +59,9 @@ static void simpleXpathMakeTag (xmlNode *node, else goto out; - - tag.lineNumber = XML_GET_LINE (node); - tag.filePosition = getInputFilePositionForLine (tag.lineNumber); + /* TODO + * - adjust the line number for the node forward if node is an attribute. */ + updateXMLTagLine (&tag, node); path = (char *)xmlGetNodePath (node); tag.extensionFields.xpath = path; @@ -239,6 +247,9 @@ extern void findXMLTagsFull (xmlXPathContext *ctx, xmlNode *root, { } +extern void updateXMLTagLine (tagEntryInfo *e, xmlNode *node) +{ +} #endif extern void findXMLTags (xmlXPathContext *ctx, xmlNode *root, diff --git a/ctags/main/lxpath.h b/ctags/main/lxpath.h index 223be19f79..de27f100d4 100644 --- a/ctags/main/lxpath.h +++ b/ctags/main/lxpath.h @@ -107,4 +107,6 @@ extern void findXMLTags (xmlXPathContext *ctx, xmlNode *root, int tableTableIndex, void *userData); +extern void updateXMLTagLine (tagEntryInfo *e, xmlNode *node); + #endif /* CTAGS_LXPATH_PARSE_H */ diff --git a/ctags/main/lxpath_p.h b/ctags/main/lxpath_p.h index 9bb73dafc0..d0667ad662 100644 --- a/ctags/main/lxpath_p.h +++ b/ctags/main/lxpath_p.h @@ -16,6 +16,7 @@ #include "general.h" /* must always come first */ #include "types.h" +#include "lxpath.h" /* diff --git a/ctags/main/main.c b/ctags/main/main.c index 6536a21616..94d555837b 100644 --- a/ctags/main/main.c +++ b/ctags/main/main.c @@ -552,7 +552,7 @@ extern int ctags_cli_main (int argc CTAGS_ATTR_UNUSED, char **argv) { cookedArgs *args; -#if defined(WIN32) && defined(HAVE_MKSTEMP) +#if defined(_WIN32) && defined(HAVE_MKSTEMP) /* MinGW-w64's mkstemp() uses rand() for generating temporary files. */ srand ((unsigned int) clock ()); #endif diff --git a/ctags/main/mbcs.c b/ctags/main/mbcs.c index 902821a4ea..e7c83b0a3d 100644 --- a/ctags/main/mbcs.c +++ b/ctags/main/mbcs.c @@ -53,7 +53,7 @@ extern bool openConverter (const char* inputEncoding, const char* outputEncoding return true; } -extern bool isConverting () +extern bool isConverting (void) { return iconv_fd != (iconv_t) -1; } @@ -101,7 +101,7 @@ extern bool convertString (vString *const string) return true; } -extern void closeConverter () +extern void closeConverter (void) { if (iconv_fd != (iconv_t) -1) { diff --git a/ctags/main/mini-geany.c b/ctags/main/mini-geany.c index 8dc324e305..e02b3c19a3 100644 --- a/ctags/main/mini-geany.c +++ b/ctags/main/mini-geany.c @@ -21,6 +21,7 @@ #include "field_p.h" #include "xtag_p.h" #include "entry_p.h" +#include "param_p.h" #include #include @@ -43,6 +44,7 @@ tagWriter customWriter = { /* we need to be able to provide an error printer which doesn't crash Geany on error */ +CTAGS_ATTR_PRINTF(2, 0) static bool nofatalErrorPrinter (const errorSelection selection, const char *const format, va_list ap, void *data CTAGS_ATTR_UNUSED) @@ -61,8 +63,20 @@ static bool nofatalErrorPrinter (const errorSelection selection, } -/* we need to be able to enable all kinds for all languages (some are disabled by default) */ -static void enableAllLangKinds() +static void enableRoles(unsigned int lang, unsigned int kind) +{ + unsigned int c = countLanguageRoles(lang, kind); + + for (unsigned int i = 0; i < c; i++) + { + roleDefinition* rdef = getLanguageRole(lang, kind, (int)i); + enableRole(rdef, true); + } +} + + +/* we need to be able to enable all kinds and roles for all languages (some are disabled by default) */ +static void enableKindsAndRoles(void) { unsigned int lang; @@ -75,6 +89,7 @@ static void enableAllLangKinds() { kindDefinition *def = getLanguageKind(lang, kind); enableKind(def, true); + enableRoles(lang, kind); } } } @@ -94,6 +109,7 @@ static void ctagsInit(void) initializeParsing (); initOptions (); + initRegexOptscript (); /* make sure all parsers are initialized */ initializeParser (LANG_AUTO); @@ -102,8 +118,8 @@ static void ctagsInit(void) enableXtag(XTAG_TAGS_GENERATED_BY_GUEST_PARSERS, true); enableXtag(XTAG_REFERENCE_TAGS, true); - /* some kinds we are interested in are disabled by default */ - enableAllLangKinds(); + /* some kinds and roles we are interested in are disabled by default */ + enableKindsAndRoles(); } @@ -165,6 +181,17 @@ static unsigned int ctagsGetLangCount(void) return countParsers(); } + +static void addIgnoreSymbol(const char *value) +{ + langType lang = getNamedLanguage ("CPreProcessor", 0); + /* + * In the future, we will rename applyParameter to + * applyLanguageParam. + */ + applyParameter (lang, "ignore", value); +} + /******************************************************************************* * So let's just use what we have for our great client... ******************************************************************************/ @@ -182,6 +209,7 @@ typedef struct { bool isFileScope; unsigned long lineNumber; int lang; + bool isAnon; } Tag; @@ -206,6 +234,7 @@ static Tag *createTag(const tagEntryInfo *info) tag->isFileScope = info->isFileScope; tag->lineNumber = info->lineNumber; tag->lang = info->langType; + tag->isAnon = isTagExtraBitMarked(info, XTAG_ANONYMOUS); return tag; } @@ -305,12 +334,19 @@ extern int main (int argc, char **argv) "are provided\n\n"); if (argc == 1) /* parsing contents of a buffer */ { - char *program = "int foo() {}\n\n int bar() {}\n\n int main() {}\n"; + char *program = "FOO int foo() {}\n\n int bar() {}\n\n int main() {}\n"; int lang = ctagsGetNamedLang("C"); const char *kinds = ctagsGetLangKinds(lang); int i; - printf("Number of all parsers is: %d\n", ctagsGetLangCount()); + /* we need to be able to set and clear ignore symbols */ + addIgnoreSymbol("int"); + /* clear */ + addIgnoreSymbol(NULL); + /* set to something else */ + addIgnoreSymbol("FOO"); + + printf("The total number of parsers is: %d\n", ctagsGetLangCount()); printf("We are parsing %s which provides the following kinds:\n", ctagsGetLangName(lang)); for (i = 0; kinds[i] != '\0'; i++) diff --git a/ctags/main/nestlevel.c b/ctags/main/nestlevel.c index d3403f78f1..b06df09d2c 100644 --- a/ctags/main/nestlevel.c +++ b/ctags/main/nestlevel.c @@ -17,11 +17,20 @@ #include "entry.h" #include "routines.h" #include "nestlevel.h" +#include "vstring.h" #include -/* TODO: Alignment */ -#define NL_SIZE(nls) (sizeof(NestingLevel) + (nls)->userDataSize) +/* struct alignment trick, copied from GObject's gtype.c, which borrows + * 2*szieof(size_t) from glibc */ +#define STRUCT_ALIGNMENT (2 * sizeof (size_t)) +#define ALIGN_STRUCT(offset) ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT) + +/* account for the user data alignment if we have user data, otherwise allocate + * exactly what's needed not to waste memory for unneeded alignment */ +#define NL_SIZE(nls) ((nls)->userDataSize ? (ALIGN_STRUCT (sizeof (NestingLevel)) + ALIGN_STRUCT ((nls)->userDataSize)) : sizeof (NestingLevel)) +#define NL_USER_DATA(nl) ((void *)(((char *) nl) + ALIGN_STRUCT (sizeof (NestingLevel)))) + #define NL_NTH(nls,n) (NestingLevel *)(((char *)((nls)->levels)) + ((n) * NL_SIZE (nls))) /* @@ -29,7 +38,7 @@ */ extern NestingLevels *nestingLevelsNewFull(size_t userDataSize, - void (* deleteUserData)(NestingLevel *)) + void (* deleteUserData)(NestingLevel *, void *)) { NestingLevels *nls = xCalloc (1, NestingLevels); nls->userDataSize = userDataSize; @@ -42,7 +51,7 @@ extern NestingLevels *nestingLevelsNew(size_t userDataSize) return nestingLevelsNewFull (userDataSize, NULL); } -extern void nestingLevelsFree(NestingLevels *nls) +extern void nestingLevelsFreeFull(NestingLevels *nls, void *ctxData) { int i; NestingLevel *nl; @@ -51,7 +60,7 @@ extern void nestingLevelsFree(NestingLevels *nls) { nl = NL_NTH(nls, i); if (nls->deleteUserData) - nls->deleteUserData (nl); + nls->deleteUserData (nl, ctxData); nl->corkIndex = CORK_NIL; } if (nls->levels) eFree(nls->levels); @@ -73,7 +82,7 @@ extern NestingLevel * nestingLevelsPush(NestingLevels *nls, int corkIndex) nl->corkIndex = corkIndex; if (nls->userDataSize > 0) - memset (nl->userData, 0, nls->userDataSize); + memset (NL_USER_DATA (nl), 0, ALIGN_STRUCT (nls->userDataSize)); return nl; } @@ -89,13 +98,13 @@ extern NestingLevel *nestingLevelsTruncate(NestingLevels *nls, int depth, int co } -extern void nestingLevelsPop(NestingLevels *nls) +extern void nestingLevelsPopFull(NestingLevels *nls, void *ctxData) { NestingLevel *nl = nestingLevelsGetCurrent(nls); Assert (nl != NULL); if (nls->deleteUserData) - nls->deleteUserData (nl); + nls->deleteUserData (nl, ctxData); nl->corkIndex = CORK_NIL; nls->n--; } @@ -117,5 +126,28 @@ extern NestingLevel *nestingLevelsGetNthParent (const NestingLevels *nls, int n) extern void *nestingLevelGetUserData (const NestingLevel *nl) { - return (void *)nl->userData; + return NL_USER_DATA (nl); +} + +/* (This function comes from ruby.c) +* +* Returns a newly allocated vString describing the scope in 'nls'. +* We record the current scope as a list of entered scopes. +* Scopes corresponding to 'if' statements and the like are +* represented by empty strings. Scopes corresponding to +* modules and classes are represented by the name of the +* module or class. +*/ +extern vString* nestingLevelsToScopeNew (const NestingLevels* nls, const char infixSeparator) +{ + int i; + vString* result = vStringNew (); + for (i = 0; i < nls->n; ++i) + { + NestingLevel *nl = nestingLevelsGetNthFromRoot (nls, i); + tagEntryInfo *e = getEntryOfNestingLevel (nl); + if (e && (*e->name != '\0') && (!e->placeholder)) + vStringJoinS (result, infixSeparator, e->name); + } + return result; } diff --git a/ctags/main/nestlevel.h b/ctags/main/nestlevel.h index 18ac9927e7..b20c38136a 100644 --- a/ctags/main/nestlevel.h +++ b/ctags/main/nestlevel.h @@ -26,7 +26,8 @@ typedef struct NestingLevels NestingLevels; struct NestingLevel { int corkIndex; - char userData []; + /* user data is allocated at the end of this struct (possibly with some + * offset for alignment), get it with nestingLevelGetUserData() */ }; struct NestingLevels @@ -35,7 +36,9 @@ struct NestingLevels int n; /* number of levels in use */ int allocated; size_t userDataSize; - void (* deleteUserData) (NestingLevel *); + /* The second argument is given via nestinglevelsPopFull + * or nestinglevelFreeFull */ + void (* deleteUserData) (NestingLevel *, void *); }; /* @@ -43,15 +46,19 @@ struct NestingLevels */ extern NestingLevels *nestingLevelsNew(size_t userDataSize); extern NestingLevels *nestingLevelsNewFull(size_t userDataSize, - void (* deleteUserData)(NestingLevel *)); -extern void nestingLevelsFree(NestingLevels *nls); + void (* deleteUserData)(NestingLevel *, void *)); +#define nestingLevelsFree(NLS) nestingLevelsFreeFull(NLS, NULL) +extern void nestingLevelsFreeFull(NestingLevels *nls, void *ctxData); extern NestingLevel *nestingLevelsPush(NestingLevels *nls, int corkIndex); extern NestingLevel * nestingLevelsTruncate(NestingLevels *nls, int depth, int corkIndex); -extern void nestingLevelsPop(NestingLevels *nls); +#define nestingLevelsPop(NLS) nestingLevelsPopFull(NLS, NULL) +extern void nestingLevelsPopFull(NestingLevels *nls, void *ctxData); #define nestingLevelsGetCurrent(NLS) nestingLevelsGetNthParent((NLS), 0) extern NestingLevel *nestingLevelsGetNthFromRoot(const NestingLevels *nls, int n); extern NestingLevel *nestingLevelsGetNthParent(const NestingLevels *nls, int n); extern void *nestingLevelGetUserData (const NestingLevel *nl); +extern vString* nestingLevelsToScopeNew (const NestingLevels* nls, const char infixSeparator); + #endif /* CTAGS_MAIN_NESTLEVEL_H */ diff --git a/ctags/main/numarray.c b/ctags/main/numarray.c index 6b33a6dc13..1de737e8f2 100644 --- a/ctags/main/numarray.c +++ b/ctags/main/numarray.c @@ -48,11 +48,14 @@ return current->count++; \ } \ \ - extern void prefix##ArrayRemoveLast (prefix##Array *const current) \ + extern type prefix##ArrayRemoveLast (prefix##Array *const current) \ { \ Assert (current != NULL); \ Assert (current->count > 0); \ + \ + type last = prefix##ArrayLast (current); \ --current->count; \ + return last; \ } \ \ extern void prefix##ArrayCombine (prefix##Array *const current, prefix##Array *const from) \ @@ -72,6 +75,11 @@ return current->count; \ } \ \ + extern bool prefix##ArrayIsEmpty (const prefix##Array *const current) \ + { \ + return (prefix##ArrayCount(current) == 0); \ + } \ + \ extern type prefix##ArrayItem (const prefix##Array *const current, const unsigned int indx) \ { \ Assert (current != NULL); \ @@ -141,7 +149,7 @@ extern void prefix##ArrayDeleteItem (prefix##Array* const current, unsigned int indx) \ { \ memmove (current->array + indx, current->array + indx + 1, \ - (current->count - indx) * sizeof (*current->array)); \ + (current->count - indx - 1) * sizeof (*current->array)); \ --current->count; \ } \ static int prefix##GreaterThan(const void *a, const void *b) \ diff --git a/ctags/main/numarray.h b/ctags/main/numarray.h index b50e9d8d4d..b08daff185 100644 --- a/ctags/main/numarray.h +++ b/ctags/main/numarray.h @@ -22,10 +22,11 @@ \ extern prefix##Array *prefix##ArrayNew (void); \ extern unsigned int prefix##ArrayAdd (prefix##Array *const current, type num); \ - extern void prefix##ArrayRemoveLast (prefix##Array *const current); \ + extern type prefix##ArrayRemoveLast (prefix##Array *const current); \ extern void prefix##ArrayCombine (prefix##Array *const current, prefix##Array *const from); \ extern void prefix##ArrayClear (prefix##Array *const current); \ extern unsigned int prefix##ArrayCount (const prefix##Array *const current); \ + extern bool prefix##ArrayIsEmpty(const prefix##Array *const current); \ extern type prefix##ArrayItem (const prefix##Array *const current, const unsigned int indx); \ extern type prefix##ArrayLast (const prefix##Array *const current); \ extern void prefix##ArrayDelete (prefix##Array *const current); \ diff --git a/ctags/main/options.c b/ctags/main/options.c index c84909104d..d121d8cadb 100644 --- a/ctags/main/options.c +++ b/ctags/main/options.c @@ -34,6 +34,7 @@ #include "routines_p.h" #include "xtag_p.h" #include "param_p.h" +#include "param.h" #include "error_p.h" #include "interactive_p.h" #include "writer_p.h" @@ -174,7 +175,7 @@ optionValues Option = { .maxRecursionDepth = 0xffffffff, .interactive = false, .fieldsReset = false, -#ifdef WIN32 +#ifdef _WIN32 .useSlashAsFilenameSeparator = FILENAME_SEP_UNSET, #endif #ifdef DEBUG @@ -194,8 +195,8 @@ typedef enum eOptionLoadingStage { OptionLoadingStageNone, OptionLoadingStageCustom, OptionLoadingStageXdg, - OptionLoadingStageHomeRecursive, - OptionLoadingStageCurrentRecursive, + OptionLoadingStageHomeDir, + OptionLoadingStageCurrentDir, OptionLoadingStageEnvVar, OptionLoadingStageCmdline, } OptionLoadingStage; @@ -337,13 +338,13 @@ static optionDescription LongOptionDescription [] = { {0,0," --put-field-prefix"}, {0,0," Put \"" CTAGS_FIELD_PREFIX "\" as prefix for the name of fields newly introduced in"}, {0,0," universal-ctags."}, - {1,0," --roles-(|all).(|all)=[+|-][|*]"}, + {1,0," --roles-(|all).(|*)=[+|-][|*]"}, {1,0," Enable/disable tag roles for kinds of language ."}, {0,0," --tag-relative=(yes|no|always|never)"}, {0,0," Should paths be relative to location of tag file [no; yes when -e]?"}, {0,0," always: be relative even if input files are passed in with absolute paths" }, {0,0," never: be absolute even if input files are passed in with relative paths" }, -#ifdef WIN32 +#ifdef _WIN32 {1,0," --use-slash-as-filename-separator[=(yes|no)]"}, {1,0," Use slash as filename separator [yes] for u-ctags output format."}, #endif @@ -381,6 +382,8 @@ static optionDescription LongOptionDescription [] = { {1,1," Copy patterns of a regex table to another regex table."}, {1,1," --_mtable-regex-=///[]"}, {1,1," Define multitable regular expression for locating tags in specific language."}, + {1,1," --_paramdef-=,"}, + {1,1," Define new param for ."}, {1,1," --_prelude-={{ optscript-code }}"}, {1,1," Specify code run before parsing with parser."}, {1,1," --_pretend-="}, @@ -485,8 +488,9 @@ static optionDescription LongOptionDescription [] = { {1,0," Print statistics about input and tag files [no]."}, {1,0," --verbose[=(yes|no)]"}, {1,0," Enable verbose messages describing actions on each input file."}, - {1,0," --version"}, - {1,0," Print version identifier to standard output."}, + {1,0," --version[=]"}, + {1,0," Print version identifier of the program to standard output."}, + {1,0," Print version identifier of the parser for ."}, {1,0," -V Equivalent to --verbose."}, #ifdef DEBUG {1,0," -b "}, @@ -548,7 +552,7 @@ static struct Feature { const char *name; const char *description; } Features [] = { -#ifdef WIN32 +#ifdef _WIN32 {"win32", "TO BE WRITTEN"}, #endif /* Following two features are always available on universal ctags */ @@ -567,7 +571,7 @@ static struct Feature { #ifdef CUSTOM_CONFIGURATION_FILE {"custom-conf", "read \"" CUSTOM_CONFIGURATION_FILE "\" as config file"}, #endif -#if defined (WIN32) +#if defined (_WIN32) {"unix-path-separator", "can use '/' as file name separator"}, #endif #ifdef HAVE_ICONV @@ -613,8 +617,8 @@ static const char *const StageDescription [] = { [OptionLoadingStageNone] = "not initialized", [OptionLoadingStageCustom] = "custom file", [OptionLoadingStageXdg] = "file(s) under $XDG_CONFIG_HOME and $HOME/.config", - [OptionLoadingStageHomeRecursive] = "file(s) under $HOME", - [OptionLoadingStageCurrentRecursive] = "file(s) under the current directory", + [OptionLoadingStageHomeDir] = "file(s) under $HOME", + [OptionLoadingStageCurrentDir] = "file(s) under the current directory", [OptionLoadingStageCmdline] = "command line", }; @@ -1181,7 +1185,7 @@ static void processExcludeOptionCommon ( else { vString *const item = vStringNewInit (parameter); -#if defined (WIN32) +#if defined (_WIN32) vStringTranslate(item, PATH_SEPARATOR, OUTPUT_PATH_SEPARATOR); #endif if (*list == NULL) @@ -1254,9 +1258,8 @@ static void processExcmdOption ( static void resetXtags (langType lang, bool mode) { - int i; - for (i = 0; i < countXtags (); i++) - if ((lang == LANG_AUTO) || (lang == getXtagOwner (i))) + for (unsigned int i = 0; i < countXtags (); i++) + if ((lang == LANG_AUTO) || (lang == getXtagLanguage (i))) enableXtag (i, mode); } @@ -1284,7 +1287,7 @@ static void processExtraTagsOption ( longName = vStringNewOrClearWithAutoRelease (longName); - while ((c = *p++) != '\0') + while ((c = (unsigned char) *p++) != '\0') { switch (c) { @@ -1343,10 +1346,8 @@ static void processExtraTagsOption ( static void resetFieldsOption (langType lang, bool mode) { - int i; - - for (i = 0; i < countFields (); ++i) - if ((lang == LANG_AUTO) || (lang == getFieldOwner (i))) + for (unsigned int i = 0; i < countFields (); ++i) + if ((lang == LANG_AUTO) || (lang == getFieldLanguage (i))) enableField (i, mode); if ((lang == LANG_AUTO || lang == LANG_IGNORE)&& !mode) @@ -1366,8 +1367,6 @@ static void processFieldsOption ( longName = vStringNewOrClearWithAutoRelease (longName); - Option.fieldsReset = false; - if (*p == '*') { resetFieldsOption (LANG_IGNORE, true); @@ -1376,7 +1375,7 @@ static void processFieldsOption ( else if (*p != '+' && *p != '-') resetFieldsOption (LANG_IGNORE, false); - while ((c = *p++) != '\0') switch (c) + while ((c = (unsigned char) *p++) != '\0') switch (c) { case '+': if (inLongName) @@ -1421,7 +1420,7 @@ static void processFieldsOption ( vStringPut (longName, c); else { - t = getFieldTypeForOption (c); + t = getFieldTypeForLetter (c); if (t == FIELD_UNKNOWN) error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c, option); @@ -1488,12 +1487,11 @@ static int excludesCompare (struct colprintLine *a, struct colprintLine *b) static void processListExcludesOption(const char *const option CTAGS_ATTR_UNUSED, const char *const parameter CTAGS_ATTR_UNUSED) { - int i; struct colprintTable *table = colprintTableNew ("L:NAME", NULL); - const int max = Excluded ? stringListCount (Excluded) : 0; + const unsigned int max = Excluded ? stringListCount (Excluded) : 0; - for (i = 0; i < max; ++i) + for (unsigned int i = 0; i < max; ++i) { struct colprintLine * line = colprintTableGetNewLine (table); colprintLineAppendColumnVString (line, stringListItem (Excluded, i)); @@ -1503,7 +1501,8 @@ static void processListExcludesOption(const char *const option CTAGS_ATTR_UNUSED colprintTablePrint (table, 0, localOption.withListHeader, localOption.machinable, stdout); colprintTableDelete (table); - if (i == 0) + /* No line is printed. */ + if (max == 0) putchar ('\n'); exit (0); @@ -1582,6 +1581,8 @@ static void processListFieldsOption(const char *const option CTAGS_ATTR_UNUSED, fieldColprintAddLanguageLines (table, i); } } + else if (strcasecmp (parameter, RSV_NONE) == 0) + fieldColprintAddCommonLines (table); else { langType language = getNamedLanguage (parameter, 0); @@ -1613,6 +1614,8 @@ static void printProgramIdentification (void) printf (" Compiled: %s, %s\n", __DATE__, __TIME__); printf (" URL: %s\n", PROGRAM_URL); + printf (" Output version: %d.%d\n", + OUTPUT_VERSION_CURRENT, OUTPUT_VERSION_AGE); printFeatureList (); } @@ -1706,7 +1709,7 @@ static void processIf0Option (const char *const option, langType lang = getNamedLanguage ("CPreProcessor", 0); const char *arg = if0? "true": "false"; - applyParameter (lang, "if0", arg); + applyLanguageParam (lang, "if0", arg); } static void processLanguageForceOption ( @@ -2015,31 +2018,6 @@ extern bool processMapOption ( return true; } -extern bool processParamOption ( - const char *const option, const char *const value) -{ - langType language; - const char* name; - const char* sep; - - language = getLanguageComponentInOption (option, "param-"); - if (language == LANG_IGNORE) - return false; - - sep = option + strlen ("param-") + strlen (getLanguageName (language)); - /* `:' is only for keeping self compatibility */ - if (! (*sep == '.' || *sep == ':' )) - error (FATAL, "no separator(.) is given for %s=%s", option, value); - name = sep + 1; - - if (value == NULL || value [0] == '\0') - error (FATAL, "no value is given for %s", option); - - applyParameter (language, name, value); - - return true; -} - static void processLicenseOption ( const char *const option CTAGS_ATTR_UNUSED, const char *const parameter CTAGS_ATTR_UNUSED) @@ -2085,6 +2063,8 @@ static void processListExtrasOption ( xtagColprintAddLanguageLines (table, i); } } + else if (strcasecmp (parameter, RSV_NONE) == 0) + xtagColprintAddCommonLines (table); else { langType language = getNamedLanguage (parameter, 0); @@ -2124,18 +2104,18 @@ static void processListParametersOption (const char *const option, const char *const parameter) { if (parameter [0] == '\0' || strcasecmp (parameter, RSV_LANG_ALL) == 0) - printLanguageParameters (LANG_AUTO, - localOption.withListHeader, localOption.machinable, - stdout); + printLanguageParams (LANG_AUTO, + localOption.withListHeader, localOption.machinable, + stdout); else { langType language = getNamedLanguage (parameter, 0); if (language == LANG_IGNORE) error (FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, option); else - printLanguageParameters (language, - localOption.withListHeader, localOption.machinable, - stdout); + printLanguageParams (language, + localOption.withListHeader, localOption.machinable, + stdout); } exit (0); } @@ -2237,6 +2217,7 @@ static void processListKinddefFlagsOptions ( exit (0); } +attr__noreturn static void processListRolesOptions (const char *const option CTAGS_ATTR_UNUSED, const char *const parameter) { @@ -2328,9 +2309,7 @@ static void freeSearchPathList (searchPathList** pathList) static vString* expandOnSearchPathList (searchPathList *pathList, const char* leaf, bool (* check) (const char *const)) { - unsigned int i; - - for (i = stringListCount (pathList); i > 0; --i) + for (unsigned int i = stringListCount (pathList); i > 0; --i) { const char* const body = vStringValue (stringListItem (pathList, i - 1)); char* tmp = combinePathAndFile (body, leaf); @@ -2582,35 +2561,31 @@ static void processHeaderListOption (const int option, const char *parameter) /* * Token ignore processing */ -static void readIgnoreList (const char *const list) +static void readIgnoreList (langType lang, const char *const list) { - langType lang = getNamedLanguage ("CPreProcessor", 0); char* newList = stringCopy (list); const char *token = strtok (newList, IGNORE_SEPARATORS); while (token != NULL) { - applyParameter (lang, "ignore", token); + applyLanguageParam (lang, "ignore", token); token = strtok (NULL, IGNORE_SEPARATORS); } eFree (newList); } -static void addIgnoreListFromFile (const char *const fileName) +static void addIgnoreListFromFile (langType lang, const char *const fileName) { - langType lang = getNamedLanguage ("CPreProcessor", 0); - stringList* tokens = stringListNewFromFile (fileName); if (tokens == NULL) error (FATAL | PERROR, "cannot open \"%s\"", fileName); int c = stringListCount(tokens); - int i; - for(i=0;iitem, "V") == 0 || strcmp (args->item, "verbose") == 0 - || strcmp (args->item, "quiet") == 0) + || strcmp (args->item, "quiet") == 0 + /* Make some fundamental options work even + * if a bropen .ctags is given. */ + || (strcmp (args->item, "version") == 0 && + (strcmp (args->parameter, RSV_NONE) == 0 + || (*args->parameter == '\0'))) + || strcmp (args->item, "help") == 0 + || strcmp (args->item, "help-full") == 0 + || strcmp (args->item, "license") == 0) parseOption (args); else if (strcmp (args->item, "options") == 0 && strcmp (args->parameter, RSV_NONE) == 0) @@ -3671,7 +3674,6 @@ static char* prependEnvvar (const char *path, const char* envvar) return full_path; } -#ifndef WIN32 static char *getConfigForXDG (const char *path CTAGS_ATTR_UNUSED, const char* extra CTAGS_ATTR_UNUSED) { @@ -3681,9 +3683,8 @@ static char *getConfigForXDG (const char *path CTAGS_ATTR_UNUSED, return prependEnvvar (".config/ctags", "HOME"); } -#endif -#ifdef WIN32 +#ifdef _WIN32 static char *getConfigAtHomeOnWindows (const char *path, const char* extra CTAGS_ATTR_UNUSED) { @@ -3757,41 +3758,39 @@ static struct preloadPathElt preload_path_list [] = { .stage = OptionLoadingStageCustom, }, #endif -#ifndef WIN32 { .path = NULL, .isDirectory = true, .makePath = getConfigForXDG, .stage = OptionLoadingStageXdg, }, -#endif { .path = ".ctags.d", .isDirectory = true, .makePath = prependEnvvar, .extra = "HOME", - .stage = OptionLoadingStageHomeRecursive, + .stage = OptionLoadingStageHomeDir, }, -#ifdef WIN32 +#ifdef _WIN32 { .path = "ctags.d", .isDirectory = true, .makePath = getConfigAtHomeOnWindows, .extra = NULL, - .stage = OptionLoadingStageHomeRecursive, + .stage = OptionLoadingStageHomeDir, }, #endif { .path = ".ctags.d", .isDirectory = true, .makePath = NULL, - .stage = OptionLoadingStageCurrentRecursive, + .stage = OptionLoadingStageCurrentDir, }, { .path = "ctags.d", .isDirectory = true, .makePath = NULL, - .stage = OptionLoadingStageCurrentRecursive, + .stage = OptionLoadingStageCurrentDir, }, { .path = NULL, @@ -3810,6 +3809,39 @@ extern void readOptionConfiguration (void) parseConfigurationFileOptions (); } +static stringList* optlibPathListNew(struct preloadPathElt *pathList) +{ + stringList * appended = stringListNew (); + + for (size_t i = 0; pathList[i].path != NULL || pathList[i].makePath != NULL; ++i) + { + struct preloadPathElt *elt = pathList + i; + preloadMakePathFunc maker = elt->makePath; + const char *path = elt->path; + + if (!elt->isDirectory) + continue; + + if (elt->stage == OptionLoadingStageCurrentDir) + continue; + + if (maker) + path = maker(elt->path, elt->extra); + + if (path == NULL) + continue; + + vString *vpath; + if (path == elt->path) + vpath = vStringNewInit (path); + else + vpath = vStringNewOwn ((char *)path); + stringListAdd(appended, vpath); + } + + return appended; +} + /* * Option initialization */ @@ -3817,7 +3849,7 @@ extern void readOptionConfiguration (void) extern void initOptions (void) { OptionFiles = stringListNew (); - OptlibPathList = stringListNew (); + OptlibPathList = optlibPathListNew (preload_path_list); verbose ("Setting option defaults\n"); installHeaderListDefaults (); diff --git a/ctags/main/options_p.h b/ctags/main/options_p.h index 4fe8a77b90..b8ada160bc 100644 --- a/ctags/main/options_p.h +++ b/ctags/main/options_p.h @@ -96,7 +96,7 @@ typedef struct sOptionValues { char* filterTerminator; /* --filter-terminator string to output */ tagRelative tagRelative; /* --tag-relative file paths relative to tag file */ int printTotals; /* --totals print cumulative statistics */ - bool lineDirectives; /* --linedirectives process #line directives */ + bool lineDirectives; /* --line-directives process #line directives */ bool printLanguage; /* --print-language */ bool guessLanguageEagerly; /* --guess-language-eagerly|-G */ bool quiet; /* --quiet */ @@ -108,7 +108,7 @@ typedef struct sOptionValues { enum interactiveMode { INTERACTIVE_NONE = 0, INTERACTIVE_DEFAULT, INTERACTIVE_SANDBOX, } interactive; /* --interactive */ -#ifdef WIN32 +#ifdef _WIN32 enum filenameSepOp { FILENAME_SEP_NO_REPLACE = false, FILENAME_SEP_USE_SLASH = true, FILENAME_SEP_UNSET, @@ -162,6 +162,7 @@ extern langType getLanguageComponentInOptionFull (const char *const option, extern void processLanguageDefineOption (const char *const option, const char *const parameter); extern bool processMapOption (const char *const option, const char *const parameter); +extern bool processParamdefOption (const char *const option, const char *const value); extern bool processParamOption (const char *const option, const char *const value); extern bool processKinddefOption (const char *const option, const char *const parameter); extern bool processKindsOption (const char *const option, const char *const parameter); diff --git a/ctags/main/param.c b/ctags/main/param.c index cea6fe1dd9..bad4c909d5 100644 --- a/ctags/main/param.c +++ b/ctags/main/param.c @@ -11,27 +11,105 @@ */ #include "general.h" +#include "options.h" #include "param.h" #include "param_p.h" #include "parse.h" #include +typedef struct sParamObject { + paramDefinition *def; + freeParamDefFunc free; +} paramObject; + +struct paramControlBlock { + paramObject *param; + unsigned int count; + langType owner; +}; + +extern struct paramControlBlock* allocParamControlBlock (parserDefinition *parser) +{ + struct paramControlBlock *pcb; + + pcb = xMalloc (1, struct paramControlBlock); + pcb->param = xMalloc (parser->paramCount, paramObject); + pcb->count = parser->paramCount; + pcb->owner = parser->id; + + for (unsigned int i = 0; i < parser->paramCount; ++i) + { + paramObject *param = pcb->param + i; + param->def = parser->paramTable + i; + param->free = NULL; + } + + return pcb; +} + +extern void freeParamControlBlock (struct paramControlBlock* pcb) +{ + for (unsigned int i = 0; i< pcb->count; ++i) + { + if (pcb->param [i].free) + pcb->param [i].free (pcb->param [i].def); + } + if (pcb->param) + eFree (pcb->param); + eFree (pcb); +} + +extern int defineParam (struct paramControlBlock* pcb, paramDefinition *def, + freeParamDefFunc freeParamDef) +{ + unsigned int id = pcb->count++; + pcb->param = xRealloc (pcb->param, pcb->count, paramObject); + pcb->param [id].def = def; + pcb->param [id].free = freeParamDef; + + verbose ("Add param[%d] \"%s,%s\" to %s\n", id, + def->name, def->desc, + getLanguageName (pcb->owner)); + + return id; +} + +extern bool applyParam (struct paramControlBlock* pcb, const char *name, const char *args) +{ + for (unsigned int i = 0; i < pcb->count; i++) + { + paramDefinition *pdef = pcb->param[i].def; + if (strcmp(pdef->name, name) == 0) + { + if (pdef->handleParam == NULL) + return true; + return pdef->handleParam (pcb->owner, name, args); + } + } + const char *lang = getLanguageName (pcb->owner); + error (FATAL, "no such parameter in %s: %s", lang, name); + return false; +} extern struct colprintTable * paramColprintTableNew (void) { return colprintTableNew ("L:LANGUAGE", "L:NAME","L:DESCRIPTION", NULL); } -extern void paramColprintAddParameter (struct colprintTable *table, - langType language, - const parameterHandlerTable *const paramHandler) +extern void paramColprintAddParams (struct colprintTable *table, + struct paramControlBlock* pcb) { - struct colprintLine *line = colprintTableGetNewLine(table); + const char *lang = getLanguageName (pcb->owner); + for (unsigned int i = 0; i < pcb->count; i++) + { + paramDefinition *pdef = pcb->param [i].def; + struct colprintLine *line = colprintTableGetNewLine(table); - colprintLineAppendColumnCString (line, getLanguageName (language)); - colprintLineAppendColumnCString (line, paramHandler->name); - colprintLineAppendColumnCString (line, paramHandler->desc); + colprintLineAppendColumnCString (line, lang); + colprintLineAppendColumnCString (line, pdef->name); + colprintLineAppendColumnCString (line, pdef->desc); + } } static int paramColprintCompareLines (struct colprintLine *a , struct colprintLine *b) diff --git a/ctags/main/param.h b/ctags/main/param.h index bbba6be3fe..9b759646c0 100644 --- a/ctags/main/param.h +++ b/ctags/main/param.h @@ -23,10 +23,10 @@ /* * DATA DECLARATIONS */ -struct sParameterHandlerTable { +struct sParamDefinition { const char *name; const char *desc; - void (* handleParameter) (langType lang, const char *name, const char *arg); + bool (* handleParam) (langType lang, const char *name, const char *arg); }; /* diff --git a/ctags/main/param_p.h b/ctags/main/param_p.h index 0205a7d165..a322448d37 100644 --- a/ctags/main/param_p.h +++ b/ctags/main/param_p.h @@ -21,16 +21,29 @@ #include "colprint_p.h" +/* +* DATA DECLARATIONS +*/ +struct paramControlBlock; +typedef void (* freeParamDefFunc) (paramDefinition *); + + /* * FUNCTION PROTOTYPES */ -extern void applyParameter (const langType language, const char *name, const char *args); + +extern bool applyParameter (const langType language, const char *name, const char *args); extern struct colprintTable * paramColprintTableNew (void); -extern void paramColprintAddParameter (struct colprintTable *table, - langType language, - const parameterHandlerTable *const paramHandler); +extern void paramColprintAddParams (struct colprintTable *table, + struct paramControlBlock* pcb); extern void paramColprintTablePrint (struct colprintTable *table, bool noparser, bool withListHeader, bool machinable, FILE *fp); +extern struct paramControlBlock* allocParamControlBlock (parserDefinition *parser); +extern void freeParamControlBlock (struct paramControlBlock* pcb); +extern int defineParam (struct paramControlBlock* pcb, paramDefinition *def, + freeParamDefFunc freeParamDef); +extern bool applyParam (struct paramControlBlock* pcb, const char *name, const char *args); + #endif /* CTAGS_MAIN_PARAM_PRIVATE_H */ diff --git a/ctags/main/parse.c b/ctags/main/parse.c index 1b1013d16d..13c6bef8f7 100644 --- a/ctags/main/parse.c +++ b/ctags/main/parse.c @@ -88,6 +88,7 @@ typedef struct sParserObject { the subparser). */ unsigned int pseudoTagPrinted:1; /* pseudo tags about this parser is emitted or not. */ + unsigned int justRunForSchedulingBase:1; unsigned int used; /* Used for printing language specific statistics. */ unsigned int anonymousIdentiferId; /* managed by anon* functions */ @@ -95,6 +96,7 @@ typedef struct sParserObject { struct slaveControlBlock *slaveControlBlock; struct kindControlBlock *kindControlBlock; struct lregexControlBlock *lregexControlBlock; + struct paramControlBlock *paramControlBlock; langType pretendingAsLanguage; /* OLDLANG in --_pretend-= is set here if this parser is NEWLANG. @@ -118,6 +120,7 @@ static void anonResetMaybe (parserObject *parser); static void setupAnon (void); static void teardownAnon (void); static void uninstallTagXpathTable (const langType language); +static bool hasLanguageAnyRegexPatterns (const langType language); /* * DATA DEFINITIONS @@ -145,6 +148,10 @@ static parserDefinitionFunc* BuiltInParsers[] = { PEG_PARSER_LIST #ifdef HAVE_PACKCC , +#endif + OPTLIB2C_PCRE2_PARSER_LIST +#ifdef HAVE_PCRE2 + , #endif #endif /* EXTERNAL_PARSER_LIST */ }; @@ -158,6 +165,8 @@ static kindDefinition defaultFileKind = { .description = KIND_FILE_DEFAULT_NAME, }; +static langType ctagsSelfTestLang; + /* * FUNCTION DEFINITIONS */ @@ -566,7 +575,7 @@ static bool processLangDefineScopesep(const langType language, error (FATAL, "the kind letter `%c' in \"--%s\" option is reserved for \"%s\" kind and no separator can be assigned to", KIND_FILE_DEFAULT_LETTER, option, KIND_FILE_DEFAULT_NAME); - else if (isalpha (parentKletter)) + else if (isalpha ((unsigned char) parentKletter)) { kindDefinition *kdef = getKindForLetter (parser->kindControlBlock, parentKletter); if (kdef == NULL) @@ -616,7 +625,7 @@ static bool processLangDefineScopesep(const langType language, error (FATAL, "the kind letter `%c' in \"--%s\" option is reserved for \"%s\" kind and no separator can be assigned to", KIND_FILE_DEFAULT_LETTER, option, KIND_FILE_DEFAULT_NAME); - else if (isalpha (kletter)) + else if (isalpha ((unsigned char) kletter)) { kindDefinition *kdef = getKindForLetter (parser->kindControlBlock, kletter); if (kdef == NULL) @@ -741,10 +750,10 @@ static vString* determineInterpreter (const char* const cmd) do { vStringClear (interpreter); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ - for ( ; *p != '\0' && ! isspace ((int) *p) ; ++p) - vStringPut (interpreter, (int) *p); + for ( ; *p != '\0' && ! isspace ((unsigned char) *p) ; ++p) + vStringPut (interpreter, *p); } while (strcmp (vStringValue (interpreter), "env") == 0); return interpreter; } @@ -779,6 +788,24 @@ static vString* extractInterpreter (MIO* input) return interpreter; } +static bool isShellZsh (const char *p) +{ + p = strstr (p, "sh-set-shell"); + if (!p) + return false; + p += strlen("sh-set-shell"); + + if (*p == ':') + p++; + while (isspace ((unsigned char) *p)) + p++; + + if (strncmp (p, "\"zsh\"", 5) == 0 + || strncmp (p, "zsh", 3) == 0) + return true; + return false; +} + static vString* determineEmacsModeAtFirstLine (const char* const line) { vString* mode = vStringNew (); @@ -788,17 +815,22 @@ static vString* determineEmacsModeAtFirstLine (const char* const line) goto out; p += strlen("-*-"); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ if (strncasecmp(p, "mode:", strlen("mode:")) == 0) { /* -*- mode: MODE; -*- */ p += strlen("mode:"); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ - for ( ; *p != '\0' && isLanguageNameChar ((int) *p) ; ++p) - vStringPut (mode, (int) *p); + for ( ; *p != '\0' && isLanguageNameChar ((unsigned char) *p) ; ++p) + vStringPut (mode, *p); + + if ((strcmp(vStringValue (mode), "sh") == 0 + || strcmp(vStringValue (mode), "shell-script") == 0) + && isShellZsh (p)) + vStringCopyS (mode, "Zsh"); } else { @@ -808,10 +840,10 @@ static vString* determineEmacsModeAtFirstLine (const char* const line) if (end == NULL) goto out; - for ( ; p < end && isLanguageNameChar ((int) *p) ; ++p) - vStringPut (mode, (int) *p); + for ( ; p < end && isLanguageNameChar ((unsigned char) *p) ; ++p) + vStringPut (mode, *p); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ if (strncmp(p, "-*-", strlen("-*-")) != 0) vStringClear (mode); @@ -848,6 +880,7 @@ static vString* determineEmacsModeAtEOF (MIO* const fp) bool headerFound = false; const char* p; vString* mode = vStringNew (); + bool is_shell_mode = false; while ((line = readLineRaw (vLine, fp)) != NULL) { @@ -857,15 +890,26 @@ static vString* determineEmacsModeAtEOF (MIO* const fp) headerFound = false; p += strlen ("mode:"); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ - for ( ; *p != '\0' && isLanguageNameChar ((int) *p) ; ++p) - vStringPut (mode, (int) *p); + for ( ; *p != '\0' && isLanguageNameChar ((unsigned char) *p) ; ++p) + vStringPut (mode, *p); + + is_shell_mode = ((strcasecmp (vStringValue (mode), "sh") == 0 + || strcasecmp (vStringValue (mode), "shell-script") == 0)); } else if (headerFound && (p = strstr(line, "End:"))) headerFound = false; else if (strstr (line, "Local Variables:")) headerFound = true; + else if (is_shell_mode && (p = strstr (line, "sh-set-shell"))) + { + p += strlen("sh-set-shell"); + while (isspace ((unsigned char) *p)) + p++; + if (strncmp (p, "\"zsh\"", 5) == 0) + vStringCopyS (mode, "Zsh"); + } } vStringDelete (vLine); return mode; @@ -912,14 +956,14 @@ static vString* determineVimFileType (const char *const modeline) continue; p += strlen(filetype_prefix[i]); - for ( ; *p != '\0' && isalnum ((int) *p) ; ++p) - vStringPut (filetype, (int) *p); + for ( ; *p != '\0' && isalnum ((unsigned char) *p) ; ++p) + vStringPut (filetype, *p); break; } return filetype; } -static vString* extractVimFileType(MIO* input) +static vString* extractVimFileTypeCommon(MIO* input, bool eof) { /* http://vimdoc.sourceforge.net/htmldoc/options.html#modeline @@ -947,7 +991,14 @@ static vString* extractVimFileType(MIO* input) i = 0; while ((readLineRaw (ring[i++], input)) != NULL) if (i == RING_SIZE) + { i = 0; + if (!eof) + { + i++; + break; + } + } j = i; do @@ -962,7 +1013,7 @@ static vString* extractVimFileType(MIO* input) if ((p = strstr (vStringValue (ring[j]), prefix[k])) != NULL) { p += strlen(prefix[k]); - for ( ; isspace ((int) *p) ; ++p) + for ( ; isspace ((unsigned char) *p) ; ++p) ; /* no-op */ filetype = determineVimFileType(p); break; @@ -984,6 +1035,16 @@ static vString* extractVimFileType(MIO* input) [text]{white}{vi:|vim:|ex:}[white]{options} */ } +static vString* extractVimFileTypeAtBOF(MIO* input) +{ + return extractVimFileTypeCommon (input, false); +} + +static vString* extractVimFileTypeAtEOF(MIO* input) +{ + return extractVimFileTypeCommon (input, true); +} + static vString* extractMarkGeneric (MIO* input, vString * (* determiner)(const char *const, void *), void *data) @@ -1007,9 +1068,11 @@ static vString* determineZshAutoloadTag (const char *const modeline, #compdef ... #autoload [ OPTIONS ] */ - if (((strncmp (modeline, "#compdef", 8) == 0) && isspace (*(modeline + 8))) + if (((strncmp (modeline, "#compdef", 8) == 0) + && isspace ((unsigned char) *(modeline + 8))) || ((strncmp (modeline, "#autoload", 9) == 0) - && (isspace (*(modeline + 9)) || *(modeline + 9) == '\0'))) + && (isspace ((unsigned char) *(modeline + 9)) + || *(modeline + 9) == '\0'))) return vStringNewInit ("zsh"); else return NULL; @@ -1077,32 +1140,36 @@ struct getLangCtx { static const struct taster { vString* (* taste) (MIO *); - const char *msg; + const char *msg; } eager_tasters[] = { - { + { .taste = extractInterpreter, .msg = "interpreter", - }, + }, { .taste = extractZshAutoloadTag, .msg = "zsh autoload tag", }, - { + { .taste = extractEmacsModeAtFirstLine, .msg = "emacs mode at the first line", - }, - { + }, + { .taste = extractEmacsModeLanguageAtEOF, .msg = "emacs mode at the EOF", - }, - { - .taste = extractVimFileType, - .msg = "vim modeline", - }, - { + }, + { + .taste = extractVimFileTypeAtBOF, + .msg = "vim modeline at the BOF", + }, + { + .taste = extractVimFileTypeAtEOF, + .msg = "vim modeline at the EOF", + }, + { .taste = extractPHPMark, .msg = "PHP marker", - } + } }; static langType tasteLanguage (struct getLangCtx *glc, const struct taster *const tasters, int n_tasters, langType *fallback); @@ -1865,19 +1932,40 @@ static void linkDependenciesAtInitializeParsing (parserDefinition *const parser) unsigned int i; parserDependency *d; langType upper; + parserDefinition *lowerParser; parserObject *upperParser; for (i = 0; i < parser->dependencyCount; i++) { d = parser->dependencies + i; - upper = getNamedLanguage (d->upperParser, 0); + + if (d->type == DEPTYPE_FOREIGNER) + { + upper = parser->id; + langType lower = getNamedLanguage (d->upperParser, 0); + if (lower == LANG_IGNORE) + error (FATAL, + "Unknown language: \"%s\" as a foreigner for %s", + d->upperParser, parser->name); + + lowerParser = LanguageTable [lower].def; + } + else + { + upper = getNamedLanguage (d->upperParser, 0); + lowerParser = parser; + } + upperParser = LanguageTable + upper; + verbose ("link dependencies: type = %s, upper = %s, lower = %s\n", + dependencyTypeString(d->type), + upperParser->def->name, lowerParser->name); linkDependencyAtInitializeParsing (d->type, upperParser->def, upperParser->slaveControlBlock, upperParser->kindControlBlock, - parser, - (LanguageTable + parser->id)->kindControlBlock, + lowerParser, + (LanguageTable + lowerParser->id)->kindControlBlock, d->data); } } @@ -1903,6 +1991,21 @@ static void initializeParsingCommon (parserDefinition *def, bool is_builtin) parser->kindControlBlock = allocKindControlBlock (def); parser->slaveControlBlock = allocSlaveControlBlock (def); parser->lregexControlBlock = allocLregexControlBlock (def); + parser->paramControlBlock = allocParamControlBlock (def); +} + +static char *acceptableLangName(char *name) +{ + for (char *c = name; *c != '\0'; c++) + { + if (isalnum ((unsigned char)*c)) + continue; + else if (*c == '+' || *c == '#') + continue; + else + return c; + } + return NULL; } extern void initializeParsing (void) @@ -1935,7 +2038,7 @@ extern void initializeParsing (void) Assert (def->name); Assert (def->name[0] != '\0'); Assert (strcmp (def->name, RSV_LANG_ALL)); - Assert (strpbrk (def->name, "!\"$%&'()*,-./:;<=>?@[\\]^`|~") == NULL); + Assert (acceptableLangName (def->name) == NULL); if (def->method & METHOD_NOT_CRAFTED) def->parser = findRegexTags; @@ -1972,6 +2075,8 @@ extern void freeParserResources (void) freeSlaveControlBlock (parser->slaveControlBlock); parser->slaveControlBlock = NULL; + freeParamControlBlock (parser->paramControlBlock); + freeList (&parser->currentPatterns); freeList (&parser->currentExtensions); freeList (&parser->currentAliases); @@ -2031,9 +2136,13 @@ extern void enableDefaultFileKind (bool state) */ struct preLangDefFlagData { + const char *const name; char *base; subparserRunDirection direction; bool autoFQTag; + intArray *foreignLanguages; + unsigned int versionCurrent; + unsigned int versionAge; }; static void pre_lang_def_flag_base_long (const char* const optflag, const char* const param, void* data) @@ -2059,6 +2168,7 @@ static void pre_lang_def_flag_base_long (const char* const optflag, const char* langType cpreproc = getNamedLanguage ("CPreProcessor", 0); if (base == cpreproc) { + /* See Tmain/optscript-preludes-stack.d */ error (WARNING, "Because of an internal limitation, Making a sub parser based on the CPreProcessor parser is not allowed: %s", param); @@ -2093,6 +2203,50 @@ static void pre_lang_def_flag_autoFQTag_long (const char* const optflag, flag_data->autoFQTag = true; } +static void pre_lang_def_flag_foreignLanguage_long (const char* const optflag, + const char* const param, + void* data) +{ + struct preLangDefFlagData * flag_data = data; + if (!param) + { + error (WARNING, "value for '%s' flag is empty", optflag); + return; + } + + langType lang = getNamedLanguage (param, 0); + if (lang == LANG_IGNORE) + error (FATAL, "language named '%s' is not found or not initialized yet", + param); + + verbose ("Foreign language for %s: %s\n", flag_data->name, getLanguageName (lang)); + intArrayAdd (flag_data->foreignLanguages, lang); +} + +static void pre_lang_def_flag_version_long (const char* const optflag CTAGS_ATTR_UNUSED, + const char* const param, + void* data) +{ + struct preLangDefFlagData * flag_data = data; + char * verstr = eStrdup (param); + char * age = strchr(verstr, '.'); + if (!age) + error (FATAL, "Faile to parse the version number ('.') for language \"%s\": %s", + flag_data->name, param); + *age = '\0'; + age++; + + if (!strToUInt (verstr, 10, &flag_data->versionCurrent)) + error (FATAL, "Faile to parse the version number (the current part) for language \"%s\": %s", + flag_data->name, param); + + if (!strToUInt (age, 10, &flag_data->versionAge)) + error (FATAL, "Faile to parse the version number (the age part) for language \"%s\": %s", + flag_data->name, param); + + eFree (verstr); +} + static flagDefinition PreLangDefFlagDef [] = { { '\0', "base", NULL, pre_lang_def_flag_base_long, "BASEPARSER", "utilize as a base parser"}, @@ -2109,45 +2263,78 @@ static flagDefinition PreLangDefFlagDef [] = { }, { '\0', "_autoFQTag", NULL, pre_lang_def_flag_autoFQTag_long, NULL, "make full qualified tags automatically based on scope information"}, + { '\0', "_foreignLanguage", NULL, pre_lang_def_flag_foreignLanguage_long, + "LANG", "initialize another parser" }, + { '\0', "version", NULL, pre_lang_def_flag_version_long, + NULL, "set the version of the parser (current.age)"}, }; static void optlibFreeDep (langType lang, bool initialized CTAGS_ATTR_UNUSED) { parserDefinition * pdef = LanguageTable [lang].def; - if (pdef->dependencyCount == 1) + for (size_t i = 0; i < pdef->dependencyCount; i++) { - parserDependency *dep = pdef->dependencies; + parserDependency *dep = pdef->dependencies + i; eFree ((char *)dep->upperParser); /* Dirty cast */ dep->upperParser = NULL; - eFree (dep->data); - dep->data = NULL; - eFree (dep); + + if (dep->data) + { + eFree (dep->data); + dep->data = NULL; + } + } + if (pdef->dependencies) + { + eFree (pdef->dependencies); pdef->dependencies = NULL; } } static parserDefinition* OptlibParser(const char *name, const char *base, - subparserRunDirection direction) + subparserRunDirection direction, + intArray *foreignLanguages) { parserDefinition *def; + parserDependency *dep = NULL; def = parserNew (name); def->initialize = lazyInitialize; def->method = METHOD_NOT_CRAFTED; + + size_t dep_count = (base? 1: 0) + intArrayCount (foreignLanguages); + + if (dep_count) + { + dep = xCalloc (dep_count, parserDependency); + def->dependencies = dep; + def->dependencyCount = dep_count; + def->finalize = optlibFreeDep; + } + if (base) { subparser *sub = xCalloc (1, subparser); - parserDependency *dep = xCalloc (1, parserDependency); sub->direction = direction; - dep->type = DEPTYPE_SUBPARSER; - dep->upperParser = eStrdup (base); - dep->data = sub; - def->dependencies = dep; - def->dependencyCount = 1; - def->finalize = optlibFreeDep; + dep[0].type = DEPTYPE_SUBPARSER; + dep[0].upperParser = eStrdup (base); + dep[0].data = sub; + } + + for (size_t i = 0 ; i < intArrayCount (foreignLanguages); i++) + { + size_t index = (base? 1: 0) + i; + langType lang = intArrayItem (foreignLanguages, i); + Assert (lang != LANG_IGNORE + && lang != LANG_AUTO + && lang < (int) LanguageCount); + + dep[index].type = DEPTYPE_FOREIGNER; + dep[index].upperParser = eStrdup (getLanguageName (lang)); + dep[index].data = NULL; } return def; @@ -2181,9 +2368,15 @@ extern void processLanguageDefineOption ( else if (strcmp(name, RSV_LANG_ALL) == 0) { eFree (name); - error (FATAL, "\"all\" is reserved; don't use it as the name for defining a new language"); + error (FATAL, "\"" RSV_LANG_ALL "\" is reserved; don't use it as the name for defining a new language"); } - else if ((unacceptable = strpbrk (name, "!\"$%&'()*,-./:;<=>?@[\\]^`|~"))) + else if (strcmp(name, RSV_NONE) == 0) + { + eFree (name); + error (FATAL, "\"" RSV_NONE "\" is reserved; don't use it as the name for defining a new language"); + + } + else if ((unacceptable = acceptableLangName(name))) { char c = *unacceptable; @@ -2201,10 +2394,15 @@ extern void processLanguageDefineOption ( memset (LanguageTable + LanguageCount, 0, sizeof(parserObject)); struct preLangDefFlagData data = { + .name = name, .base = NULL, .direction = SUBPARSER_UNKNOWN_DIRECTION, .autoFQTag = false, + .versionCurrent = 0, + .versionAge = 0, }; + data.foreignLanguages = intArrayNew (); + flagsEval (flags, PreLangDefFlagDef, ARRAY_SIZE (PreLangDefFlagDef), &data); if (data.base == NULL && data.direction != SUBPARSER_UNKNOWN_DIRECTION) @@ -2213,11 +2411,14 @@ extern void processLanguageDefineOption ( if (data.base && data.direction == SUBPARSER_UNKNOWN_DIRECTION) data.direction = SUBPARSER_BASE_RUNS_SUB; - def = OptlibParser (name, data.base, data.direction); + def = OptlibParser (name, data.base, data.direction, + data.foreignLanguages); if (data.base) eFree (data.base); def->requestAutomaticFQTag = data.autoFQTag; + def->versionCurrent = data.versionCurrent; + def->versionAge = data.versionAge; initializeParsingCommon (def, false); linkDependenciesAtInitializeParsing (def); @@ -2227,9 +2428,37 @@ extern void processLanguageDefineOption ( LanguageTable [def->id].pretendingAsLanguage = LANG_IGNORE; LanguageTable [def->id].pretendedAsLanguage = LANG_IGNORE; + intArrayDelete (data.foreignLanguages); eFree (name); } +extern bool doesLanguageHaveForeignDependency (const langType lang, + const langType foreignLang) +{ + Assert (lang != LANG_IGNORE + && lang != LANG_AUTO + && lang < (int) LanguageCount); + Assert (foreignLang != LANG_IGNORE + && foreignLang != LANG_AUTO + && foreignLang < (int) LanguageCount); + + + parserDefinition * pdef = LanguageTable [lang].def; + + for (size_t i = 0; i < pdef->dependencyCount; i++) + { + parserDependency *dep = pdef->dependencies + i; + + if (dep->type == DEPTYPE_FOREIGNER) + { + if (getNamedLanguage (dep->upperParser, 0) == foreignLang) + return true; + } + } + + return false; +} + extern bool isLanguageKindEnabled (const langType language, int kindIndex) { kindDefinition * kdef = getLanguageKind (language, kindIndex); @@ -2318,7 +2547,7 @@ static void processLangKindDefinition ( longName = vStringNewOrClearWithAutoRelease (longName); - while ((c = *p++) != '\0') + while ((c = (unsigned char) *p++) != '\0') { switch (c) { @@ -2477,7 +2706,7 @@ static bool processLangDefineKind(const langType language, { if (p == name_start) { - if (!isalpha(*p)) + if (!isalpha((unsigned char) *p)) { char *name_in_msg = eStrndup (name_start, marker_end - name_start); error (FATAL, @@ -2488,7 +2717,7 @@ static bool processLangDefineKind(const langType language, } else { - if (!isalnum (*p)) + if (!isalnum ((unsigned char) *p)) { char *name_in_msg = eStrndup (name_start, marker_end - name_start); error (FATAL, @@ -2609,7 +2838,7 @@ static bool processLangDefineRole(const langType language, const char * tmp_start = p; while (p != tmp_end) { - if (!isalnum (*p)) + if (!isalnum ((unsigned char) *p)) error (FATAL, "unacceptable char as part of role name in \"--%s\" option: %c", option, *p); p++; @@ -2952,7 +3181,7 @@ static void processLangKindRoleDefinition ( char *rname = eStrndup (p, q - p); roleDefinition *rdef = getLanguageRoleForName (language, kindIndex, rname); if (!rdef) - error (WARNING, "no such role: %s of %c kind in language %s", + error (WARNING, "no such role: \"%s\" in kind \'%c\' in language \"%s\"", rname, getLanguageKind (language, kindIndex)->letter, getLanguageName (language)); else @@ -3235,23 +3464,112 @@ extern void printLanguageKinds (const langType language, bool allKindFields, } } -static void printParameters (struct colprintTable *table, langType language) +extern bool processParamOption ( + const char *const option, const char *const value) { - const parserDefinition* lang; + langType language; + const char* name; + const char* sep; + + language = getLanguageComponentInOption (option, "param-"); + if (language == LANG_IGNORE) + return false; + + sep = option + strlen ("param-") + strlen (getLanguageName (language)); + /* `:' is only for keeping self compatibility */ + if (! (*sep == '.' || *sep == ':' )) + error (FATAL, "no separator(.) is given for %s=%s", option, value); + name = sep + 1; + + if (value == NULL || value [0] == '\0') + error (FATAL, "no value is given for %s", option); + + if (applyLanguageParam (language, name, value)) + propagateParamToOptscript (LanguageTable [language].lregexControlBlock, + name, value); + return true; +} + +static void freePdef (paramDefinition *pdef) +{ + eFree ((void *)pdef->name); + eFree ((void *)pdef->desc); + eFree (pdef); +} + +static bool processLangDefineParam (const langType language, + const char *const option, + const char *const parameter) +{ + parserObject *parser; + + paramDefinition *pdef; + const char * p = parameter; + const char *name_end; + const char *desc; + const char *flags; + Assert (0 <= language && language < (int) LanguageCount); + Assert (p); - initializeParser (language); - lang = LanguageTable [language].def; - if (lang->parameterHandlerTable != NULL) + if (p[0] == '\0') + error (FATAL, "no parameter definition specified in \"--%s\" option", option); + + name_end = strchr (p, ','); + if (!name_end) + error (FATAL, "no parameter description specified in \"--%s\" option", option); + else if (name_end == p) + error (FATAL, "the parameter name in \"--%s\" option is empty", option); + + for (; p < name_end; p++) { - for (unsigned int i = 0; i < lang->parameterHandlerCount; ++i) - paramColprintAddParameter(table, language, lang->parameterHandlerTable + i); + if (!isalnum ((unsigned char) *p) && *p != '_') + error (FATAL, "unacceptable char as part of extra name in \"--%s\" option", + option); } + p++; + if (p [0] == '\0' || p [0] == LONG_FLAGS_OPEN) + error (FATAL, "parameter description in \"--%s\" option is empty", option); + + desc = extractDescriptionAndFlags (p, &flags); + + pdef = xCalloc (1, paramDefinition); + pdef->name = eStrndup (parameter, name_end - parameter); + pdef->desc = desc; + +#if 0 + if (flags) + flagsEval (flags, NULL, 0, pdef); +#endif + + parser = LanguageTable + language; + defineParam (parser->paramControlBlock, pdef, freePdef); + return true; } -extern void printLanguageParameters (const langType language, - bool withListHeader, bool machinable, FILE *fp) +extern bool processParamdefOption (const char *const option, const char *const value) +{ + langType language; + + language = getLanguageComponentInOption (option, "_paramdef-"); + if (language == LANG_IGNORE) + return false; + + return processLangDefineParam (language, option, value); +} + +static void printParams (struct colprintTable *table, langType language) +{ + Assert (0 <= language && language < (int) LanguageCount); + + initializeParser (language); + paramColprintAddParams (table, + LanguageTable [language].paramControlBlock); +} + +extern void printLanguageParams (const langType language, + bool withListHeader, bool machinable, FILE *fp) { struct colprintTable *table = paramColprintTableNew(); @@ -3264,11 +3582,11 @@ extern void printLanguageParameters (const langType language, if (lang->invisible) continue; - printParameters (table, i); + printParams (table, i); } } else - printParameters (table, language); + printParams (table, language); paramColprintTablePrint (table, (language != LANG_AUTO), withListHeader, machinable, fp); @@ -3500,7 +3818,7 @@ static void aliasColprintAddLanguage (struct colprintTable * table, for (unsigned int i = 0; i < count; i++) { struct colprintLine * line = colprintTableGetNewLine (table); - vString *alias = stringListItem (parser->currentAliases, i);; + vString *alias = stringListItem (parser->currentAliases, i); colprintLineAppendColumnCString (line, parser->def->name); colprintLineAppendColumnVString (line, alias); @@ -3601,7 +3919,7 @@ static bool processLangDefineExtra (const langType language, for (; p < name_end; p++) { - if (!isalnum (*p)) + if (!isalnum ((unsigned char) *p)) error (FATAL, "unacceptable char as part of extra name in \"--%s\" option", option); } @@ -3670,7 +3988,7 @@ static bool processLangDefineField (const langType language, for (; p < name_end; p++) { - if (!isalpha (*p)) + if (!isalpha ((unsigned char) *p)) error (FATAL, "unacceptable char as part of field name in \"--%s\" option", option); } @@ -3725,7 +4043,7 @@ static rescanReason createTagsForFile (const langType language, parserDefinition *const lang = LanguageTable [language].def; rescanReason rescan = RESCAN_NONE; - resetInputFile (language); + resetInputFile (language, passCount > 1); Assert (lang->parser || lang->parser2); @@ -3743,12 +4061,16 @@ static rescanReason createTagsForFile (const langType language, extern void notifyLanguageRegexInputStart (langType language) { - notifyRegexInputStart((LanguageTable + language)->lregexControlBlock); + parserObject *pobj = LanguageTable + language; + + notifyRegexInputStart(pobj->lregexControlBlock); } extern void notifyLanguageRegexInputEnd (langType language) { - notifyRegexInputEnd((LanguageTable + language)->lregexControlBlock); + parserObject *pobj = LanguageTable + language; + + notifyRegexInputEnd(pobj->lregexControlBlock); } static unsigned int parserCorkFlags (parserDefinition *parser) @@ -3827,7 +4149,7 @@ static bool createTagsWithFallback1 (const langType language, langType *exclusive_subparser) { bool tagFileResized = false; - unsigned long numTags = numTagsAdded (); + unsigned long numTags; MIOPos tagfpos; int lastPromise = getLastPromise (); unsigned int passCount = 0; @@ -3849,9 +4171,11 @@ static bool createTagsWithFallback1 (const langType language, if (isXtagEnabled (XTAG_PSEUDO_TAGS)) addParserPseudoTags (language); initializeParserStats (parser); + numTags = numTagsAdded (); tagFilePosition (&tagfpos); anonResetMaybe (parser); + parser->justRunForSchedulingBase = 0; while ( ( whyRescan = createTagsForFile (language, ++passCount) ) @@ -3882,8 +4206,9 @@ static bool createTagsWithFallback1 (const langType language, } } - /* Force filling allLines buffer and kick the multiline regex parser */ - if (hasLanguageMultilineRegexPatterns (language)) + if (!parser->justRunForSchedulingBase + /* Force applying regex patterns */ + && hasLanguageAnyRegexPatterns (language)) while (readLineFromInputFile () != NULL) ; /* Do nothing */ @@ -3918,6 +4243,7 @@ extern bool runParserInNarrowedInputStream (const langType language, endLine, endCharOffset); pushNarrowedInputStream ( + doesParserRequireMemoryStream (language), startLine, startCharOffset, endLine, endCharOffset, sourceLineOffset, @@ -4260,11 +4586,20 @@ static bool lregexQueryParserAndSubparsers (const langType language, bool (* pre return r; } +extern bool hasLanguagePostRunRegexPatterns (const langType language) +{ + return lregexQueryParserAndSubparsers (language, regexIsPostRun); +} + extern bool hasLanguageMultilineRegexPatterns (const langType language) { return lregexQueryParserAndSubparsers (language, regexNeedsMultilineBuffer); } +static bool hasLanguageAnyRegexPatterns (const langType language) +{ + return lregexQueryParserAndSubparsers (language, lregexControlBlockHasAny); +} extern void addLanguageCallbackRegex (const langType language, const char *const regex, const char *const flags, const regexCallback callback, bool *disabled, void *userData) @@ -4283,16 +4618,16 @@ extern bool doesLanguageExpectCorkInRegex (const langType language) return hasScopeAction; } -extern void matchLanguageRegex (const langType language, const vString* const line) +extern void matchLanguageRegex (const langType language, const vString* const line, bool postrun) { subparser *tmp; - matchRegex ((LanguageTable + language)->lregexControlBlock, line); + matchRegex ((LanguageTable + language)->lregexControlBlock, line, postrun); foreachSubparser(tmp, true) { langType t = getSubparserLanguage (tmp); enterSubparser (tmp); - matchLanguageRegex (t, line); + matchLanguageRegex (t, line, postrun); leaveSubparser (); } } @@ -4482,7 +4817,6 @@ extern bool makeKindSeparatorsPseudoTags (const langType language, if (kindCount == 0) return r; - vString *sepval = vStringNew(); for (i = 0; i < kindCount; ++i) { kind = getKind (kcb, i); @@ -4516,14 +4850,10 @@ extern bool makeKindSeparatorsPseudoTags (const langType language, } - vStringClear (sepval); - vStringCatSWithEscaping (sepval, sep->separator); - - r = writePseudoTag (pdesc, vStringValue (sepval), + r = writePseudoTag (pdesc, sep->separator? sep->separator: "", name, lang->name) || r; } } - vStringDelete (sepval); return r; } @@ -4539,23 +4869,17 @@ static bool makeKindDescriptionPseudoTag (kindDefinition *kind, { struct makeKindDescriptionPseudoTagData *data = user_data; vString *letter_and_name; - vString *description; - const char *d; letter_and_name = vStringNew (); - description = vStringNew (); vStringPut (letter_and_name, kind -> letter); vStringPut (letter_and_name, ','); vStringCatS (letter_and_name, kind -> name); - d = kind->description? kind->description: kind->name; - vStringCatSWithEscapingAsPattern (description, d); data->written |= writePseudoTag (data->pdesc, vStringValue (letter_and_name), - vStringValue (description), - data->langName); + kind->description? kind->description: kind->name, + data->langName); - vStringDelete (description); vStringDelete (letter_and_name); return false; @@ -4571,15 +4895,10 @@ static bool makeRoleDescriptionPseudoTag (kindDefinition *kind, vStringCatS (parser_and_kind_name, PSEUDO_TAG_SEPARATOR); vStringCatS (parser_and_kind_name, kind->name); - vString *description = vStringNew (); - const char *d = role->description? role->description: role->name; - vStringCatSWithEscapingAsPattern (description, d); - data->written |= writePseudoTag (data->pdesc, role->name, - vStringValue (description), + role->description? role->description: role->name, vStringValue (parser_and_kind_name)); - vStringDelete (description); vStringDelete (parser_and_kind_name); return false; @@ -4612,6 +4931,13 @@ extern bool makeKindDescriptionsPseudoTags (const langType language, continue; kind = getKind (kcb, i); + if (language == ctagsSelfTestLang + && (kind == NULL || kind->name == NULL)) + { + /* The Self test parser may have broken kinds. + * Let's skip it. */ + continue; + } makeKindDescriptionPseudoTag (kind, &data); } @@ -4622,31 +4948,23 @@ static bool makeFieldDescriptionPseudoTag (const langType language, fieldType f, const ptagDesc *pdesc) { - vString *description; const char *name = getFieldName (f); if (name == NULL || name [0] == '\0') return false; - description = vStringNew (); - vStringCatSWithEscapingAsPattern (description, - getFieldDescription (f)); - - bool r = writePseudoTag (pdesc, name, - vStringValue (description), - language == LANG_IGNORE? NULL: getLanguageName (language)); - - vStringDelete (description); - return r; + return writePseudoTag (pdesc, name, + getFieldDescription (f), + language == LANG_IGNORE? NULL: getLanguageName (language)); } extern bool makeFieldDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc) { bool written = false; - for (int i = 0; i < countFields (); i++) + for (unsigned int i = 0; i < countFields (); i++) { - if (getFieldOwner (i) == language + if (getFieldLanguage (i) == language && isFieldEnabled (i)) { if (makeFieldDescriptionPseudoTag (language, i, pdesc)) @@ -4660,31 +4978,23 @@ static bool makeExtraDescriptionPseudoTag (const langType language, xtagType x, const ptagDesc *pdesc) { - vString *description; const char *name = getXtagName (x); if (name == NULL || name [0] == '\0') return false; - description = vStringNew (); - vStringCatSWithEscapingAsPattern (description, - getXtagDescription (x)); - - bool r = writePseudoTag (pdesc, name, - vStringValue (description), - language == LANG_IGNORE? NULL: getLanguageName (language)); - - vStringDelete (description); - return r; + return writePseudoTag (pdesc, name, + getXtagDescription (x), + language == LANG_IGNORE? NULL: getLanguageName (language)); } extern bool makeExtraDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc) { bool written = false; - for (int i = 0; i < countXtags (); i++) + for (unsigned int i = 0; i < countXtags (); i++) { - if (getXtagOwner (i) == language + if (getXtagLanguage (i) == language && isXtagEnabled (i)) { if (makeExtraDescriptionPseudoTag (language, i, pdesc)) @@ -4735,6 +5045,26 @@ extern bool makeRoleDescriptionsPseudoTags (const langType language, return data.written; } +extern unsigned int getLanguageVersionCurrent (const langType language) +{ + parserObject *parser; + parserDefinition* lang; + Assert (0 <= language && language < (int) LanguageCount); + parser = LanguageTable + language; + lang = parser->def; + return lang->versionCurrent; +} + +extern unsigned int getLanguageVersionAge (const langType language) +{ + parserObject *parser; + parserDefinition* lang; + Assert (0 <= language && language < (int) LanguageCount); + parser = LanguageTable + language; + lang = parser->def; + return lang->versionAge; +} + /* * Copyright (c) 2016, Szymon Tomasz Stefanek * @@ -4780,56 +5110,42 @@ extern void anonHashString (const char *filename, char buf[9]) sprintf(buf, "%08x", anonHash((const unsigned char *)filename)); } +extern void anonConcatFull (vString *buffer, langType lang, int kind) +{ + anonGenerateFull (buffer, NULL, lang, kind); +} -extern void anonGenerate (vString *buffer, const char *prefix, int kind) +extern void anonGenerateFull (vString *buffer, const char *prefix, langType lang, int kind) { - parserObject* parser = LanguageTable + getInputLanguage (); + Assert(lang != LANG_IGNORE); + parserObject* parser = LanguageTable + ((lang == LANG_AUTO)? getInputLanguage (): lang); parser -> anonymousIdentiferId ++; char szNum[32]; char buf [9]; - vStringCopyS(buffer, prefix); + if (prefix) + vStringCopyS(buffer, prefix); anonHashString (getInputFileName(), buf); sprintf(szNum,"%s%02x%02x",buf,parser -> anonymousIdentiferId, kind); vStringCatS(buffer,szNum); } -extern vString *anonGenerateNew (const char *prefix, int kind) +extern vString *anonGenerateNewFull (const char *prefix, langType lang, int kind) { vString *buffer = vStringNew (); - anonGenerate (buffer, prefix, kind); + anonGenerateFull (buffer, prefix, lang, kind); return buffer; } - -extern void applyParameter (const langType language, const char *name, const char *args) +extern bool applyLanguageParam (const langType language, const char *name, const char *args) { - parserDefinition* parser; - - Assert (0 <= language && language < (int) LanguageCount); initializeParserOne (language); - parser = LanguageTable [language].def; - - if (parser->parameterHandlerTable) - { - unsigned int i; - - for (i = 0; i < parser->parameterHandlerCount; i++) - { - if (strcmp (parser->parameterHandlerTable [i].name, name) == 0) - { - parser->parameterHandlerTable [i].handleParameter (language, name, args); - return; - } - } - } - - error (FATAL, "no such parameter in %s: %s", parser->name, name); + return applyParam (LanguageTable [language].paramControlBlock, name, args); } extern subparser *getNextSubparser(subparser *last, @@ -4874,9 +5190,12 @@ extern slaveParser *getNextSlaveParser(slaveParser *last) extern void scheduleRunningBaseparser (int dependencyIndex) { langType current = getInputLanguage (); - parserDefinition *current_parser = LanguageTable [current].def; + parserObject *current_pobj = LanguageTable + current; + parserDefinition *current_parser = current_pobj->def; parserDependency *dep = NULL; + current_pobj->justRunForSchedulingBase = 1; + if (dependencyIndex == RUN_DEFAULT_SUBPARSERS) { for (unsigned int i = 0; i < current_parser->dependencyCount; ++i) @@ -5187,6 +5506,7 @@ typedef enum { #if defined(DEBUG) && defined(HAVE_SECCOMP) K_CALL_GETPPID, #endif + K_QUIT, K_DISABLED, K_ENABLED, K_ROLES, @@ -5263,6 +5583,7 @@ static kindDefinition CTST_Kinds[KIND_COUNT] = { #if defined(DEBUG) && defined(HAVE_SECCOMP) {true, 'P', "callGetPPid", "trigger calling getppid(2) that seccomp sandbox disallows"}, #endif + {true, 'Q', "quit", "stop the parsing"}, {false,'d', "disabled", "a kind disabled by default", .referenceOnly = false, ATTACH_ROLES (CTST_DisabledKindRoles)}, {true, 'e', "enabled", "a kind enabled by default", @@ -5305,9 +5626,11 @@ static void createCTSTTags (void) int found_enabled_disabled[2] = {0, 0}; + bool quit = false; + TRACE_ENTER_TEXT("Parsing starts"); - while ((line = readLineFromInputFile ()) != NULL) + while (!quit && (line = readLineFromInputFile ()) != NULL) { int c = line[0]; @@ -5365,6 +5688,9 @@ static void createCTSTTags (void) getppid(); break; #endif + case K_QUIT: + quit = true; + break; case K_DISABLED: case K_ENABLED: { @@ -5432,7 +5758,7 @@ static void createCTSTTags (void) name [0] = c++; initTagEntry (&e, name, i); - attachParserField (&e, false, + attachParserField (&e, CTSTFields[F_BOOLEAN_FIELD].ftype, ""); makeTagEntry (&e); @@ -5442,13 +5768,13 @@ static void createCTSTTags (void) name [0] = c++; initTagEntry (&e, name, i); - attachParserField (&e, false, + attachParserField (&e, CTSTFields[F_BOOLEAN_AND_STRING_FIELD].ftype, "val"); makeTagEntry (&e); name [0] = c++; initTagEntry (&e, name, i); - attachParserField (&e, false, + attachParserField (&e, CTSTFields[F_BOOLEAN_AND_STRING_FIELD].ftype, ""); makeTagEntry (&e); @@ -5458,6 +5784,9 @@ static void createCTSTTags (void) notice ("notice output for testing: %s", CTST_Kinds [i].name); break; } + + if (quit) + break; } } @@ -5475,6 +5804,11 @@ static void printStatsCTST (langType lang CTAGS_ATTR_UNUSED) CTST_num_handled_char); } +static void initCTST (langType language) +{ + ctagsSelfTestLang = language; +} + static parserDefinition *CTagsSelfTestParser (void) { static const char *const extensions[] = { NULL }; @@ -5483,6 +5817,7 @@ static parserDefinition *CTagsSelfTestParser (void) def->kindTable = CTST_Kinds; def->kindCount = KIND_COUNT; def->parser = createCTSTTags; + def->initialize = initCTST; def->invisible = true; def->useMemoryStreamInput = true; def->useCork = CORK_QUEUE; diff --git a/ctags/main/parse.h b/ctags/main/parse.h index b13089a045..6f3051b619 100644 --- a/ctags/main/parse.h +++ b/ctags/main/parse.h @@ -80,6 +80,25 @@ enum scriptHook { struct sParserDefinition { /* defined by parser */ char* name; /* name of language */ + + /* The concept of CURRENT and AGE is taken from libtool. + * However, we deleted REVISION in libtool when importing + * the concept of versioning from libtool. + * + * If kinds, roles, fields, and/or extras have been added, + * removed or changed since last release, increment CURRENT. + * If they have been added since last release, increment AGE. + * If they have been removed since last release, set AGE to 0 + * + * From the command line of ctags, you can see the version + * information with --version=. + * + * In the tags file, !_TAGS_PARSER_VERSION! shows the + * information for . + */ + unsigned int versionCurrent; + unsigned int versionAge; + kindDefinition* kindTable; /* tag kinds handled by parser */ unsigned int kindCount; /* size of `kinds' list */ const char *const *extensions; /* list of default extensions */ @@ -110,8 +129,8 @@ struct sParserDefinition { parserDependency * dependencies; unsigned int dependencyCount; - parameterHandlerTable *parameterHandlerTable; - unsigned int parameterHandlerCount; + paramDefinition *paramTable; + unsigned int paramCount; xpathFileSpec *xpathFileSpecs; unsigned int xpathFileSpecCount; @@ -154,7 +173,9 @@ extern bool isLanguageEnabled (const langType language); extern bool isLanguageKindEnabled (const langType language, int kindIndex); extern bool isLanguageRoleEnabled (const langType language, int kindIndex, int roleIndex); -extern kindDefinition* getLanguageKindForLetter (const langType language, char kindLetter); +extern kindDefinition* getLanguageKindForName (const langType language, const char *kindName); +extern roleDefinition* getLanguageRoleForName (const langType language, int kindIndex, + const char *roleName); extern void initializeParser (langType language); extern unsigned int getLanguageCorkUsage (langType language); @@ -180,8 +201,12 @@ extern void addLanguageTagMultiTableRegex(const langType language, extern void addLanguageOptscriptToHook (langType language, enum scriptHook hook, const char *const src); -extern void anonGenerate (vString *buffer, const char *prefix, int kind); -extern vString *anonGenerateNew (const char *prefix, int kind); +extern void anonGenerateFull (vString *buffer, const char *prefix, langType lang, int kind); +#define anonGenerate(B,P,K) anonGenerateFull((B), (P), LANG_AUTO, (K)) +extern void anonConcatFull (vString *buffer, langType lang, int kind); +#define anonConcat(B,K) anonConcatFull((B), LANG_AUTO, (K)) +extern vString *anonGenerateNewFull (const char *prefix, langType lang, int kind); +#define anonGenerateNew(P,K) anonGenerateNewFull((P), LANG_AUTO, (K)) extern void anonHashString (const char *filename, char buf[9]); #endif /* CTAGS_MAIN_PARSE_H */ diff --git a/ctags/main/parse_p.h b/ctags/main/parse_p.h index 089908aadd..a49ae638f1 100644 --- a/ctags/main/parse_p.h +++ b/ctags/main/parse_p.h @@ -58,6 +58,9 @@ extern parserDefinitionFunc YAML_PARSER_LIST; #ifdef HAVE_PACKCC extern parserDefinitionFunc PEG_PARSER_LIST; #endif +#ifdef HAVE_PCRE2 +extern parserDefinitionFunc OPTLIB2C_PCRE2_PARSER_LIST; +#endif #endif /* EXTERNAL_PARSER_LIST */ extern bool doesLanguageAllowNullTag (const langType language); @@ -66,11 +69,10 @@ extern bool doesLanguageRequestAutomaticFQTag (const langType language); extern langType getNamedLanguageFull (const char *const name, size_t len, bool noPretending, bool include_aliases); extern kindDefinition* getLanguageKind(const langType language, int kindIndex); -extern kindDefinition* getLanguageKindForName (const langType language, const char *kindName); +extern kindDefinition* getLanguageKindForLetter (const langType language, char kindLetter); extern roleDefinition* getLanguageRole(const langType language, int kindIndex, int roleIndex); -extern roleDefinition* getLanguageRoleForName (const langType language, int kindIndex, - const char *roleName); - +extern unsigned int getLanguageVersionCurrent (const langType language); +extern unsigned int getLanguageVersionAge (const langType language); extern int defineLanguageKind (const langType language, kindDefinition *def, freeKindDefFunc freeKindDef); @@ -114,8 +116,8 @@ extern void printLanguageRoles (const langType language, const char* letters, extern void printLanguageAliases (const langType language, bool withListHeader, bool machinable, FILE *fp); extern void printLanguageList (void); -extern void printLanguageParameters (const langType language, - bool withListHeader, bool machinable, FILE *fp); +extern void printLanguageParams (const langType language, + bool withListHeader, bool machinable, FILE *fp); extern void printLanguageSubparsers (const langType language, bool withListHeader, bool machinable, FILE *fp); extern void printLangdefFlags (bool withListHeader, bool machinable, FILE *fp); @@ -136,12 +138,15 @@ extern bool runParserInNarrowedInputStream (const langType language, extern void freeEncodingResources (void); #endif +extern bool doesLanguageHaveForeignDependency (const langType language, const langType foreign_lang); + /* Regex interface */ extern bool processLanguageRegexOption (langType language, enum regexParserType regptype, const char *const parameter); extern void notifyLanguageRegexInputStart (langType language); extern void notifyLanguageRegexInputEnd (langType language); -extern void matchLanguageRegex (const langType language, const vString* const line); +extern bool hasLanguagePostRunRegexPatterns (const langType language); +extern void matchLanguageRegex (const langType language, const vString* const line, bool postrun); extern void freeRegexResources (void); extern bool checkRegex (void); extern void useRegexMethod (const langType language); @@ -172,8 +177,13 @@ extern bool makeExtraDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc); extern bool makeRoleDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc); +extern bool makeParserVersionPseudoTags (const langType language, + const ptagDesc *pdesc); extern void printLanguageMultitableStatistics (langType language); extern void printParserStatisticsIfUsed (langType lang); +/* For keeping the API compatibility with Geany, we use a macro here. */ +#define applyLanguageParam applyParameter + #endif /* CTAGS_MAIN_PARSE_PRIVATE_H */ diff --git a/ctags/main/parsers_p.h b/ctags/main/parsers_p.h index fc2e5f12c4..3dfd9e8bbc 100644 --- a/ctags/main/parsers_p.h +++ b/ctags/main/parsers_p.h @@ -20,6 +20,7 @@ PlistXMLParser, \ RelaxNGParser, \ SvgParser, \ + XrcParser, \ XmlParser, \ XsltParser #else @@ -29,7 +30,10 @@ #ifdef HAVE_LIBYAML #define YAML_PARSER_LIST \ YamlParser, \ - AnsiblePlaybookParser + AnsiblePlaybookParser, \ + I18nRubyGemParser, \ + OpenAPIParser, \ + YamlFrontMatter #else #define YAML_PARSER_LIST #endif @@ -38,11 +42,19 @@ #define PEG_PARSER_LIST \ VarlinkParser, \ KotlinParser, \ - ThriftParser + ThriftParser, \ + ElmParser #else #define PEG_PARSER_LIST #endif +#ifdef HAVE_PCRE2 +#define OPTLIB2C_PCRE2_PARSER_LIST \ + RDocParser +#else +#define OPTLIB2C_PCRE2_PARSER_LIST +#endif + /* Add the name of any new parser definition function here */ #define PARSER_LIST \ AbaqusParser, \ @@ -57,8 +69,10 @@ AutomakeParser, \ AwkParser, \ BasicParser, \ + BatsParser, \ BetaParser, \ - BibtexParser, \ + BibLaTeXParser, \ + BibtexParser, \ ClojureParser, \ CMakeParser, \ CParser, \ @@ -78,16 +92,20 @@ DosBatchParser, \ EiffelParser, \ ElixirParser, \ - ElmParser, \ EmacsLispParser, \ ErlangParser, \ FalconParser, \ FlexParser, \ + ForthParser, \ FortranParser, \ + FrontMatterParser, \ FunctionParametersParser, \ FyppParser, \ GdbinitParser, \ + GDScriptParser, \ + GemSpecParser, \ GoParser, \ + GPerfParser, \ HaskellParser, \ HaxeParser, \ HtmlParser, \ @@ -117,14 +135,14 @@ MyrddinParser, \ NsisParser, \ ObjcParser, \ - OldCppParser, \ - OldCParser, \ OcamlParser, \ + OrgParser, \ PasswdParser, \ PascalParser, \ PerlParser, \ Perl6Parser, \ PhpParser, \ + PkgConfigParser, \ PodParser, \ PowerShellParser, \ ProtobufParser, \ @@ -133,7 +151,11 @@ PythonLoggingConfigParser, \ QemuHXParser, \ QtMocParser, \ + QuartoParser, \ + RMarkdownParser, \ RParser, \ + RakeParser, \ + RakuParser, \ R6ClassParser, \ RSpecParser, \ RexxParser, \ @@ -154,11 +176,14 @@ SystemTapParser, \ TclParser, \ TclOOParser, \ + TerraformParser, \ + TerraformVariablesParser, \ TexParser, \ TexBeamerParser, \ TTCNParser, \ Txt2tagsParser, \ TypeScriptParser, \ + VParser, \ VeraParser, \ VerilogParser, \ SystemVerilogParser, \ @@ -167,6 +192,7 @@ WindResParser, \ YACCParser, \ YumRepoParser, \ - ZephirParser + ZephirParser, \ + ZshParser #endif /* CTAGS_MAIN_PARSERS_H */ diff --git a/ctags/main/promise.c b/ctags/main/promise.c index 0e8e35f968..01e84f5ca9 100644 --- a/ctags/main/promise.c +++ b/ctags/main/promise.c @@ -71,15 +71,21 @@ int makePromise (const char *parser, int r; langType lang = LANG_IGNORE; - verbose("makePromise: %s start(line: %lu, offset: %lu, srcline: %lu), end(line: %lu, offset: %lu)\n", + const bool is_thin_stream_spec = + isThinStreamSpec(startLine, startCharOffset, + endLine, endCharOffset, + sourceLineOffset); + + if (!is_thin_stream_spec + && (startLine > endLine + || (startLine == endLine && startCharOffset >= endCharOffset))) + return -1; + + verbose("makePromise: %s start(line: %lu, offset: %ld, srcline: %lu), end(line: %lu, offset: %ld)\n", parser? parser: "*", startLine, startCharOffset, sourceLineOffset, endLine, endCharOffset); - if ((!isThinStreamSpec(startLine, - startCharOffset, - endLine, - endCharOffset, - sourceLineOffset)) + if ((!is_thin_stream_spec) && ( !isXtagEnabled (XTAG_GUEST))) return -1; @@ -197,7 +203,7 @@ int getLastPromise (void) return promise_count - 1; } -static unsigned char* fill_or_skip (unsigned char *input, unsigned char *input_end, bool filling) +static unsigned char* fill_or_skip (unsigned char *input, const unsigned char *const input_end, const bool filling) { if ( !(input < input_end)) return NULL; @@ -221,53 +227,59 @@ static unsigned char* fill_or_skip (unsigned char *input, unsigned char *input_e } } -static void line_filler (unsigned char *input, size_t size, - unsigned long startLine, long startCharOffset, - unsigned long endLine, long endCharOffset, +static void line_filler (unsigned char *input, size_t const size, + unsigned long const startLine, long const startCharOffset, + unsigned long const endLine, long const endCharOffset, void *data) { - ulongArray *lines = data; + const ulongArray *lines = data; + const size_t count = ulongArrayCount (lines); unsigned int start_index, end_index; unsigned int i; - for (i = 0; i < ulongArrayCount (lines); i++) + for (i = 0; i < count; i++) { - unsigned long line = ulongArrayItem (lines, i); + const unsigned long line = ulongArrayItem (lines, i); if (line >= startLine) + { + if (line > endLine) + return; /* Not over-wrapping */ break; + } } - if (i == ulongArrayCount (lines)) - return; - if (i > endLine) - return; + if (i == count) + return; /* Not over-wrapping */ start_index = i; - for (; i < ulongArrayCount (lines); i++) + for (; i < count; i++) { - unsigned long line = ulongArrayItem (lines, i); + const unsigned long line = ulongArrayItem (lines, i); if (line > endLine) break; } end_index = i; unsigned long input_line = startLine; + const unsigned char *const input_end = input + size; for (i = start_index; i < end_index; i++) { - unsigned long line = ulongArrayItem (lines, i); + const unsigned long line = ulongArrayItem (lines, i); while (1) { if (input_line == line) { - input = fill_or_skip (input, input + size, true); + input = fill_or_skip (input, input_end, true); input_line++; break; } else { - input = fill_or_skip (input, input + size, false); + input = fill_or_skip (input, input_end, false); input_line++; } + if (input == NULL) + return; } } } diff --git a/ctags/main/promise.h b/ctags/main/promise.h index 3f1fd18214..8fd9167c9c 100644 --- a/ctags/main/promise.h +++ b/ctags/main/promise.h @@ -17,6 +17,8 @@ #include "parse.h" #include "numarray.h" +#define EOL_CHAR_OFFSET -1 + /* parser can be NULL; give a name with promiseUpdateLanguage() * when the name can be determined. */ int makePromise (const char *parser, diff --git a/ctags/main/ptag.c b/ctags/main/ptag.c index eef71a74d1..f91fd83f26 100644 --- a/ctags/main/ptag.c +++ b/ctags/main/ptag.c @@ -128,6 +128,26 @@ static bool ptagMakeProcCwd (ptagDesc *desc, langType language, return writePseudoTag (desc, CurrentDirectory, "", NULL); } +static bool ptagMakeParserVersion(ptagDesc *desc, langType language, + const void *data CTAGS_ATTR_UNUSED) +{ + char buf[32]; /* 2^32 '.' 2^32 '\0' */ + snprintf (buf, sizeof(buf), "%u.%u", + getLanguageVersionCurrent(language), + getLanguageVersionAge(language)); + const char *name = getLanguageName(language); + return writePseudoTag (desc, buf, "current.age", name); +} + +static bool ptagMakeOutputVersion (ptagDesc *desc, langType language, + const void *data CTAGS_ATTR_UNUSED) +{ + char buf[32]; /* 2^32 '.' 2^32 '\0' */ + snprintf (buf, sizeof(buf), "%u.%u", + OUTPUT_VERSION_CURRENT, OUTPUT_VERSION_AGE); + return writePseudoTag (desc, buf, "current.age", NULL); +} + static ptagDesc ptagDescs [] = { { /* The prefix is not "TAG_". @@ -170,22 +190,23 @@ static ptagDesc ptagDescs [] = { "the separators used in kinds", ptagMakeKindSeparators, PTAGF_PARSER }, - { false, "TAG_KIND_DESCRIPTION", + { true, "TAG_KIND_DESCRIPTION", "the letters, names and descriptions of enabled kinds in the language", ptagMakeKindDescriptions, PTAGF_PARSER }, - { false, "TAG_FIELD_DESCRIPTION", + { true, "TAG_FIELD_DESCRIPTION", "the names and descriptions of enabled fields", ptagMakeFieldDescriptions, PTAGF_COMMON|PTAGF_PARSER }, - { false, "TAG_EXTRA_DESCRIPTION", + { true, "TAG_EXTRA_DESCRIPTION", "the names and descriptions of enabled extras", ptagMakeExtraDescriptions, PTAGF_COMMON|PTAGF_PARSER }, - { false, "TAG_ROLE_DESCRIPTION", + { true, "TAG_ROLE_DESCRIPTION", "the names and descriptions of enabled roles", ptagMakeRoleDescriptions, - PTAGF_PARSER }, + PTAGF_PARSER, + .jsonObjectKey = "kindName" }, { true, "TAG_OUTPUT_MODE", "the output mode: u-ctags or e-ctags", ptagMakeCtagsOutputMode, @@ -206,6 +227,14 @@ static ptagDesc ptagDescs [] = { "the excmd: number, pattern, mixed, or combine", ptagMakeCtagsOutputExcmd, PTAGF_COMMON }, + { true, "TAG_PARSER_VERSION", + "the version of the parser (current.age)", + ptagMakeParserVersion, + PTAGF_PARSER }, + { true, "TAG_OUTPUT_VERSION", + "the version of the output interface (current.age)", + ptagMakeOutputVersion, + PTAGF_COMMON }, }; extern bool makePtagIfEnabled (ptagType type, langType language, const void *data) diff --git a/ctags/main/ptag_p.h b/ctags/main/ptag_p.h index 1bfbad632f..f71431fc6e 100644 --- a/ctags/main/ptag_p.h +++ b/ctags/main/ptag_p.h @@ -43,6 +43,8 @@ typedef enum ePtagType { /* pseudo tag content control */ PTAG_PATTERN_TRUNCATION, PTAG_PROC_CWD, PTAG_OUTPUT_EXCMD, + PTAG_PARSER_VERSION, + PTAG_OUTPUT_VERSION, PTAG_COUNT } ptagType; @@ -69,6 +71,9 @@ struct sPtagDesc { bool (* makeTag) (ptagDesc *, langType, const void *); ptagFlag flags; + + /* See writer-json.c */ + const char *jsonObjectKey; }; extern bool makePtagIfEnabled (ptagType type, langType language, const void *data); diff --git a/ctags/main/ptrarray.c b/ctags/main/ptrarray.c index 299ae810a0..23aa72013e 100644 --- a/ctags/main/ptrarray.c +++ b/ctags/main/ptrarray.c @@ -27,6 +27,7 @@ struct sPtrArray { unsigned int max; unsigned int count; void **array; + int refcount; ptrArrayDeleteFunc deleteFunc; }; @@ -40,6 +41,7 @@ extern ptrArray *ptrArrayNew (ptrArrayDeleteFunc deleteFunc) result->max = 8; result->count = 0; result->array = xMalloc (result->max, void*); + result->refcount = 1; result->deleteFunc = deleteFunc; return result; } @@ -145,10 +147,20 @@ extern void ptrArrayClear (ptrArray *const current) current->count = 0; } -extern void ptrArrayDelete (ptrArray *const current) +extern void ptrArrayRef (ptrArray *const current) +{ + current->refcount++; +} + +extern void ptrArrayUnref (ptrArray *const current) { if (current != NULL) { + current->refcount--; + if (current->refcount > 0) + return; + Assert(current->refcount == 0); + ptrArrayClear (current); eFree (current->array); eFree (current); @@ -199,7 +211,7 @@ extern void ptrArrayDeleteItem (ptrArray* const current, unsigned int indx) current->deleteFunc (ptr); memmove (current->array + indx, current->array + indx + 1, - (current->count - indx) * sizeof (*current->array)); + (current->count - indx - 1) * sizeof (*current->array)); --current->count; } @@ -208,7 +220,7 @@ extern void*ptrArrayRemoveItem (ptrArray* const current, unsigned int indx) void *ptr = current->array[indx]; memmove (current->array + indx, current->array + indx + 1, - (current->count - indx) * sizeof (*current->array)); + (current->count - indx - 1) * sizeof (*current->array)); --current->count; return ptr; diff --git a/ctags/main/ptrarray.h b/ctags/main/ptrarray.h index 88476a3b8d..701b46ff28 100644 --- a/ctags/main/ptrarray.h +++ b/ctags/main/ptrarray.h @@ -41,7 +41,9 @@ extern unsigned int ptrArrayCount (const ptrArray *const current); extern void* ptrArrayItem (const ptrArray *const current, const unsigned int indx); extern void* ptrArrayItemFromLast (const ptrArray *const current, const unsigned int indx); #define ptrArrayLast(A) ptrArrayItemFromLast(A, 0) -extern void ptrArrayDelete (ptrArray *const current); +extern void ptrArrayUnref (ptrArray *const current); +#define ptrArrayDelete ptrArrayUnref +extern void ptrArrayRef(ptrArray *const current); extern bool ptrArrayHasTest (const ptrArray *const current, bool (*test)(const void *ptr, void *userData), void *userData); diff --git a/ctags/main/rbtree.c b/ctags/main/rbtree.c index 8a402c8fbc..4a018c0713 100644 --- a/ctags/main/rbtree.c +++ b/ctags/main/rbtree.c @@ -1,23 +1,8 @@ -/* - * ============================================================================= - * - * Filename: rbtree.c - * - * Description: rbtree(Red-Black tree) implementation adapted from linux - * kernel thus can be used in userspace c program. - * - * Created: 09/02/2012 11:38:12 PM - * - * Author: Fu Haiping (forhappy), haipingf@gmail.com - * Company: ICT ( Institute Of Computing Technology, CAS ) - * - * ============================================================================= - */ - /* Red Black Trees (C) 1999 Andrea Arcangeli (C) 2002 David Woodhouse + (C) 2012 Michel Lespinasse This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,332 +22,392 @@ */ #include "general.h" -#include "rbtree.h" - -static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *right = node->rb_right; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_right = right->rb_left)) - rb_set_parent(right->rb_left, node); - right->rb_left = node; +#include +#include "rbtree_augmented.h" +#include "inline.h" - rb_set_parent(right, parent); +/* + * red-black trees properties: http://en.wikipedia.org/wiki/Rbtree + * + * 1) A node is either red or black + * 2) The root is black + * 3) All leaves (NULL) are black + * 4) Both children of every red node are black + * 5) Every simple path from root to leaves contains the same number + * of black nodes. + * + * 4 and 5 give the O(log n) guarantee, since 4 implies you cannot have two + * consecutive red nodes in a path and every red node is therefore followed by + * a black. So if B is the number of black nodes on every simple path (as per + * 5), then the longest possible path due to 4 is 2B. + * + * We shall indicate color with case, where black nodes are uppercase and red + * nodes will be lowercase. Unknown color nodes shall be drawn as red within + * parentheses and have some accompanying text comment. + */ - if (parent) - { - if (node == parent->rb_left) - parent->rb_left = right; - else - parent->rb_right = right; - } - else - root->rb_node = right; - rb_set_parent(node, right); +CTAGS_INLINE void rb_set_black(struct rb_node *rb) +{ + rb->__rb_parent_color |= RB_BLACK; } -static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) +CTAGS_INLINE struct rb_node *rb_red_parent(struct rb_node *red) { - struct rb_node *left = node->rb_left; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_left = left->rb_right)) - rb_set_parent(left->rb_right, node); - left->rb_right = node; - - rb_set_parent(left, parent); + return (struct rb_node *)red->__rb_parent_color; +} - if (parent) - { - if (node == parent->rb_right) - parent->rb_right = left; - else - parent->rb_left = left; - } - else - root->rb_node = left; - rb_set_parent(node, left); +/* + * Helper function for rotations: + * - old's parent and color get assigned to new + * - old gets assigned new as a parent and 'color' as a color. + */ +CTAGS_INLINE void +__rb_rotate_set_parents(struct rb_node *old, struct rb_node *new, + struct rb_root *root, int color) +{ + struct rb_node *parent = rb_parent(old); + new->__rb_parent_color = old->__rb_parent_color; + rb_set_parent_color(old, new, color); + __rb_change_child(old, new, parent, root); } -void rb_insert_color(struct rb_node *node, struct rb_root *root) +CTAGS_INLINE void +__rb_insert(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { - struct rb_node *parent, *gparent; - - while ((parent = rb_parent(node)) && rb_is_red(parent)) - { - gparent = rb_parent(parent); - - if (parent == gparent->rb_left) - { - { - register struct rb_node *uncle = gparent->rb_right; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } + struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; + + while (true) { + /* + * Loop invariant: node is red + * + * If there is a black parent, we are done. + * Otherwise, take some corrective action as we don't + * want a red root or two consecutive red nodes. + */ + if (!parent) { + rb_set_parent_color(node, NULL, RB_BLACK); + break; + } else if (rb_is_black(parent)) + break; + + gparent = rb_red_parent(parent); + + tmp = gparent->rb_right; + if (parent != tmp) { /* parent == gparent->rb_left */ + if (tmp && rb_is_red(tmp)) { + /* + * Case 1 - color flips + * + * G g + * / \ / \ + * p u --> P U + * / / + * n n + * + * However, since g's parent might be red, and + * 4) does not allow this, we need to recurse + * at g. + */ + rb_set_parent_color(tmp, gparent, RB_BLACK); + rb_set_parent_color(parent, gparent, RB_BLACK); + node = gparent; + parent = rb_parent(node); + rb_set_parent_color(node, parent, RB_RED); + continue; } - if (parent->rb_right == node) - { - register struct rb_node *tmp; - __rb_rotate_left(parent, root); - tmp = parent; + tmp = parent->rb_right; + if (node == tmp) { + /* + * Case 2 - left rotate at parent + * + * G G + * / \ / \ + * p U --> n U + * \ / + * n p + * + * This still leaves us in violation of 4), the + * continuation into Case 3 will fix that. + */ + parent->rb_right = tmp = node->rb_left; + node->rb_left = parent; + if (tmp) + rb_set_parent_color(tmp, parent, + RB_BLACK); + rb_set_parent_color(parent, node, RB_RED); + augment_rotate(parent, node); parent = node; - node = tmp; + tmp = node->rb_right; } - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_right(gparent, root); + /* + * Case 3 - right rotate at gparent + * + * G P + * / \ / \ + * p U --> n g + * / \ + * n U + */ + gparent->rb_left = tmp; /* == parent->rb_right */ + parent->rb_right = gparent; + if (tmp) + rb_set_parent_color(tmp, gparent, RB_BLACK); + __rb_rotate_set_parents(gparent, parent, root, RB_RED); + augment_rotate(gparent, parent); + break; } else { - { - register struct rb_node *uncle = gparent->rb_left; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } + tmp = gparent->rb_left; + if (tmp && rb_is_red(tmp)) { + /* Case 1 - color flips */ + rb_set_parent_color(tmp, gparent, RB_BLACK); + rb_set_parent_color(parent, gparent, RB_BLACK); + node = gparent; + parent = rb_parent(node); + rb_set_parent_color(node, parent, RB_RED); + continue; } - if (parent->rb_left == node) - { - register struct rb_node *tmp; - __rb_rotate_right(parent, root); - tmp = parent; + tmp = parent->rb_left; + if (node == tmp) { + /* Case 2 - right rotate at parent */ + parent->rb_left = tmp = node->rb_right; + node->rb_right = parent; + if (tmp) + rb_set_parent_color(tmp, parent, + RB_BLACK); + rb_set_parent_color(parent, node, RB_RED); + augment_rotate(parent, node); parent = node; - node = tmp; + tmp = node->rb_left; } - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_left(gparent, root); + /* Case 3 - left rotate at gparent */ + gparent->rb_right = tmp; /* == parent->rb_left */ + parent->rb_left = gparent; + if (tmp) + rb_set_parent_color(tmp, gparent, RB_BLACK); + __rb_rotate_set_parents(gparent, parent, root, RB_RED); + augment_rotate(gparent, parent); + break; } } - - rb_set_black(root->rb_node); } -static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, - struct rb_root *root) +/* + * Inline version for rb_erase() use - we want to be able to inline + * and eliminate the dummy_rotate callback there + */ +CTAGS_INLINE void +____rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { - struct rb_node *other; - - while ((!node || rb_is_black(node)) && node != root->rb_node) - { - if (parent->rb_left == node) - { - other = parent->rb_right; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_left(parent, root); - other = parent->rb_right; - } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); + struct rb_node *node = NULL, *sibling, *tmp1, *tmp2; + + while (true) { + /* + * Loop invariants: + * - node is black (or NULL on first iteration) + * - node is not the root (parent is not NULL) + * - All leaf paths going through parent and node have a + * black node count that is 1 lower than other leaf paths. + */ + sibling = parent->rb_right; + if (node != sibling) { /* node == parent->rb_left */ + if (rb_is_red(sibling)) { + /* + * Case 1 - left rotate at parent + * + * P S + * / \ / \ + * N s --> p Sr + * / \ / \ + * Sl Sr N Sl + */ + parent->rb_right = tmp1 = sibling->rb_left; + sibling->rb_left = parent; + rb_set_parent_color(tmp1, parent, RB_BLACK); + __rb_rotate_set_parents(parent, sibling, root, + RB_RED); + augment_rotate(parent, sibling); + sibling = tmp1; } - else - { - if (!other->rb_right || rb_is_black(other->rb_right)) - { - rb_set_black(other->rb_left); - rb_set_red(other); - __rb_rotate_right(other, root); - other = parent->rb_right; + tmp1 = sibling->rb_right; + if (!tmp1 || rb_is_black(tmp1)) { + tmp2 = sibling->rb_left; + if (!tmp2 || rb_is_black(tmp2)) { + /* + * Case 2 - sibling color flip + * (p could be either color here) + * + * (p) (p) + * / \ / \ + * N S --> N s + * / \ / \ + * Sl Sr Sl Sr + * + * This leaves us violating 5) which + * can be fixed by flipping p to black + * if it was red, or by recursing at p. + * p is red when coming from Case 1. + */ + rb_set_parent_color(sibling, parent, + RB_RED); + if (rb_is_red(parent)) + rb_set_black(parent); + else { + node = parent; + parent = rb_parent(node); + if (parent) + continue; + } + break; } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - rb_set_black(other->rb_right); - __rb_rotate_left(parent, root); - node = root->rb_node; - break; - } - } - else - { - other = parent->rb_left; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_right(parent, root); - other = parent->rb_left; + /* + * Case 3 - right rotate at sibling + * (p could be either color here) + * + * (p) (p) + * / \ / \ + * N S --> N Sl + * / \ \ + * sl Sr s + * \ + * Sr + */ + sibling->rb_left = tmp1 = tmp2->rb_right; + tmp2->rb_right = sibling; + parent->rb_right = tmp2; + if (tmp1) + rb_set_parent_color(tmp1, sibling, + RB_BLACK); + augment_rotate(sibling, tmp2); + tmp1 = sibling; + sibling = tmp2; } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); + /* + * Case 4 - left rotate at parent + color flips + * (p and sl could be either color here. + * After rotation, p becomes black, s acquires + * p's color, and sl keeps its color) + * + * (p) (s) + * / \ / \ + * N S --> P Sr + * / \ / \ + * (sl) sr N (sl) + */ + parent->rb_right = tmp2 = sibling->rb_left; + sibling->rb_left = parent; + rb_set_parent_color(tmp1, sibling, RB_BLACK); + if (tmp2) + rb_set_parent(tmp2, parent); + __rb_rotate_set_parents(parent, sibling, root, + RB_BLACK); + augment_rotate(parent, sibling); + break; + } else { + sibling = parent->rb_left; + if (rb_is_red(sibling)) { + /* Case 1 - right rotate at parent */ + parent->rb_left = tmp1 = sibling->rb_right; + sibling->rb_right = parent; + rb_set_parent_color(tmp1, parent, RB_BLACK); + __rb_rotate_set_parents(parent, sibling, root, + RB_RED); + augment_rotate(parent, sibling); + sibling = tmp1; } - else - { - if (!other->rb_left || rb_is_black(other->rb_left)) - { - rb_set_black(other->rb_right); - rb_set_red(other); - __rb_rotate_left(other, root); - other = parent->rb_left; + tmp1 = sibling->rb_left; + if (!tmp1 || rb_is_black(tmp1)) { + tmp2 = sibling->rb_right; + if (!tmp2 || rb_is_black(tmp2)) { + /* Case 2 - sibling color flip */ + rb_set_parent_color(sibling, parent, + RB_RED); + if (rb_is_red(parent)) + rb_set_black(parent); + else { + node = parent; + parent = rb_parent(node); + if (parent) + continue; + } + break; } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - rb_set_black(other->rb_left); - __rb_rotate_right(parent, root); - node = root->rb_node; - break; + /* Case 3 - right rotate at sibling */ + sibling->rb_right = tmp1 = tmp2->rb_left; + tmp2->rb_left = sibling; + parent->rb_left = tmp2; + if (tmp1) + rb_set_parent_color(tmp1, sibling, + RB_BLACK); + augment_rotate(sibling, tmp2); + tmp1 = sibling; + sibling = tmp2; } + /* Case 4 - left rotate at parent + color flips */ + parent->rb_left = tmp2 = sibling->rb_right; + sibling->rb_right = parent; + rb_set_parent_color(tmp1, sibling, RB_BLACK); + if (tmp2) + rb_set_parent(tmp2, parent); + __rb_rotate_set_parents(parent, sibling, root, + RB_BLACK); + augment_rotate(parent, sibling); + break; } } - if (node) - rb_set_black(node); } -void rb_erase(struct rb_node *node, struct rb_root *root) +/* Non-inline version for rb_erase_augmented() use */ +void __rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { - struct rb_node *child, *parent; - int color; - - if (!node->rb_left) - child = node->rb_right; - else if (!node->rb_right) - child = node->rb_left; - else - { - struct rb_node *old = node, *left; - - node = node->rb_right; - while ((left = node->rb_left) != NULL) - node = left; - - if (rb_parent(old)) { - if (rb_parent(old)->rb_left == old) - rb_parent(old)->rb_left = node; - else - rb_parent(old)->rb_right = node; - } else - root->rb_node = node; - - child = node->rb_right; - parent = rb_parent(node); - color = rb_color(node); - - if (parent == old) { - parent = node; - } else { - if (child) - rb_set_parent(child, parent); - parent->rb_left = child; - - node->rb_right = old->rb_right; - rb_set_parent(old->rb_right, node); - } - - node->rb_parent_color = old->rb_parent_color; - node->rb_left = old->rb_left; - rb_set_parent(old->rb_left, node); - - goto color; - } - - parent = rb_parent(node); - color = rb_color(node); - - if (child) - rb_set_parent(child, parent); - if (parent) - { - if (parent->rb_left == node) - parent->rb_left = child; - else - parent->rb_right = child; - } - else - root->rb_node = child; - - color: - if (color == RB_BLACK) - __rb_erase_color(child, parent, root); + ____rb_erase_color(parent, root, augment_rotate); } -static void rb_augment_path(struct rb_node *node, rb_augment_f func, void *data) -{ - struct rb_node *parent; - -up: - func(node, data); - parent = rb_parent(node); - if (!parent) - return; +/* + * Non-augmented rbtree manipulation functions. + * + * We use dummy augmented callbacks here, and have the compiler optimize them + * out of the rb_insert_color() and rb_erase() function definitions. + */ - if (node == parent->rb_left && parent->rb_right) - func(parent->rb_right, data); - else if (parent->rb_left) - func(parent->rb_left, data); +CTAGS_INLINE void dummy_propagate(struct rb_node *node, struct rb_node *stop) {} +CTAGS_INLINE void dummy_copy(struct rb_node *old, struct rb_node *new) {} +CTAGS_INLINE void dummy_rotate(struct rb_node *old, struct rb_node *new) {} - node = parent; - goto up; -} +static const struct rb_augment_callbacks dummy_callbacks = { + dummy_propagate, dummy_copy, dummy_rotate +}; -/* - * after inserting @node into the tree, update the tree to account for - * both the new entry and any damage done by rebalance - */ -void rb_augment_insert(struct rb_node *node, rb_augment_f func, void *data) +void rb_insert_color(struct rb_node *node, struct rb_root *root) { - if (node->rb_left) - node = node->rb_left; - else if (node->rb_right) - node = node->rb_right; - - rb_augment_path(node, func, data); + __rb_insert(node, root, dummy_rotate); } -/* - * before removing the node, find the deepest node on the rebalance path - * that will still be there after @node gets removed - */ -struct rb_node *rb_augment_erase_begin(struct rb_node *node) +void rb_erase(struct rb_node *node, struct rb_root *root) { - struct rb_node *deepest; - - if (!node->rb_right && !node->rb_left) - deepest = rb_parent(node); - else if (!node->rb_right) - deepest = node->rb_left; - else if (!node->rb_left) - deepest = node->rb_right; - else { - deepest = rb_next(node); - if (deepest->rb_right) - deepest = deepest->rb_right; - else if (rb_parent(deepest) != node) - deepest = rb_parent(deepest); - } - - return deepest; + struct rb_node *rebalance; + rebalance = __rb_erase_augmented(node, root, &dummy_callbacks); + if (rebalance) + ____rb_erase_color(rebalance, root, dummy_rotate); } /* - * after removal, update the tree to account for the removed entry - * and any rebalance damage. + * Augmented rbtree manipulation functions. + * + * This instantiates the same __always_inline functions as in the non-augmented + * case, but this time with user-defined callbacks. */ -void rb_augment_erase_end(struct rb_node *node, rb_augment_f func, void *data) + +void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { - if (node) - rb_augment_path(node, func, data); + __rb_insert(node, root, augment_rotate); } /* @@ -396,11 +441,13 @@ struct rb_node *rb_next(const struct rb_node *node) { struct rb_node *parent; - if (rb_parent(node) == node) + if (RB_EMPTY_NODE(node)) return NULL; - /* If we have a right-hand child, go down and then left as far - as we can. */ + /* + * If we have a right-hand child, go down and then left as far + * as we can. + */ if (node->rb_right) { node = node->rb_right; while (node->rb_left) @@ -408,12 +455,13 @@ struct rb_node *rb_next(const struct rb_node *node) return (struct rb_node *)node; } - /* No right-hand children. Everything down and left is - smaller than us, so any 'next' node must be in the general - direction of our parent. Go up the tree; any time the - ancestor is a right-hand child of its parent, keep going - up. First time it's a left-hand child of its parent, said - parent is our 'next' node. */ + /* + * No right-hand children. Everything down and left is smaller than us, + * so any 'next' node must be in the general direction of our parent. + * Go up the tree; any time the ancestor is a right-hand child of its + * parent, keep going up. First time it's a left-hand child of its + * parent, said parent is our 'next' node. + */ while ((parent = rb_parent(node)) && node == parent->rb_right) node = parent; @@ -424,11 +472,13 @@ struct rb_node *rb_prev(const struct rb_node *node) { struct rb_node *parent; - if (rb_parent(node) == node) + if (RB_EMPTY_NODE(node)) return NULL; - /* If we have a left-hand child, go down and then right as far - as we can. */ + /* + * If we have a left-hand child, go down and then right as far + * as we can. + */ if (node->rb_left) { node = node->rb_left; while (node->rb_right) @@ -436,8 +486,10 @@ struct rb_node *rb_prev(const struct rb_node *node) return (struct rb_node *)node; } - /* No left-hand children. Go up till we find an ancestor which - is a right-hand child of its parent */ + /* + * No left-hand children. Go up till we find an ancestor which + * is a right-hand child of its parent. + */ while ((parent = rb_parent(node)) && node == parent->rb_left) node = parent; @@ -445,19 +497,12 @@ struct rb_node *rb_prev(const struct rb_node *node) } void rb_replace_node(struct rb_node *victim, struct rb_node *new, - struct rb_root *root) + struct rb_root *root) { struct rb_node *parent = rb_parent(victim); /* Set the surrounding nodes to point to the replacement */ - if (parent) { - if (victim == parent->rb_left) - parent->rb_left = new; - else - parent->rb_right = new; - } else { - root->rb_node = new; - } + __rb_change_child(victim, new, parent, root); if (victim->rb_left) rb_set_parent(victim->rb_left, new); if (victim->rb_right) @@ -466,3 +511,41 @@ void rb_replace_node(struct rb_node *victim, struct rb_node *new, /* Copy the pointers/colour from the victim to the replacement */ *new = *victim; } + +static struct rb_node *rb_left_deepest_node(const struct rb_node *node) +{ + for (;;) { + if (node->rb_left) + node = node->rb_left; + else if (node->rb_right) + node = node->rb_right; + else + return (struct rb_node *)node; + } +} + +struct rb_node *rb_next_postorder(const struct rb_node *node) +{ + const struct rb_node *parent; + if (!node) + return NULL; + parent = rb_parent(node); + + /* If we're sitting on node, we've already seen our children */ + if (parent && node == parent->rb_left && parent->rb_right) { + /* If we are the parent's left node, go to the parent's right + * node then all the way down to the left */ + return rb_left_deepest_node(parent->rb_right); + } else + /* Otherwise we are the parent's right node, and the parent + * should be next */ + return (struct rb_node *)parent; +} + +struct rb_node *rb_first_postorder(const struct rb_root *root) +{ + if (!root->rb_node) + return NULL; + + return rb_left_deepest_node(root->rb_node); +} diff --git a/ctags/main/rbtree.h b/ctags/main/rbtree.h index e07f25949c..86f499a7b3 100644 --- a/ctags/main/rbtree.h +++ b/ctags/main/rbtree.h @@ -1,19 +1,3 @@ -/* - * ============================================================================= - * - * Filename: rbtree.h - * - * Description: rbtree(Red-Black tree) implementation adapted from linux - * kernel thus can be used in userspace c program. - * - * Created: 09/02/2012 11:36:11 PM - * - * Author: Fu Haiping (forhappy), haipingf@gmail.com - * Company: ICT ( Institute Of Computing Technology, CAS ) - * - * ============================================================================= - */ - /* Red Black Trees (C) 1999 Andrea Arcangeli @@ -39,174 +23,69 @@ I know it's not the cleaner way, but in C (not in C++) to get performances and genericity... - Some example of insert and search follows here. The search is a plain - normal search over an ordered tree. The insert instead must be implemented - in two steps: First, the code must insert the element in order as a red leaf - in the tree, and then the support library function rb_insert_color() must - be called. Such function will do the not trivial work to rebalance the - rbtree, if necessary. - ------------------------------------------------------------------------ -static inline struct page * rb_search_page_cache(struct inode * inode, - unsigned long offset) -{ - struct rb_node * n = inode->i_rb_page_cache.rb_node; - struct page * page; - - while (n) - { - page = rb_entry(n, struct page, rb_page_cache); - - if (offset < page->offset) - n = n->rb_left; - else if (offset > page->offset) - n = n->rb_right; - else - return page; - } - return NULL; -} - -static inline struct page * __rb_insert_page_cache(struct inode * inode, - unsigned long offset, - struct rb_node * node) -{ - struct rb_node ** p = &inode->i_rb_page_cache.rb_node; - struct rb_node * parent = NULL; - struct page * page; - - while (*p) - { - parent = *p; - page = rb_entry(parent, struct page, rb_page_cache); - - if (offset < page->offset) - p = &(*p)->rb_left; - else if (offset > page->offset) - p = &(*p)->rb_right; - else - return page; - } - - rb_link_node(node, parent, p); - - return NULL; -} - -static inline struct page * rb_insert_page_cache(struct inode * inode, - unsigned long offset, - struct rb_node * node) -{ - struct page * ret; - if ((ret = __rb_insert_page_cache(inode, offset, node))) - goto out; - rb_insert_color(node, &inode->i_rb_page_cache); - out: - return ret; -} ------------------------------------------------------------------------ + See Documentation/rbtree.txt for documentation and samples. */ #ifndef CTAGS_MAIN_RBTREE_H #define CTAGS_MAIN_RBTREE_H #include "general.h" +#include +#include + #include "inline.h" #include "gcc-attr.h" -#include #if defined(container_of) #undef container_of #if defined(HAVE_TYPEOF) && defined(HAVE_STATEMENT_EXPRESSION_EXT) #define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) #else #define container_of(ptr, type, member) \ - ((type *)( (char *)ptr - offsetof(type,member))) + ((type *)( (char *)ptr - offsetof(type,member))) #endif /* defined(HAVE_TYPEOF) && defined(HAVE_STATEMENT_EXPRESSION_EXT) */ #else #if defined(HAVE_TYPEOF) && defined(HAVE_STATEMENT_EXPRESSION_EXT) #define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) #else #define container_of(ptr, type, member) \ - ((type *)( (char *)ptr - offsetof(type,member))) + ((type *)( (char *)ptr - offsetof(type,member))) #endif /* defined(HAVE_TYPEOF) && defined(HAVE_STATEMENT_EXPRESSION_EXT) */ #endif /* defined(container_of) */ -#if defined(offsetof) - #undef offsetof - #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) -#else - #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) -#endif - -#undef NULL -#if defined(__cplusplus) - #define NULL 0 -#else - #define NULL ((void *)0) -#endif - -struct rb_node -{ - uintptr_t rb_parent_color; -#define RB_RED 0 -#define RB_BLACK 1 +struct rb_node { + uintptr_t __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } CTAGA_ATTR_ALIGNED(sizeof(long)); - /* The alignment might seem pointless, but allegedly CRIS needs it */ + /* The alignment might seem pointless, but allegedly CRIS needs it */ -struct rb_root -{ +struct rb_root { struct rb_node *rb_node; }; -#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3)) -#define rb_color(r) ((r)->rb_parent_color & 1) -#define rb_is_red(r) (!rb_color(r)) -#define rb_is_black(r) rb_color(r) -#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) -#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) - -CTAGS_INLINE void rb_set_parent(struct rb_node *rb, struct rb_node *p) -{ - rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p; -} -CTAGS_INLINE void rb_set_color(struct rb_node *rb, int color) -{ - rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; -} +#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3)) #define RB_ROOT (struct rb_root) { NULL, } #define rb_entry(ptr, type, member) container_of(ptr, type, member) -#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) -#define RB_EMPTY_NODE(node) (rb_parent(node) == node) -#define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) +#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) + +/* 'empty' nodes are nodes that are known not to be inserted in an rbtree */ +#define RB_EMPTY_NODE(node) \ + ((node)->__rb_parent_color == (uintptr_t)(node)) +#define RB_CLEAR_NODE(node) \ + ((node)->__rb_parent_color = (uintptr_t)(node)) -CTAGS_INLINE void rb_init_node(struct rb_node *rb) -{ - rb->rb_parent_color = 0; - rb->rb_right = NULL; - rb->rb_left = NULL; - RB_CLEAR_NODE(rb); -} extern void rb_insert_color(struct rb_node *, struct rb_root *); extern void rb_erase(struct rb_node *, struct rb_root *); -typedef void (*rb_augment_f)(struct rb_node *node, void *data); - -extern void rb_augment_insert(struct rb_node *node, - rb_augment_f func, void *data); -extern struct rb_node *rb_augment_erase_begin(struct rb_node *node); -extern void rb_augment_erase_end(struct rb_node *node, - rb_augment_f func, void *data); /* Find logical next and previous nodes in a tree */ extern struct rb_node *rb_next(const struct rb_node *); @@ -214,17 +93,41 @@ extern struct rb_node *rb_prev(const struct rb_node *); extern struct rb_node *rb_first(const struct rb_root *); extern struct rb_node *rb_last(const struct rb_root *); +/* Postorder iteration - always visit the parent after its children */ +extern struct rb_node *rb_first_postorder(const struct rb_root *); +extern struct rb_node *rb_next_postorder(const struct rb_node *); + /* Fast replacement of a single node without remove/rebalance/add/rebalance */ extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, - struct rb_root *root); + struct rb_root *root); CTAGS_INLINE void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link) { - node->rb_parent_color = (uintptr_t)parent; + node->__rb_parent_color = (uintptr_t)parent; node->rb_left = node->rb_right = NULL; *rb_link = node; } +#define rb_entry_safe(ptr, type, member) \ + ({ typeof(ptr) ____ptr = (ptr); \ + ____ptr ? rb_entry(____ptr, type, member) : NULL; \ + }) + +/** + * rbtree_postorder_for_each_entry_safe - iterate over rb_root in post order of + * given type safe against removal of rb_node entry + * + * @pos: the 'type *' to use as a loop cursor. + * @n: another 'type *' to use as temporary storage + * @root: 'rb_root *' of the rbtree. + * @field: the name of the rb_node field within 'type'. + */ +#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ + for (pos = rb_entry_safe(rb_first_postorder(root), typeof(*pos), field); \ + pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ + typeof(*pos), field); 1; }); \ + pos = n) + #endif /* CTAGS_MAIN_RBTREE_H */ diff --git a/ctags/main/rbtree_augmented.h b/ctags/main/rbtree_augmented.h new file mode 100644 index 0000000000..4c581c8285 --- /dev/null +++ b/ctags/main/rbtree_augmented.h @@ -0,0 +1,247 @@ +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + (C) 2002 David Woodhouse + (C) 2012 Michel Lespinasse + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + linux/include/linux/rbtree_augmented.h +*/ + +#ifndef CTAGS_MAIN_RBTREE_AUGMENTED_H +#define CTAGS_MAIN_RBTREE_AUGMENTED_H + +#include "general.h" + +#include +#include +#include "rbtree.h" + +#include "inline.h" + +/* + * Please note - only struct rb_augment_callbacks and the prototypes for + * rb_insert_augmented() and rb_erase_augmented() are intended to be public. + * The rest are implementation details you are not expected to depend on. + * + * See Documentation/rbtree.txt for documentation and samples. + */ + +struct rb_augment_callbacks { + void (*propagate)(struct rb_node *node, struct rb_node *stop); + void (*copy)(struct rb_node *old, struct rb_node *new); + void (*rotate)(struct rb_node *old, struct rb_node *new); +}; + +extern void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)); +/* + * Fixup the rbtree and update the augmented information when rebalancing. + * + * On insertion, the user must update the augmented information on the path + * leading to the inserted node, then call rb_link_node() as usual and + * rb_augment_inserted() instead of the usual rb_insert_color() call. + * If rb_augment_inserted() rebalances the rbtree, it will callback into + * a user provided function to update the augmented information on the + * affected subtrees. + */ +CTAGS_INLINE void +rb_insert_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + __rb_insert_augmented(node, root, augment->rotate); +} + +#define RB_DECLARE_CALLBACKS(rbstatic, rbname, rbstruct, rbfield, \ + rbtype, rbaugmented, rbcompute) \ +CTAGS_INLINE void \ +rbname ## _propagate(struct rb_node *rb, struct rb_node *stop) \ +{ \ + while (rb != stop) { \ + rbstruct *node = rb_entry(rb, rbstruct, rbfield); \ + rbtype augmented = rbcompute(node); \ + if (node->rbaugmented == augmented) \ + break; \ + node->rbaugmented = augmented; \ + rb = rb_parent(&node->rbfield); \ + } \ +} \ +CTAGS_INLINE void \ +rbname ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + rbstruct *old = rb_entry(rb_old, rbstruct, rbfield); \ + rbstruct *new = rb_entry(rb_new, rbstruct, rbfield); \ + new->rbaugmented = old->rbaugmented; \ +} \ +static void \ +rbname ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + rbstruct *old = rb_entry(rb_old, rbstruct, rbfield); \ + rbstruct *new = rb_entry(rb_new, rbstruct, rbfield); \ + new->rbaugmented = old->rbaugmented; \ + old->rbaugmented = rbcompute(old); \ +} \ +rbstatic const struct rb_augment_callbacks rbname = { \ + rbname ## _propagate, rbname ## _copy, rbname ## _rotate \ +}; + + +#define RB_RED 0 +#define RB_BLACK 1 + +#define __rb_parent(pc) ((struct rb_node *)(pc & ~3)) + +#define __rb_color(pc) ((pc) & 1) +#define __rb_is_black(pc) __rb_color(pc) +#define __rb_is_red(pc) (!__rb_color(pc)) +#define rb_color(rb) __rb_color((rb)->__rb_parent_color) +#define rb_is_red(rb) __rb_is_red((rb)->__rb_parent_color) +#define rb_is_black(rb) __rb_is_black((rb)->__rb_parent_color) + +CTAGS_INLINE void rb_set_parent(struct rb_node *rb, struct rb_node *p) +{ + rb->__rb_parent_color = rb_color(rb) | (uintptr_t)p; +} + +CTAGS_INLINE void rb_set_parent_color(struct rb_node *rb, + struct rb_node *p, int color) +{ + rb->__rb_parent_color = (uintptr_t)p | color; +} + +CTAGS_INLINE void +__rb_change_child(struct rb_node *old, struct rb_node *new, + struct rb_node *parent, struct rb_root *root) +{ + if (parent) { + if (parent->rb_left == old) + parent->rb_left = new; + else + parent->rb_right = new; + } else + root->rb_node = new; +} + +extern void __rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *new)); + +CTAGS_INLINE struct rb_node * +__rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *child = node->rb_right, *tmp = node->rb_left; + struct rb_node *parent, *rebalance; + uintptr_t pc; + + if (!tmp) { + /* + * Case 1: node to erase has no more than 1 child (easy!) + * + * Note that if there is one child it must be red due to 5) + * and node must be black due to 4). We adjust colors locally + * so as to bypass __rb_erase_color() later on. + */ + pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, child, parent, root); + if (child) { + child->__rb_parent_color = pc; + rebalance = NULL; + } else + rebalance = __rb_is_black(pc) ? parent : NULL; + tmp = parent; + } else if (!child) { + /* Still case 1, but this time the child is node->rb_left */ + tmp->__rb_parent_color = pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, tmp, parent, root); + rebalance = NULL; + tmp = parent; + } else { + struct rb_node *successor = child, *child2; + tmp = child->rb_left; + if (!tmp) { + /* + * Case 2: node's successor is its right child + * + * (n) (s) + * / \ / \ + * (x) (s) -> (x) (c) + * \ + * (c) + */ + parent = successor; + child2 = successor->rb_right; + augment->copy(node, successor); + } else { + /* + * Case 3: node's successor is leftmost under + * node's right child subtree + * + * (n) (s) + * / \ / \ + * (x) (y) -> (x) (y) + * / / + * (p) (p) + * / / + * (s) (c) + * \ + * (c) + */ + do { + parent = successor; + successor = tmp; + tmp = tmp->rb_left; + } while (tmp); + parent->rb_left = child2 = successor->rb_right; + successor->rb_right = child; + rb_set_parent(child, successor); + augment->copy(node, successor); + augment->propagate(parent, successor); + } + + successor->rb_left = tmp = node->rb_left; + rb_set_parent(tmp, successor); + + pc = node->__rb_parent_color; + tmp = __rb_parent(pc); + __rb_change_child(node, successor, tmp, root); + if (child2) { + successor->__rb_parent_color = pc; + rb_set_parent_color(child2, parent, RB_BLACK); + rebalance = NULL; + } else { + uintptr_t pc2 = successor->__rb_parent_color; + successor->__rb_parent_color = pc; + rebalance = __rb_is_black(pc2) ? parent : NULL; + } + tmp = successor; + } + + augment->propagate(tmp, NULL); + return rebalance; +} + +CTAGS_INLINE void +rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *rebalance = __rb_erase_augmented(node, root, augment); + if (rebalance) + __rb_erase_color(rebalance, root, augment->rotate); +} + +#endif /* CTAGS_MAIN_RBTREE_AUGMENTED_H */ diff --git a/ctags/main/read.c b/ctags/main/read.c index 9f0a35f80a..67586051db 100644 --- a/ctags/main/read.c +++ b/ctags/main/read.c @@ -26,6 +26,7 @@ #include "routines_p.h" #include "options_p.h" #include "parse_p.h" +#include "promise.h" #include "promise_p.h" #include "stats_p.h" #include "trace.h" @@ -68,6 +69,7 @@ typedef struct sComputPos { long offset; bool open; int crAdjustment; + size_t posInAllLines; } compoundPos; typedef struct sInputLineFposMap { @@ -148,7 +150,24 @@ extern unsigned long getInputLineNumber (void) extern int getInputLineOffset (void) { unsigned char *base = (unsigned char *) vStringValue (File.line); - int ret = File.currentLine - base - File.ungetchIdx; + int ret; + + if (File.currentLine) + ret = File.currentLine - base - File.ungetchIdx; + else if (File.input.lineNumber) + { + /* When EOF is saw, currentLine is set to NULL. + * So the way to calculate the offset at the end of file is tricky. + */ + ret = (mio_tell (File.mio) - (File.bomFound? 3: 0)) + - getInputFileOffsetForLine(File.input.lineNumber); + } + else + { + /* At the first line of file. */ + ret = mio_tell (File.mio) - (File.bomFound? 3: 0); + } + return ret >= 0 ? ret : 0; } @@ -191,7 +210,9 @@ extern MIOPos getInputFilePositionForLine (unsigned int line) extern long getInputFileOffsetForLine (unsigned int line) { compoundPos *cpos = getInputFileCompoundPosForLine (line); - return cpos->offset - (File.bomFound? 3: 0); + long r = cpos->offset - (File.bomFound? 3: 0) - cpos->crAdjustment; + Assert (r >= 0); + return r; } extern langType getInputLanguage (void) @@ -245,9 +266,9 @@ extern bool doesInputLanguageAllowNullTag (void) return doesLanguageAllowNullTag (getInputLanguage ()); } -extern bool doesInputLanguageRequestAutomaticFQTag (void) +extern bool doesInputLanguageRequestAutomaticFQTag (const tagEntryInfo *e) { - return doesLanguageRequestAutomaticFQTag (getInputLanguage ()); + return doesLanguageRequestAutomaticFQTag (e->langType); } extern const char *getSourceFileTagPath (void) @@ -316,8 +337,14 @@ static void allocLineFposMap (inputLineFposMap *lineFposMap) lineFposMap->count = 0; } +static void resetLineFposMap (inputLineFposMap *lineFposMap) +{ + memset(lineFposMap->pos, 0, sizeof(compoundPos) * lineFposMap->size); + lineFposMap->count = 0; +} + static void appendLineFposMap (inputLineFposMap *lineFposMap, compoundPos *pos, - bool crAdjustment) + bool crAdjustment, size_t posInAllLines) { int lastCrAdjustment = 0; @@ -339,6 +366,7 @@ static void appendLineFposMap (inputLineFposMap *lineFposMap, compoundPos *pos, lineFposMap->pos [lineFposMap->count].open = true; lineFposMap->pos [lineFposMap->count].crAdjustment = lastCrAdjustment + ((crAdjustment)? 1: 0); + lineFposMap->pos [lineFposMap->count].posInAllLines = posInAllLines; lineFposMap->count++; } @@ -436,7 +464,7 @@ static void resetLangOnStack (inputLangInfo *langInfo, langType lang) langStackPush (& (langInfo->stack), lang); } -extern langType baseLangOnStack (inputLangInfo *langInfo) +static langType baseLangOnStack (inputLangInfo *langInfo) { Assert (langInfo->stack.count > 0); return langStackBotom (& (langInfo->stack)); @@ -509,7 +537,7 @@ static unsigned long readLineNumber (char **str) skipWhite (str); s = *str; - while (*s != '\0' && isdigit (*s)) + while (*s != '\0' && isdigit ((unsigned char) *s)) { lNum = (lNum * 10) + (*s - '0'); s++; @@ -558,7 +586,7 @@ static bool parseLineDirective (char *s) skipWhite (&s); DebugStatement ( const char* lineStr = ""; ) - if (isdigit (*s)) + if (isdigit ((unsigned char) *s)) result = true; else if (strncmp (s, "line", 4) == 0) { @@ -739,8 +767,8 @@ extern bool openInputFile (const char *const fileName, const langType language, File.filePosition.offset = StartOfLine.offset = mio_tell (File.mio); File.currentLine = NULL; - if (File.line != NULL) - vStringClear (File.line); + File.line = vStringNewOrClear (File.line); + File.ungetchIdx = 0; setInputFileParameters (vStringNewInit (fileName), language); File.input.lineNumberOrigin = 0L; @@ -767,7 +795,7 @@ extern time_t getInputFileMtime (void) return File.mtime; } -extern void resetInputFile (const langType language) +extern void resetInputFile (const langType language, bool resetLineFposMap_) { Assert (File.mio); @@ -777,11 +805,17 @@ extern void resetInputFile (const langType language) File.filePosition.offset = StartOfLine.offset = mio_tell (File.mio); File.currentLine = NULL; - if (File.line != NULL) - vStringClear (File.line); - if (hasLanguageMultilineRegexPatterns (language)) + Assert (File.line); + vStringClear (File.line); + File.ungetchIdx = 0; + + if (hasLanguageMultilineRegexPatterns (language) + || hasLanguagePostRunRegexPatterns (language)) File.allLines = vStringNew (); + if (resetLineFposMap_) + resetLineFposMap(&File.lineFposMap); + resetLangOnStack (& inputLang, language); File.input.lineNumber = File.input.lineNumberOrigin; sourceLang = language; @@ -815,13 +849,13 @@ extern void *getInputFileUserData(void) /* Action to take for each encountered input newline. */ -static void fileNewline (bool crAdjustment) +static void fileNewline (bool crAdjustment, size_t posInAllLines) { File.filePosition = StartOfLine; if (BackupFile.mio == NULL) appendLineFposMap (&File.lineFposMap, &File.filePosition, - crAdjustment); + crAdjustment, posInAllLines); File.input.lineNumber++; File.source.lineNumber++; @@ -899,15 +933,13 @@ static vString *iFileGetLine (bool chop_newline) eolType eol; langType lang = getInputLanguage(); - if (File.line == NULL) - File.line = vStringNew (); - + Assert (File.line); eol = readLine (File.line, File.mio); if (vStringLength (File.line) > 0) { /* Use StartOfLine from previous iFileGetLine() call */ - fileNewline (eol == eol_cr_nl); + fileNewline (eol == eol_cr_nl, File.allLines? vStringLength(File.allLines): 0); /* Store StartOfLine for the next iFileGetLine() call */ mio_getpos (File.mio, &StartOfLine.pos); StartOfLine.offset = mio_tell (File.mio); @@ -920,7 +952,7 @@ static vString *iFileGetLine (bool chop_newline) bool chopped = vStringStripNewline (File.line); - matchLanguageRegex (lang, File.line); + matchLanguageRegex (lang, File.line, false); if (chopped && !chop_newline) vStringPutNewlinAgainUnsafe (File.line); @@ -934,6 +966,36 @@ static vString *iFileGetLine (bool chop_newline) matchLanguageMultilineRegex (lang, File.allLines); matchLanguageMultitableRegex (lang, File.allLines); + if (hasLanguagePostRunRegexPatterns (lang)) + { + + unsigned input_ln = File.input.lineNumber; + unsigned source_ln = File.source.lineNumber; + MIOPos pos = File.filePosition.pos; + + vString *line = vStringNew(); + for (size_t i = 0; i < File.lineFposMap.count; i++) + { + File.input.lineNumber = i + 1; + File.source.lineNumber = File.input.lineNumber; + File.filePosition.pos = File.lineFposMap.pos[i].pos; + + vStringNCopySUnsafe(line, + vStringValue(File.allLines) + + File.lineFposMap.pos[i].posInAllLines, + (((i + 1) < File.lineFposMap.count) + ? File.lineFposMap.pos[i+1].posInAllLines + : vStringLength (File.allLines)) + - File.lineFposMap.pos[i].posInAllLines); + matchLanguageRegex (lang, line, true); + } + vStringDelete(line); + + File.filePosition.pos = pos; + File.input.lineNumber = input_ln; + File.source.lineNumber = source_ln; + } + /* To limit the execution of multiline/multitable parser(s) only ONCE, clear File.allLines field. */ vStringDelete (File.allLines); @@ -1022,18 +1084,25 @@ extern int skipToCharacterInInputFile2 (int c0, int c1) * the terminating newline. A NULL return value means that all lines in the * file have been read and we are at the end of file. */ -extern const unsigned char *readLineFromInputFile (void) +extern const unsigned char *readLineFromInputFileWithLength (size_t *length) { vString* const line = iFileGetLine (true); const unsigned char* result = NULL; if (line != NULL) { result = (const unsigned char*) vStringValue (line); + *length = vStringLength (line); DebugStatement ( debugPrintf (DEBUG_READ, "%s\n", result); ) } return result; } +extern const unsigned char *readLineFromInputFile (void) +{ + size_t dummy; + return readLineFromInputFileWithLength(&dummy); +} + /* * Raw file line reading with automatic buffer sizing */ @@ -1076,6 +1145,7 @@ extern char *readLineFromBypass ( } extern void pushNarrowedInputStream ( + bool useMemoryStreamInput, unsigned long startLine, long startCharOffset, unsigned long endLine, long endCharOffset, unsigned long sourceLineOffset, @@ -1090,9 +1160,17 @@ extern void pushNarrowedInputStream ( endLine, endCharOffset, sourceLineOffset)) { - File.thinDepth++; - verbose ("push thin stream (%d)\n", File.thinDepth); - return; + if ((!useMemoryStreamInput + || mio_memory_get_data (File.mio, NULL))) + { + File.thinDepth++; + verbose ("push thin stream (%d)\n", File.thinDepth); + return; + } + error(WARNING, "INTERNAL ERROR: though pushing thin MEMORY stream, " + "underlying input stream is a FILE stream: %s@%s", + vStringValue (File.input.name), vStringValue (File.input.tagPath)); + AssertNotReached (); } Assert (File.thinDepth == 0); @@ -1105,7 +1183,17 @@ extern void pushNarrowedInputStream ( tmp = getInputFilePositionForLine (endLine); mio_setpos (File.mio, &tmp); - mio_seek (File.mio, endCharOffset, SEEK_CUR); + if (endCharOffset == EOL_CHAR_OFFSET) + { + long line_start = mio_tell (File.mio); + vString *tmpstr = vStringNew (); + readLine (tmpstr, File.mio); + endCharOffset = mio_tell (File.mio) - line_start; + vStringDelete (tmpstr); + Assert (endCharOffset >= 0); + } + else + mio_seek (File.mio, endCharOffset, SEEK_CUR); q = mio_tell (File.mio); mio_setpos (File.mio, &original); @@ -1155,10 +1243,10 @@ extern unsigned int getNestedInputBoundaryInfo (unsigned long lineNumber) info = 0; if (File.nestedInputStreamInfo.startLine == lineNumber && File.nestedInputStreamInfo.startCharOffset != 0) - info |= BOUNDARY_START; + info |= INPUT_BOUNDARY_START; if (File.nestedInputStreamInfo.endLine == lineNumber && File.nestedInputStreamInfo.endCharOffset != 0) - info |= BOUNDARY_END; + info |= INPUT_BOUNDARY_END; return info; } diff --git a/ctags/main/read.h b/ctags/main/read.h index c388623783..17edd75330 100644 --- a/ctags/main/read.h +++ b/ctags/main/read.h @@ -29,26 +29,6 @@ * DATA DECLARATIONS */ -enum eCharacters { - /* white space characters */ - SPACE = ' ', - NEWLINE = '\n', - CRETURN = '\r', - FORMFEED = '\f', - TAB = '\t', - VTAB = '\v', - - /* some hard to read characters */ - DOUBLE_QUOTE = '"', - SINGLE_QUOTE = '\'', - BACKSLASH = '\\', - - /* symbolic representations, above 0xFF not to conflict with any byte */ - STRING_SYMBOL = ('S' + 0xff), - CHAR_SYMBOL = ('C' + 0xff) -}; - - /* * FUNCTION PROTOTYPES */ @@ -74,6 +54,7 @@ extern int skipToCharacterInInputFile (int c); extern int skipToCharacterInInputFile2 (int c0, int c1); extern void ungetcToInputFile (int c); extern const unsigned char *readLineFromInputFile (void); +extern const unsigned char *readLineFromInputFileWithLength (size_t *length); extern unsigned long getSourceLineNumber (void); diff --git a/ctags/main/read_p.h b/ctags/main/read_p.h index 95dd11187f..985f6ad7d0 100644 --- a/ctags/main/read_p.h +++ b/ctags/main/read_p.h @@ -23,8 +23,8 @@ */ enum nestedInputBoundaryFlag { - BOUNDARY_START = 1UL << 0, - BOUNDARY_END = 1UL << 1, + INPUT_BOUNDARY_START = 1UL << 0, + INPUT_BOUNDARY_END = 1UL << 1, }; /* @@ -40,7 +40,7 @@ extern unsigned int countInputLanguageKinds (void); extern unsigned int countInputLanguageRoles (int kindIndex); extern bool doesInputLanguageAllowNullTag (void); -extern bool doesInputLanguageRequestAutomaticFQTag (void); +extern bool doesInputLanguageRequestAutomaticFQTag (const tagEntryInfo *e); extern bool doesParserRunAsGuest (void); extern bool doesSubparserRun (void); extern langType getLanguageForBaseParser (void); @@ -55,7 +55,7 @@ extern void freeInputFileResources (void); extern bool openInputFile (const char *const fileName, const langType language, MIO *mio, time_t mtime); extern MIO *getMio (const char *const fileName, const char *const openMode, bool memStreamRequired); -extern void resetInputFile (const langType language); +extern void resetInputFile (const langType language, bool resetLineFposMap_); extern void closeInputFile (void); extern void *getInputFileUserData(void); @@ -69,6 +69,7 @@ extern time_t getInputFileMtime (void); /* Bypass: reading from fp in inputFile WITHOUT updating fields in input fields */ extern char *readLineFromBypass (vString *const vLine, MIOPos location, long *const pSeekValue); extern void pushNarrowedInputStream ( + bool useMemoryStreamInput, unsigned long startLine, long startCharOffset, unsigned long endLine, long endCharOffset, unsigned long sourceLineOffset, diff --git a/ctags/main/repoinfo.h b/ctags/main/repoinfo.h index cd5f7a8117..25f0d94a27 100644 --- a/ctags/main/repoinfo.h +++ b/ctags/main/repoinfo.h @@ -1 +1 @@ -#define CTAGS_REPOINFO "01342f01" +#define CTAGS_REPOINFO "p6.0.20231001.0" diff --git a/ctags/main/routines.c b/ctags/main/routines.c index 6c5a592966..10150742bb 100644 --- a/ctags/main/routines.c +++ b/ctags/main/routines.c @@ -121,7 +121,7 @@ /* Hack for ridiculous practice of Microsoft Visual C++. */ -#if defined (WIN32) +#if defined (_WIN32) # if defined (_MSC_VER) || defined (__MINGW32__) # ifndef stat # define stat _stat @@ -163,7 +163,7 @@ extern int stat (const char *, struct stat *); #ifdef NEED_PROTO_LSTAT extern int lstat (const char *, struct stat *); #endif -#if defined (WIN32) +#if defined (_WIN32) # define lstat(fn,buf) stat(fn,buf) #endif @@ -204,7 +204,7 @@ extern const char *getExecutablePath (void) */ static bool fnmChEq (int c1, int c2) { -#ifdef WIN32 +#ifdef _WIN32 return tolower( c1 ) == tolower( c2 ); /* case-insensitive */ #else return c1 == c2 ; /* case- sensitive */ @@ -285,7 +285,7 @@ extern int struppercmp (const char *s1, const char *s2) int result; do { - result = toupper ((int) *s1) - toupper ((int) *s2); + result = toupper ((unsigned char) *s1) - toupper ((unsigned char) *s2); } while (result == 0 && *s1++ != '\0' && *s2++ != '\0'); return result; } @@ -295,24 +295,11 @@ extern int strnuppercmp (const char *s1, const char *s2, size_t n) int result; do { - result = toupper ((int) *s1) - toupper ((int) *s2); + result = toupper ((unsigned char) *s1) - toupper ((unsigned char) *s2); } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0'); return result; } -#ifndef HAVE_STRSTR -extern char* strstr (const char *str, const char *substr) -{ - const size_t length = strlen (substr); - const char *p; - - for (p = str ; *p != '\0' ; ++p) - if (strncmp (p, substr, length) == 0) - return (char*) p; - return NULL; -} -#endif - extern char* strrstr (const char *str, const char *substr) { const size_t length = strlen (substr); @@ -343,7 +330,7 @@ extern void toLowerString (char* str) { while (*str != '\0') { - *str = tolower ((int) *str); + *str = (char) tolower ((unsigned char) *str); ++str; } } @@ -352,7 +339,7 @@ extern void toUpperString (char* str) { while (*str != '\0') { - *str = toupper ((int) *str); + *str = (char) toupper ((unsigned char) *str); ++str; } } @@ -364,7 +351,7 @@ extern char* newLowerString (const char* str) char* const result = xMalloc (strlen (str) + 1, char); int i = 0; do - result [i] = tolower ((int) str [i]); + result [i] = (char) tolower ((unsigned char) str [i]); while (str [i++] != '\0'); return result; } @@ -376,7 +363,7 @@ extern char* newUpperString (const char* str) char* const result = xMalloc (strlen (str) + 1, char); int i = 0; do - result [i] = toupper ((int) str [i]); + result [i] = (char) toupper ((unsigned char) str [i]); while (str [i++] != '\0'); return result; } @@ -688,7 +675,7 @@ extern bool isAbsolutePath (const char *const path) #if defined (MSDOS_STYLE_PATH) if (isPathSeparator (path [0])) result = true; - else if (isalpha (path [0]) && path [1] == ':') + else if (isalpha ((unsigned char) path [0]) && path [1] == ':') { if (isPathSeparator (path [2])) result = true; @@ -819,8 +806,8 @@ extern char* absoluteFilename (const char *file) { #ifdef MSDOS_STYLE_PATH /* Canonicalize drive letter case. */ - if (res [1] == ':' && islower (res [0])) - res [0] = toupper (res [0]); + if (res [1] == ':' && islower ((unsigned char) res [0])) + res [0] = toupper ((unsigned char) res [0]); #endif } canonicalizePath (res); @@ -862,7 +849,7 @@ extern char* relativeFilename (const char *file, const char *dir) absdir = absoluteFilename (file); fp = absdir; dp = dir; - while (fnmChEq (*fp++, *dp++)) + while (fnmChEq ((unsigned char) *fp++, (unsigned char) *dp++)) continue; fp--; dp--; /* back to the first differing char */ @@ -894,17 +881,16 @@ extern char* relativeFilename (const char *file, const char *dir) return res; } -extern MIO *tempFile (const char *const mode, char **const pName) +extern FILE *tempFileFP (const char *const mode, char **const pName) { char *name; FILE *fp; - MIO *mio; int fd; #if defined(HAVE_MKSTEMP) const char *const pattern = "tags.XXXXXX"; const char *tmpdir = NULL; fileStatus *file = eStat (ExecutableProgram); -# ifdef WIN32 +# ifdef _WIN32 tmpdir = getenv ("TMP"); # else if (! file->isSetuid) @@ -915,7 +901,7 @@ extern MIO *tempFile (const char *const mode, char **const pName) name = xMalloc (strlen (tmpdir) + 1 + strlen (pattern) + 1, char); sprintf (name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern); fd = mkstemp (name); -# ifdef WIN32 +# ifdef _WIN32 if (fd == -1) { /* mkstemp() sometimes fails with unknown reasons. @@ -931,7 +917,7 @@ extern MIO *tempFile (const char *const mode, char **const pName) eStatFree (file); #elif defined(HAVE_TEMPNAM) const char *tmpdir = NULL; -# ifdef WIN32 +# ifdef _WIN32 tmpdir = getenv ("TMP"); # endif if (tmpdir == NULL) @@ -951,10 +937,13 @@ extern MIO *tempFile (const char *const mode, char **const pName) fp = fdopen (fd, mode); if (fp == NULL) error (FATAL | PERROR, "cannot open temporary file"); - mio = mio_new_fp (fp, fclose); - DebugStatement ( - debugPrintf (DEBUG_STATUS, "opened temporary file %s\n", name); ) Assert (*pName == NULL); *pName = name; - return mio; + return fp; +} + +extern MIO *tempFile (const char *const mode, char **const pName) +{ + FILE *fp = tempFileFP (mode, pName); + return mio_new_fp (fp, fclose); } diff --git a/ctags/main/routines.h b/ctags/main/routines.h index 475cf76e90..a95ef01015 100644 --- a/ctags/main/routines.h +++ b/ctags/main/routines.h @@ -57,9 +57,6 @@ extern void eFreeIndirect(void **ptr); /* String manipulation functions */ extern int struppercmp (const char *s1, const char *s2); extern int strnuppercmp (const char *s1, const char *s2, size_t n); -#ifndef HAVE_STRSTR -extern char* strstr (const char *str, const char *substr); -#endif extern char* strrstr (const char *str, const char *substr); extern char* eStrdup (const char* str); extern char* eStrndup (const char* str, size_t len); @@ -76,4 +73,6 @@ extern bool strToLong(const char *const string, int base, long *value); extern const char *baseFilename (const char *const filePath); extern const char *fileExtension (const char *const fileName); +extern FILE *tempFileFP (const char *const mode, char **const pName); + #endif /* CTAGS_MAIN_ROUTINES_H */ diff --git a/ctags/main/script.c b/ctags/main/script.c index 1d7bd0d4b6..5c81da8560 100644 --- a/ctags/main/script.c +++ b/ctags/main/script.c @@ -39,7 +39,7 @@ static void vStringCatToupperS (vString *str, const char *s) { for (const char *tmp = s; *tmp != '\0'; tmp++) { - int c = toupper (*tmp); + int c = toupper ((unsigned char) *tmp); vStringPut (str, c); } } @@ -82,7 +82,7 @@ static EsObject* lrop_get_field_value (OptVM *vm, EsObject *name) int n = es_integer_get (nobj); tagEntryInfo *e = getEntryInCorkQueue (n); if (e == NULL) - return OPTSCRIPT_ERR_NOTAGENTRY;; + return OPTSCRIPT_ERR_NOTAGENTRY; void * data = es_symbol_get_data (name); fieldType ftype = HT_PTR_TO_INT (data); @@ -119,7 +119,7 @@ static EsObject* lrop_set_field_value (OptVM *vm, EsObject *name) int n = es_integer_get (indexobj); tagEntryInfo *e = getEntryInCorkQueue (n); if (e == NULL) - return OPTSCRIPT_ERR_NOTAGENTRY;; + return OPTSCRIPT_ERR_NOTAGENTRY; void * data = es_symbol_get_data (name); fieldType ftype = HT_PTR_TO_INT (data); @@ -210,7 +210,7 @@ static void optscriptInstallFieldSetter (EsObject *dict, fieldType ftype, const char *vtype = getFieldSetterValueType (ftype); unsigned int fdata_type = getFieldDataType (ftype); - vStringCatS (op_desc, "int "); + vStringCatS (op_desc, "tag:int "); if (vtype) vStringCatS (op_desc, vtype); @@ -349,7 +349,7 @@ extern EsObject* optscriptEval (OptVM *vm, EsObject *code) } es_object_unref (o); - EsObject *r = opt_vm_eval (vm, exec);; + EsObject *r = opt_vm_eval (vm, exec); if (es_error_p (r)) opt_vm_report_error (vm, r, NULL); return r; diff --git a/ctags/main/script_p.h b/ctags/main/script_p.h index 5122322598..7e8852da53 100644 --- a/ctags/main/script_p.h +++ b/ctags/main/script_p.h @@ -52,7 +52,11 @@ extern void optscriptHelp (OptVM *vm, FILE *fp, EsObject *procdocs extern xtagType optscriptGetXtagType (const EsObject *extra); typedef struct { - unsigned long delta; /* for _advanceto operator */ + unsigned long base; /* useless when the parser type is + * REG_PARSER_SINGLE_LINE. + * base + delta is the file offset. */ + unsigned long delta; /* for _advanceto operator, relateive from + * the base. */ unsigned long line; MIOPos pos; } matchLoc; diff --git a/ctags/main/seccomp.c b/ctags/main/seccomp.c index f87b353513..d60133d451 100644 --- a/ctags/main/seccomp.c +++ b/ctags/main/seccomp.c @@ -41,7 +41,9 @@ int installSyscallFilter (void) seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (exit_group), 0); // The bowels of stdio want to know the size of a file, even for stdout. +#if (defined(__SNR_fstat) && __SNR_fstat) seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (fstat), 0); +#endif seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (fstat64), 0); #ifdef __SNR_newfstatat seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (newfstatat), 0); diff --git a/ctags/main/selectors.c b/ctags/main/selectors.c index 8a05edb834..41eff48d43 100644 --- a/ctags/main/selectors.c +++ b/ctags/main/selectors.c @@ -18,107 +18,112 @@ #include "options.h" #include "selectors.h" #include "vstring.h" +#include "mio.h" static const char *TR_UNKNOWN = NULL; -static const char *TR_PERL5 = "Perl"; -static const char *TR_PERL6 = "Perl6"; +static const char *TR_PERL5 = "Perl"; +static const char *TR_PERL6 = "Perl6"; -static const char *TR_OBJC = "ObjectiveC"; +static const char *TR_OBJC = "ObjectiveC"; static const char *TR_MATLAB = "MatLab"; -static const char *TR_CPP = "C++"; +static const char *TR_R = "R"; +static const char *TR_ASM = "Asm"; -static const char *TR_R = "R"; -static const char *TR_ASM = "Asm"; - -static const char *TR_REXX = "REXX"; +static const char *TR_REXX = "REXX"; static const char *TR_DOSBATCH = "DosBatch"; -static const char *TR_LISP = "Lisp"; -static const char *TR_LEX = "LEX"; +static const char *TR_LISP = "Lisp"; +static const char *TR_LEX = "LEX"; + +static const char *TR_FORTH = "Forth"; +static const char *TR_FORTRAN = "Fortran"; + +static const char *TR_V = "V"; +static const char *TR_VERILOG = "Verilog"; -#define startsWith(line,prefix) \ - (strncmp(line, prefix, strlen(prefix)) == 0? true: false) +#define startsWith(line,prefix) \ + (strncmp(line, prefix, strlen(prefix)) == 0? true: false) static const char *selectByLines (MIO *input, - const char* (* lineTaster) (const char *, void *), - const char* defaultLang, - void *userData) + const char* (* lineTaster) (const char *, void *), + const char* defaultLang, + void *userData) { - char line[0x800]; - while (mio_gets(input, line, sizeof(line))) { - const char *lang = lineTaster (line, userData); - if (lang) - return lang; - } - return defaultLang; + char line[0x800]; + while (mio_gets(input, line, sizeof(line))) { + const char *lang = lineTaster (line, userData); + if (lang) + return lang; + } + return defaultLang; } /* Returns "Perl" or "Perl6" or NULL if it does not taste like anything */ static const char * tastePerlLine (const char *line, void *data CTAGS_ATTR_UNUSED) { - while (isspace(*line)) - ++line; + while (isspace((unsigned char) *line)) + ++line; #define STRLEN(s) (sizeof(s) - 1) /* Assume the first character has been checked: */ -#define CHECK_PART(line, s) ( \ - 0 == strncmp((line) + 1, (s) + 1, STRLEN(s) - 1) && \ - !isalnum((line)[STRLEN(s)])) - switch (line[0]) { - case '#': /* TODO: taste modeline */ - case '\0': - return TR_UNKNOWN; - case '=': - if (CHECK_PART(line, "=head1")) - return TR_PERL5; - if (CHECK_PART(line, "=head2")) - return TR_PERL5; - break; - case 'c': - if (CHECK_PART(line, "class")) - return TR_PERL6; - break; - case 'g': - if (CHECK_PART(line, "grammar")) - return TR_PERL6; - break; - case 'm': - /* TODO: my may be many things: class, role, etc. */ - if (CHECK_PART(line, "my class")) - return TR_PERL6; - if (CHECK_PART(line, "method")) - return TR_PERL6; - if (CHECK_PART(line, "multi")) - return TR_PERL6; - break; - case 'n': - if (CHECK_PART(line, "need")) - return TR_PERL6; - break; - case 'p': - if (CHECK_PART(line, "package")) - return TR_PERL5; - break; - case 'r': - if (CHECK_PART(line, "role")) - return TR_PERL6; - if (CHECK_PART(line, "require 5")) - return TR_PERL5; - break; - case 'u': - if (CHECK_PART(line, "unit")) - return TR_PERL6; - if (CHECK_PART(line, "use v6")) - return TR_PERL6; - if (CHECK_PART(line, "use nqp")) - return TR_PERL5; - if (CHECK_PART(line, "use warnings")) - return TR_PERL5; - break; - } +#define CHECK_PART(line, s) ( \ + 0 == strncmp((line) + 1, (s) + 1, STRLEN(s) - 1) && \ + !isalnum((unsigned char) (line)[STRLEN(s)])) + switch (line[0]) { + case '#': /* TODO: taste modeline */ + case '\0': + return TR_UNKNOWN; + case '=': + if (CHECK_PART(line, "=head1")) + return TR_PERL5; + if (CHECK_PART(line, "=head2")) + return TR_PERL5; + break; + case 'c': + if (CHECK_PART(line, "class")) + return TR_PERL6; + break; + case 'g': + if (CHECK_PART(line, "grammar")) + return TR_PERL6; + break; + case 'm': + /* TODO: my may be many things: class, role, etc. */ + if (CHECK_PART(line, "my class")) + return TR_PERL6; + if (CHECK_PART(line, "method")) + return TR_PERL6; + if (CHECK_PART(line, "multi")) + return TR_PERL6; + break; + case 'n': + if (CHECK_PART(line, "need")) + return TR_PERL6; + break; + case 'p': + if (CHECK_PART(line, "package")) + return TR_PERL5; + break; + case 'r': + if (CHECK_PART(line, "role")) + return TR_PERL6; + if (CHECK_PART(line, "require 5")) + return TR_PERL5; + break; + case 'u': + if (CHECK_PART(line, "unit")) + return TR_PERL6; + if (CHECK_PART(line, "use v6")) + return TR_PERL6; + if (CHECK_PART(line, "use nqp")) + return TR_PERL5; + if (CHECK_PART(line, "use warnings")) + return TR_PERL5; + break; + } #undef CHECK_PART - return TR_UNKNOWN; + return TR_UNKNOWN; } const char * @@ -126,42 +131,42 @@ selectByPickingPerlVersion (MIO *input, langType *candidates CTAGS_ATTR_UNUSED, unsigned int nCandidates CTAGS_ATTR_UNUSED) { - /* Default to Perl 5 */ - return selectByLines (input, tastePerlLine, TR_PERL5, NULL); + /* Default to Perl 5 */ + return selectByLines (input, tastePerlLine, TR_PERL5, NULL); } static const char * tasteObjectiveCOrMatLabLines (const char *line, void *data CTAGS_ATTR_UNUSED) { - if (startsWith (line, "% ") - || startsWith (line, "%{")) - return TR_MATLAB; - else if (startsWith (line, "// ") - || startsWith (line, "/* ")) - return TR_OBJC; - else if (startsWith (line, "#include") - || startsWith (line, "#import") - || startsWith (line, "#define ") - || startsWith (line, "#ifdef ")) - return TR_OBJC; - else if (startsWith (line, "@interface ") - || startsWith (line, "@implementation ") - || startsWith (line, "@protocol ")) - return TR_OBJC; - else if (startsWith (line, "struct ") - || startsWith (line, "union ") - || startsWith (line, "typedef ")) - return TR_OBJC; - else { - if (startsWith (line, "function ")) { - const char *p = line + strlen ("function "); - while (isspace(*p)) - p++; - if (*p != '\0' && *p != '(') + if (startsWith (line, "% ") + || startsWith (line, "%{")) return TR_MATLAB; + else if (startsWith (line, "// ") + || startsWith (line, "/* ")) + return TR_OBJC; + else if (startsWith (line, "#include") + || startsWith (line, "#import") + || startsWith (line, "#define ") + || startsWith (line, "#ifdef ")) + return TR_OBJC; + else if (startsWith (line, "@interface ") + || startsWith (line, "@implementation ") + || startsWith (line, "@protocol ")) + return TR_OBJC; + else if (startsWith (line, "struct ") + || startsWith (line, "union ") + || startsWith (line, "typedef ")) + return TR_OBJC; + else { + if (startsWith (line, "function ")) { + const char *p = line + strlen ("function "); + while (isspace((unsigned char) *p)) + p++; + if (*p != '\0' && *p != '(') + return TR_MATLAB; + } } - } - return NULL; + return NULL; } const char * @@ -169,48 +174,64 @@ selectByObjectiveCAndMatLabKeywords (MIO * input, langType *candidates CTAGS_ATTR_UNUSED, unsigned int nCandidates CTAGS_ATTR_UNUSED) { - return selectByLines (input, tasteObjectiveCOrMatLabLines, - NULL, NULL); + return selectByLines (input, tasteObjectiveCOrMatLabLines, + NULL, NULL); } static const char * tasteObjectiveC (const char *line, void *data CTAGS_ATTR_UNUSED) { - if (startsWith (line, "#import") - || startsWith (line, "@interface ") - || startsWith (line, "@implementation ") - || startsWith (line, "@protocol ")) - return TR_OBJC; - return NULL; + if (startsWith (line, "#import") + || startsWith (line, "@interface ") + || startsWith (line, "@implementation ") + || startsWith (line, "@protocol ")) + return TR_OBJC; + return NULL; } const char * selectByObjectiveCKeywords (MIO * input, - langType *candidates CTAGS_ATTR_UNUSED, - unsigned int nCandidates CTAGS_ATTR_UNUSED) + langType *candidates, + unsigned int nCandidates) { - /* TODO: Ideally opening input should be delayed til - enable/disable based selection is done. */ - - static langType objc = LANG_IGNORE; - static langType cpp = LANG_IGNORE; + /* TODO: Ideally opening input should be delayed til + enable/disable based selection is done. */ - if (objc == LANG_IGNORE) - objc = getNamedLanguage (TR_OBJC, 0); + static langType objc = LANG_IGNORE; + if (objc == LANG_IGNORE) + objc = getNamedLanguage (TR_OBJC, 0); - if (cpp == LANG_IGNORE) - cpp = getNamedLanguage (TR_CPP, 0); - - Assert (0 <= objc); - Assert (0 <= cpp); + bool objcInCandidate = false; + const char *altCandidateName = NULL; + for (unsigned int i = 0; i < nCandidates; i++) + { + if (candidates[i] == objc) + { + objcInCandidate = true; + break; + } + else if (altCandidateName == NULL) + { + if (isLanguageEnabled (candidates[i])) + { + altCandidateName = getLanguageName (candidates[i]); + Assert (altCandidateName); + } + } + } - if (! isLanguageEnabled (objc)) - return TR_CPP; - else if (! isLanguageEnabled (cpp)) - return TR_OBJC; + if (!objcInCandidate) + return altCandidateName; + if (altCandidateName == NULL) + { + if (isLanguageEnabled (objc)) + return TR_OBJC; + return NULL; + } - return selectByLines (input, tasteObjectiveC, TR_CPP, - NULL); + Assert (altCandidateName); + return selectByLines (input, tasteObjectiveC, altCandidateName, + NULL); } static const char * @@ -231,28 +252,28 @@ selectByArrowOfR (MIO *input, langType *candidates CTAGS_ATTR_UNUSED, unsigned int nCandidates CTAGS_ATTR_UNUSED) { - /* TODO: Ideally opening input should be delayed till - enable/disable based selection is done. */ + /* TODO: Ideally opening input should be delayed till + enable/disable based selection is done. */ - static langType R = LANG_IGNORE; - static langType Asm = LANG_IGNORE; + static langType R = LANG_IGNORE; + static langType Asm = LANG_IGNORE; - if (R == LANG_IGNORE) - R = getNamedLanguage (TR_R, 0); + if (R == LANG_IGNORE) + R = getNamedLanguage (TR_R, 0); - if (Asm == LANG_IGNORE) - Asm = getNamedLanguage (TR_ASM, 0); + if (Asm == LANG_IGNORE) + Asm = getNamedLanguage (TR_ASM, 0); - Assert (0 <= R); - Assert (0 <= Asm); + Assert (0 <= R); + Assert (0 <= Asm); - if (! isLanguageEnabled (R)) - return TR_ASM; - else if (! isLanguageEnabled (Asm)) - return TR_R; + if (! isLanguageEnabled (R)) + return TR_ASM; + else if (! isLanguageEnabled (Asm)) + return TR_R; - return selectByLines (input, tasteR, NULL, - NULL); + return selectByLines (input, tasteR, NULL, + NULL); } static const char * @@ -263,7 +284,7 @@ tasteREXXOrDosBatch (const char *line, void *data) if (startsWith (line, ":")) return TR_DOSBATCH; else if (*in_rexx_comment - && strstr (line, "*/")) + && strstr (line, "*/")) return TR_REXX; else if (strstr (line, "/*")) { @@ -279,29 +300,29 @@ selectByRexxCommentAndDosbatchLabelPrefix (MIO *input, langType *candidates CTAGS_ATTR_UNUSED, unsigned int nCandidates CTAGS_ATTR_UNUSED) { - /* TODO: Ideally opening input should be delayed till - enable/disable based selection is done. */ + /* TODO: Ideally opening input should be delayed till + enable/disable based selection is done. */ - static langType rexx = LANG_IGNORE; - static langType dosbatch = LANG_IGNORE; - bool in_rexx_comment = false; + static langType rexx = LANG_IGNORE; + static langType dosbatch = LANG_IGNORE; + bool in_rexx_comment = false; - if (rexx == LANG_IGNORE) - rexx = getNamedLanguage (TR_R, 0); + if (rexx == LANG_IGNORE) + rexx = getNamedLanguage (TR_R, 0); - if (dosbatch == LANG_IGNORE) - dosbatch = getNamedLanguage (TR_DOSBATCH, 0); + if (dosbatch == LANG_IGNORE) + dosbatch = getNamedLanguage (TR_DOSBATCH, 0); - Assert (0 <= rexx); - Assert (0 <= dosbatch); + Assert (0 <= rexx); + Assert (0 <= dosbatch); - if (! isLanguageEnabled (rexx)) - return TR_DOSBATCH; - else if (! isLanguageEnabled (dosbatch)) - return TR_REXX; + if (! isLanguageEnabled (rexx)) + return TR_DOSBATCH; + else if (! isLanguageEnabled (dosbatch)) + return TR_REXX; - return selectByLines (input, tasteREXXOrDosBatch, - NULL, &in_rexx_comment); + return selectByLines (input, tasteREXXOrDosBatch, + NULL, &in_rexx_comment); } static const char * @@ -322,8 +343,84 @@ selectLispOrLEXByLEXMarker (MIO *input, return selectByLines (input, tasteLispOrLEXLines, TR_LISP, NULL); } -#ifdef HAVE_LIBXML +static const char * +tasteFortranOrForthLines (const char *line, void *data CTAGS_ATTR_UNUSED) +{ + if (line[0] && ((line[0] == ':' && line[1] && isspace((unsigned char)line[1])) + || line[0] == '\\')) + return TR_FORTH; + return TR_UNKNOWN; +} + +const char * +selectFortranOrForthByForthMarker (MIO *input, + langType *candidates CTAGS_ATTR_UNUSED, + unsigned int nCandidates CTAGS_ATTR_UNUSED) +{ + return selectByLines (input, tasteFortranOrForthLines, TR_FORTRAN, NULL); +} + +struct VOrVerilogScore { + int v; + int verilog; +}; + +static const char * +tasteVOrVerilogLines (const char *line, void *data) +{ + struct VOrVerilogScore *score = (struct VOrVerilogScore *)data; + + while ((*line == ' ') + || (*line == '\t')) + line++; + + /* top 10 line-starting words most commonly present in first 150 lines of + * all files in V v0.4 project source code (at time of writing) */ + if (strncmp(line, "fn", 2) == 0 || /* present in 82.8% files */ + strncmp(line, "return", 6) == 0 || /* present in 46.2% files */ + strncmp(line, "mut", 3) == 0 || /* present in 43.7% files */ + strncmp(line, "println", 7) == 0 || /* present in 38.9% files */ + strncmp(line, "assert", 6) == 0 || /* present in 38.8% files */ + strncmp(line, "struct", 6) == 0 || /* present in 34.5% files */ + /* "module" is present in 29.6% files, but also ued in verilog */ + strncmp(line, "import", 6) == 0 || /* present in 27.6% files */ + strncmp(line, "if", 2) == 0 || /* present in 24.9% files */ + strncmp(line, "pub", 3) == 0) /* present in 24.1% files */ + score->v++; + /* `define, end, begin, and reg imply Verilog */ + else if (strncmp(line, "end", 3) == 0 + || strncmp(line, "begin", 5) == 0 + || strncmp(line, "reg", 3) == 0 + || strncmp(line, "wire", 4) == 0 + || strncmp(line, "parameter", 9) == 0 + || strncmp(line, "`define", 5) == 0) + score->verilog++; + + return TR_UNKNOWN; +} + +const char * +selectVOrVerilogByKeywords (MIO *input, + langType *candidates CTAGS_ATTR_UNUSED, + unsigned int nCandidates CTAGS_ATTR_UNUSED) +{ + struct VOrVerilogScore score = { + .v = 0, + .verilog = 0, + }; + selectByLines (input, tasteVOrVerilogLines, TR_UNKNOWN, &score); + + int d = score.v - score.verilog; + if (d > 0) + return TR_V; + else if (d < 0) + return TR_VERILOG; + else + return TR_UNKNOWN; +} +#ifdef HAVE_LIBXML +#include #include #include @@ -459,7 +556,7 @@ selectParserForXmlDoc (xmlDocPtr doc, { unsigned int lang_index; - bool xml_parser_is_in_candidate = false;; + bool xml_parser_is_in_candidate = false; verbose (" Xml[rootElementName]: %s\n", (doc->children && doc->children->name) @@ -527,7 +624,7 @@ selectByXpathFileSpec (MIO *input, xmlFreeDoc (doc); else mio_attach_user_data (input, - doc,(MIODestroyNotify)xmlFreeDoc); + doc,(MIODestroyNotify)xmlFreeDoc); return r; } diff --git a/ctags/main/selectors.h b/ctags/main/selectors.h index e90bf26903..fe48c64773 100644 --- a/ctags/main/selectors.h +++ b/ctags/main/selectors.h @@ -13,24 +13,30 @@ #include "types.h" const char * -selectByPickingPerlVersion (MIO *, langType *, unsigned int); +selectByPickingPerlVersion (struct _MIO *, langType *, unsigned int); const char * -selectByObjectiveCAndMatLabKeywords (MIO *, langType *, unsigned int); +selectByObjectiveCAndMatLabKeywords (struct _MIO *, langType *, unsigned int); const char * -selectByObjectiveCKeywords(MIO *, langType *, unsigned int); +selectByObjectiveCKeywords(struct _MIO *, langType *, unsigned int); const char * -selectByArrowOfR (MIO *, langType *, unsigned int); +selectByArrowOfR (struct _MIO *, langType *, unsigned int); const char * -selectByRexxCommentAndDosbatchLabelPrefix (MIO *, langType *, unsigned int); +selectByRexxCommentAndDosbatchLabelPrefix (struct _MIO *, langType *, unsigned int); const char * -selectLispOrLEXByLEXMarker (MIO *, langType *, unsigned int); +selectLispOrLEXByLEXMarker (struct _MIO *, langType *, unsigned int); const char * -selectByXpathFileSpec (MIO *input, langType *candidates, unsigned int nCandidates); +selectVOrVerilogByKeywords (struct _MIO *, langType *, unsigned int); + +const char * +selectByXpathFileSpec (struct _MIO *input, langType *candidates, unsigned int nCandidates); + +const char * +selectFortranOrForthByForthMarker (struct _MIO *input, langType *candidates, unsigned int nCandidates); #endif diff --git a/ctags/main/sort.c b/ctags/main/sort.c index d6b0171c0c..09c6c5fdc6 100644 --- a/ctags/main/sort.c +++ b/ctags/main/sort.c @@ -12,6 +12,7 @@ */ #include "general.h" /* must always come first */ +#include #if defined (HAVE_IO_H) # include #endif @@ -67,7 +68,7 @@ extern void catFile (MIO *mio) */ static void appendCstringWithQuotes (vString *dest, const char* cstr) { -#ifdef WIN32 +#ifdef _WIN32 vStringCatS (dest, cstr); #else vStringPut (dest, '\''); @@ -92,11 +93,12 @@ extern void externalSortTags (const bool toStdout, MIO *tagFile) PE_CONST char *const sortOrder1 = "LC_COLLATE=C"; PE_CONST char *const sortOrder2 = "LC_ALL=C"; # endif - vString *cmd = vStringNew (); int ret = -1; + int system_errno = 0; - if (cmd != NULL) + vString *cmd = vStringNew (); { + /* Ensure ASCII value sort order. */ #if defined (HAVE_SETENV) || defined (HAVE_PUTENV) @@ -107,28 +109,20 @@ extern void externalSortTags (const bool toStdout, MIO *tagFile) putenv (sortOrder1); putenv (sortOrder2); # endif - vStringCatS (cmd, sortCommand); - if (! toStdout) - { - vStringCatS (cmd, " -o "); - appendCstringWithQuotes (cmd, tagFileName ()); - vStringPut (cmd, ' '); - appendCstringWithQuotes (cmd, tagFileName ()); - } #else vStringCatS (cmd, sortOrder1); vStringPut (cmd, ' '); vStringCatS (cmd, sortOrder2); vStringPut (cmd, ' '); +#endif vStringCatS (cmd, sortCommand); if (! toStdout) { - vStringCats (cmd, " -o "); + vStringCatS (cmd, " -o "); appendCstringWithQuotes (cmd, tagFileName ()); vStringPut (cmd, ' '); appendCstringWithQuotes (cmd, tagFileName ()); } -#endif verbose ("system (\"%s\")\n", vStringValue (cmd)); if (toStdout) { @@ -143,16 +137,36 @@ extern void externalSortTags (const bool toStdout, MIO *tagFile) if (lseek (fdstdin, 0, SEEK_SET) != 0) error (FATAL | PERROR, "cannot rewind tag file"); ret = system (vStringValue (cmd)); + system_errno = errno; if (dup2 (fdsave, fdstdin) < 0) error (FATAL | PERROR, "cannot restore stdin fd"); close (fdsave); } else + { ret = system (vStringValue (cmd)); - vStringDelete (cmd); + system_errno = errno; + } } if (ret != 0) - error (FATAL | PERROR, "cannot sort tag file"); + { + errorSelection selection = FATAL; + if (ret == -1) + { + errno = system_errno; + error (selection|PERROR, "cannot sort tag file"); + } + +#ifdef HAVE_STRSIGNAL + error (selection, "cannot sort tag file: system (\"%s\") exited with %d (%s?)", + vStringValue(cmd), ret, strsignal(ret)); +#else + error (selection, "cannot sort tag file: system (\"%s\") exited with %d", + vStringValue(cmd), ret); +#endif + + } + vStringDelete (cmd); } #else diff --git a/ctags/main/strlist.c b/ctags/main/strlist.c index 75e250dfc0..9ec079ab4e 100644 --- a/ctags/main/strlist.c +++ b/ctags/main/strlist.c @@ -255,7 +255,7 @@ extern vString* stringListFileFinds ( unsigned int i; const char * normalized = fileName; -#if defined (WIN32) +#if defined (_WIN32) vString *tmp = vStringNewInit (fileName); vStringTranslate (tmp, PATH_SEPARATOR, OUTPUT_PATH_SEPARATOR); normalized = vStringValue (tmp); @@ -267,7 +267,7 @@ extern vString* stringListFileFinds ( matched = fileNameMatched (vstr, normalized); } -#if defined (WIN32) +#if defined (_WIN32) vStringDelete (tmp); #endif diff --git a/ctags/main/subparser.h b/ctags/main/subparser.h index 3ba6f7b25c..c148caa74f 100644 --- a/ctags/main/subparser.h +++ b/ctags/main/subparser.h @@ -62,6 +62,7 @@ extern subparser* getSubparserRunningBaseparser (void); extern void chooseExclusiveSubparser (subparser *s, void *data); extern subparser *getLanguageSubparser (langType sublang, bool including_none_crafted_parser); +extern langType getSubparserLanguage (subparser *s); /* Interface for Subparsers */ #define RUN_DEFAULT_SUBPARSERS -1 diff --git a/ctags/main/subparser_p.h b/ctags/main/subparser_p.h index 801e821c9b..375c6de188 100644 --- a/ctags/main/subparser_p.h +++ b/ctags/main/subparser_p.h @@ -25,7 +25,6 @@ * FUNCTION PROTOTYPES */ extern subparser *getFirstSubparser(struct slaveControlBlock *controlBlock); -extern langType getSubparserLanguage (subparser *s); /* A base parser doesn't have to call the following three functions. The main part calls them internally. */ diff --git a/ctags/main/trace.h b/ctags/main/trace.h index b4912bf189..ae2b373212 100644 --- a/ctags/main/trace.h +++ b/ctags/main/trace.h @@ -42,17 +42,17 @@ bool isMainTraced(void); #ifdef DO_TRACING - #define TRACE_ENTER() traceEnter(__func__,"") - #define TRACE_LEAVE() traceLeave(__func__,"") + #define TRACE_ENTER() traceEnter(ASSERT_FUNCTION,"") + #define TRACE_LEAVE() traceLeave(ASSERT_FUNCTION,"") #define TRACE_ENTER_TEXT(_szFormat,...) \ - traceEnter(__func__,_szFormat,## __VA_ARGS__) + traceEnter(ASSERT_FUNCTION,_szFormat,## __VA_ARGS__) #define TRACE_LEAVE_TEXT(_szFormat,...) \ - traceLeave(__func__,_szFormat,## __VA_ARGS__) + traceLeave(ASSERT_FUNCTION,_szFormat,## __VA_ARGS__) #define TRACE_PRINT(_szFormat,...) \ - tracePrint(__func__,_szFormat,## __VA_ARGS__) + tracePrint(ASSERT_FUNCTION,_szFormat,## __VA_ARGS__) /* TRACE_PRINT prints line at once. * If you want to print a trace line incrementally, @@ -63,7 +63,7 @@ bool isMainTraced(void); * TRACE_PRINT_NEWLINE: just print a newline. */ #define TRACE_PRINT_PREFIX() \ - tracePrintPrefix(__func__) + tracePrintPrefix(ASSERT_FUNCTION) #define TRACE_PRINT_FMT(_szFormat,...) \ tracePrintFmt(_szFormat,## __VA_ARGS__) #define TRACE_PRINT_NEWLINE() \ @@ -73,7 +73,7 @@ bool isMainTraced(void); do { \ if(!(_condition)) \ { \ - tracePrint(__func__,_szFormat,## __VA_ARGS__); \ + tracePrint(ASSERT_FUNCTION,_szFormat,## __VA_ARGS__); \ Assert(false); \ } \ } while(0) diff --git a/ctags/main/trashbox.c b/ctags/main/trashbox.c index 2ace4f6733..303e4f2d03 100644 --- a/ctags/main/trashbox.c +++ b/ctags/main/trashbox.c @@ -13,6 +13,7 @@ #include "debug.h" #include "routines.h" #include "trashbox.h" +#include "trashbox_p.h" typedef TrashBoxDestroyItemProc TrashDestroyItemProc; typedef struct sTrash { diff --git a/ctags/main/types.h b/ctags/main/types.h index b36bf7711a..e22138529e 100644 --- a/ctags/main/types.h +++ b/ctags/main/types.h @@ -45,7 +45,7 @@ typedef struct sFieldDefinition fieldDefinition; struct sXtagDefinition; typedef struct sXtagDefinition xtagDefinition; -struct sParameterHandlerTable; -typedef struct sParameterHandlerTable parameterHandlerTable; +struct sParamDefinition; +typedef struct sParamDefinition paramDefinition; #endif /* CTAGS_MAIN_TYPES_H */ diff --git a/ctags/main/unwindi.c b/ctags/main/unwindi.c index 6a87849e6b..1934f8da3e 100644 --- a/ctags/main/unwindi.c +++ b/ctags/main/unwindi.c @@ -165,7 +165,7 @@ CTAGS_INLINE void uugcInjectC (int chr) uugcUngetC (c); } -CTAGS_INLINE long uugcGetLineNumber () +CTAGS_INLINE long uugcGetLineNumber (void) { Assert (uugcInputFile); @@ -248,7 +248,7 @@ extern void uwiDeactivate (struct sUwiStats *statsToBeUpdated) uugcDeactive(); } -extern int uwiGetC () +extern int uwiGetC (void) { int c; uugcChar *chr = uugciGetC (); @@ -337,7 +337,7 @@ extern void uwiClearMarker (const int upto, const bool revertChars) } } -extern void uwiDropMaker () +extern void uwiDropMaker (void) { uwiPopMarker (0, false); } diff --git a/ctags/main/utf8_str.c b/ctags/main/utf8_str.c new file mode 100644 index 0000000000..98ee154ba2 --- /dev/null +++ b/ctags/main/utf8_str.c @@ -0,0 +1,83 @@ +/* +* +* Copyright (c) 2023, Yinzuo Jiang +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* Defines functions for UTF-8 string manipulation. +* utf8_strlen is derived from parsers/rst.c +*/ + +#include "general.h" + +#include "utf8_str.h" + +/* computes the length of an UTF-8 string + * if the string doesn't look like UTF-8, return -1 + * FIXME consider East_Asian_Width Unicode property */ +int utf8_strlen (const char *buf, int buf_len) +{ + int len = 0; + const char *end = buf + buf_len; + + for (len = 0; buf < end; len++) + { + /* perform quick and naive validation (no sub-byte checking) */ + if (!(*buf & 0x80)) + buf++; + else if ((*buf & 0xe0) == 0xc0) + buf += 2; + else if ((*buf & 0xf0) == 0xe0) + buf += 3; + else if ((*buf & 0xf8) == 0xf0) + buf += 4; + else /* not a valid leading UTF-8 byte, abort */ + return -1; + + if (buf > end) /* incomplete last byte */ + return -1; + } + + return len; +} + +/* computes the raw buf length of an UTF-8 (ascii excluded) string + * if the string doesn't look like UTF-8, return -1 */ +int utf8_raw_strlen (const char *buf, int buf_len) +{ + int raw_len = 0; + const char *end = buf + buf_len; + + while (buf < end) + { + /* perform quick and naive validation (no sub-byte checking) */ + if (!(*buf & 0x80)) + { + /* stop at ascii character */ + return raw_len; + } + else if ((*buf & 0xe0) == 0xc0) + { + buf += 2; + raw_len += 2; + } + else if ((*buf & 0xf0) == 0xe0) + { + buf += 3; + raw_len += 3; + } + else if ((*buf & 0xf8) == 0xf0) + { + buf += 4; + raw_len += 4; + } + else /* not a valid leading UTF-8 byte, abort */ + return -1; + + if (buf > end) /* incomplete last byte */ + return -1; + } + + return raw_len; +} diff --git a/ctags/main/utf8_str.h b/ctags/main/utf8_str.h new file mode 100644 index 0000000000..90b44800be --- /dev/null +++ b/ctags/main/utf8_str.h @@ -0,0 +1,22 @@ +/* +* +* Copyright (c) 2023, Yinzuo Jiang +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* Defines functions for UTF-8 string manipulation. +* utf8_strlen is derived from parsers/rst.c +*/ + +#ifndef CTAGS_MAIN_UTF8_STR_H +#define CTAGS_MAIN_UTF8_STR_H + +/* +* FUNCTION PROTOTYPES +*/ + +extern int utf8_strlen (const char *buf, int buf_len); +extern int utf8_raw_strlen (const char *buf, int buf_len); + +#endif /* CTAGS_MAIN_UTF8_STR_H */ diff --git a/ctags/main/vstring.c b/ctags/main/vstring.c index 1b5f684d09..2f36c3e1be 100644 --- a/ctags/main/vstring.c +++ b/ctags/main/vstring.c @@ -124,9 +124,8 @@ extern void vStringNCat ( extern void vStringNCatS ( vString *const string, const char *const s, const size_t length) { - size_t len = strlen (s); + size_t len = strnlen (s, length); - len = len < length ? len : length; stringCat (string, s, len); } @@ -175,8 +174,16 @@ extern void vStringStripLeading (vString *const string) { size_t n = 0; - while (n < string->length && isspace ((int) string->buffer [n])) + while (n < string->length && isspace ((unsigned char) string->buffer [n])) n++; + vStringTruncateLeading (string, n); +} + +extern void vStringTruncateLeading (vString *const string, const size_t length) +{ + size_t n = vStringLength (string); + if (n > length) + n = length; if (n > 0) { memmove (string->buffer, string->buffer + n, string->length - n); @@ -189,7 +196,7 @@ extern void vStringStripLeading (vString *const string) extern void vStringStripTrailing (vString *const string) { while (string->length > 0 && - isspace ((int) string->buffer [string->length - 1])) + isspace ((unsigned char) string->buffer [string->length - 1])) { string->length--; string->buffer [string->length] = '\0'; @@ -233,6 +240,13 @@ extern void vStringNCopyS ( vStringNCatS (string, s, length); } +extern void vStringNCopySUnsafe ( + vString *const string, const char *const s, const size_t length) +{ + vStringClear (string); + vStringNCatSUnsafe (string, s, length); +} + extern void vStringCopyToLower (vString *const dest, const vString *const src) { const size_t length = src->length; @@ -244,11 +258,7 @@ extern void vStringCopyToLower (vString *const dest, const vString *const src) vStringResize (dest, src->size); d = dest->buffer; for (i = 0 ; i < length ; ++i) - { - int c = s [i]; - - d [i] = tolower (c); - } + d [i] = (char) tolower ((unsigned char) s [i]); d [i] = '\0'; } @@ -294,7 +304,7 @@ extern char *vStringStrdup (const vString *const string) return str; } -static char valueToXDigit (int v) +static int valueToXDigit (int v) { Assert (v >= 0 && v <= 0xF); @@ -308,7 +318,7 @@ extern void vStringCatSWithEscaping (vString* b, const char *s) { for(; *s; s++) { - int c = *s; + unsigned char c = (unsigned char) *s; /* escape control characters (incl. \t) */ if ((c > 0x00 && c <= 0x1F) || c == 0x7F || c == '\\') diff --git a/ctags/main/vstring.h b/ctags/main/vstring.h index b423d46a9f..0ec883460f 100644 --- a/ctags/main/vstring.h +++ b/ctags/main/vstring.h @@ -18,8 +18,8 @@ #include +#include "debug.h" #include "inline.h" -#include "mio.h" /* * MACROS @@ -64,10 +64,10 @@ extern void vStringCat (vString *const string, const vString *const s); extern void vStringCatS (vString *const string, const char *const s); extern void vStringNCat (vString *const string, const vString *const s, const size_t length); -/* vStringNCatS calls strlen(S) thought it takes LENGTH because - * the handle the case that strlen(S) is smaller than LENGTH. +/* vStringNCatS calls strnlen(S,LENGTH) thought it takes LENGTH because + * the handle the case that the length of S is smaller than LENGTH. * - * In the case a caller knows strlen(S) equals to or is greater than LENGTH, + * In the case a caller knows the length equals to or is greater than LENGTH, * calling strlen is just overhead. vStringNCatSUnsafe doesn't call strlen. */ extern void vStringNCatS (vString *const string, const char *const s, const size_t length); extern void vStringNCatSUnsafe (vString *const string, const char *const s, const size_t length); @@ -79,9 +79,12 @@ extern void vStringCopy (vString *const string, const vString *const s); extern void vStringCopyS (vString *const string, const char *const s); extern void vStringNCopy (vString *const string, const vString *const s, const size_t length); extern void vStringNCopyS (vString *const string, const char *const s, const size_t length); +extern void vStringNCopySUnsafe (vString *const string, const char *const s, const size_t length); extern void vStringCopyToLower (vString *const dest, const vString *const src); extern void vStringSetLength (vString *const string); extern void vStringTruncate (vString *const string, const size_t length); +#define vStringTruncateTrailing vStringTruncate +extern void vStringTruncateLeading (vString *const string, const size_t length); extern void vStringTranslate(vString *const string, char fromC, char toC); extern vString *vStringNewOrClear (vString *const string); @@ -102,21 +105,63 @@ CTAGS_INLINE void vStringPutNewlinAgainUnsafe (vString *const string) string->buffer [string->length++] = '\n'; } -CTAGS_INLINE void vStringPut (vString *const string, const int c) +CTAGS_INLINE void vStringPutImpl (vString *const string, const int c) { + /* verify the given character is an unsigned char value */ + Assert (c >= 0 && c <= 0xff); + if (string->length + 1 == string->size) /* check for buffer overflow */ vStringResize (string, string->size * 2); - string->buffer [string->length] = c; + string->buffer [string->length] = (char) c; if (c != '\0') string->buffer [++string->length] = '\0'; } -CTAGS_INLINE void vStringPutWithLimit (vString *const string, const int c, - unsigned int maxlen) +#define vStringPut(s, c) (sizeof(c) == sizeof(char) \ + ? vStringPutImpl((s), (unsigned char) (c)) \ + : vStringPutImpl((s), (c))) + +CTAGS_INLINE bool vStringPutWithLimitImpl (vString *const string, const int c, + unsigned int maxlen) { if (vStringLength (string) < maxlen || maxlen == 0) + { vStringPut (string, c); + return true; + } + return false; +} + +#define vStringPutWithLimit(s, c, l) \ + (sizeof(c) == sizeof(char) \ + ? vStringPutWithLimitImpl((s), (unsigned char) (c), (l)) \ + : vStringPutWithLimitImpl((s), (c), (l))) + +CTAGS_INLINE void vStringAccumulate (vString *accumulator, vString *string) +{ + vStringCat (accumulator, string); + vStringClear (string); } +#define vStringPutUnlessEmpty(s, c) \ + do { \ + if (!vStringIsEmpty(s)) \ + vStringPut ((s), (c)); \ + } while (0) + +#define vStringJoin(string, c, s) do { \ + vStringPutUnlessEmpty ((string), (c)); \ + vStringCat((string), (s)); \ + } while (0) + +#define vStringJoinS(string, c, s) do { \ + vStringPutUnlessEmpty ((string), (c)); \ + vStringCatS((string), (s)); \ + } while (0) + +/* If cstrlit is a C string listeral and LTO is enabled, + * this macro is efficient */ +#define vStringEqC(vstr, cstrlit) ((vStringLength ((vstr)) == strlen ((cstrlit)) \ + && strncmp (vStringValue ((vstr)), (cstrlit), strlen ((cstrlit))) == 0)) #endif /* CTAGS_MAIN_VSTRING_H */ diff --git a/ctags/main/writer-ctags.c b/ctags/main/writer-ctags.c index 541ba5c046..43bcaf768a 100644 --- a/ctags/main/writer-ctags.c +++ b/ctags/main/writer-ctags.c @@ -21,6 +21,7 @@ #include "xtag.h" #include "xtag_p.h" +#include #define CTAGS_FILE "tags" @@ -37,9 +38,9 @@ static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, static bool treatFieldAsFixed (int fieldType); static void checkCtagsOptions (tagWriter *writer, bool fieldsWereReset); -#ifdef WIN32 +#ifdef _WIN32 static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentSetting); -#endif /* WIN32 */ +#endif /* _WIN32 */ struct rejection { bool rejectionInThisInput; @@ -54,7 +55,7 @@ tagWriter uCtagsWriter = { .rescanFailedEntry = NULL, .treatFieldAsFixed = treatFieldAsFixed, .checkOptions = checkCtagsOptions, -#ifdef WIN32 +#ifdef _WIN32 .overrideFilenameSeparator = overrideFilenameSeparator, #endif .defaultFileName = CTAGS_FILE, @@ -77,7 +78,7 @@ static bool endECTagsFile (tagWriter *writer, MIO * mio CTAGS_ATTR_UNUSED, const return rej->rejectionInThisInput; } -#ifdef WIN32 +#ifdef _WIN32 static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentSetting) { if (currentSetting == FILENAME_SEP_UNSET) @@ -244,7 +245,7 @@ static int addExtensionFields (tagWriter *writer, MIO *mio, const tagEntryInfo * char sep [] = {';', '"', '\0'}; int length = 0; - const char *str = NULL;; + const char *str = NULL; kindDefinition *kdef = getLanguageKind(tag->langType, tag->kindIndex); const char kind_letter_str[2] = {kdef->letter, '\0'}; @@ -356,7 +357,7 @@ static int writeCtagsEntry (tagWriter *writer, return length; } -static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, +static int writeCtagsPtagEntry (tagWriter *writer, MIO * mio, const ptagDesc *desc, const char *const fileName, const char *const pattern, @@ -370,18 +371,74 @@ static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, const char *fieldx = extras? getFieldName (FIELD_EXTRAS): ""; const char *xptag = extras? getXtagName (XTAG_PSEUDO_TAGS): ""; - return parserName + /* Escaping: + * + * NAME: + * Defined in ctags. As far as we give a good pseudo tag name, + * we can print the name as is. + * + * parserName: + * This can be a part of NAME. An optlib can give a + * parserName but the characters that can be used in the name are + * limited in [a-zA-Z0-9+#]. We can print the name as is. + * + * fileName: + * Any characters can be used. Escaping is needed. + * + * pattern: + * Any characters can be used. Escaping is needed always. + * + * fieldx: + * No escaping is needed. + * + * xptag: + * No escaping is needed. + * + */ + + vString *vfileName = vStringNew (); + if (writer->type == WRITER_U_CTAGS +#ifdef _WIN32 + && getFilenameSeparator(Option.useSlashAsFilenameSeparator) == FILENAME_SEP_USE_SLASH +#endif + ) + { + if (fileName) + vStringCatSWithEscaping (vfileName, fileName); + } + else if (fileName) + { + char *c = NULL; + if ((c = strchr (fileName, '\t')) || (c = strchr (fileName, '\n'))) + { + vStringDelete (vfileName); + error (WARNING, "skip priting %s%s pseudo tag; the input field of the pseudo tag includes a %s character: %s", + PSEUDO_TAG_PREFIX, desc->name, + *c == '\t'? "tab": "newline", + fileName); + return 0; + } + vStringCatS (vfileName, fileName); + } + + vString *vpattern = vStringNew (); + if (pattern) + vStringCatSWithEscapingAsPattern (vpattern, pattern); -#define OPT(X) ((X)?(X):"") + int r = parserName ? mio_printf (mio, "%s%s%s%s\t%s\t/%s/%s%s%s%s\n", PSEUDO_TAG_PREFIX, desc->name, PSEUDO_TAG_SEPARATOR, parserName, - OPT(fileName), OPT(pattern), + vStringValue (vfileName), vStringValue (vpattern), xsep, fieldx, fsep, xptag) : mio_printf (mio, "%s%s\t%s\t/%s/%s%s%s%s\n", PSEUDO_TAG_PREFIX, desc->name, - OPT(fileName), OPT(pattern), + vStringValue (vfileName), vStringValue (vpattern), xsep, fieldx, fsep, xptag); -#undef OPT + + vStringDelete (vpattern); + vStringDelete (vfileName); + + return r; } static fieldType fixedFields [] = { diff --git a/ctags/main/writer-json.c b/ctags/main/writer-json.c index 3434c1754e..fe5280dfbe 100644 --- a/ctags/main/writer-json.c +++ b/ctags/main/writer-json.c @@ -25,6 +25,19 @@ #ifdef HAVE_JANSSON #include +/* The concept of CURRENT and AGE is taken from libtool. + * However, we delete REVISION. + * We will update more CURRENT frequently than the assumption + * in libtool. + * + * If KEYS have been added, removed or changed since last release, + * increment CURRENT. + * If they have been added since last release, increment AGE. + * If they have been removed since last release, set AGE to 0 + */ +#define JSON_WRITER_CURRENT 1 +#define JSON_WRITER_AGE 0 + #ifndef json_boolean /* compat with jansson < 2.4 */ #define json_boolean(val) ((val) ? json_true() : json_false()) #endif @@ -238,8 +251,23 @@ static int writeJsonPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, { #define OPT(X) ((X)?(X):"") json_t *response; + char *parserName0 = NULL; - if (parserName) + const char *rest = ((JSON_WRITER_CURRENT > 0) && parserName && desc->jsonObjectKey) + ? strchr(parserName, '!') + : NULL; + if (rest) + { + parserName0 = eStrndup(parserName, rest - parserName); + response = json_pack ("{ss ss ss ss ss ss}", + "_type", "ptag", + "name", desc->name, + "parserName", parserName0, + desc->jsonObjectKey, rest + 1, + "path", OPT(fileName), + "pattern", OPT(pattern)); + } + else if (parserName) { response = json_pack ("{ss ss ss ss ss}", "_type", "ptag", @@ -261,6 +289,8 @@ static int writeJsonPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, int length = mio_printf (mio, "%s\n", buf); free (buf); json_decref (response); + if (parserName0) + eFree(parserName0); return length; #undef OPT @@ -270,7 +300,7 @@ extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_A const void *data CTAGS_ATTR_UNUSED) { return writePseudoTag (desc, - "0.0", + STRINGIFY(JSON_WRITER_CURRENT) "." STRINGIFY(JSON_WRITER_AGE), "in development", NULL); } diff --git a/ctags/main/writer.c b/ctags/main/writer.c index e8ba67a604..f109682bea 100644 --- a/ctags/main/writer.c +++ b/ctags/main/writer.c @@ -124,7 +124,7 @@ extern bool writerDoesTreatFieldAsFixed (int fieldType) return false; } -#ifdef WIN32 +#ifdef _WIN32 extern enum filenameSepOp getFilenameSeparator (enum filenameSepOp currentSetting) { if (writer->overrideFilenameSeparator) @@ -138,7 +138,7 @@ extern bool ptagMakeCtagsOutputFilesep (ptagDesc *desc, const void *data) { const char *sep = "slash"; -#ifdef WIN32 +#ifdef _WIN32 const optionValues *opt = data; if (getFilenameSeparator (opt->useSlashAsFilenameSeparator) != FILENAME_SEP_USE_SLASH) diff --git a/ctags/main/writer_p.h b/ctags/main/writer_p.h index 2367748218..e52aa591cb 100644 --- a/ctags/main/writer_p.h +++ b/ctags/main/writer_p.h @@ -54,9 +54,9 @@ struct sTagWriter { void (* checkOptions) (tagWriter *writer, bool fieldsWereReset); -#ifdef WIN32 +#ifdef _WIN32 enum filenameSepOp (* overrideFilenameSeparator) (enum filenameSepOp currentSetting); -#endif /* WIN32 */ +#endif /* _WIN32 */ const char *defaultFileName; @@ -100,7 +100,7 @@ extern bool writerDoesTreatFieldAsFixed (int fieldType); extern void writerCheckOptions (bool fieldsWereReset); extern bool writerPrintPtagByDefault (void); -#ifdef WIN32 +#ifdef _WIN32 extern enum filenameSepOp getFilenameSeparator (enum filenameSepOp currentSetting); -#endif /* WIN32 */ +#endif /* _WIN32 */ #endif /* CTAGS_MAIN_WRITER_PRIVATE_H */ diff --git a/ctags/main/xtag.c b/ctags/main/xtag.c index 4e3e60cc80..60add848ef 100644 --- a/ctags/main/xtag.c +++ b/ctags/main/xtag.c @@ -282,7 +282,7 @@ extern bool isCommonXtag (xtagType type) return (type < XTAG_COUNT)? true: false; } -extern langType getXtagOwner (xtagType type) +extern langType getXtagLanguage (xtagType type) { return getXtagObject (type)->language; } @@ -324,7 +324,7 @@ extern void initXtagObjects (void) } } -extern int countXtags (void) +extern unsigned int countXtags (void) { return xtagObjectUsed; } @@ -355,7 +355,7 @@ extern int defineXtag (xtagDefinition *def, langType language) Assert (def->name); for (i = 0; i < strlen (def->name); i++) { - Assert ( isalnum (def->name [i]) ); + Assert ( isalnum ((unsigned char) def->name [i]) ); } def->letter = NUL_XTAG_LETTER; diff --git a/ctags/main/xtag_p.h b/ctags/main/xtag_p.h index 2b9968475e..6c4bc493d7 100644 --- a/ctags/main/xtag_p.h +++ b/ctags/main/xtag_p.h @@ -37,13 +37,13 @@ extern bool isXtagFixed (xtagType type); extern bool isCommonXtag (xtagType type); /* Return LANG_IGNORE for common fields. */ -extern langType getXtagOwner (xtagType type); +extern langType getXtagLanguage (xtagType type); extern const char* getXtagName (xtagType type); extern const char* getXtagDescription (xtagType type); extern void initXtagObjects (void); -extern int countXtags (void); +extern unsigned int countXtags (void); extern int defineXtag (xtagDefinition *def, langType language); extern xtagType nextSiblingXtag (xtagType type); diff --git a/ctags/parsers/abaqus.c b/ctags/parsers/abaqus.c index ea71581e06..33b85fe679 100644 --- a/ctags/parsers/abaqus.c +++ b/ctags/parsers/abaqus.c @@ -44,7 +44,9 @@ static int getWord(const char *ref, const char **ptr) { const char *p = *ptr; - while ((*ref != '\0') && (*p != '\0') && (tolower(*ref) == tolower(*p))) ref++, p++; + while ((*ref != '\0') && (*p != '\0') && + (tolower((unsigned char) *ref) == tolower((unsigned char) *p))) + ref++, p++; if (*ref) return false; @@ -70,7 +72,7 @@ static void createTag(AbaqusKind kind, const char *buf) do { - vStringPut(name, (int) *buf); + vStringPut(name, *buf); ++buf; } while ((*buf != '\0') && (*buf != ',')); makeSimpleTag(name, kind); diff --git a/ctags/parsers/ada.c b/ctags/parsers/ada.c index 3b837bfb96..4ab3621b3f 100644 --- a/ctags/parsers/ada.c +++ b/ctags/parsers/ada.c @@ -637,7 +637,8 @@ static void movePos (int amount) * cmp () because comments don't have to have whitespace or separation-type * characters following the "--" */ #define isAdaComment(buf, pos, len) \ - (((pos) == 0 || (!isalnum ((buf)[(pos) - 1]) && (buf)[(pos) - 1] != '_')) && \ + (((pos) == 0 || (!isalnum ((unsigned char) (buf)[(pos) - 1]) && \ + (buf)[(pos) - 1] != '_')) && \ (pos) < (len) && \ strncasecmp (&(buf)[(pos)], "--", strlen ("--")) == 0) #define isAdaStringLiteral(buf, pos, len) \ @@ -673,7 +674,7 @@ static bool cmp (const char *buf, int len, const char *match) if ((strncasecmp (buf, match, matchLen) == 0) && (matchLen == len || (matchLen < len && - (isspace (buf[matchLen]) || buf[matchLen] == '(' || + (isspace ((unsigned char) buf[matchLen]) || buf[matchLen] == '(' || buf[matchLen] == ')' || buf[matchLen] == ':' || buf[matchLen] == ';')))) { @@ -740,7 +741,7 @@ static void skipUntilWhiteSpace (void) * check to be true immediately */ skipComments (); - while (!eof_reached && !isspace (line[pos])) + while (!eof_reached && !isspace ((unsigned char) line[pos])) { /* don't use movePos () because if we read in a new line with this function * we need to stop */ @@ -776,7 +777,7 @@ static void skipWhiteSpace (void) * check to fail immediately */ skipComments (); - while (!eof_reached && isspace (line[pos])) + while (!eof_reached && isspace ((unsigned char) line[pos])) { movePos (1); @@ -866,7 +867,7 @@ static void skipPastWord (void) /* now increment until we hit a non-word character... Specifically, * whitespace, '(', ')', ':', and ';' */ - while (!eof_reached && !isspace (line[pos]) && + while (!eof_reached && !isspace ((unsigned char) line[pos]) && line[pos] != '(' && line[pos] != ')' && line[pos] != ':' && line[pos] != ';') { @@ -987,8 +988,9 @@ static adaTokenInfo *adaParseBlock (adaTokenInfo *parent, adaKind kind) /* we are at the start of what should be the tag now... But we have to get * it's length. So loop until we hit whitespace, init the counter to 1 * since we know that the current position is not whitespace */ - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]) && - line[pos + i] != '(' && line[pos + i] != ';'; i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]) && + line[pos + i] != '(' && line[pos + i] != ';'; i++); /* we have reached the tag of the package, so create the tag */ token = newAdaToken (&line[pos], i, kind, isSpec, parent); @@ -1088,8 +1090,9 @@ static adaTokenInfo *adaParseSubprogram (adaTokenInfo *parent, adaKind kind) * it's length. So loop until we hit whitespace or the beginning of the * parameter list. Init the counter to 1 * since we know that the current * position is not whitespace */ - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]) && - line[pos + i] != '(' && line[pos + i] != ';'; i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]) && + line[pos + i] != '(' && line[pos + i] != ';'; i++); /* we have reached the tag of the subprogram, so create the tag... Init the * isSpec flag to false and we will adjust it when we see if there is an @@ -1205,8 +1208,9 @@ static adaTokenInfo *adaParseType (adaTokenInfo *parent, adaKind kind) skipWhiteSpace (); /* get the name of the type */ - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]) && - line[pos + i] != '(' && line[pos + i] != ';'; i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]) && + line[pos + i] != '(' && line[pos + i] != ';'; i++); token = newAdaToken (&line[pos], i, kind, false, parent); @@ -1405,12 +1409,13 @@ static adaTokenInfo *adaParseVariables (adaTokenInfo *parent, adaKind kind) * because if it "constant" or "exception" then we must tag this slightly * differently, But only check this for normal variables */ else if (kind == ADA_KIND_VARIABLE && varEndPos != -1 && - !isspace (buf[bufPos]) && tokenStart == -1) + !isspace ((unsigned char) buf[bufPos]) && tokenStart == -1) { tokenStart = bufPos; } else if (kind == ADA_KIND_VARIABLE && varEndPos != -1 && tokenStart >= 0 && - ((bufPos + 1) >= bufLen || isspace (buf[bufPos + 1]) || + ((bufPos + 1) >= bufLen || + isspace ((unsigned char) buf[bufPos + 1]) || buf[bufPos + 1] == ';')) { if (cmp (&buf[tokenStart], bufLen - tokenStart, @@ -1487,8 +1492,8 @@ static adaTokenInfo *adaParseVariables (adaTokenInfo *parent, adaKind kind) * buf */ for ( ; i < varEndPos && buf[i] != '\0'; i++); } - else if (tokenStart != -1 && (isspace (buf[i]) || buf[i] == ',' || - buf[i] == '\0')) + else if (tokenStart != -1 && (isspace ((unsigned char) buf[i]) || + buf[i] == ',' || buf[i] == '\0')) { /* only store the word if it is not an in/out keyword */ if (!cmp (&buf[tokenStart], varEndPos, "in") && @@ -1499,13 +1504,13 @@ static adaTokenInfo *adaParseVariables (adaTokenInfo *parent, adaKind kind) /* now set the proper line and file position counts for this * new token */ - token->tag.lineNumber = lineNum + filePosIndex; - token->tag.filePosition = filePos[filePosIndex]; + updateTagLine (&token->tag, lineNum + filePosIndex, + filePos[filePosIndex]); } tokenStart = -1; } - else if (tokenStart == -1 && !(isspace (buf[i]) || buf[i] == ',' || - buf[i] == '\0')) + else if (tokenStart == -1 && !(isspace ((unsigned char) buf[i]) || + buf[i] == ',' || buf[i] == '\0')) { /* only set the tokenStart for non-newline characters */ tokenStart = i; @@ -1526,8 +1531,8 @@ static adaTokenInfo *adaParseVariables (adaTokenInfo *parent, adaKind kind) /* now set the proper line and file position counts for this * new token */ - token->tag.lineNumber = lineNum + filePosIndex; - token->tag.filePosition = filePos[filePosIndex]; + updateTagLine (&token->tag, lineNum + filePosIndex, + filePos[filePosIndex]); } } @@ -1550,7 +1555,8 @@ static adaTokenInfo *adaParseLoopVar (adaTokenInfo *parent) adaTokenInfo *token = NULL; skipWhiteSpace (); - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]); i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]); i++); token = newAdaToken (&line[pos], i, ADA_KIND_AUTOMATIC_VARIABLE, false, parent); movePos (i); @@ -1640,7 +1646,7 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) /* get length of tag */ for (i = 1; (pos + i) < lineLen && line[pos + i] != ')' && - !isspace (line[pos + i]); i++); + !isspace ((unsigned char) line[pos + i]); i++); /* the original comment before we introduced reference tags: * ----------------------------------------------------------------- @@ -1730,8 +1736,9 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) skipWhiteSpace (); /* get length of tag */ - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]) && - line[pos + i] != '(' && line[pos + i] != ';'; i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]) && + line[pos + i] != '(' && line[pos + i] != ';'; i++); appendAdaToken (&genericParamsRoot, newAdaToken (&line[pos], i, ADA_KIND_FORMAL, false, @@ -1749,8 +1756,9 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) skipWhiteSpace (); /* get length of tag */ - for (i = 1; (pos + i) < lineLen && !isspace (line[pos + i]) && - line[pos + i] != '(' && line[pos + i] != ';'; i++); + for (i = 1; (pos + i) < lineLen && + !isspace ((unsigned char) line[pos + i]) && + line[pos + i] != '(' && line[pos + i] != ';'; i++); appendAdaToken (&genericParamsRoot, newAdaToken (&line[pos], i, ADA_KIND_FORMAL, false, @@ -1931,8 +1939,7 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) token = newAdaToken (NULL, 0, ADA_KIND_ANONYMOUS, false, parent); /* save the correct starting line */ - token->tag.lineNumber = matchLineNum; - token->tag.filePosition = matchFilePos; + updateTagLine (&token->tag, matchLineNum, matchFilePos); adaParse (ADA_DECLARATIONS, token); } @@ -1947,8 +1954,7 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) token = newAdaToken (NULL, 0, ADA_KIND_ANONYMOUS, false, parent); /* save the correct starting line */ - token->tag.lineNumber = matchLineNum; - token->tag.filePosition = matchFilePos; + updateTagLine (&token->tag, matchLineNum, matchFilePos); adaParse (ADA_CODE, token); } @@ -2029,8 +2035,7 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) ADA_KIND_ANONYMOUS, false, parent); /* save the correct starting line */ - token->tag.lineNumber = matchLineNum; - token->tag.filePosition = matchFilePos; + updateTagLine (&token->tag, matchLineNum, matchFilePos); /* parse the loop body */ skipWhiteSpace (); @@ -2089,7 +2094,8 @@ static adaTokenInfo *adaParse (adaParseMode mode, adaTokenInfo *parent) /* there is a possibility that this may be a loop or block * identifier, so check for a [ ]?: statement */ for (i = 1; (pos + i) < lineLen; i++) - if(!(isalnum (line[pos + i]) || line[pos + i] == '_')) + if (!(isalnum ((unsigned char) line[pos + i]) || + line[pos + i] == '_')) break; i_end = i; /* Records the end of identifier. */ diff --git a/ctags/parsers/asciidoc.c b/ctags/parsers/asciidoc.c index 766fce6157..6329cf6910 100644 --- a/ctags/parsers/asciidoc.c +++ b/ctags/parsers/asciidoc.c @@ -28,6 +28,7 @@ #include "parse.h" #include "read.h" #include "vstring.h" +#include "utf8_str.h" #include "nestlevel.h" #include "routines.h" @@ -99,12 +100,12 @@ static int makeAsciidocTag (const vString* const name, const int kind, const boo if (two_line) { /* we want the line before the '---' underline chars */ - const unsigned long line = getInputLineNumber(); - Assert (line > 0); - if (line > 0) + Assert (e.lineNumber > 1); + if (e.lineNumber > 1) { - e.lineNumber--; - e.filePosition = getInputFilePositionForLine(line - 1); + unsigned long lineNumber = e.lineNumber - 1; + updateTagLine (&e, lineNumber, + getInputFilePositionForLine(lineNumber)); } } @@ -276,37 +277,6 @@ static void process_name(vString *const name, const int kind, vStringNCatS(name, (const char*)(&(line[start])), end - start + 1); } - -/* computes the length of an UTF-8 string - * if the string doesn't look like UTF-8, return -1 - * FIXME consider East_Asian_Width Unicode property */ -static int utf8_strlen(const char *buf, int buf_len) -{ - int len = 0; - const char *end = buf + buf_len; - - for (len = 0; buf < end; len ++) - { - /* perform quick and naive validation (no sub-byte checking) */ - if (! (*buf & 0x80)) - buf ++; - else if ((*buf & 0xe0) == 0xc0) - buf += 2; - else if ((*buf & 0xf0) == 0xe0) - buf += 3; - else if ((*buf & 0xf8) == 0xf0) - buf += 4; - else /* not a valid leading UTF-8 byte, abort */ - return -1; - - if (buf > end) /* incomplete last byte */ - return -1; - } - - return len; -} - - static void findAsciidocTags(void) { vString *name = vStringNew(); diff --git a/ctags/parsers/asm.c b/ctags/parsers/asm.c index a3679499e0..a7f2c49d97 100644 --- a/ctags/parsers/asm.c +++ b/ctags/parsers/asm.c @@ -17,21 +17,26 @@ #include "cpreprocessor.h" #include "debug.h" +#include "dependency.h" #include "entry.h" #include "keyword.h" +#include "param.h" #include "parse.h" #include "read.h" #include "routines.h" #include "selectors.h" +#include "trace.h" #include "vstring.h" /* * DATA DECLARATIONS */ typedef enum { + K_PSUEDO_FOREIGN_LD_SCRIPT_SYMBOL = -4, + K_PSUEDO_FOREIGN_LD_SCRIPT_SECTION = -3, K_PSUEDO_MACRO_END = -2, K_NONE = -1, K_DEFINE, K_LABEL, K_MACRO, K_TYPE, - K_SECTION, + K_PARAM, } AsmKind; typedef enum { @@ -45,6 +50,7 @@ typedef enum { OP_ENDS, OP_EQU, OP_EQUAL, + OP_GLOBAL, OP_LABEL, OP_MACRO, OP_PROC, @@ -56,31 +62,32 @@ typedef enum { OP_LAST } opKeyword; -typedef enum { - ASM_SECTION_PLACEMENT, -} asmSectionRole; - typedef struct { opKeyword keyword; AsmKind kind; } opKind; +typedef enum { + F_PROPERTIES, +} asmField; + +static fieldDefinition AsmFields[] = { + { .name = "properties", + .description = "properties (req, vararg for parameters)", + .enabled = true }, +}; + /* * DATA DEFINITIONS */ static langType Lang_asm; -static roleDefinition asmSectionRoles [] = { - { true, "placement", "placement where the assembled code goes" }, -}; - static kindDefinition AsmKinds [] = { { true, 'd', "define", "defines" }, { true, 'l', "label", "labels" }, { true, 'm', "macro", "macros" }, { true, 't', "type", "types (structs and records)" }, - { true, 's', "section", "sections", - .referenceOnly = true, ATTACH_ROLES(asmSectionRoles)}, + { false,'z', "parameter", "parameters for a macro" }, }; static const keywordTable AsmKeywords [] = { @@ -91,6 +98,8 @@ static const keywordTable AsmKeywords [] = { { "endp", OP_ENDP }, { "ends", OP_ENDS }, { "equ", OP_EQU }, + { "global", OP_GLOBAL }, + { "globl", OP_GLOBAL }, { "label", OP_LABEL }, { "macro", OP_MACRO }, { ":=", OP_COLON_EQUAL }, @@ -119,16 +128,31 @@ static const opKind OpKinds [] = { { OP_ENDS, K_NONE }, { OP_EQU, K_DEFINE }, { OP_EQUAL, K_DEFINE }, + { OP_GLOBAL, K_PSUEDO_FOREIGN_LD_SCRIPT_SYMBOL }, { OP_LABEL, K_LABEL }, { OP_MACRO, K_MACRO }, { OP_PROC, K_LABEL }, { OP_RECORD, K_TYPE }, { OP_SECTIONS, K_NONE }, - { OP_SECTION, K_SECTION }, + { OP_SECTION, K_PSUEDO_FOREIGN_LD_SCRIPT_SECTION }, { OP_SET, K_DEFINE }, { OP_STRUCT, K_TYPE } }; +#define DEFAULT_COMMENT_CHARS_BOL ";*@" +static const char defaultCommentCharAtBOL [] = DEFAULT_COMMENT_CHARS_BOL; +static const char *commentCharsAtBOL = defaultCommentCharAtBOL; + +#define DEFAULT_COMMENT_CHARS_MOL "" +static const char defaultCommentCharInMOL [] = DEFAULT_COMMENT_CHARS_MOL; +static const char *commentCharsInMOL = defaultCommentCharInMOL; + +#define DEFAULT_EXTRA_LINESEP_CHARS "" +static const char defaultExtraLinesepChars [] = DEFAULT_EXTRA_LINESEP_CHARS; +static const char *extraLinesepChars = defaultExtraLinesepChars; + +static bool useCPreProcessor = true; + /* * FUNCTION DEFINITIONS */ @@ -177,40 +201,88 @@ static bool isDefineOperator (const vString *const operator) (unsigned char*) vStringValue (operator); const size_t length = vStringLength (operator); const bool result = (bool) (length > 0 && - toupper ((int) *op) == 'D' && + toupper (*op) == 'D' && (length == 2 || (length == 4 && (int) op [2] == '.') || (length == 5 && (int) op [3] == '.'))); return result; } -static void makeAsmTag ( +static int makeTagForLdScript (const char * name, int kind, int *scope) +{ + tagEntryInfo e; + static langType lang = LANG_AUTO; + + if(lang == LANG_AUTO) + lang = getNamedLanguage("LdScript", 0); + if(lang == LANG_IGNORE) + return CORK_NIL; + + if (kind == K_PSUEDO_FOREIGN_LD_SCRIPT_SYMBOL) + { + static kindDefinition * kdef = NULL; + if(kdef == NULL) + kdef = getLanguageKindForName (lang, "symbol"); + if(kdef == NULL) + return CORK_NIL; + + initForeignTagEntry(&e, name, lang, kdef->id); + e.extensionFields.scopeIndex = *scope; + return makeTagEntry (&e); + } + else + { + static kindDefinition * kdef = NULL; + if(kdef == NULL) + kdef = getLanguageKindForName (lang, "inputSection"); + if(kdef == NULL) + return CORK_NIL; + + static roleDefinition *rdef = NULL; + if(rdef == NULL) + rdef = getLanguageRoleForName (lang, kdef->id, "destination"); + if (rdef == NULL) + return CORK_NIL; + + initForeignRefTagEntry(&e, name, lang, kdef->id, rdef->id); + *scope = makeTagEntry (&e); + return *scope; + } +} + +static int makeAsmTag ( const vString *const name, const vString *const operator, const bool labelCandidate, const bool nameFollows, const bool directive, - int *lastMacroCorkIndex) + int *sectionScope, + int *macroScope) { + int r = CORK_NIL; + if (vStringLength (name) > 0) { - bool found; - const AsmKind kind = operatorKind (operator, &found); + bool found = false; + AsmKind kind = directive? K_NONE: operatorKind (operator, &found); + if (found) { if (kind > K_NONE) - makeSimpleTag (name, kind); + r = makeSimpleTag (name, kind); } else if (isDefineOperator (operator)) { if (! nameFollows) - makeSimpleTag (name, K_DEFINE); + r = makeSimpleTag (name, K_DEFINE); } else if (labelCandidate) { operatorKind (name, &found); if (! found) - makeSimpleTag (name, K_LABEL); + { + r = makeSimpleTag (name, K_LABEL); + } } else if (directive) { @@ -223,27 +295,35 @@ static void makeAsmTag ( case K_NONE: break; case K_MACRO: - *lastMacroCorkIndex = makeSimpleTag (operator, - kind_for_directive); - if (*lastMacroCorkIndex != CORK_NIL) - registerEntry (*lastMacroCorkIndex); + r = makeSimpleTag (operator, kind_for_directive); + macro_tag = getEntryInCorkQueue (r); + if (macro_tag) + { + macro_tag->extensionFields.scopeIndex = *macroScope; + registerEntry (r); + *macroScope = r; + } break; case K_PSUEDO_MACRO_END: - macro_tag = getEntryInCorkQueue (*lastMacroCorkIndex); + macro_tag = getEntryInCorkQueue (*macroScope); if (macro_tag) - macro_tag->extensionFields.endLine = getInputLineNumber (); - *lastMacroCorkIndex = CORK_NIL; + { + setTagEndLine (macro_tag, getInputLineNumber ()); + *macroScope = macro_tag->extensionFields.scopeIndex; + } break; - case K_SECTION: - makeSimpleRefTag (operator, - kind_for_directive, - ASM_SECTION_PLACEMENT); + case K_PSUEDO_FOREIGN_LD_SCRIPT_SYMBOL: + case K_PSUEDO_FOREIGN_LD_SCRIPT_SECTION: + r = makeTagForLdScript (vStringValue (operator), + kind_for_directive, sectionScope); break; default: - makeSimpleTag (operator, kind_for_directive); + r = makeSimpleTag (operator, kind_for_directive); + break; } } } + return r; } static const unsigned char *readSymbol ( @@ -252,9 +332,9 @@ static const unsigned char *readSymbol ( { const unsigned char *cp = start; vStringClear (sym); - if (isInitialSymbolCharacter ((int) *cp)) + if (isInitialSymbolCharacter (*cp)) { - while (isSymbolCharacter ((int) *cp)) + while (isSymbolCharacter (*cp)) { vStringPut (sym, *cp); ++cp; @@ -269,7 +349,7 @@ static const unsigned char *readOperator ( { const unsigned char *cp = start; vStringClear (operator); - while (*cp != '\0' && ! isspace ((int) *cp) && *cp != ',') + while (*cp != '\0' && ! isspace (*cp) && *cp != ',') { vStringPut (operator, *cp); ++cp; @@ -277,61 +357,423 @@ static const unsigned char *readOperator ( return cp; } -static const unsigned char *asmReadLineFromInputFile (void) +// We stop applying macro replacements if the unget buffer gets too big +// as it is a sign of recursive macro expansion +#define ASM_PARSER_MAXIMUM_UNGET_BUFFER_SIZE_FOR_MACRO_REPLACEMENTS 65536 + +// We stop applying macro replacements if a macro is used so many +// times in a recursive macro expansion. +#define ASM_PARSER_MAXIMUM_MACRO_USE_COUNT 8 + +static bool collectCppMacroArguments (ptrArray *args) +{ + vString *s = vStringNew (); + int c; + int depth = 1; + + do + { + c = cppGetc (); + if (c == EOF || c == '\n') + break; + else if (c == ')') + { + depth--; + if (depth == 0) + { + char *cstr = vStringDeleteUnwrap (s); + ptrArrayAdd (args, cstr); + s = NULL; + } + else + vStringPut (s, c); + } + else if (c == '(') + { + depth++; + vStringPut (s, c); + } + else if (c == ',') + { + char *cstr = vStringDeleteUnwrap (s); + ptrArrayAdd (args, cstr); + s = vStringNew (); + } + else if (c == CPP_STRING_SYMBOL || c == CPP_CHAR_SYMBOL) + vStringPut (s, ' '); + else + vStringPut (s, c); + } + while (depth > 0); + + vStringDelete (s); /* NULL is acceptable. */ + + if (depth > 0) + TRACE_PRINT("unbalanced argument list"); + + return (depth > 0)? false: true; +} + +static bool expandCppMacro (cppMacroInfo *macroInfo) +{ + ptrArray *args = NULL; + + if (macroInfo->hasParameterList) + { + int c; + + while (1) + { + c = cppGetc (); + if (c == CPP_STRING_SYMBOL || c == CPP_CHAR_SYMBOL || !isspace (c)) + break; + } + + if (c != '(') + { + cppUngetc (c); + return false; + } + + args = ptrArrayNew (eFree); + if (!collectCppMacroArguments (args)) + { + /* The input stream is already corrupted. + * It is hard to recover. */ + ptrArrayDelete (args); + return false; + } + } + + cppBuildMacroReplacementWithPtrArrayAndUngetResult(macroInfo, args); + + ptrArrayDelete (args); /* NULL is acceptable. */ + return true; +} + +static void truncateLastIdetifier (vString *line, vString *identifier) +{ + Assert (vStringLength (line) >= vStringLength (identifier)); + size_t len = vStringLength (line) - vStringLength (identifier); + Assert (strcmp (vStringValue (line) + len, + vStringValue (identifier)) == 0); + vStringTruncate (line, len); +} + +static bool processCppMacroX (vString *identifier, int lastChar, vString *line) +{ + TRACE_ENTER(); + + bool r = false; + cppMacroInfo *macroInfo = cppFindMacro (vStringValue (identifier)); + + if (!macroInfo) + goto out; + + if(macroInfo && (macroInfo->useCount >= ASM_PARSER_MAXIMUM_MACRO_USE_COUNT)) + goto out; + + if (lastChar != EOF) + cppUngetc (lastChar); + + TRACE_PRINT("Macro expansion: %s<%p>%s", macroInfo->name, + macroInfo, macroInfo->hasParameterList? "(...)": ""); + + r = expandCppMacro (macroInfo); + + out: + if (r) + truncateLastIdetifier (line, identifier); + + vStringClear (identifier); + + TRACE_LEAVE(); + return r; +} + +/* If a section name is built with a macro expansion, the following + * strings may appear in parts of the string. + * - \param + * - \() + * - \@ + */ +static bool isCharInMarcoParamref(char c) +{ + return (c == '\\' || c == '(' || c == ')' || c == '@')? true: false; +} + +static bool isEligibleAsSectionName (const vString *str) +{ + char *c = vStringValue(str); + while (*c) + { + if (!(isalnum(((unsigned char)*c)) + || (*c == '.') + || (*c == '-') + || (*c == '_') + || isCharInMarcoParamref(*c))) + return false; + c++; + } + return true; +} + +static const unsigned char *readLineViaCpp (const char *commentChars) { static vString *line; int c; + bool truncation = false; line = vStringNewOrClear (line); + vString *identifier = vStringNew (); + + cont: while ((c = cppGetc()) != EOF) { - if (c == '\n') - break; - else if (c == STRING_SYMBOL || c == CHAR_SYMBOL) + if (c == CPP_STRING_SYMBOL || c == CPP_CHAR_SYMBOL) { + /* c == CHAR_SYMBOL is subtle condition. + * If the last char of IDENTIFIER is [0-9a-f], + * cppGetc() never returns CHAR_SYMBOL to + * Handle c++14 digit separator. + */ + if (!vStringIsEmpty (identifier) + && processCppMacroX (identifier, ' ', line)) + continue; + /* We cannot store these values to vString - * Store a whitespace as a dummy value for them. + * Store a whitespace as a dummy value for them, but... */ - vStringPut (line, ' '); + if (!truncation) + { + vStringPut (line, ' '); + + /* Quoted from the info document of Gas: + ------------------------------------- + For ELF targets, the assembler supports another type of '.section' + directive for compatibility with the Solaris assembler: + + .section "NAME"[, FLAGS...] + ------------------------------------- + + If we replace "..." with ' ' here, we can lost the name + of the section. */ + const vString *str = cppGetLastCharOrStringContents(); + if (str) + { + const char *section = strrstr (vStringValue (line), ".section"); + if (section && isEligibleAsSectionName(str)) + { + section += strlen(".section"); + while (isspace((unsigned char)*section)) + section++; + if (*section == '\0') + { + vStringCat (line, str); + vStringPut (line, ' '); + } + } + } + } + } + else if (c == '\n' || (extraLinesepChars[0] != '\0' + && strchr (extraLinesepChars, c) != NULL)) + { + if (!vStringIsEmpty (identifier) + && processCppMacroX (identifier, c, line)) + continue; + break; + } + else if ((vStringIsEmpty (identifier) && (isalpha (c) || c == '_')) + || (!vStringIsEmpty (identifier) && (isalnum (c) || c == '_'))) + { + vStringPut (identifier, c); + if (!truncation) + vStringPut (line, c); } else - vStringPut (line, c); + { + if (!vStringIsEmpty (identifier) + && processCppMacroX (identifier, c, line)) + continue; + + if (truncation == false && commentChars[0] && strchr (commentChars, c)) + truncation = true; + + if (!truncation) + vStringPut (line, c); + } } - if ((vStringLength (line) == 0)&& (c == EOF)) + if (c == EOF + && !vStringIsEmpty(identifier) + && processCppMacroX (identifier, EOF, line)) + goto cont; + + vStringDelete (identifier); + + TRACE_PRINT("line: %s\n", vStringValue (line)); + + if ((vStringLength (line) == 0) && (c == EOF)) return NULL; else return (unsigned char *)vStringValue (line); } -static void findAsmTags (void) +static const unsigned char *readLineNoCpp (const char *commentChars) +{ + static vString *line; + int c; + bool truncation = false; + + line = vStringNewOrClear (line); + + while ((c = getcFromInputFile ()) != EOF) + { + if (c == '\n' || (extraLinesepChars[0] != '\0' + && strchr (extraLinesepChars, c) != NULL)) + break; + else + { + if (truncation == false && commentChars[0] && strchr (commentChars, c)) + truncation = true; + + if (!truncation) + vStringPut (line, c); + } + } + if ((vStringLength (line) == 0) && (c == EOF)) + return NULL; + else + return (unsigned char *)vStringValue (line); +} + +static const unsigned char *asmReadLineFromInputFile (const char *commentChars, bool useCpp) +{ + if (useCpp) + return readLineViaCpp (commentChars); + else + return readLineNoCpp (commentChars); +} + +static void readMacroParameters (int index, tagEntryInfo *e, const unsigned char *cp) +{ + vString *name = vStringNew (); + vString *signature = vStringNew (); + int nth = 0; + + if (*cp == ',') + ++cp; + + while (*cp) + { + const unsigned char *tmp; + tagEntryInfo *e = NULL; + + while (isspace (*cp)) + ++cp; + + tmp = cp; + cp = readSymbol (cp, name); + if (cp == tmp) + break; + + { + int r = makeSimpleTag (name, K_PARAM); + e = getEntryInCorkQueue (r); + if (e) + { + e->extensionFields.scopeIndex = index; + e->extensionFields.nth = nth++; + } + if (vStringLength (signature) > 0 && vStringLast (signature) != ' ') + vStringPut (signature, ' '); + vStringCat (signature, name); + } + + if (*cp == ':') + { + cp++; + if (strncmp((const char *)cp, "req" ,3) == 0) + { + cp += 3; + if (e) + attachParserField (e, AsmFields[F_PROPERTIES].ftype, + "req"); + vStringCatS (signature, ":req"); + } + else if (strncmp((const char *)cp, "vararg", 6) == 0) + { + cp += 6; + if (e) + attachParserField (e, AsmFields[F_PROPERTIES].ftype, + "vararg"); + vStringCatS (signature, ":vararg"); + } + cp = (const unsigned char *)strpbrk ((const char *)cp , " \t,="); + if (cp == NULL) + break; + } + if (*cp == '=') + { + const unsigned char *start = cp; + cp = (const unsigned char *)strpbrk ((const char *)cp , " \t,"); + + if (cp) + vStringNCatS (signature, (const char *)start, cp - start); + else + { + vStringCatS (signature, (const char *)start); + break; + } + } + + while (isspace (*cp)) + ++cp; + + if (*cp == ',') + cp++; + } + + if (vStringLength (signature) > 0) + { + e->extensionFields.signature = vStringDeleteUnwrap (signature); + signature = NULL; + } + vStringDelete (signature); /* NULL is acceptable. */ + vStringDelete (name); +} + +static void findAsmTagsCommon (bool useCpp) { vString *name = vStringNew (); vString *operator = vStringNew (); const unsigned char *line; - cppInit (false, false, false, false, - KIND_GHOST_INDEX, 0, KIND_GHOST_INDEX, KIND_GHOST_INDEX, 0, 0, - FIELD_UNKNOWN); + if (useCpp) + cppInit (false, false, false, false, + KIND_GHOST_INDEX, 0, 0, KIND_GHOST_INDEX, KIND_GHOST_INDEX, 0, 0, + FIELD_UNKNOWN); - int lastMacroCorkIndex = CORK_NIL; + int sectionScope = CORK_NIL; + int macroScope = CORK_NIL; - while ((line = asmReadLineFromInputFile ()) != NULL) - { + while ((line = asmReadLineFromInputFile (commentCharsInMOL, useCpp)) != NULL) + { const unsigned char *cp = line; - bool labelCandidate = (bool) (! isspace ((int) *cp)); + bool labelCandidate = (bool) (! isspace (*cp)); bool nameFollows = false; bool directive = false; const bool isComment = (bool) - (*cp != '\0' && strchr (";*@", *cp) != NULL); + (*cp != '\0' && strchr (commentCharsAtBOL, *cp) != NULL); /* skip comments */ if (isComment) continue; /* skip white space */ - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; /* read symbol */ @@ -352,15 +794,15 @@ static void findAsmTags (void) } else if (anyKindEntryInScope (CORK_NIL, vStringValue (name), - K_MACRO)) + K_MACRO, true)) labelCandidate = false; } - if (! isspace ((int) *cp) && *cp != '\0') + if (! isspace (*cp) && *cp != '\0') continue; /* skip white space */ - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; /* skip leading dot */ @@ -374,26 +816,101 @@ static void findAsmTags (void) /* attempt second read of symbol */ if (vStringLength (name) == 0) { - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; cp = readSymbol (cp, name); nameFollows = true; } - makeAsmTag (name, operator, labelCandidate, nameFollows, directive, - &lastMacroCorkIndex); + int r = makeAsmTag (name, operator, labelCandidate, nameFollows, directive, + §ionScope, ¯oScope); + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e && e->langType == Lang_asm + && e->kindIndex == K_MACRO && isRoleAssigned(e, ROLE_DEFINITION_INDEX)) + readMacroParameters (r, e, cp); } - cppTerminate (); + if (useCpp) + cppTerminate (); vStringDelete (name); vStringDelete (operator); } +static void findAsmTags (void) +{ + findAsmTagsCommon (useCPreProcessor); +} + static void initialize (const langType language) { Lang_asm = language; } +/* dummy definition to allow/require an extra semicolon */ +#define END_DEF(sfx) typedef int ctags_dummy_int_type_ignore_me_##sfx + +#define defineCommentCharSetter(PREPOS, POS) \ + static bool asmSetCommentChars##PREPOS##POS (const langType language CTAGS_ATTR_UNUSED, \ + const char *optname CTAGS_ATTR_UNUSED, const char *arg) \ + { \ + if (commentChars##PREPOS##POS != defaultCommentChar##PREPOS##POS) \ + eFree ((void *)commentChars##PREPOS##POS); \ + \ + if (arg && (arg[0] != '\0')) \ + commentChars##PREPOS##POS = eStrdup (arg); \ + else \ + commentChars##PREPOS##POS = defaultCommentChar##PREPOS##POS; \ + return true; \ + } END_DEF(asmSetCommentChars##PREPOS##POS) + +defineCommentCharSetter(At, BOL); +defineCommentCharSetter(In, MOL); + +static bool asmSetExtraLinesepChars(const langType language CTAGS_ATTR_UNUSED, + const char *optname CTAGS_ATTR_UNUSED, const char *arg) +{ + if (extraLinesepChars != defaultExtraLinesepChars) + eFree ((void *)extraLinesepChars); + + if (arg && (arg[0] != '\0')) + extraLinesepChars = eStrdup (arg); + else + extraLinesepChars = defaultExtraLinesepChars; + + return true; +} + +static bool setUseCPreProcessor(const langType language CTAGS_ATTR_UNUSED, + const char *name, const char *arg) +{ + useCPreProcessor = paramParserBool (arg, useCPreProcessor, + name, "parameter"); + return true; +} + +static paramDefinition AsmParams [] = { + { + .name = "commentCharsAtBOL", + .desc = "line comment chraracters at the beginning of line ([" DEFAULT_COMMENT_CHARS_BOL "])", + .handleParam = asmSetCommentCharsAtBOL, + }, + { + .name = "commentCharsInMOL", + .desc = "line comment chraracters in the beginning of line ([" DEFAULT_COMMENT_CHARS_MOL "])", + .handleParam = asmSetCommentCharsInMOL, + }, + { + .name = "extraLinesepChars", + .desc = "extra characters used as a line separator ([])", + .handleParam = asmSetExtraLinesepChars, + }, + { + .name = "useCPreProcessor", + .desc = "run CPreProcessor parser for extracting macro definitions ([true] or false)", + .handleParam = setUseCPreProcessor, + }, +}; + extern parserDefinition* AsmParser (void) { static const char *const extensions [] = { @@ -406,10 +923,17 @@ extern parserDefinition* AsmParser (void) "*.[xX][68][68]", NULL }; - static selectLanguage selectors[] = { selectByArrowOfR, - NULL }; + static selectLanguage selectors[] = { selectByArrowOfR, NULL }; + + static parserDependency dependencies [] = { + { DEPTYPE_FOREIGNER, "LdScript", NULL }, + }; parserDefinition* def = parserNew ("Asm"); + def->versionCurrent = 1; + def->versionAge = 0; + def->dependencies = dependencies; + def->dependencyCount = ARRAY_SIZE (dependencies); def->kindTable = AsmKinds; def->kindCount = ARRAY_SIZE (AsmKinds); def->extensions = extensions; @@ -420,5 +944,11 @@ extern parserDefinition* AsmParser (void) def->keywordCount = ARRAY_SIZE (AsmKeywords); def->selectLanguage = selectors; def->useCork = CORK_QUEUE | CORK_SYMTAB; + def->fieldTable = AsmFields; + def->fieldCount = ARRAY_SIZE (AsmFields); + + def->paramTable = AsmParams; + def->paramCount = ARRAY_SIZE(AsmParams); + return def; } diff --git a/ctags/parsers/autoit.c b/ctags/parsers/autoit.c index 56df9a0974..c6f0618470 100644 --- a/ctags/parsers/autoit.c +++ b/ctags/parsers/autoit.c @@ -124,15 +124,15 @@ static int makeSimpleAutoItTag (const NestingLevels *const nls, static void setEndLine (const NestingLevels *const nls) { NestingLevel *nl = nestingLevelsGetCurrent (nls); - tagEntryInfo *entry; - if (nl && (entry = getEntryInCorkQueue (nl->corkIndex)) != NULL) - entry->extensionFields.endLine = getInputLineNumber (); + if (nl) + setTagEndLineToCorkEntry (nl->corkIndex, + getInputLineNumber ()); } static void skipSpaces (const unsigned char **p) { - while (isspace ((int) **p)) + while (isspace (**p)) ++(*p); } @@ -143,9 +143,9 @@ static int parseFunc (const unsigned char *p, NestingLevels *nls) vString *name = vStringNew (); skipSpaces (&p); - while (isIdentChar ((int) *p)) + while (isIdentChar (*p)) { - vStringPut (name, (int) *p); + vStringPut (name, *p); ++p; } skipSpaces (&p); @@ -154,7 +154,7 @@ static int parseFunc (const unsigned char *p, NestingLevels *nls) vString *signature = vStringNew (); do - vStringPut (signature, (int) *p); + vStringPut (signature, *p); while (*p != ')' && *p++); k = makeAutoItTag (nls, name, K_FUNCTION, signature); @@ -190,15 +190,13 @@ static void findAutoItTags (void) else if (match (p, "region", &p)) { skipSpaces (&p); - while (*p != '\0') - { - vStringPut (name, (int) *p); - ++p; - } - if (vStringLength(name) > 0) + if (*p != '\0') { - int k = makeSimpleAutoItTag (nls, name, K_REGION); + int k; + + vStringCatS (name, (const char *) p); + k = makeSimpleAutoItTag (nls, name, K_REGION); nestingLevelsPush (nls, k); vStringClear (name); } @@ -220,7 +218,7 @@ static void findAutoItTags (void) ++p; while (*p != '\0' && *p != '>' && *p != '"') { - vStringPut (name, (int) *p); + vStringPut (name, *p); ++p; } if (vStringLength(name) > 0) @@ -284,9 +282,9 @@ static void findAutoItTags (void) if (*p == '$') { p++; - while (isIdentChar ((int) *p)) + while (isIdentChar (*p)) { - vStringPut (name, (int) *p); + vStringPut (name, *p); ++p; } if (vStringLength(name) > 0) @@ -315,5 +313,7 @@ parserDefinition *AutoItParser (void) def->extensions = extensions; def->parser = findAutoItTags; def->useCork = CORK_QUEUE; + def->versionCurrent = 1; + def->versionAge = 0; return def; } diff --git a/ctags/parsers/basic.c b/ctags/parsers/basic.c index b1286e64d2..98bb231336 100644 --- a/ctags/parsers/basic.c +++ b/ctags/parsers/basic.c @@ -8,6 +8,9 @@ * (BlitzMax), PureBasic and FreeBasic language files. For now, this is kept * quite simple - but feel free to ask for more things added any time - * patches are of course most welcome. + * + * FreeBasic: + * - https://www.freebasic.net/wiki/DocToc */ /* @@ -17,9 +20,12 @@ #include +#include "entry.h" +#include "keyword.h" #include "parse.h" #include "read.h" #include "routines.h" +#include "trace.h" #include "vstring.h" /* @@ -31,56 +37,158 @@ typedef enum { K_LABEL, K_TYPE, K_VARIABLE, - K_ENUM + K_ENUM, + K_NAMESPACE, } BasicKind; -typedef struct { - char const *token; - BasicKind kind; -} KeyWord; +typedef enum { + BASIC_FUNCTION_DECL, +} basciFunctionRole; + +static roleDefinition BasicFunctionRoles [] = { + { true, "decl", "declared" }, +}; static kindDefinition BasicKinds[] = { {true, 'c', "constant", "constants"}, - {true, 'f', "function", "functions"}, + {true, 'f', "function", "functions", + .referenceOnly = false, ATTACH_ROLES(BasicFunctionRoles)}, {true, 'l', "label", "labels"}, {true, 't', "type", "types"}, {true, 'v', "variable", "variables"}, - {true, 'g', "enum", "enumerations"} + {true, 'g', "enum", "enumerations"}, + {true, 'n', "namespace", "namespace"}, }; -static KeyWord basic_keywords[] = { +/* To force to trigger bugs, we make the orders of + * enum eKeywordID and BasicKind different. */ +enum eKeywordID { + KEYWORD_ENUM, + KEYWORD_CONST, + KEYWORD_FUNCTION, + KEYWORD_LABEL, + KEYWORD_TYPE, + KEYWORD_VARIABLE, + KEYWORD_NAMESPACE, + KEYWORD_END, + KEYWORD_ACCESS, + KEYWORD_DECLARE, +}; +typedef int keywordId; /* to allow KEYWORD_NONE */ + +static const keywordTable BasicKeywordTable[] = { /* freebasic */ - {"const", K_CONST}, - {"dim", K_VARIABLE}, - {"common", K_VARIABLE}, - {"function", K_FUNCTION}, - {"sub", K_FUNCTION}, - {"private sub", K_FUNCTION}, - {"public sub", K_FUNCTION}, - {"private function", K_FUNCTION}, - {"public function", K_FUNCTION}, - {"property", K_FUNCTION}, - {"constructor", K_FUNCTION}, - {"destructor", K_FUNCTION}, - {"type", K_TYPE}, - {"enum", K_ENUM}, + {"const", KEYWORD_CONST}, + {"dim", KEYWORD_VARIABLE}, + {"common", KEYWORD_VARIABLE}, + {"function", KEYWORD_FUNCTION}, + {"sub", KEYWORD_FUNCTION}, + {"private", KEYWORD_ACCESS}, + {"public", KEYWORD_ACCESS}, + {"property", KEYWORD_FUNCTION}, + {"constructor", KEYWORD_FUNCTION}, + {"destructor", KEYWORD_FUNCTION}, + {"type", KEYWORD_TYPE}, + {"enum", KEYWORD_ENUM}, + {"namespace", KEYWORD_NAMESPACE}, + {"end", KEYWORD_END}, + {"declare", KEYWORD_DECLARE}, /* blitzbasic, purebasic */ - {"global", K_VARIABLE}, + {"global", KEYWORD_VARIABLE}, /* purebasic */ - {"newlist", K_VARIABLE}, - {"procedure", K_FUNCTION}, - {"interface", K_TYPE}, - {"structure", K_TYPE}, + {"newlist", KEYWORD_VARIABLE}, + {"procedure", KEYWORD_FUNCTION}, + {"interface", KEYWORD_TYPE}, + {"structure", KEYWORD_TYPE}, +}; - {NULL, 0} +struct BasicKeywordAttr { + int kind; +} keywordAttrs [] = { + [KEYWORD_ENUM] = { + .kind = K_ENUM, + }, + [KEYWORD_CONST] = { + .kind = K_CONST, + }, + [KEYWORD_FUNCTION] = { + .kind = K_FUNCTION, + }, + [KEYWORD_LABEL] = { + .kind = K_LABEL, + }, + [KEYWORD_TYPE] = { + .kind = K_TYPE, + }, + [KEYWORD_VARIABLE] = { + .kind = K_VARIABLE, + }, + [KEYWORD_NAMESPACE] = { + .kind = K_NAMESPACE, + }, + [KEYWORD_END] = { + .kind = KIND_GHOST_INDEX, + }, + [KEYWORD_ACCESS] = { + .kind = KIND_GHOST_INDEX, + }, + [KEYWORD_DECLARE] = { + .kind = KIND_GHOST_INDEX, + }, }; +struct matchState { + const char *access; + bool end; + bool declaration; +}; + +static int currentScope; /* * FUNCTION DEFINITIONS */ +static void pushScope (int corkIndex) +{ +#ifdef DEBUG + tagEntryInfo *e = getEntryInCorkQueue (corkIndex); + TRACE_PRINT ("scope push: %s<%d>", e? e->name: "-", corkIndex); +#endif + currentScope = corkIndex; +} + +static void popScope (void) +{ + tagEntryInfo *e = getEntryInCorkQueue (currentScope); +#ifdef DEBUG + TRACE_PRINT ("scope pop: %s<%d>", e? e->name: "-", currentScope); +#endif + if (e) + { + setTagEndLine (e, getInputLineNumber()); + currentScope = e->extensionFields.scopeIndex; + } + else + currentScope = CORK_NIL; +} + +static void updateScope (int corkIndex, int kindIndex, int keywordId) +{ + if (corkIndex != CORK_NIL && kindIndex == K_NAMESPACE) + pushScope (corkIndex); + else if (corkIndex == CORK_NIL && kindIndex == K_NAMESPACE) + popScope (); +} + +static int keywordToKind (keywordId keywordId) +{ + if (keywordId == KEYWORD_NONE) + return KIND_GHOST_INDEX; + return keywordAttrs [keywordId].kind; +} + static const char *skipToMatching (char begin, char end, const char *pos) { int counter = 1; @@ -116,11 +224,25 @@ static const char *nextPos (const char *pos) return pos; } -static bool isIdentChar (char c) +static bool isIdentChar (int c) { return c && !isspace (c) && c != '(' && c != ',' && c != '='; } +static int makeBasicRefTag (vString *name, int kindIndex, int roleIndex) +{ + int r = makeSimpleRefTag (name, kindIndex, roleIndex); + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e) + e->extensionFields.scopeIndex = currentScope; + return r; +} + +static int makeBasicTag (vString *name, int kindIndex) +{ + return makeBasicRefTag (name, kindIndex, ROLE_DEFINITION_INDEX); +} + /* Match the name of a dim or const starting at pos. */ static void extract_dim (char const *pos, BasicKind kind) { @@ -129,7 +251,7 @@ static void extract_dim (char const *pos, BasicKind kind) if (strncasecmp (pos, "shared", 6) == 0) pos += 6; /* skip keyword "shared" */ - while (isspace (*pos)) + while (isspace ((unsigned char) *pos)) pos++; /* capture "dim as String str" */ @@ -137,32 +259,33 @@ static void extract_dim (char const *pos, BasicKind kind) { pos += 2; /* skip keyword "as" */ - while (isspace (*pos)) + while (isspace ((unsigned char) *pos)) pos++; - while (!isspace (*pos) && *pos) /* skip next part which is a type */ + while (!isspace ((unsigned char) *pos) && *pos) /* skip next part which is a type */ pos++; - while (isspace (*pos)) + while (isspace ((unsigned char) *pos)) pos++; /* now we are at the name */ } /* capture "dim as foo ptr bar" */ - if (strncasecmp (pos, "ptr", 3) == 0 && isspace(*(pos+3))) + if (strncasecmp (pos, "ptr", 3) == 0 && isspace((unsigned char) *(pos+3))) { pos += 3; /* skip keyword "ptr" */ - while (isspace (*pos)) + while (isspace ((unsigned char) *pos)) pos++; } /* capture "dim as string * 4096 chunk" */ if (strncmp (pos, "*", 1) == 0) { pos += 1; /* skip "*" */ - while (isspace (*pos) || isdigit(*pos) || ispunct(*pos)) + while (isspace ((unsigned char) *pos) || isdigit((unsigned char) *pos) || + ispunct((unsigned char) *pos)) pos++; } - for (; isIdentChar (*pos); pos++) + for (; isIdentChar ((unsigned char) *pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + makeBasicTag (name, kind); /* if the line contains a ',', we have multiple declarations */ while (*pos && strchr (pos, ',')) @@ -174,69 +297,119 @@ static void extract_dim (char const *pos, BasicKind kind) if (*pos == '\'') break; /* break if we are in a comment */ - while (isspace (*pos) || *pos == ',') + while (isspace ((unsigned char) *pos) || *pos == ',') pos++; if (*pos == '\'') break; /* break if we are in a comment */ vStringClear (name); - for (; isIdentChar (*pos); pos++) + for (; isIdentChar ((unsigned char) *pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + makeBasicTag (name, kind); } vStringDelete (name); } /* Match the name of a tag (function, variable, type, ...) starting at pos. */ -static void extract_name (char const *pos, BasicKind kind) +static int extract_name (char const *pos, BasicKind kind, struct matchState *state) { + int r = CORK_NIL; vString *name = vStringNew (); - for (; isIdentChar (*pos); pos++) + for (; isIdentChar ((unsigned char) *pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + if (state && state->declaration) + { + if (kind == K_FUNCTION) + r = makeBasicRefTag (name, kind, BASIC_FUNCTION_DECL); + } + else + r = makeBasicTag (name, kind); vStringDelete (name); + + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e && state && state->access) + e->extensionFields.access = eStrdup (state->access); + + return r; } /* Match a keyword starting at p (case insensitive). */ -static bool match_keyword (const char *p, KeyWord const *kw) +static void match_state_reset (struct matchState *state) +{ + state->access = NULL; + state->end = false; + state->declaration =false; +} + +static bool match_keyword (const char **cp, vString *buf, + struct matchState *state) { - size_t i; - const char *old_p; - for (i = 0; i < strlen (kw->token); i++) + const char *p; + + for (p = *cp; *p != '\0' && !isspace((unsigned char)*p); p++) { - if (tolower (p[i]) != kw->token[i]) - return false; + int c = tolower ((unsigned char)*p); + vStringPut (buf, c); } - p += i; - old_p = p; - while (isspace (*p)) + int kw = lookupKeyword (vStringValue (buf), getInputLanguage ()); + if (kw == KEYWORD_NONE) + return false; + + const char *old_p = p; + while (isspace ((unsigned char) *p)) p++; - /* create tags only if there is some space between the keyword and the identifier */ - if (old_p == p) - return false; + if (kw == KEYWORD_ACCESS) + { + state->access = vStringValue(buf)[1] == 'r'? "private": "public"; + *cp = p; + return true; + } + else if (kw == KEYWORD_END) + { + state->end = true; + *cp = p; + return true; + } + else if (kw == KEYWORD_DECLARE) + { + state->declaration = true; + *cp = p; + return true; + } - if (kw->kind == K_VARIABLE) - extract_dim (p, kw->kind); /* extract_dim adds the found tag(s) */ - else - extract_name (p, kw->kind); - return true; + int kind = keywordToKind (kw); + int index = CORK_NIL; + if (!state->end) + { + /* create tags only if there is some space between the keyword and the identifier */ + if (kind != KIND_GHOST_INDEX && old_p == p) + return false; + + if (kind == K_VARIABLE) + extract_dim (p, kind); /* extract_dim adds the found tag(s) */ + else + index = extract_name (p, kind, state); + } + + updateScope (index, kind, kw); + + return false; } /* Match a "label:" style label. */ static void match_colon_label (char const *p) { char const *end = p + strlen (p) - 1; - while (isspace (*end)) + while (isspace ((unsigned char) *end)) end--; if (*end == ':') { - vString *name = vStringNew (); - vStringNCatS (name, p, end - p); - makeSimpleTag (name, K_LABEL); + vString *name = vStringNewNInit (p, end - p); + makeBasicTag (name, K_LABEL); vStringDelete (name); } } @@ -244,20 +417,21 @@ static void match_colon_label (char const *p) /* Match a ".label" style label. */ static void match_dot_label (char const *p) { - if (*p == '.') - extract_name (p + 1, K_LABEL); + extract_name (p + 1, K_LABEL, NULL); } static void findBasicTags (void) { const char *line; + currentScope = CORK_NIL; + vString *buf = vStringNew (); + while ((line = (const char *) readLineFromInputFile ()) != NULL) { const char *p = line; - KeyWord const *kw; - while (isspace (*p)) + while (isspace ((unsigned char) *p)) p++; /* Empty line? */ @@ -266,7 +440,7 @@ static void findBasicTags (void) /* REM comment? */ if (strncasecmp (p, "REM", 3) == 0 && - (isspace (*(p + 3)) || *(p + 3) == '\0')) + (isspace ((unsigned char) *(p + 3)) || *(p + 3) == '\0')) continue; /* Single-quote comment? */ @@ -274,8 +448,12 @@ static void findBasicTags (void) continue; /* In Basic, keywords always are at the start of the line. */ - for (kw = basic_keywords; kw->token; kw++) - if (match_keyword (p, kw)) break; + struct matchState state; + match_state_reset (&state); + do + vStringClear (buf); + while (match_keyword (&p, buf, &state)); + /* Is it a label? */ if (*p == '.') @@ -283,6 +461,7 @@ static void findBasicTags (void) else match_colon_label (p); } + vStringDelete (buf); } parserDefinition *BasicParser (void) @@ -293,5 +472,8 @@ parserDefinition *BasicParser (void) def->kindCount = ARRAY_SIZE (BasicKinds); def->extensions = extensions; def->parser = findBasicTags; + def->keywordTable = BasicKeywordTable; + def->keywordCount = ARRAY_SIZE (BasicKeywordTable); + def->useCork = CORK_QUEUE; return def; } diff --git a/ctags/parsers/bibtex.c b/ctags/parsers/bibtex.c index 088481260f..e23a377456 100644 --- a/ctags/parsers/bibtex.c +++ b/ctags/parsers/bibtex.c @@ -20,6 +20,7 @@ #include #include "debug.h" +#include "bibtex.h" #include "entry.h" #include "keyword.h" #include "parse.h" @@ -33,7 +34,7 @@ #define isType(token,t) (bool) ((token)->type == (t)) #define isKeyword(token,k) (bool) ((token)->keyword == (k)) #define isIdentChar(c) \ - (isalpha (c) || isdigit (c) || (c) == '_' || (c) == '-' || (c) == '+' || (c) == ':') + (isalpha (c) || isdigit (c) || (c) == '_' || (c) == '-' || (c) == '+' || (c) == ':' || (c) == '.' || (c) == '/') /* * DATA DECLARATIONS @@ -169,17 +170,13 @@ static void deleteToken (tokenInfo *const token) */ static void makeBibTag (tokenInfo *const token, bibKind kind) { - if (BibKinds [kind].enabled) - { - const char *const name = vStringValue (token->string); - tagEntryInfo e; - initTagEntry (&e, name, kind); + const char *const name = vStringValue (token->string); + tagEntryInfo e; + initTagEntry (&e, name, kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); - makeTagEntry (&e); - } + makeTagEntry (&e); } /* @@ -275,7 +272,7 @@ static void copyToken (tokenInfo *const dest, tokenInfo *const src) * Scanning functions */ -static bool parseTag (tokenInfo *const token, bibKind kind) +static bool parseTag (tokenInfo *const token, bool foreignKeyword, int kind) { tokenInfo * const name = newToken (); vString * currentid; @@ -291,7 +288,7 @@ static bool parseTag (tokenInfo *const token, bibKind kind) * a comma brace for the tag name. * */ - if (isType (token, TOKEN_KEYWORD)) + if (isType (token, TOKEN_KEYWORD) || foreignKeyword) { copyToken (name, token); if (!readToken (token)) @@ -330,6 +327,34 @@ static bool parseTag (tokenInfo *const token, bibKind kind) return eof; } +static bool mayParseTokenInSubparser (tokenInfo *const token) +{ + bool eof = false; + subparser *sub; + + if (*vStringValue (token->string) != '@') + return eof; + + foreachSubparser (sub, true) + { + bibTexSubparser *bibsub = (bibTexSubparser *)sub; + if (bibsub->isKeywordForTagging) + { + int kind; + enterSubparser (sub); + kind = bibsub->isKeywordForTagging (bibsub, + vStringValue (token->string) + 1); + if (kind != KIND_GHOST_INDEX) + eof = parseTag (token, true, kind); + leaveSubparser (); + if (kind != KIND_GHOST_INDEX) + break; + } + } + + return eof; +} + static void parseBibFile (tokenInfo *const token) { bool eof = false; @@ -339,62 +364,66 @@ static void parseBibFile (tokenInfo *const token) if (!readToken (token)) break; + bibKind kind = KIND_GHOST_INDEX;; + if (isType (token, TOKEN_KEYWORD)) { switch (token->keyword) { case KEYWORD_article: - eof = parseTag (token, BIBTAG_ARTICLE); + kind = BIBTAG_ARTICLE; break; case KEYWORD_book: - eof = parseTag (token, BIBTAG_BOOK); + kind = BIBTAG_BOOK; break; case KEYWORD_booklet: - eof = parseTag (token, BIBTAG_BOOKLET); + kind = BIBTAG_BOOKLET; break; case KEYWORD_conference: - eof = parseTag (token, BIBTAG_CONFERENCE); + kind = BIBTAG_CONFERENCE; break; case KEYWORD_inbook: - eof = parseTag (token, BIBTAG_INBOOK); + kind = BIBTAG_INBOOK; break; case KEYWORD_incollection: - eof = parseTag (token, BIBTAG_INCOLLECTION); + kind = BIBTAG_INCOLLECTION; break; case KEYWORD_inproceedings: - eof = parseTag (token, BIBTAG_INPROCEEDINGS); + kind = BIBTAG_INPROCEEDINGS; break; case KEYWORD_manual: - eof = parseTag (token, BIBTAG_MANUAL); + kind = BIBTAG_MANUAL; break; case KEYWORD_mastersthesis: - eof = parseTag (token, BIBTAG_MASTERSTHESIS); + kind = BIBTAG_MASTERSTHESIS; break; case KEYWORD_misc: - eof = parseTag (token, BIBTAG_MISC); + kind = BIBTAG_MISC; break; case KEYWORD_phdthesis: - eof = parseTag (token, BIBTAG_PHDTHESIS); + kind = BIBTAG_PHDTHESIS; break; case KEYWORD_proceedings: - eof = parseTag (token, BIBTAG_PROCEEDINGS); + kind = BIBTAG_PROCEEDINGS; break; case KEYWORD_string: - eof = parseTag (token, BIBTAG_STRING); + kind = BIBTAG_STRING; break; case KEYWORD_techreport: - eof = parseTag (token, BIBTAG_TECHREPORT); + kind = BIBTAG_TECHREPORT; break; case KEYWORD_unpublished: - eof = parseTag (token, BIBTAG_UNPUBLISHED); - break; - default: + kind = BIBTAG_UNPUBLISHED; break; } } - if (eof) - break; - } while (true); + + if (kind != KIND_GHOST_INDEX) + eof = parseTag (token, false, kind); + else + eof = mayParseTokenInSubparser(token); + + } while (!eof); } static void initialize (const langType language) diff --git a/ctags/parsers/bibtex.h b/ctags/parsers/bibtex.h new file mode 100644 index 0000000000..b094144794 --- /dev/null +++ b/ctags/parsers/bibtex.h @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2023, Masatake YAMATO +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +*/ +#ifndef CTAGS_PARSER_BIBTEX_H +#define CTAGS_PARSER_BIBTEX_H + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include "subparser.h" + +typedef struct sBibTexSubparser bibTexSubparser; + +struct sBibTexSubparser { + subparser subparser; + int (* isKeywordForTagging) (bibTexSubparser *, + const char *string); +}; + +#endif /* CTAGS_PARSER_BIBTEX_H */ diff --git a/ctags/parsers/clojure.c b/ctags/parsers/clojure.c index f609cd0487..d404bb7524 100644 --- a/ctags/parsers/clojure.c +++ b/ctags/parsers/clojure.c @@ -29,27 +29,31 @@ static kindDefinition ClojureKinds[] = { static int isNamespace (const char *strp) { - return strncmp (++strp, "ns", 2) == 0 && isspace (strp[2]); + return strncmp (++strp, "ns", 2) == 0 && isspace ((unsigned char) strp[2]); } static int isCoreNamespace (const char *strp) { - return strncmp (++strp, "clojure.core/ns", 15) == 0 && isspace (strp[15]); + return strncmp (++strp, "clojure.core/ns", 15) == 0 && + isspace ((unsigned char) strp[15]); } static int isFunction (const char *strp) { - return (strncmp (++strp, "defn", 4) == 0 && isspace (strp[4])); + return (strncmp (++strp, "defn", 4) == 0 && + isspace ((unsigned char) strp[4])); } static int isCoreFunction (const char *strp) { - return (strncmp (++strp, "clojure.core/defn", 17) == 0 && isspace (strp[17])); + return (strncmp (++strp, "clojure.core/defn", 17) == 0 && + isspace ((unsigned char) strp[17])); } static int isQuote (const char *strp) { - return strncmp (++strp, "quote", 5) == 0 && isspace (strp[5]); + return strncmp (++strp, "quote", 5) == 0 && + isspace ((unsigned char) strp[5]); } static void functionName (vString * const name, const char *dbp) @@ -61,12 +65,12 @@ static void functionName (vString * const name, const char *dbp) else if (*dbp == '(' && isQuote (dbp)) { dbp += 7; - while (isspace (*dbp)) + while (isspace ((unsigned char) *dbp)) dbp++; } - for (p = dbp; *p != '\0' && *p != '(' && !isspace ((int) *p) && *p != ')'; - p++) + for (p = dbp; *p != '\0' && *p != '(' && !isspace ((unsigned char) *p) + && *p != ')'; p++) vStringPut (name, *p); } @@ -109,14 +113,7 @@ static int makeNamespaceTag (vString * const name, const char *dbp) dbp = skipMetadata (dbp); functionName (name, dbp); if (vStringLength (name) > 0 && ClojureKinds[K_NAMESPACE].enabled) - { - tagEntryInfo e; - initTagEntry (&e, vStringValue (name), K_NAMESPACE); - e.lineNumber = getInputLineNumber (); - e.filePosition = getInputFilePosition (); - - return makeTagEntry (&e); - } + return makeSimpleTag (name, K_NAMESPACE); else return CORK_NIL; } @@ -129,9 +126,6 @@ static void makeFunctionTag (vString * const name, const char *dbp, int scope_in { tagEntryInfo e; initTagEntry (&e, vStringValue (name), K_FUNCTION); - e.lineNumber = getInputLineNumber (); - e.filePosition = getInputFilePosition (); - e.extensionFields.scopeIndex = scope_index; makeTagEntry (&e); } @@ -139,9 +133,9 @@ static void makeFunctionTag (vString * const name, const char *dbp, int scope_in static void skipToSymbol (const char **p) { - while (**p != '\0' && !isspace ((int) **p)) + while (**p != '\0' && !isspace ((unsigned char) **p)) *p = *p + 1; - while (isspace ((int) **p)) + while (isspace ((unsigned char) **p)) *p = *p + 1; } @@ -155,7 +149,7 @@ static void findClojureTags (void) { vStringClear (name); - while (isspace (*p)) + while (isspace ((unsigned char) *p)) p++; if (*p == '(') diff --git a/ctags/parsers/cobol.c b/ctags/parsers/cobol.c index 56e7b21e14..926b0fa6a6 100644 --- a/ctags/parsers/cobol.c +++ b/ctags/parsers/cobol.c @@ -180,7 +180,7 @@ static void cblppAppendLine (vString *buffer, if (*indicator == '-') { vStringStripTrailing (buffer); - while (isspace (*lineStart)) + while (isspace ((unsigned char) *lineStart)) lineStart++; } @@ -242,8 +242,7 @@ static void initCOBOLRefTagEntry (tagEntryInfo *e, const char *name, const cobolKind kind, const int role) { initRefTagEntry (e, name, kind, role); - e->lineNumber = CblInputState.lineNumber; - e->filePosition = CblInputState.filePosition; + updateTagLine (e, CblInputState.lineNumber, CblInputState.filePosition); } static void initCOBOLTagEntry (tagEntryInfo *e, const char *name, const cobolKind kind) @@ -328,14 +327,14 @@ static void findCOBOLTags (const CobolFormat format) do { \ const char READ_LITERAL__q = isQuote (*line) ? *line++ : 0; \ READ_WHILE (word, (READ_LITERAL__q && READ_LITERAL__q != *line) || \ - isIdentifierChar (*line)); \ + isIdentifierChar ((unsigned char) *line)); \ if (READ_LITERAL__q && READ_LITERAL__q == *line) \ line++; \ keyword = lookupCaseKeyword (word, Lang_cobol); \ } while (0) #define READ_WORD(word, keyword) \ do { \ - READ_WHILE (word, isIdentifierChar (*line)); \ + READ_WHILE (word, isIdentifierChar ((unsigned char) *line)); \ keyword = lookupCaseKeyword (word, Lang_cobol); \ } while (0) #define READ_KEYWORD(keyword) \ @@ -343,7 +342,8 @@ static void findCOBOLTags (const CobolFormat format) char READ_KEYWORD__word[64]; \ READ_WORD (READ_KEYWORD__word, keyword); \ } while (0) -#define SKIP_SPACES() do { while (isspace (*line)) line++; } while (0) +#define SKIP_SPACES() \ + do { while (isspace ((unsigned char) *line)) line++; } while (0) SKIP_SPACES (); READ_WORD (word, keyword); diff --git a/ctags/parsers/cpreprocessor.c b/ctags/parsers/cpreprocessor.c index 7b88e5821c..6fb10c586a 100644 --- a/ctags/parsers/cpreprocessor.c +++ b/ctags/parsers/cpreprocessor.c @@ -25,6 +25,7 @@ #include "vstring.h" #include "param.h" #include "parse.h" +#include "promise.h" #include "xtag.h" #include "cxx/cxx_debug.h" @@ -38,6 +39,25 @@ /* * DATA DECLARATIONS */ +enum eCppCharacters { + /* white space characters */ + SPACE = ' ', + NEWLINE = '\n', + CRETURN = '\r', + FORMFEED = '\f', + TAB = '\t', + VTAB = '\v', + + /* some hard to read characters */ + DOUBLE_QUOTE = '"', + SINGLE_QUOTE = '\'', + BACKSLASH = '\\', + + /* symbolic representations, above 0xFF not to conflict with any byte */ + STRING_SYMBOL = CPP_STRING_SYMBOL, + CHAR_SYMBOL = CPP_CHAR_SYMBOL +}; + typedef enum { COMMENT_NONE, COMMENT_C, COMMENT_CPLUS, COMMENT_D } Comment; enum eCppLimits { @@ -45,6 +65,21 @@ enum eCppLimits { MaxDirectiveName = 10 }; +/* For tracking __ASSEMBLER__ area. */ +enum eIfSubstate { + IF_IF, + IF_IFDEF, + IF_IFNDEF, + IF_ELSE, + IF_ELIF, + IF_ENDIF, +}; + +struct asmAreaInfo { + enum eIfSubstate ifSubstate; + unsigned long line; +}; + /* Defines the one nesting level of a preprocessor conditional. */ typedef struct sConditionalInfo { @@ -53,6 +88,9 @@ typedef struct sConditionalInfo { bool branchChosen; /* branch already selected */ bool ignoring; /* current ignore state */ int enterExternalParserBlockNestLevel; /* the parser state when entering this conditional: used only by cxx */ + + /* tracking __ASSEMBLER__ area */ + struct asmAreaInfo asmArea; } conditionalInfo; enum eState { @@ -60,6 +98,7 @@ enum eState { DRCTV_DEFINE, /* "#define" encountered */ DRCTV_HASH, /* initial '#' read; determine directive */ DRCTV_IF, /* "#if" or "#ifdef" encountered */ + DRCTV_ELIF, /* "#elif" encountered */ DRCTV_PRAGMA, /* #pragma encountered */ DRCTV_UNDEF, /* "#undef" encountered */ DRCTV_INCLUDE, /* "#include" encountered */ @@ -88,6 +127,7 @@ typedef struct sCppState { bool useClientLangDefineMacroKindIndex; int defineMacroKindIndex; int macroUndefRoleIndex; + int macroConditionRoleIndex; bool useClientLangMacroParamKindIndex; int macroParamKindIndex; @@ -101,6 +141,8 @@ typedef struct sCppState { struct sDirective { enum eState state; /* current directive being processed */ + enum eIfSubstate ifsubstate; /* For tracking __ASSEMBLER__. + * assigned only when state == DICTV_IF */ bool accept; /* is a directive syntactically permitted? */ vString * name; /* macro name */ unsigned int nestLevel; /* level 0 is not used */ @@ -115,10 +157,12 @@ typedef struct sCppState { typedef enum { CPREPRO_MACRO_KIND_UNDEF_ROLE, + CPREPRO_MACRO_KIND_CONDITION_ROLE, } cPreProMacroRole; static roleDefinition CPREPROMacroRoles [] = { RoleTemplateUndef, + RoleTemplateCondition, }; @@ -201,6 +245,7 @@ static cppState Cpp = { .useClientLangDefineMacroKindIndex = false, .defineMacroKindIndex = CPREPRO_MACRO, .macroUndefRoleIndex = CPREPRO_MACRO_KIND_UNDEF_ROLE, + .macroConditionRoleIndex = CPREPRO_MACRO_KIND_CONDITION_ROLE, .useClientLangMacroParamKindIndex = false, .macroParamKindIndex = CPREPRO_PARAM, .useClientLangHeaderKindIndex = false, @@ -251,6 +296,7 @@ static void cppInitCommon(langType clientLang, const bool hasSingleQuoteLiteralNumbers, int defineMacroKindIndex, int macroUndefRoleIndex, + int macroConditionRoleIndex, int macroParamKindIndex, int headerKindIndex, int headerSystemRoleIndex, int headerLocalRoleIndex, @@ -288,6 +334,7 @@ static void cppInitCommon(langType clientLang, Cpp.useClientLangDefineMacroKindIndex = true; Cpp.macroUndefRoleIndex = macroUndefRoleIndex; + Cpp.macroConditionRoleIndex = macroConditionRoleIndex; Cpp.macrodefFieldIndex = macrodefFieldIndex; } else @@ -296,6 +343,7 @@ static void cppInitCommon(langType clientLang, Cpp.useClientLangDefineMacroKindIndex = false; Cpp.macroUndefRoleIndex = CPREPRO_MACRO_KIND_UNDEF_ROLE; + Cpp.macroConditionRoleIndex = CPREPRO_MACRO_KIND_CONDITION_ROLE; Cpp.macrodefFieldIndex = CPreProFields [F_MACRODEF].ftype; } @@ -355,6 +403,7 @@ extern void cppInit (const bool state, const bool hasAtLiteralStrings, const bool hasSingleQuoteLiteralNumbers, int defineMacroKindIndex, int macroUndefRoleIndex, + int macroConditionRoleIndex, int macroParamKindIndex, int headerKindIndex, int headerSystemRoleIndex, int headerLocalRoleIndex, @@ -364,7 +413,8 @@ extern void cppInit (const bool state, const bool hasAtLiteralStrings, cppInitCommon (client, state, hasAtLiteralStrings, hasCxxRawLiteralStrings, hasSingleQuoteLiteralNumbers, - defineMacroKindIndex, macroUndefRoleIndex, macroParamKindIndex, + defineMacroKindIndex, macroUndefRoleIndex, macroConditionRoleIndex, + macroParamKindIndex, headerKindIndex, headerSystemRoleIndex, headerLocalRoleIndex, macrodefFieldIndex); } @@ -430,6 +480,20 @@ extern void cppEndStatement (void) /* This puts a character back into the input queue for the input File. */ extern void cppUngetc (const int c) { + if (c == STRING_SYMBOL || c == CHAR_SYMBOL) + { + Assert(Cpp.charOrStringContents != NULL); + cppUngetc(c == STRING_SYMBOL ? '"' : '\''); + cppUngetString(vStringValue(Cpp.charOrStringContents), vStringLength(Cpp.charOrStringContents)); + cppUngetc(c == STRING_SYMBOL ? '"' : '\''); + vStringClear(Cpp.charOrStringContents); + return; + } + else if (c == EOF) + { + return; + } + if(!Cpp.ungetPointer) { // no unget data @@ -467,7 +531,7 @@ extern void cppUngetc (const int c) Cpp.ungetDataSize++; } -int cppUngetBufferSize() +int cppUngetBufferSize(void) { return Cpp.ungetBufferSize; } @@ -716,6 +780,7 @@ static bool pushConditional (const bool firstBranchChosen) ! firstBranchChosen && ! BraceFormat && (ifdef->singleBranch || !doesExaminCodeWithInIf0Branch))); ifdef->enterExternalParserBlockNestLevel = externalParserBlockNestLevel; + ifdef->asmArea.line = 0; ignoreBranch = ifdef->ignoring; } return ignoreBranch; @@ -826,7 +891,7 @@ static void makeIncludeTag (const char *const name, bool systemHeader) if (isLanguageRoleEnabled(lang, Cpp.headerKindIndex, role_index)) { - if (doesCPreProRunAsStandaloneParser (CPREPRO_HEADER)) + if (standing_alone) pushLanguage (Cpp.lang); initRefTagEntry (&e, name, Cpp.headerKindIndex, role_index); @@ -834,39 +899,41 @@ static void makeIncludeTag (const char *const name, bool systemHeader) e.truncateLineAfterTag = true; makeTagEntry (&e); - if (doesCPreProRunAsStandaloneParser (CPREPRO_HEADER)) + if (standing_alone) popLanguage (); } } -static void makeParamTag (vString *name, short nth, bool placeholder) +static int makeParamTag (vString *name, short nth, bool placeholder) { bool standing_alone = doesCPreProRunAsStandaloneParser(CPREPRO_MACRO); - langType lang = standing_alone ? Cpp.lang: Cpp.clientLang; Assert (Cpp.macroParamKindIndex != KIND_GHOST_INDEX); - int r; - pushLanguage (lang); - r = makeSimpleTag (name, Cpp.macroParamKindIndex); - popLanguage (); + if (standing_alone) + pushLanguage (Cpp.lang); + int r = makeSimpleTag (name, Cpp.macroParamKindIndex); + if (standing_alone) + popLanguage (); tagEntryInfo *e = getEntryInCorkQueue (r); if (e) { e->extensionFields.nth = nth; if (placeholder) - e->placeholder = 1; + markTagAsPlaceholder (e, placeholder); } + return r; } -static void regenreateSignatureFromParameters (vString * buffer, int from, int to) +static void makeSignatureStringFromParameters (vString * buffer, intArray *parameters) { vStringPut(buffer, '('); - for (int pindex = from; pindex < to; pindex++) + for (size_t i = 0; i < intArrayCount (parameters); i++) { + int pindex = intArrayItem (parameters, i); tagEntryInfo *e = getEntryInCorkQueue (pindex); - if (e && !isTagExtra (e)) + if (e) { vStringCatS (buffer, e->name); vStringPut (buffer, ','); @@ -907,6 +974,7 @@ static int directiveDefine (const int c, bool undef) if (p == '(') { + intArray *params = intArrayNew (); vString *param = vStringNew (); int param_start = (int)countEntryInCorkQueue(); do { @@ -921,7 +989,8 @@ static int directiveDefine (const int c, bool undef) if (vStringLength (param) > 0) { - makeParamTag (param, nth++, vStringChar(param, 0) == '.'); + int r = makeParamTag (param, nth++, vStringChar(param, 0) == '.'); + intArrayAdd (params, r); vStringClear (param); } if (p == '\\') @@ -933,18 +1002,18 @@ static int directiveDefine (const int c, bool undef) if (p == ')') { vString *signature = vStringNew (); - regenreateSignatureFromParameters (signature, param_start, param_end); + makeSignatureStringFromParameters (signature, params); r = makeDefineTag (vStringValue (Cpp.directive.name), vStringValue (signature), undef); vStringDelete (signature); } else r = makeDefineTag (vStringValue (Cpp.directive.name), NULL, undef); + intArrayDelete (params); tagEntryInfo *e = getEntryInCorkQueue (r); if (e) { - e->lineNumber = lineNumber; - e->filePosition = filePosition; + updateTagLine (e, lineNumber, filePosition); patchScopeFieldOfParameters (param_start, param_end, r); } } @@ -996,10 +1065,62 @@ static void directivePragma (int c) Cpp.directive.state = DRCTV_NONE; } -static bool directiveIf (const int c) +/* + * __ASSEMBLER__ ("3.7.1 Standard Predefined Macros" in GNU cpp info), + * __ASSEMBLY__ (Used in Linux kernel) + */ +static bool isAssemblerBlock (int c) +{ + if (c != '_') + return false; + + bool r = false; + vString *cond = vStringNew (); + readIdentifier (c, cond); + if (strcmp (vStringValue (cond), "__ASSEMBLER__") == 0 + || strcmp (vStringValue (cond), "__ASSEMBLY__") == 0) + r = true; + + CXX_DEBUG_PRINT("ASSEMBLER[%s]: %s", r? "true": "false", vStringValue(cond)); + + size_t len = vStringLength (cond); + /* Pushing back to the stream. + * The first character is not read in this function. + * So don't touch the character here. */ + for (size_t i = len; i > 1; i--) + { + c = vStringChar (cond, i - 1); + cppUngetc (c); + } + + vStringDelete (cond); + return r; +} + +static bool directiveIf (const int c, enum eIfSubstate if_substate) { + static langType asmLang = LANG_IGNORE; + if (asmLang == LANG_IGNORE) + asmLang = getNamedLanguage ("Asm", 0); + DebugStatement ( const bool ignore0 = isIgnore (); ) - const bool ignore = pushConditional ((bool) (c != '0')); + bool firstBranchChosen = (bool) (c != '0'); + bool assemblerBlock = false; + if (Cpp.clientLang != asmLang && firstBranchChosen) + { + assemblerBlock = isAssemblerBlock(c); + if (assemblerBlock && if_substate != IF_IFNDEF) + firstBranchChosen = false; + } + + CXX_DEBUG_PRINT("firstBranchChosen: %d", firstBranchChosen); + const bool ignore = pushConditional (firstBranchChosen); + if (assemblerBlock) + { + conditionalInfo *ifdef = currentConditional (); + ifdef->asmArea.ifSubstate = if_substate; + ifdef->asmArea.line = getInputLineNumber(); + } Cpp.directive.state = DRCTV_NONE; DebugStatement ( debugCppNest (true, Cpp.directive.nestLevel); @@ -1008,6 +1129,10 @@ static bool directiveIf (const int c) return ignore; } +static void directiveElif (const int c) +{ + Cpp.directive.state = DRCTV_NONE; +} static void directiveInclude (const int c) { @@ -1021,6 +1146,36 @@ static void directiveInclude (const int c) Cpp.directive.state = DRCTV_NONE; } +static void promiseOrPrepareAsm (conditionalInfo *ifdef, enum eIfSubstate currentState) +{ + if (!ifdef->asmArea.line) + return; + + if (((ifdef->asmArea.ifSubstate == IF_IF || ifdef->asmArea.ifSubstate == IF_IFDEF) + && (currentState == IF_ELSE || currentState == IF_ELIF || currentState == IF_ENDIF)) + || ((ifdef->asmArea.ifSubstate == IF_ELSE) + && (currentState == IF_ENDIF))) + { + unsigned long start = ifdef->asmArea.line + 1; + unsigned long end = getInputLineNumber (); + + if (start < end) + makePromise ("Asm", start, 0, end, 0, start); + + ifdef->asmArea.line = 0; + } + else if (ifdef->asmArea.ifSubstate == IF_IFNDEF) + { + if (currentState == IF_ELIF) + ifdef->asmArea.line = 0; + else if (currentState == IF_ELSE) + { + ifdef->asmArea.ifSubstate = IF_ELSE; + ifdef->asmArea.line = getInputLineNumber (); + } + } +} + static bool directiveHash (const int c) { bool ignore = false; @@ -1035,19 +1190,33 @@ static bool directiveHash (const int c) else if (stringMatch (directive, "undef")) Cpp.directive.state = DRCTV_UNDEF; else if (strncmp (directive, "if", (size_t) 2) == 0) + { Cpp.directive.state = DRCTV_IF; + Cpp.directive.ifsubstate = IF_IF; + if (directive[2] == 'd') + Cpp.directive.ifsubstate = IF_IFDEF; + else if (directive[2] == 'n') + Cpp.directive.ifsubstate = IF_IFNDEF; + } else if (stringMatch (directive, "elif") || stringMatch (directive, "else")) { + enum eIfSubstate s = (directive[2] == 's')? IF_ELSE: IF_ELIF; + conditionalInfo *ifdef = currentConditional (); + promiseOrPrepareAsm (ifdef, s); + ignore = setIgnore (isIgnoreBranch ()); CXX_DEBUG_PRINT("Found #elif or #else: ignore is %d",ignore); - if (! ignore && stringMatch (directive, "else")) + if (! ignore && s == IF_ELSE) chooseBranch (); - Cpp.directive.state = DRCTV_NONE; + Cpp.directive.state = (s == IF_ELIF)? DRCTV_ELIF: DRCTV_NONE; DebugStatement ( if (ignore != ignore0) debugCppIgnore (ignore); ) } else if (stringMatch (directive, "endif")) { + conditionalInfo *ifdef = currentConditional (); + promiseOrPrepareAsm (ifdef, IF_ENDIF); + DebugStatement ( debugCppNest (false, Cpp.directive.nestLevel); ) ignore = popConditional (); Cpp.directive.state = DRCTV_NONE; @@ -1063,7 +1232,7 @@ static bool directiveHash (const int c) /* Handles a pre-processor directive whose first character is given by "c". */ -static bool handleDirective (const int c, int *macroCorkIndex) +static bool handleDirective (const int c, int *macroCorkIndex, bool *inspect_conidtion) { bool ignore = isIgnore (); @@ -1074,10 +1243,17 @@ static bool handleDirective (const int c, int *macroCorkIndex) *macroCorkIndex = directiveDefine (c, false); break; case DRCTV_HASH: ignore = directiveHash (c); break; - case DRCTV_IF: ignore = directiveIf (c); break; + case DRCTV_IF: + ignore = directiveIf (c, Cpp.directive.ifsubstate); + *inspect_conidtion = true; + break; + case DRCTV_ELIF: + directiveElif (c); + *inspect_conidtion = true; + break; case DRCTV_PRAGMA: directivePragma (c); break; - case DRCTV_UNDEF: directiveUndef (c); break; - case DRCTV_INCLUDE: directiveInclude (c); break; + case DRCTV_UNDEF: directiveUndef (c); break; + case DRCTV_INCLUDE: directiveInclude (c); break; } return ignore; } @@ -1193,10 +1369,19 @@ static int skipToEndOfString (bool ignoreBackslash) { if (c == BACKSLASH && ! ignoreBackslash) { - vStringPutWithLimit (Cpp.charOrStringContents, c, 1024); - c = cppGetcFromUngetBufferOrFile (); /* throw away next character, too */ - if (c != EOF) - vStringPutWithLimit (Cpp.charOrStringContents, c, 1024); + int c0 = cppGetcFromUngetBufferOrFile (); + if (c0 == '\n') + continue; + if (c0 == EOF) + break; + + if (vStringPutWithLimit (Cpp.charOrStringContents, c, 1024)) + { + if (vStringPutWithLimit (Cpp.charOrStringContents, c0, 1024)) + continue; + /* delete the last back slash at the end of the vstring. */ + vStringChop(Cpp.charOrStringContents); + } } else if (c == DOUBLE_QUOTE) break; @@ -1259,7 +1444,7 @@ static int skipToEndOfCxxRawLiteralString (void) * special character to symbolically represent a generic character. * Also detects Vera numbers that include a base specifier (ie. 'b1010). */ -static int skipToEndOfChar () +static int skipToEndOfChar (void) { int c; int count = 0, veraBase = '\0'; @@ -1271,10 +1456,19 @@ static int skipToEndOfChar () ++count; if (c == BACKSLASH) { - vStringPutWithLimit (Cpp.charOrStringContents, c, 10); - c = cppGetcFromUngetBufferOrFile (); /* throw away next character, too */ - if (c != EOF) - vStringPutWithLimit (Cpp.charOrStringContents, c, 10); + int c0 = cppGetcFromUngetBufferOrFile (); + if (c0 == '\n') + continue; + if (c0 == EOF) + break; + + if (vStringPutWithLimit (Cpp.charOrStringContents, c, 10)) + { + if (vStringPutWithLimit (Cpp.charOrStringContents, c0, 10)) + continue; + /* delete the last back slash at the end of the vstring.*/ + vStringChop(Cpp.charOrStringContents); + } } else if (c == SINGLE_QUOTE) break; @@ -1307,14 +1501,79 @@ static int skipToEndOfChar () static void attachFields (int macroCorkIndex, unsigned long endLine, const char *macrodef) { tagEntryInfo *tag = getEntryInCorkQueue (macroCorkIndex); - if (!tag) + if (tag) + { + setTagEndLine (tag, endLine); + if (macrodef) + attachParserField (tag, Cpp.macrodefFieldIndex, macrodef); + } +} + +static vString * conditionMayFlush (vString* condition, bool del) +{ + bool standing_alone = doesCPreProRunAsStandaloneParser(CPREPRO_MACRO); + + if (condition == NULL) + return condition; + + size_t len = vStringLength(condition); + if (len > 0 + && (! ( + (len == 7 + && strcmp (vStringValue (condition), "defined") == 0) + ))) + { + if (standing_alone) + pushLanguage (Cpp.lang); + + makeSimpleRefTag (condition, Cpp.defineMacroKindIndex, Cpp.macroConditionRoleIndex); + + if (standing_alone) + popLanguage (); + } + + if (del) + { + vStringDelete (condition); + return NULL; + } + + vStringClear(condition); + return condition; +} + +static void conditionMayPut (vString *condition, int c) +{ + if (condition == NULL) return; - tag->extensionFields.endLine = endLine; - if (macrodef) - attachParserFieldToCorkEntry (macroCorkIndex, Cpp.macrodefFieldIndex, macrodef); + if (vStringLength (condition) > 0 + || (!isdigit(c))) + vStringPut(condition, c); } +extern void cppVStringPut (vString* string, const int c) +{ + if (c <= 0xff) + vStringPut (string, c); + else + { + char marker = '"'; + switch (c) + { + case CHAR_SYMBOL: + marker = '\''; + /* Fall through */ + case STRING_SYMBOL: + vStringPut (string, marker); + vStringCat (string, cppGetLastCharOrStringContents ()); + vStringPut (string, marker); + break; + default: + AssertNotReached(); + } + } +} /* This function returns the next character, stripping out comments, * C pre-processor directives, and the contents of single and double @@ -1328,6 +1587,7 @@ extern int cppGetc (void) int c; int macroCorkIndex = CORK_NIL; vString *macrodef = NULL; + vString *condition = NULL; do { @@ -1346,6 +1606,7 @@ extern int cppGetc (void) macrodef? vStringValue (macrodef): NULL); macroCorkIndex = CORK_NIL; } + condition = conditionMayFlush(condition, true); break; case TAB: @@ -1353,9 +1614,12 @@ extern int cppGetc (void) if (macrodef && vStringLength (macrodef) > 0 && vStringLast (macrodef) != ' ') vStringPut (macrodef, ' '); + condition = conditionMayFlush(condition, false); break; /* ignore most white space */ case NEWLINE: + if (directive) + condition = conditionMayFlush(condition, true); if (directive && ! ignore) { directive = false; @@ -1371,6 +1635,8 @@ extern int cppGetc (void) break; case DOUBLE_QUOTE: + condition = conditionMayFlush(condition, false); + if (Cpp.directive.state == DRCTV_INCLUDE) goto enter; else @@ -1392,6 +1658,8 @@ extern int cppGetc (void) break; case '#': + condition = conditionMayFlush(condition, false); + if (Cpp.directive.accept) { directive = true; @@ -1403,18 +1671,25 @@ extern int cppGetc (void) break; case SINGLE_QUOTE: + condition = conditionMayFlush(condition, false); + Cpp.directive.accept = false; c = skipToEndOfChar (); /* We assume none may want to know the content of the * literal; just put ''. */ if (macrodef) - vStringCatS (macrodef, "''"); - + { + vStringPut (macrodef, '\''); + vStringCat (macrodef, Cpp.charOrStringContents); + vStringPut (macrodef, '\''); + } break; case '/': { + condition = conditionMayFlush(condition, false); + const Comment comment = isComment (); if (comment == COMMENT_C) @@ -1438,6 +1713,8 @@ extern int cppGetc (void) case BACKSLASH: { + condition = conditionMayFlush(condition, false); + int next = cppGetcFromUngetBufferOrFile (); if (next == NEWLINE) @@ -1453,6 +1730,8 @@ extern int cppGetc (void) case '?': { + condition = conditionMayFlush(condition, false); + int next = cppGetcFromUngetBufferOrFile (); if (next != '?') { @@ -1490,6 +1769,8 @@ extern int cppGetc (void) */ case '<': { + condition = conditionMayFlush(condition, false); + /* Quoted from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3237.html: ------ @@ -1537,6 +1818,8 @@ extern int cppGetc (void) } case ':': { + condition = conditionMayFlush(condition, false); + int next = cppGetcFromUngetBufferOrFile (); if (next == '>') c = ']'; @@ -1550,6 +1833,8 @@ extern int cppGetc (void) } case '%': { + condition = conditionMayFlush(condition, false); + int next = cppGetcFromUngetBufferOrFile (); switch (next) { @@ -1567,6 +1852,8 @@ extern int cppGetc (void) default: if (c == '@' && Cpp.hasAtLiteralStrings) { + condition = conditionMayFlush(condition, false); + int next = cppGetcFromUngetBufferOrFile (); if (next == DOUBLE_QUOTE) { @@ -1585,6 +1872,8 @@ extern int cppGetc (void) } else if (c == 'R' && Cpp.hasCxxRawLiteralStrings) { + conditionMayPut(condition, c); + /* OMG!11 HACK!!11 Get the previous character. * * We need to know whether the previous character was an identifier or not, @@ -1644,22 +1933,34 @@ extern int cppGetc (void) cppUngetc(next); if (macrodef) vStringPut (macrodef, c); - + conditionMayPut(condition, c); } else { if (macrodef) vStringPut (macrodef, c); + if (isalnum(c) || c == '_') + conditionMayPut(condition, c); + else + condition = conditionMayFlush(condition, false); } enter: Cpp.directive.accept = false; if (directive) { - ignore = handleDirective (c, ¯oCorkIndex); + bool inspect_conidtion = false; + ignore = handleDirective (c, ¯oCorkIndex, &inspect_conidtion); if (Cpp.macrodefFieldIndex != FIELD_UNKNOWN && macroCorkIndex != CORK_NIL && macrodef == NULL) macrodef = vStringNew (); + if (condition == NULL + && inspect_conidtion) + { + condition = vStringNew (); + if (isalpha(c) || c == '_') + conditionMayPut(condition, c); + } } break; } @@ -1668,7 +1969,10 @@ extern int cppGetc (void) if (macrodef) vStringDelete (macrodef); - DebugStatement ( debugPutc (DEBUG_CPP, c); ) + if (condition) + vStringDelete (condition); + + DebugStatement ( cppDebugPutc (DEBUG_CPP, c); ) DebugStatement ( if (c == NEWLINE) debugPrintf (DEBUG_CPP, "%6ld: ", getInputLineNumber () + 1); ) @@ -1678,7 +1982,8 @@ extern int cppGetc (void) static void findCppTags (void) { cppInitCommon (Cpp.lang, 0, false, false, false, - KIND_GHOST_INDEX, 0, KIND_GHOST_INDEX, + KIND_GHOST_INDEX, 0, 0, + KIND_GHOST_INDEX, KIND_GHOST_INDEX, 0, 0, FIELD_UNKNOWN); @@ -1701,7 +2006,7 @@ static bool buildMacroInfoFromTagEntry (int corkIndex, { cppMacroInfo **info = data; - if (entry->langType == Cpp.clientLang + if ((entry->langType == Cpp.clientLang || entry->langType == Cpp.lang) && entry->kindIndex == Cpp.defineMacroKindIndex && isRoleAssigned (entry, ROLE_DEFINITION_INDEX)) { @@ -1722,7 +2027,7 @@ static bool buildMacroInfoFromTagEntry (int corkIndex, return true; } -extern cppMacroInfo * cppFindMacroFromSymtab (const char *const name) +static cppMacroInfo * cppFindMacroFromSymtab (const char *const name) { cppMacroInfo *info = NULL; foreachEntriesInScope (CORK_NIL, name, buildMacroInfoFromTagEntry, &info); @@ -1807,6 +2112,54 @@ extern vString * cppBuildMacroReplacement( return ret; } +// We stop applying macro replacements if the unget buffer gets too big +// as it is a sign of recursive macro expansion +#define CPP_PARSER_MAXIMUM_UNGET_BUFFER_SIZE_FOR_MACRO_REPLACEMENTS 65536 + +extern void cppBuildMacroReplacementWithPtrArrayAndUngetResult( + cppMacroInfo * macro, + const ptrArray * args) +{ + vString * replacement = NULL; + + // Detect other cases of nasty macro expansion that cause + // the unget buffer to grow fast (but the token chain to grow slowly) + // -D'p=a' -D'a=p+p' + if ((cppUngetBufferSize() < CPP_PARSER_MAXIMUM_UNGET_BUFFER_SIZE_FOR_MACRO_REPLACEMENTS) + && macro->replacements) + { + int argc = 0; + const char ** argv = NULL; + + if (args) + { + argc = ptrArrayCount (args); + argv = (const char **)eMalloc (sizeof(char *) * argc); + for (int i = 0; i < argc; i++) + { + TRACE_PRINT("Arg[%d] for %s<%p>: %s", + i, macro->name, macro, ptrArrayItem (args, i)); + argv[i] = ptrArrayItem (args, i); + } + } + + replacement = cppBuildMacroReplacement(macro, argv, argc); + + if (argv) + eFree ((void *)argv); + } + + if (replacement) + { + cppUngetStringBuiltByMacro(vStringValue(replacement), vStringLength(replacement), + macro); + TRACE_PRINT("Replacement for %s<%p>: %s", macro->name, macro, vStringValue (replacement)); + vStringDelete (replacement); + } + else + TRACE_PRINT("Replacement for %s<%p>: ", macro->name, macro); + +} static void saveIgnoreToken(const char * ignoreToken) { @@ -1869,8 +2222,8 @@ static void saveIgnoreToken(const char * ignoreToken) } info->useCount = 0; info->next = NULL; - - hashTablePutItem(cmdlineMacroTable,eStrndup(tokenBegin,tokenEnd - tokenBegin),info); + info->name = eStrndup(tokenBegin,tokenEnd - tokenBegin); + hashTablePutItem(cmdlineMacroTable,info->name,info); verbose (" ignore token: %s\n", ignoreToken); } @@ -1896,7 +2249,7 @@ static cppMacroInfo * saveMacro(hashTable *table, const char * macro) return NULL; } - if(!(isalpha(*c) || (*c == '_' || (*c == '$') ))) + if(!(isalpha((unsigned char) *c) || (*c == '_' || (*c == '$') ))) { CXX_DEBUG_LEAVE_TEXT("Macro does not start with an alphanumeric character"); return NULL; // must be a sequence of letters and digits @@ -1904,7 +2257,7 @@ static cppMacroInfo * saveMacro(hashTable *table, const char * macro) const char * identifierBegin = c; - while(*c && (isalnum(*c) || (*c == '_') || (*c == '$') )) + while(*c && (isalnum((unsigned char) *c) || (*c == '_') || (*c == '$') )) c++; const char * identifierEnd = c; @@ -2037,14 +2390,14 @@ static cppMacroInfo * saveMacro(hashTable *table, const char * macro) while(*c) { - if(isalpha(*c) || (*c == '_')) + if(isalpha((unsigned char) *c) || (*c == '_')) { if(c > begin) ADD_CONSTANT_REPLACEMENT(begin,c - begin); const char * tokenBegin = c; - while(*c && (isalnum(*c) || (*c == '_'))) + while(*c && (isalnum((unsigned char) *c) || (*c == '_'))) c++; // check if it is a parameter @@ -2165,7 +2518,8 @@ static cppMacroInfo * saveMacro(hashTable *table, const char * macro) ADD_CONSTANT_REPLACEMENT(begin,c - begin); } - hashTablePutItem(table,eStrndup(identifierBegin,identifierEnd - identifierBegin),info); + info->name = eStrndup(identifierBegin,identifierEnd - identifierBegin); + hashTablePutItem(table,info->name,info); CXX_DEBUG_LEAVE(); return info; @@ -2184,6 +2538,7 @@ static void freeMacroInfo(cppMacroInfo * info) pPart = pPart->next; eFree(pPartToDelete); } + eFree(info->name); eFree(info); } @@ -2193,7 +2548,7 @@ static hashTable *makeMacroTable (void) 1024, hashCstrhash, hashCstreq, - eFree, + NULL, /* Keys refers values' name fields. */ (void (*)(void *))freeMacroInfo ); } @@ -2212,13 +2567,14 @@ static void finalizeCpp (const langType language, bool initialized) } } -static void CpreProExpandMacrosInInput (const langType language CTAGS_ATTR_UNUSED, const char *name, const char *arg) +static bool CpreProExpandMacrosInInput (const langType language CTAGS_ATTR_UNUSED, const char *name, const char *arg) { doesExpandMacros = paramParserBool (arg, doesExpandMacros, name, "parameter"); + return true; } -static void CpreProInstallIgnoreToken (const langType language CTAGS_ATTR_UNUSED, const char *optname CTAGS_ATTR_UNUSED, const char *arg) +static bool CpreProInstallIgnoreToken (const langType language CTAGS_ATTR_UNUSED, const char *optname CTAGS_ATTR_UNUSED, const char *arg) { if (arg == NULL || arg[0] == '\0') { @@ -2233,9 +2589,10 @@ static void CpreProInstallIgnoreToken (const langType language CTAGS_ATTR_UNUSED cmdlineMacroTable = makeMacroTable (); saveIgnoreToken(arg); } + return true; } -static void CpreProInstallMacroToken (const langType language CTAGS_ATTR_UNUSED, const char *optname CTAGS_ATTR_UNUSED, const char *arg) +static bool CpreProInstallMacroToken (const langType language CTAGS_ATTR_UNUSED, const char *optname CTAGS_ATTR_UNUSED, const char *arg) { if (arg == NULL || arg[0] == '\0') { @@ -2250,30 +2607,32 @@ static void CpreProInstallMacroToken (const langType language CTAGS_ATTR_UNUSED, cmdlineMacroTable = makeMacroTable (); saveMacro(cmdlineMacroTable, arg); } + return true; } -static void CpreProSetIf0 (const langType language CTAGS_ATTR_UNUSED, const char *name, const char *arg) +static bool CpreProSetIf0 (const langType language CTAGS_ATTR_UNUSED, const char *name, const char *arg) { doesExaminCodeWithInIf0Branch = paramParserBool (arg, doesExaminCodeWithInIf0Branch, name, "parameter"); + return true; } -static parameterHandlerTable CpreProParameterHandlerTable [] = { +static paramDefinition CpreProParams [] = { { .name = "if0", .desc = "examine code within \"#if 0\" branch (true or [false])", - .handleParameter = CpreProSetIf0, + .handleParam = CpreProSetIf0, }, { .name = "ignore", .desc = "a token to be specially handled", - .handleParameter = CpreProInstallIgnoreToken, + .handleParam = CpreProInstallIgnoreToken, }, { .name = "define", .desc = "define replacement for an identifier (name(params,...)=definition)", - .handleParameter = CpreProInstallMacroToken, + .handleParam = CpreProInstallMacroToken, }, { .name = "_expand", .desc = "expand macros if their definitions are in the current C/C++/CUDA input file (true or [false])", - .handleParameter = CpreProExpandMacrosInInput, + .handleParam = CpreProExpandMacrosInInput, } }; @@ -2289,9 +2648,23 @@ extern parserDefinition* CPreProParser (void) def->fieldTable = CPreProFields; def->fieldCount = ARRAY_SIZE (CPreProFields); - def->parameterHandlerTable = CpreProParameterHandlerTable; - def->parameterHandlerCount = ARRAY_SIZE(CpreProParameterHandlerTable); + def->paramTable = CpreProParams; + def->paramCount = ARRAY_SIZE(CpreProParams); def->useCork = CORK_QUEUE | CORK_SYMTAB; return def; } + +#ifdef DEBUG +extern void cppDebugPutc (const int level, const int c) +{ + if (debug (level) && c != EOF) + { + if (c == STRING_SYMBOL) printf ("\"string\""); + else if (c == CHAR_SYMBOL) printf ("'c'"); + else putchar (c); + + fflush (stdout); + } +} +#endif diff --git a/ctags/parsers/cpreprocessor.h b/ctags/parsers/cpreprocessor.h index fe031bd21a..3bd78bb654 100644 --- a/ctags/parsers/cpreprocessor.h +++ b/ctags/parsers/cpreprocessor.h @@ -13,12 +13,18 @@ * INCLUDE FILES */ #include "general.h" /* must always come first */ + +#include "debug.h" +#include "ptrarray.h" #include "types.h" #include "vstring.h" /* * MACROS */ +/* symbolic representations, above 0xFF not to conflict with any byte */ +#define CPP_STRING_SYMBOL ('S' + 0xff) +#define CPP_CHAR_SYMBOL ('C' + 0xff) /* * cppIs... macros are for the value returned from cppGetc(). Don't @@ -64,6 +70,7 @@ #define RoleTemplateUndef { true, "undef", "undefined" } +#define RoleTemplateCondition { false, "condition", "used in part of #if/#ifdef/#elif conditions" } #define RoleTemplateSystem { true, "system", "system header" } #define RoleTemplateLocal { true, "local", "local header" } @@ -83,6 +90,7 @@ extern void cppInit (const bool state, const bool hasSingleQuoteLiteralNumbers, int defineMacroKindIndex, int macroUndefRoleIndex, + int macroConditionRoleIndex, int headerKindIndex, int headerSystemRoleIndex, int headerLocalRoleIndex, int macroParamKindIndex, @@ -92,11 +100,16 @@ extern void cppTerminate (void); extern void cppBeginStatement (void); extern void cppEndStatement (void); extern void cppUngetc (const int c); -extern int cppUngetBufferSize(); +extern int cppUngetBufferSize(void); extern void cppUngetString(const char * string,int len); extern int cppGetc (void); extern const vString * cppGetLastCharOrStringContents (void); +/* + * Replacement for vStringPut that can handle c > 0xff + */ +extern void cppVStringPut (vString * string, const int c); + /* Notify the external parser state for the purpose of conditional * branch choice. The CXX parser stores the block level here. */ extern void cppPushExternalParserBlock(void); @@ -113,6 +126,7 @@ typedef struct sCppMacroReplacementPartInfo { } cppMacroReplacementPartInfo; typedef struct sCppMacroInfo { + char *name; /* the name of macro. Useful for debugging. */ bool hasParameterList; /* true if the macro has a trailing () */ cppMacroReplacementPartInfo * replacements; int useCount; @@ -134,4 +148,14 @@ extern vString * cppBuildMacroReplacement( int parameterCount ); +/* Do the same as cppBuildMacroReplacement with ptrArray, + * and unget the result of expansion to input cpp stream. */ +extern void cppBuildMacroReplacementWithPtrArrayAndUngetResult( + cppMacroInfo * macro, + const ptrArray * args); + +#ifdef DEBUG +extern void cppDebugPutc (const int level, const int c); +#endif + #endif /* CTAGS_MAIN_GET_H */ diff --git a/ctags/parsers/css.c b/ctags/parsers/css.c index 60fbb6befe..024a48346b 100644 --- a/ctags/parsers/css.c +++ b/ctags/parsers/css.c @@ -58,7 +58,7 @@ static void parseSelector (vString *const string, const int firstChar) int c = firstChar; do { - vStringPut (string, (char) c); + vStringPut (string, c); c = getcFromInputFile (); } while (isSelectorChar (c)); ungetcToInputFile (c); @@ -190,9 +190,7 @@ static void findCssTags (void) vString *selector = vStringNew (); do { - if (vStringLength (selector) > 0) - vStringPut (selector, ' '); - vStringCat (selector, token.string); + vStringJoin (selector, ' ', token.string); kind = classifySelector (token.string); lineNumber = getInputLineNumber (); @@ -227,8 +225,7 @@ static void findCssTags (void) tagEntryInfo e; initTagEntry (&e, vStringValue (selector), kind); - e.lineNumber = lineNumber; - e.filePosition = filePosition; + updateTagLine (&e, lineNumber, filePosition); makeTagEntry (&e); } diff --git a/ctags/parsers/cxx/cxx.c b/ctags/parsers/cxx/cxx.c index a96d756a80..e155c7f2ce 100644 --- a/ctags/parsers/cxx/cxx.c +++ b/ctags/parsers/cxx/cxx.c @@ -82,6 +82,10 @@ parserDefinition * CParser (void) static selectLanguage selectors[] = { selectByObjectiveCKeywords, NULL }; + static parserDependency dependencies [] = { + [0] = { DEPTYPE_FOREIGNER, "LdScript", NULL }, + }; + parserDefinition* def = parserNew("C"); def->kindTable = cxxTagGetCKindDefinitions(); @@ -93,8 +97,13 @@ parserDefinition * CParser (void) def->initialize = cxxCParserInitialize; def->finalize = cxxParserCleanup; def->selectLanguage = selectors; + def->dependencies = dependencies; + def->dependencyCount = ARRAY_SIZE (dependencies); def->useCork = CORK_QUEUE|CORK_SYMTAB; // We use corking to block output until the end of file + def->versionCurrent = 1; + def->versionAge = 1; + return def; } @@ -111,6 +120,7 @@ parserDefinition * CppParser (void) }; static parserDependency dependencies [] = { { DEPTYPE_KIND_OWNER, "C" }, + { DEPTYPE_FOREIGNER, "LdScript", NULL }, }; static selectLanguage selectors[] = { selectByObjectiveCKeywords, NULL }; @@ -130,6 +140,9 @@ parserDefinition * CppParser (void) def->selectLanguage = selectors; def->useCork = CORK_QUEUE|CORK_SYMTAB; // We use corking to block output until the end of file + def->versionCurrent = 1; + def->versionAge = 1; + return def; } @@ -159,5 +172,8 @@ parserDefinition * CUDAParser (void) def->selectLanguage = NULL; def->useCork = CORK_QUEUE|CORK_SYMTAB; // We use corking to block output until the end of file + def->versionCurrent = 1; + def->versionAge = 1; + return def; } diff --git a/ctags/parsers/cxx/cxx_debug.c b/ctags/parsers/cxx/cxx_debug.c index c6ec096554..52be0bf963 100644 --- a/ctags/parsers/cxx/cxx_debug.c +++ b/ctags/parsers/cxx/cxx_debug.c @@ -115,6 +115,12 @@ static void cxxDebugDumpToken0 (CXXToken *pToken, cxxDebugDumpToken0 (pToken->pPrev, pTokenChecker, pTokenChecker, false); debugDec(); + debugIndent (); + fprintf (stderr, " sideChain: "); + debugInc(); + cxxDebugDumpChain0 (pToken->pChain, pTokenChecker, pTokenChecker, false); + debugDec(); + debugIndent (); fprintf (stderr, ">\n"); } @@ -123,11 +129,11 @@ typedef void (* cxxDebugDumpCommonFunc)(void *, struct circularRefChecker *, struct circularRefChecker *, bool); -void cxxDebugDumpCommon (void *data, - void (* func)(void *, - struct circularRefChecker *, - struct circularRefChecker *, - bool)) +static void cxxDebugDumpCommon (void *data, + void (* func)(void *, + struct circularRefChecker *, + struct circularRefChecker *, + bool)) { static struct circularRefChecker *pTokenChecker; static struct circularRefChecker *pChainChecker; @@ -172,6 +178,7 @@ const char* cxxDebugScopeDecode(enum CXXScopeType scope) [CXXScopeTypeVariable] = "variable", [CXXScopeTypePrototype] = "prototype", [CXXScopeTypeTypedef] = "typedef", + [CXXScopeTypeModule] = "module", }; if (CXXScopeTypeLAST > scope) return table[scope]; diff --git a/ctags/parsers/cxx/cxx_keyword.c b/ctags/parsers/cxx/cxx_keyword.c index 0eede461bb..f79b857722 100644 --- a/ctags/parsers/cxx/cxx_keyword.c +++ b/ctags/parsers/cxx/cxx_keyword.c @@ -34,7 +34,13 @@ enum CXXKeywordFlag // of the type itself. Keywords that do NOT have this flag simply cannot appear // in a variable declaration. // Examples: __global__, __host__, restrict, register... - CXXKeywordMayAppearInVariableDeclaration = (1 << 5) + CXXKeywordMayAppearInVariableDeclaration = (1 << 5), + // decltype, __typeof, __typeof__, and typeof + CXXKeywordIsDecltype = (1 << 6), + // keywords making the parsers too complicated; they are dropped in + // cxxParserParseNextToken(). + // Examples: __attribute__(), __declspec, ... + CXXKeywordMayDropInTokenizer = (1 << 7), }; typedef struct _CXXKeywordDescriptor @@ -50,7 +56,7 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { { "__attribute__", CXXLanguageC | CXXLanguageCPP | CXXLanguageCUDA, - CXXKeywordMayAppearInVariableDeclaration + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordMayDropInTokenizer }, { "__constant__", @@ -60,7 +66,7 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { { "__declspec", CXXLanguageC | CXXLanguageCPP | CXXLanguageCUDA, - CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames | CXXKeywordMayDropInTokenizer }, { "__device__", @@ -136,10 +142,35 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { CXXLanguageCPP, 0 }, + { + "__thread", + CXXLanguageC | CXXLanguageCPP, + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames, + }, + { + "__typeof", + CXXLanguageC | CXXLanguageCPP, + CXXKeywordIsDecltype | CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName + }, + { + "__typeof__", + CXXLanguageC | CXXLanguageCPP, + CXXKeywordIsDecltype | CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName + }, + { + "_Alignas", + CXXLanguageC, + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordMayDropInTokenizer, + }, + { + "_Thread_local", + CXXLanguageC, + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames, + }, { "alignas", - CXXLanguageCPP, - CXXKeywordMayAppearInVariableDeclaration + CXXLanguageC | CXXLanguageCPP, + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordMayDropInTokenizer, }, { "alignof", @@ -212,11 +243,21 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { CXXLanguageC | CXXLanguageCPP | CXXLanguageCUDA, CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName }, + { + "consteval", + CXXLanguageCPP, + CXXKeywordExcludeFromTypeNames + }, { "constexpr", CXXLanguageCPP, CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames }, + { + "constinit", + CXXLanguageCPP, + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames + }, { "const_cast", CXXLanguageCPP, @@ -230,7 +271,7 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { { "decltype", CXXLanguageCPP, - CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName + CXXKeywordIsDecltype | CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName }, { "default", @@ -276,7 +317,7 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { { "export", CXXLanguageCPP, - 0 + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames }, { "extern", @@ -334,6 +375,13 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { CXXLanguageC | CXXLanguageCPP | CXXLanguageCUDA, CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName }, + /* + { + "module", + CXXLanguageCPP, + 0 + }, + */ { "mutable", CXXLanguageCPP, @@ -465,7 +513,7 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { { "thread_local", CXXLanguageCPP, - CXXKeywordMayAppearInVariableDeclaration + CXXKeywordMayAppearInVariableDeclaration | CXXKeywordExcludeFromTypeNames }, { "throw", @@ -498,6 +546,11 @@ static CXXKeywordDescriptor g_aCXXKeywordTable[] = { CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName | CXXKeywordIsTypeRefMarker }, + { + "typeof", + CXXLanguageC | CXXLanguageCPP, + CXXKeywordIsDecltype | CXXKeywordMayAppearInVariableDeclaration | CXXKeywordFlagMayBePartOfTypeName + }, { "union", CXXLanguageC | CXXLanguageCPP | CXXLanguageCUDA, @@ -589,6 +642,18 @@ bool cxxKeywordIsDisabled(CXXKeyword eKeywordId) CXXKeywordIsDisabled; } +bool cxxKeywordIsDecltype(CXXKeyword eKeywordId) +{ + return g_aCXXKeywordTable[eKeywordId].uFlags & + CXXKeywordIsDecltype; +} + +bool cxxKeywordMayDropInTokenizer(CXXKeyword eKeywordId) +{ + return g_aCXXKeywordTable[eKeywordId].uFlags & + CXXKeywordMayDropInTokenizer; +} + bool cxxKeywordEnablePublicProtectedPrivate(bool bEnableIt) { bool bEnabledNow = diff --git a/ctags/parsers/cxx/cxx_keyword.h b/ctags/parsers/cxx/cxx_keyword.h index 14a108a1e1..624ce9dd64 100644 --- a/ctags/parsers/cxx/cxx_keyword.h +++ b/ctags/parsers/cxx/cxx_keyword.h @@ -33,7 +33,12 @@ typedef enum _CXXKeyword CXXKeyword__SHARED__, // CUDA CXXKeyword__STDCALL, // Microsoft C/C++ CXXKeyword__THISCALL, // Microsoft C/C++ - CXXKeywordALIGNAS, // (since C++11) + CXXKeyword__THREAD, // GCC (https://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Thread-Local.html#Thread-Local) + CXXKeyword__TYPEOF, // GCC accepts this. + CXXKeyword__TYPEOF__, // GCC (https://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Typeof.html#Typeof) + CXXKeyword_ALIGNAS, // C11 + CXXKeyword_THREAD_LOCAL, // C11 + CXXKeywordALIGNAS, // (since C++11, C11 (stdalign.h), C23) CXXKeywordALIGNOF, // (since C++11) //CXXKeywordAND, //CXXKeywordAND_EQ, @@ -52,7 +57,9 @@ typedef enum _CXXKeyword //CXXKeywordCOMPL, CXXKeywordCONCEPT, // Concepts TS CXXKeywordCONST, + CXXKeywordCONSTEVAL, // (since C++20) CXXKeywordCONSTEXPR, // (since C++11) + CXXKeywordCONSTINIT, // (since C++20) CXXKeywordCONST_CAST, CXXKeywordCONTINUE, CXXKeywordDECLTYPE, // (since C++11) @@ -76,6 +83,7 @@ typedef enum _CXXKeyword CXXKeywordINLINE, CXXKeywordINT, CXXKeywordLONG, + // CXXKeywordMODULE, CXXKeywordMUTABLE, CXXKeywordNAMESPACE, CXXKeywordNEW, @@ -112,6 +120,7 @@ typedef enum _CXXKeyword CXXKeywordTYPEDEF, CXXKeywordTYPEID, CXXKeywordTYPENAME, + CXXKeywordTYPEOF, // GCC (https://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Typeof.html#Typeof) CXXKeywordUNION, CXXKeywordUNSIGNED, CXXKeywordUSING, @@ -131,6 +140,8 @@ bool cxxKeywordIsTypeRefMarker(CXXKeyword eKeywordId); bool cxxKeywordExcludeFromTypeNames(CXXKeyword eKeywordId); bool cxxKeywordMayAppearInVariableDeclaration(CXXKeyword eKeywordId); bool cxxKeywordIsCPPSpecific(CXXKeyword eKeywordId); +bool cxxKeywordIsDecltype(CXXKeyword eKeywordId); +bool cxxKeywordMayDropInTokenizer(CXXKeyword eKeywordId); const char * cxxKeywordName(CXXKeyword eKeywordId); @@ -175,4 +186,4 @@ void cxxKeywordEnableFinal(bool bEnableIt); bool cxxKeywordIsDisabled(CXXKeyword eKeywordId); -#endif //!ctags_cxx_keyword_h_ \ No newline at end of file +#endif //!ctags_cxx_keyword_h_ diff --git a/ctags/parsers/cxx/cxx_parser.c b/ctags/parsers/cxx/cxx_parser.c index ec29454398..6db2800404 100644 --- a/ctags/parsers/cxx/cxx_parser.c +++ b/ctags/parsers/cxx/cxx_parser.c @@ -44,8 +44,9 @@ bool g_bFirstRun = true; // - Clear the token chain // - Reset "seen" keywords // -void cxxParserNewStatement(void) +void cxxParserNewStatementFull(bool bExported) { + CXX_DEBUG_PRINT("NEW STATE: %d", bExported); cxxTokenChainClear(g_cxx.pTokenChain); if(g_cxx.pTemplateTokenChain) { @@ -56,13 +57,18 @@ void cxxParserNewStatement(void) // we don't care about stale specializations as they // are destroyed wen the base template prefix is extracted } - g_cxx.uKeywordState = 0; + g_cxx.uKeywordState = bExported? CXXParserKeywordStateSeenExport: 0; // FIXME: this cpp handling of end/statement is kind of broken: // it works only because the moon is in the correct phase. cppEndStatement(); } +void cxxParserNewStatement(void) +{ + cxxParserNewStatementFull(false); +} + // // Parse a subchain of input delimited by matching pairs selected from // [],(),{} and <>. @@ -423,11 +429,7 @@ void cxxParserSetEndLineForTagInCorkQueue(int iCorkQueueIndex,unsigned long lEnd { CXX_DEBUG_ASSERT(iCorkQueueIndex > CORK_NIL,"The cork queue index is not valid"); - tagEntryInfo * tag = getEntryInCorkQueue (iCorkQueueIndex); - - CXX_DEBUG_ASSERT(tag,"No tag entry in the cork queue"); - - tag->extensionFields.endLine = lEndLine; + setTagEndLineToCorkEntry (iCorkQueueIndex, lEndLine); } // @@ -494,10 +496,13 @@ static bool cxxParserParseEnumStructClassOrUnionFullDeclarationTrailer( MIOPos oFilePosition = getInputFilePosition(); int iFileLine = getInputLineNumber(); + int eMaybeTokenTypeOpeningBracket = (g_cxx.bConfirmedCPPLanguage + ? 0 + : CXXTokenTypeOpeningBracket); if(!cxxParserParseUpToOneOf( CXXTokenTypeEOF | CXXTokenTypeSemicolon | - CXXTokenTypeOpeningBracket | CXXTokenTypeAssignment, + eMaybeTokenTypeOpeningBracket | CXXTokenTypeAssignment, false )) { @@ -551,6 +556,25 @@ static bool cxxParserParseEnumStructClassOrUnionFullDeclarationTrailer( if(cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeOpeningBracket)) { CXX_DEBUG_PRINT("Found opening bracket: possibly a function declaration?"); + + // Revert keyword states. + // Here we assume the following kind of input: + // + // static struct S {...} fn (...) { ... } + // + // To fill properties: field of fn with "static", g_cxx.uKeywordState + // must be set here. + // + // See cxxParserEmitFunctionTags. + // + // NOTE: C++ doesn't accept such kind of input. So we propagate + // the state only meaningful in C languages. + g_cxx.uKeywordState |= uKeywordState & (0 + | CXXParserKeywordStateSeenStatic + | CXXParserKeywordStateSeenInline + | CXXParserKeywordStateSeenExtern + | CXXParserKeywordStateSeenAttributeDeprecated + ); if(!cxxParserParseBlockHandleOpeningBracket()) { CXX_DEBUG_LEAVE_TEXT("Failed to handle the opening bracket"); @@ -575,7 +599,26 @@ static bool cxxParserParseEnumStructClassOrUnionFullDeclarationTrailer( if(uKeywordState & CXXParserKeywordStateSeenTypedef) cxxParserExtractTypedef(g_cxx.pTokenChain,true,false); else + { + // Revert keyword states. + // Here we assume the following kind of input: + // + // static struct S {...} s; + // + // To fill properties: field of s with "static", g_cxx.uKeywordState + // must be set here. + g_cxx.uKeywordState |= uKeywordState & (0 + | CXXParserKeywordStateSeenStatic + | CXXParserKeywordStateSeenExtern + | CXXParserKeywordStateSeenMutable + | CXXParserKeywordStateSeenInline + | CXXParserKeywordStateSeenAttributeDeprecated + | CXXParserKeywordStateSeenConstexpr + | CXXParserKeywordStateSeenConstinit + | CXXParserKeywordStateSeenThreadLocal + ); cxxParserExtractVariableDeclarations(g_cxx.pTokenChain,0); + } CXX_DEBUG_LEAVE(); return true; @@ -661,8 +704,12 @@ bool cxxParserParseEnum(void) { if(uInitialKeywordState & CXXParserKeywordStateSeenTypedef) { + bool bR; + g_cxx.uKeywordState &= ~CXXParserKeywordStateSeenTypedef; CXX_DEBUG_LEAVE_TEXT("Found parenthesis after typedef: parsing as generic typedef"); - return cxxParserParseGenericTypedef(); + bR = cxxParserParseGenericTypedef(); + cxxParserNewStatement(); + return bR; } // probably a function declaration/prototype // something like enum x func().... @@ -758,11 +805,30 @@ bool cxxParserParseEnum(void) return false; } - if(cxxTokenTypeIsOneOf(g_cxx.pToken,CXXTokenTypeEOF | CXXTokenTypeSemicolon)) + if(cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeEOF)) { // tolerate EOF, treat as forward declaration cxxParserNewStatement(); - CXX_DEBUG_LEAVE_TEXT("EOF or semicolon before enum block: can't decode this"); + CXX_DEBUG_LEAVE_TEXT("EOF before enum block: can't decode this"); + return true; + } + + if(cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeSemicolon)) + { + bool bMember = (cxxScopeGetVariableKind() == CXXTagKindMEMBER); + if (bMember) + { + // enum type structure member with bit-width: + // e.g. + // sturct { enum E m: 2; } v; + CXX_DEBUG_PRINT("Found semicolon, member definition with bit-width"); + cxxParserExtractVariableDeclarations(g_cxx.pTokenChain, 0); + } + cxxParserNewStatement(); + if (bMember) + CXX_DEBUG_LEAVE_TEXT("Semicolon before enum block: can't decode this"); + else + CXX_DEBUG_LEAVE(); return true; } @@ -819,7 +885,7 @@ bool cxxParserParseEnum(void) CXX_DEBUG_PRINT("Enum name is %s",vStringValue(pEnumName->pszWord)); cxxTokenChainTake(g_cxx.pTokenChain,pEnumName); } else { - pEnumName = cxxTokenCreateAnonymousIdentifier(CXXTagKindENUM); + pEnumName = cxxTokenCreateAnonymousIdentifier(CXXTagKindENUM, NULL); bAnonymous = true; CXX_DEBUG_PRINT( "Enum name is %s (anonymous)", @@ -850,10 +916,18 @@ bool cxxParserParseEnum(void) pTypeName = cxxTagCheckAndSetTypeField(pTypeBegin,pTypeEnd); } + + unsigned int uProperties = 0; if(bIsScopedEnum) - pszProperties = cxxTagSetProperties(CXXTagPropertyScopedEnum); + uProperties |= CXXTagPropertyScopedEnum; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + + if(uProperties) + pszProperties = cxxTagSetProperties(uProperties); iCorkQueueIndex = cxxTagCommit(&iCorkQueueIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, pEnumName); if (pszProperties) vStringDelete (pszProperties); @@ -1047,8 +1121,12 @@ static bool cxxParserParseClassStructOrUnionInternal( { if(uInitialKeywordState & CXXParserKeywordStateSeenTypedef) { + bool bR; + g_cxx.uKeywordState &= ~CXXParserKeywordStateSeenTypedef; CXX_DEBUG_LEAVE_TEXT("Found parenthesis after typedef: parsing as generic typedef"); - return cxxParserParseGenericTypedef(); + bR = cxxParserParseGenericTypedef(); + cxxParserNewStatement(); + return bR; } // probably a function declaration/prototype @@ -1216,7 +1294,7 @@ static bool cxxParserParseClassStructOrUnionInternal( ); cxxTokenChainTake(g_cxx.pTokenChain,pClassName); } else { - pClassName = cxxTokenCreateAnonymousIdentifier(uTagKind); + pClassName = cxxTokenCreateAnonymousIdentifier(uTagKind, NULL); bAnonymous = true; CXX_DEBUG_PRINT( "Class/struct/union name is %s (anonymous)", @@ -1321,8 +1399,18 @@ static bool cxxParserParseClassStructOrUnionInternal( tag->isFileScope = !isInputHeaderFile(); + unsigned int uProperties = 0; + if(uInitialKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + vString * pszProperties = NULL; + + if(uProperties) + pszProperties = cxxTagSetProperties(uProperties); + iCorkQueueIndex = cxxTagCommit(&iCorkQueueIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, pClassName); + vStringDelete (pszProperties); /* NULL is acceptable. */ } cxxScopePush( @@ -1465,7 +1553,8 @@ void cxxParserAnalyzeOtherStatement(void) CXXToken * t = cxxTokenChainFirst(g_cxx.pTokenChain); - if(!cxxTokenTypeIsOneOf(t,CXXTokenTypeIdentifier | CXXTokenTypeKeyword)) + if(!cxxTokenTypeIsOneOf(t,CXXTokenTypeIdentifier | CXXTokenTypeKeyword + | CXXTokenTypeMultipleColons)) { CXX_DEBUG_LEAVE_TEXT("Statement does not start with an identifier or keyword"); return; @@ -1502,7 +1591,14 @@ void cxxParserAnalyzeOtherStatement(void) const bool bPrototypeParams = cxxTagKindEnabled(CXXTagKindPROTOTYPE) && cxxTagKindEnabled(CXXTagKindPARAMETER); check_function_signature: - if(cxxParserLookForFunctionSignature(g_cxx.pTokenChain,&oInfo,bPrototypeParams?&oParamInfo:NULL)) + if( + cxxParserLookForFunctionSignature(g_cxx.pTokenChain,&oInfo,bPrototypeParams?&oParamInfo:NULL) + // Even if we saw "();", we cannot say it is a function prototype; a macro expansion can be used to + // initialize a top-level variable like: + // #define INIT() 0 + // int i = INIT();" + && ! (oInfo.pTypeEnd && cxxTokenTypeIs(oInfo.pTypeEnd,CXXTokenTypeAssignment)) + ) { CXX_DEBUG_PRINT("Found function prototype"); @@ -1860,6 +1956,7 @@ static rescanReason cxxParserMain(const unsigned int passCount) int kind_for_header = CXXTagKindINCLUDE; int kind_for_macro_param = CXXTagKindMACROPARAM; int role_for_macro_undef = CR_MACRO_UNDEF; + int role_for_macro_condition = CR_MACRO_CONDITION; int role_for_header_system = CR_HEADER_SYSTEM; int role_for_header_local = CR_HEADER_LOCAL; @@ -1872,6 +1969,7 @@ static rescanReason cxxParserMain(const unsigned int passCount) false, kind_for_define, role_for_macro_undef, + role_for_macro_condition, kind_for_macro_param, kind_for_header, role_for_header_system, @@ -1943,6 +2041,7 @@ rescanReason cxxCppParserMain(const unsigned int passCount) cxxKeywordEnablePublicProtectedPrivate(g_cxx.bConfirmedCPPLanguage); rescanReason r = cxxParserMain(passCount); + cxxParserDestroyCurrentModuleToken(); CXX_DEBUG_LEAVE(); return r; } diff --git a/ctags/parsers/cxx/cxx_parser_block.c b/ctags/parsers/cxx/cxx_parser_block.c index 714026d216..73cf0d0ba3 100644 --- a/ctags/parsers/cxx/cxx_parser_block.c +++ b/ctags/parsers/cxx/cxx_parser_block.c @@ -15,6 +15,7 @@ #include "cxx_token_chain.h" #include "cxx_scope.h" #include "cxx_tag.h" +#include "cxx_side_chain.h" #include "parse.h" #include "vstring.h" @@ -27,12 +28,14 @@ #include +static bool cxxParserParseBlockFull(bool bExpectClosingBracket, bool bExported); + bool cxxParserParseBlockHandleOpeningBracket(void) { CXX_DEBUG_ENTER(); CXX_DEBUG_ASSERT( - g_cxx.pToken->eType == CXXTokenTypeOpeningBracket, + cxxTokenTypeIs(g_cxx.pToken, CXXTokenTypeOpeningBracket), "This must be called when pointing at an opening bracket!" ); @@ -144,6 +147,7 @@ bool cxxParserParseBlockHandleOpeningBracket(void) int iCorkQueueIndexFQ = CORK_NIL; CXXFunctionSignatureInfo oInfo; + bool bExported = false; if(eScopeType != CXXScopeTypeFunction) { @@ -153,6 +157,7 @@ bool cxxParserParseBlockHandleOpeningBracket(void) // FIXME: Handle syntax (5) of list initialization: // Class::Class() : member { arg1, arg2, ... } {... + bExported = (g_cxx.uKeywordState & CXXParserKeywordStateSeenExport); } else { // some kind of other block: // - anonymous block @@ -162,9 +167,9 @@ bool cxxParserParseBlockHandleOpeningBracket(void) iScopes = 0; } - cxxParserNewStatement(); + cxxParserNewStatementFull(bExported); - if(!cxxParserParseBlock(true)) + if(!cxxParserParseBlockFull(true, bExported)) { CXX_DEBUG_LEAVE_TEXT("Failed to parse nested block"); return false; @@ -253,14 +258,14 @@ bool cxxParserParseBlockHandleOpeningBracket(void) return true; } -static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) +static bool cxxParserParseBlockInternal(bool bExpectClosingBracket, bool bExported) { CXX_DEBUG_ENTER(); //char * szScopeName = cxxScopeGetFullName(); //CXX_DEBUG_PRINT("Scope name is '%s'",szScopeName ? szScopeName : ""); - cxxParserNewStatement(); + cxxParserNewStatementFull(bExported); if(bExpectClosingBracket) { @@ -269,11 +274,14 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) cppBeginStatement(); } + CXXTokenChain *pSideChain = NULL; for(;;) { if(!cxxParserParseNextToken()) { found_eof: + cxxTokenChainDestroy(pSideChain); + pSideChain = NULL; if(bExpectClosingBracket) { @@ -287,6 +295,8 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) CXX_DEBUG_LEAVE_TEXT("EOF in main block"); return true; // EOF } + cxxSideChainAppendChain(pSideChain,g_cxx.pToken); + pSideChain = NULL; process_token: @@ -300,10 +310,14 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) && cxxScopeGetType() == CXXScopeTypeClass && cxxSubparserNewIdentifierAsHeadOfMemberNotify(g_cxx.pToken)) { + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); continue; } + if (bExported) + g_cxx.uKeywordState |= CXXParserKeywordStateSeenExport; + switch(g_cxx.pToken->eType) { case CXXTokenTypeKeyword: @@ -440,6 +454,7 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) } } break; + break; case CXXKeywordPUBLIC: case CXXKeywordPROTECTED: case CXXKeywordPRIVATE: @@ -561,11 +576,15 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) case CXXKeywordEXTERN: g_cxx.uKeywordState |= CXXParserKeywordStateSeenExtern; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); if(!cxxParserParseNextToken()) goto found_eof; + cxxSideChainAppendChain(pSideChain,g_cxx.pToken); + pSideChain = NULL; + if(cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeStringConstant)) { // assume extern "language" @@ -600,6 +619,7 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) break; case CXXKeywordSTATIC: g_cxx.uKeywordState |= CXXParserKeywordStateSeenStatic; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); break; case CXXKeywordINLINE: @@ -608,10 +628,12 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) case CXXKeyword__FORCEINLINE: case CXXKeyword__FORCEINLINE__: g_cxx.uKeywordState |= CXXParserKeywordStateSeenInline; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); break; case CXXKeywordEXPLICIT: g_cxx.uKeywordState |= CXXParserKeywordStateSeenExplicit; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); break; case CXXKeywordOPERATOR: @@ -619,10 +641,12 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) break; case CXXKeywordVIRTUAL: g_cxx.uKeywordState |= CXXParserKeywordStateSeenVirtual; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); break; case CXXKeywordMUTABLE: g_cxx.uKeywordState |= CXXParserKeywordStateSeenMutable; + pSideChain = cxxSideChainEject(g_cxx.pToken); cxxTokenChainDestroyLast(g_cxx.pTokenChain); break; case CXXKeywordFRIEND: @@ -637,6 +661,24 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) case CXXKeywordCONST: g_cxx.uKeywordState |= CXXParserKeywordStateSeenConst; break; + case CXXKeywordCONSTEXPR: + g_cxx.uKeywordState |= CXXParserKeywordStateSeenConstexpr; + break; + case CXXKeywordCONSTEVAL: + g_cxx.uKeywordState |= CXXParserKeywordStateSeenConsteval; + break; + case CXXKeywordCONSTINIT: + g_cxx.uKeywordState |= CXXParserKeywordStateSeenConstinit; + break; + case CXXKeywordTHREAD_LOCAL: + case CXXKeyword__THREAD: + case CXXKeyword_THREAD_LOCAL: + g_cxx.uKeywordState |= CXXParserKeywordStateSeenThreadLocal; + break; + case CXXKeywordEXPORT: + g_cxx.uKeywordState |= CXXParserKeywordStateSeenExport; + break; + default: if(g_cxx.uKeywordState & CXXParserKeywordStateSeenTypedef) { @@ -759,7 +801,44 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) } break; case CXXTokenTypeIdentifier: - if(g_cxx.uKeywordState & CXXParserKeywordStateSeenTypedef) + if( + /* Accept only "export" as a preceding token. */ + (! (g_cxx.uKeywordState & ~CXXParserKeywordStateSeenExport)) + && (g_cxx.pToken->pPrev == NULL || cxxTokenIsKeyword(g_cxx.pToken->pPrev, CXXKeywordEXPORT)) + /* C++ */ + && cxxParserCurrentLanguageIsCPP() + /* At the toplevel */ + && cxxScopeIsGlobal() + /* Handle this token as a keyword, not an identifier. */ + && (strcmp(vStringValue(g_cxx.pToken->pszWord), "module") == 0) + ) + { + /* "module" introduced in C++20, can be a keyworkd in limited contexts. + * If the parsing is not in the context, the parser should handle it + * as an identifier. */ + if(!cxxParserParseModule()) + { + CXX_DEBUG_LEAVE_TEXT("Failed to parse module"); + return false; + } + break; + } + else if( + (! (g_cxx.uKeywordState & ~CXXParserKeywordStateSeenExport)) + && (g_cxx.pToken->pPrev == NULL || cxxTokenIsKeyword(g_cxx.pToken->pPrev, CXXKeywordEXPORT)) + && cxxParserCurrentLanguageIsCPP() + && cxxScopeIsGlobal() + && (strcmp(vStringValue(g_cxx.pToken->pszWord), "import") == 0) + ) + { + if(!cxxParserParseImport()) + { + CXX_DEBUG_LEAVE_TEXT("Failed to parse import"); + return false; + } + break; + } + else if(g_cxx.uKeywordState & CXXParserKeywordStateSeenTypedef) { g_cxx.uKeywordState &= ~CXXParserKeywordStateSeenTypedef; if(!cxxParserParseGenericTypedef()) @@ -790,15 +869,20 @@ static bool cxxParserParseBlockInternal(bool bExpectClosingBracket) // When the statement ends without finding any characteristic token the chain // is passed to an analysis routine which does a second scan pass. // -bool cxxParserParseBlock(bool bExpectClosingBracket) +static bool cxxParserParseBlockFull(bool bExpectClosingBracket, bool bExported) { cxxSubparserNotifyEnterBlock (); cppPushExternalParserBlock(); - bool bRet = cxxParserParseBlockInternal(bExpectClosingBracket); + bool bRet = cxxParserParseBlockInternal(bExpectClosingBracket, bExported); cppPopExternalParserBlock(); cxxSubparserNotifyLeaveBlock (); return bRet; } + +bool cxxParserParseBlock(bool bExpectClosingBracket) +{ + return cxxParserParseBlockFull(bExpectClosingBracket, false); +} diff --git a/ctags/parsers/cxx/cxx_parser_function.c b/ctags/parsers/cxx/cxx_parser_function.c index 83325b98a1..5ae3c5f1a4 100644 --- a/ctags/parsers/cxx/cxx_parser_function.c +++ b/ctags/parsers/cxx/cxx_parser_function.c @@ -14,6 +14,7 @@ #include "cxx_token.h" #include "cxx_token_chain.h" #include "cxx_scope.h" +#include "cxx_side_chain.h" #include "parse.h" #include "vstring.h" @@ -284,6 +285,7 @@ int cxxParserMaybeParseKnRStyleFunctionDefinition(void) if(tag) { + cxxSideChainScan(pIdentifier->pSideChain); if(pParenthesis->pChain->pTail) { // normalize signature @@ -304,6 +306,7 @@ int cxxParserMaybeParseKnRStyleFunctionDefinition(void) tag->extensionFields.signature = vStringValue(pszSignature); iCorkQueueIndex = cxxTagCommit(&iCorkQueueIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, pIdentifier); if(pszSignature) vStringDelete(pszSignature); @@ -638,6 +641,42 @@ static bool cxxParserLookForFunctionSignatureCheckParenthesisAndIdentifier( return false; } +// If pInfo->pIdentifierStart has the same name as the name of +// class|enum|union|struct scope, we consider pInfo->pIdentifierStart +// points a constructor. +static bool cxxParserisConstructor(const char *szFuncname) +{ + if (cxxScopeIsGlobal()) + return false; + + switch (cxxScopeGetType()) + { + case CXXScopeTypeClass: + case CXXScopeTypeEnum: + case CXXScopeTypeUnion: + case CXXScopeTypeStruct: + break; + default: + return false; + } + + const char *szScope = cxxScopeGetName(); + const char *szTmp = strrstr (szScope, szFuncname); + + if (szTmp == NULL) + return false; + + /* szFuncname == "C", szScope == "C" */ + if (szTmp == szScope) + return true; + + /* szFuncname == "C", szScope == "X::C" */ + if (szTmp[-1] == ':') + return true; + + return false; +} + // // Look for a function signature in the specified chain. // @@ -1022,9 +1061,6 @@ bool cxxParserLookForFunctionSignature( if( // The previous token is > cxxTokenTypeIs(pToken->pPrev,CXXTokenTypeGreaterThanSign) && - // We extracted an initial template<*> token chain - // (which has been removed from the currently examined chain) - g_cxx.pTemplateTokenChain && // We skipped an additional <...> block in *this* chain bSkippedAngleBrackets ) @@ -1039,7 +1075,17 @@ bool cxxParserLookForFunctionSignature( if( pSpecBegin && pSpecBegin->pPrev && - cxxTokenTypeIs(pSpecBegin->pPrev,CXXTokenTypeIdentifier) + cxxTokenTypeIs(pSpecBegin->pPrev,CXXTokenTypeIdentifier) && + ( + // We extracted an initial template<*> token chain + // (which has been removed from the currently examined chain) + g_cxx.pTemplateTokenChain || + // g_cxx.pTemplateTokenChain is set to null if "{" after "class" keyword + // is found. As a result, a constructor with <*> defined in a template + // class could not be tagged. The next additional condition is for + // tagging the condition in this situation. + cxxParserisConstructor(vStringValue(pSpecBegin->pPrev->pszWord)) + ) ) { // template specialisation @@ -1063,7 +1109,7 @@ bool cxxParserLookForFunctionSignature( pParamInfo ) ) - goto next_token; + goto next_token; } @@ -1213,7 +1259,7 @@ bool cxxParserLookForFunctionSignature( // hmm.. probably a template specialisation pAux = pSmallerThan->pPrev; pInfo->uFlags |= CXXFunctionSignatureInfoScopeTemplateSpecialization; - } else if(pAux->eType == CXXTokenTypeAngleBracketChain) + } else if(cxxTokenTypeIs(pAux, CXXTokenTypeAngleBracketChain)) { // same as above, but already condensed (though it should never happen) if(!pAux->pPrev) @@ -1499,6 +1545,7 @@ int cxxParserEmitFunctionTags( 0 ); + cxxSideChainCollectInRange(pInfo->pIdentifierStart,pInfo->pIdentifierEnd,pIdentifier); cxxTokenChainDestroyRange(pInfo->pIdentifierChain,pInfo->pIdentifierStart,pInfo->pIdentifierEnd); CXX_DEBUG_ASSERT( @@ -1609,6 +1656,7 @@ int cxxParserEmitFunctionTags( CXXToken * pTokenBeforeParenthesis = pInfo->pParenthesis->pPrev; cxxTokenChainTake(pInfo->pParenthesisContainerChain,pInfo->pParenthesis); + cxxSideChainCollectInRange(pInfo->pTypeStart,pInfo->pTypeEnd, pIdentifier); pTypeName = cxxTagCheckAndSetTypeField(pInfo->pTypeStart,pInfo->pTypeEnd); cxxTokenChainInsertAfter( @@ -1620,6 +1668,7 @@ int cxxParserEmitFunctionTags( pTypeName = NULL; } } else { + cxxSideChainCollectInRange(pInfo->pTypeStart,pInfo->pTypeEnd, pIdentifier); pTypeName = cxxTagCheckAndSetTypeField(pInfo->pTypeStart,pInfo->pTypeEnd); } } else { @@ -1686,6 +1735,11 @@ int cxxParserEmitFunctionTags( uProperties |= CXXTagPropertyExtern; if(g_cxx.uKeywordState & CXXParserKeywordStateSeenAttributeDeprecated) uProperties |= CXXTagPropertyDeprecated; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenConstexpr) + uProperties |= CXXTagPropertyConstexpr; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenConsteval) + uProperties |= CXXTagPropertyConsteval; + // constinit is not here; it is for variables. if(pInfo->pSignatureConst) uProperties |= CXXTagPropertyConst; if(pInfo->uFlags & CXXFunctionSignatureInfoPure) @@ -1707,11 +1761,16 @@ int cxxParserEmitFunctionTags( CXXTagPropertyTemplateSpecialization; if((pInfo->uFlags & CXXFunctionSignatureInfoTemplateSpecialization) || bIsEmptyTemplate) uProperties |= CXXTagPropertyTemplateSpecialization; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; pszProperties = cxxTagSetProperties(uProperties); } + cxxSideChainScan(pIdentifier->pSideChain); + int iCorkQueueIndex = cxxTagCommit(piCorkQueueIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, pIdentifier); if(piCorkQueueIndex) *piCorkQueueIndex = iCorkQueueIndex; @@ -1878,7 +1937,6 @@ void cxxParserEmitFunctionParameterTags(CXXTypedVariableSet * pInfo) } else { pTypeName = NULL; } - tag->extensionFields.nth = i; tag->isFileScope = true; @@ -2178,7 +2236,7 @@ bool cxxParserTokenChainLooksLikeFunctionParameterList( * * void f(int *); */ - pIdentifier = cxxTokenCreateAnonymousIdentifier(CXXTagKindPARAMETER); + pIdentifier = cxxTokenCreateAnonymousIdentifier(CXXTagKindPARAMETER, NULL); pIdentifier->iLineNumber = t->pPrev->iLineNumber; pIdentifier->oFilePosition = t->pPrev->oFilePosition; pParamInfo->uAnonymous |= (0x1u << pParamInfo->uCount); @@ -2222,7 +2280,7 @@ bool cxxParserTokenChainLooksLikeFunctionParameterList( * */ CXXToken * pFakeStart = cxxTokenCopy(pStart); - CXXToken * pFakeId = cxxTokenCreateAnonymousIdentifier(CXXTagKindPARAMETER); + CXXToken * pFakeId = cxxTokenCreateAnonymousIdentifier(CXXTagKindPARAMETER, NULL); pFakeId->iLineNumber = pStart->iLineNumber; pFakeId->oFilePosition = pStart->oFilePosition; diff --git a/ctags/parsers/cxx/cxx_parser_internal.h b/ctags/parsers/cxx/cxx_parser_internal.h index 78ed5f237d..b1a1254796 100644 --- a/ctags/parsers/cxx/cxx_parser_internal.h +++ b/ctags/parsers/cxx/cxx_parser_internal.h @@ -219,7 +219,13 @@ void cxxParserExtractTypedef( // cxx_parser_namespace.c bool cxxParserParseNamespace(void); +// cxx_parser_module.c +bool cxxParserParseModule(void); +bool cxxParserParseImport(void); +void cxxParserDestroyCurrentModuleToken(void); + // cxx_parser.c +void cxxParserNewStatementFull(bool bExported); void cxxParserNewStatement(void); bool cxxParserSkipToSemicolonOrEOF(void); bool cxxParserParseToEndOfQualifedName(void); @@ -284,6 +290,16 @@ typedef enum _CXXParserKeywordState CXXParserKeywordStateSeenAttributeDeprecated = (1 << 11), // "friend" has been seen at block level CXXParserKeywordStateSeenFriend = (1 << 12), + // "constexpr" has been seen + CXXParserKeywordStateSeenConstexpr = (1 << 13), + // "consteval" has been seen + CXXParserKeywordStateSeenConsteval = (1 << 14), + // "constinit" has been seen + CXXParserKeywordStateSeenConstinit = (1 << 15), + // "thread_local" has been seen + CXXParserKeywordStateSeenThreadLocal = (1 << 16), + // "export" has been seen + CXXParserKeywordStateSeenExport = (1 << 17), } CXXParserKeywordState; #define CXX_PARSER_MAXIMUM_NESTING_LEVELS 1024 diff --git a/ctags/parsers/cxx/cxx_parser_lambda.c b/ctags/parsers/cxx/cxx_parser_lambda.c index e12991551d..d2a9ffd8da 100644 --- a/ctags/parsers/cxx/cxx_parser_lambda.c +++ b/ctags/parsers/cxx/cxx_parser_lambda.c @@ -164,7 +164,7 @@ bool cxxParserHandleLambda(CXXToken * pParenthesis) CXX_DEBUG_ASSERT(cxxParserCurrentLanguageIsCPP(),"C++ only"); - CXXToken * pIdentifier = cxxTokenCreateAnonymousIdentifier(CXXTagKindFUNCTION); + CXXToken * pIdentifier = cxxTokenCreateAnonymousIdentifier(CXXTagKindFUNCTION, NULL); CXXTokenChain * pSave = g_cxx.pTokenChain; CXXTokenChain * pNew = cxxTokenChainCreate(); @@ -268,6 +268,7 @@ bool cxxParserHandleLambda(CXXToken * pParenthesis) tag->extensionFields.signature = vStringValue(pszSignature); iCorkQueueIndex = cxxTagCommit(&iCorkQueueIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, pIdentifier); if(pTypeName) cxxTokenDestroy(pTypeName); diff --git a/ctags/parsers/cxx/cxx_parser_module.c b/ctags/parsers/cxx/cxx_parser_module.c new file mode 100644 index 0000000000..e035b93513 --- /dev/null +++ b/ctags/parsers/cxx/cxx_parser_module.c @@ -0,0 +1,331 @@ +/* +* Copyright (c) 2024, Masatake YAMATO +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for parsing and scanning C++ source files +* +* Reference: +* - https://en.cppreference.com/w/cpp/language/modules +*/ + +#include "cxx_parser.h" +#include "cxx_parser_internal.h" + +#include "cxx_debug.h" +#include "cxx_keyword.h" +#include "cxx_token.h" +#include "cxx_token_chain.h" +#include "cxx_scope.h" + +#include "../cpreprocessor.h" + +#include "parse.h" +#include "vstring.h" +#include "read.h" +#include "trashbox.h" + +static CXXToken * pCurrentModuleToken; + +static void cxxParserSetCurrentModuleToken(CXXToken * pMod) +{ + if (pCurrentModuleToken) + cxxTokenDestroy(pCurrentModuleToken); + pCurrentModuleToken = pMod; +} + +void cxxParserDestroyCurrentModuleToken(void) +{ + cxxParserSetCurrentModuleToken(NULL); +} + + +static CXXToken * cxxTokenModuleTokenCreate(CXXToken *pBegin, CXXToken *pEnd) +{ + CXXToken * pRet = cxxTokenCopy(pBegin); + + if (pBegin != pEnd) + { + vString * pszRest = cxxTokenChainJoinRange(pBegin->pNext, pEnd, "", + CXXTokenChainJoinNoTrailingSpaces); + vStringCat(pRet->pszWord, pszRest); + vStringDelete(pszRest); + } + + return pRet; +} + +bool cxxParserParseModule(void) +{ + CXX_DEBUG_ENTER(); + + unsigned int uProperties = 0; + + if(cxxTagFieldEnabled(CXXTagFieldProperties)) + { + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + } + + cxxParserNewStatement(); + + if(!cxxParserParseUpToOneOf( + CXXTokenTypeSemicolon | CXXTokenTypeEOF, + false + )) + { + CXX_DEBUG_LEAVE_TEXT("Failed to parse up to the next ;"); + return false; + } + + CXXToken * pFirst = cxxTokenChainFirst(g_cxx.pTokenChain); + if (cxxTokenTypeIsOneOf(pFirst, CXXTokenTypeSemicolon | CXXTokenTypeEOF)) + { + CXXToken * pMod = cxxTokenCreateAnonymousIdentifier(CXXTagCPPKindMODULE, "__gmod_"); + tagEntryInfo * tag = cxxTagBegin(CXXTagCPPKindMODULE,pMod); + if (tag) + cxxTagCommit(NULL); + cxxTokenDestroy(pMod); + } + else if (cxxTokenTypeIs(pFirst, CXXTokenTypeSingleColon) + && pFirst->pNext && cxxTokenIsKeyword(pFirst->pNext, CXXKeywordPRIVATE)) + { + CXXToken * pPart = pFirst->pNext; + if (pCurrentModuleToken) + { + CXXToken *pMod = cxxTokenCopy(pCurrentModuleToken); + cxxScopePush(pMod, CXXScopeTypeModule, CXXScopeAccessUnknown); + } + + tagEntryInfo * tag = cxxTagBegin(CXXTagCPPKindPARTITION,pPart); + if (tag) + cxxTagCommit(NULL); + /* export is never set to the private partition. */ + + if (pCurrentModuleToken) + cxxScopePop(); + } + else if (cxxTokenTypeIs(pFirst, CXXTokenTypeIdentifier)) + { + CXXToken * pModBegin = pFirst; + CXXToken * pSep = cxxTokenChainNextTokenOfType(pModBegin, + CXXTokenTypeSingleColon + | CXXTokenTypeSemicolon + | CXXTokenTypeEOF); + bool bHasPart = (cxxTokenTypeIs(pSep, CXXTokenTypeSingleColon) + && pSep->pNext && cxxTokenTypeIs(pSep->pNext, CXXTokenTypeIdentifier) + && g_cxx.pToken->pPrev && g_cxx.pToken->pPrev != pSep); + + CXXToken * pMod = NULL; + { + pMod = cxxTokenModuleTokenCreate(pModBegin, pSep->pPrev); + tagEntryInfo * tag = cxxRefTagBegin(CXXTagCPPKindMODULE, + bHasPart? CXXTagMODULERolePartOwner: ROLE_DEFINITION_INDEX, + pMod); + if (tag) + { + vString * pszProperties = (!bHasPart && uProperties) ? cxxTagSetProperties(uProperties) : NULL; + + cxxTagCommit(NULL); + cxxParserSetCurrentModuleToken(cxxTokenCopy(pMod)); + cxxScopePush(pMod, CXXScopeTypeModule, CXXScopeAccessUnknown); + vStringDelete(pszProperties); /* NULL is acceptable. */ + } + else + { + cxxParserSetCurrentModuleToken(pMod); + pMod = NULL; + } + } + + if (pMod && bHasPart) + { + CXXToken * pPart = cxxTokenModuleTokenCreate(pSep->pNext, g_cxx.pToken->pPrev); + + tagEntryInfo * tag = cxxTagBegin(CXXTagCPPKindPARTITION,pPart); + if (tag) + { + vString * pszProperties = uProperties ? cxxTagSetProperties(uProperties) : NULL; + + cxxTagCommit(NULL); + vStringDelete(pszProperties); /* NULL is acceptable */ + } + cxxTokenDestroy(pPart); + } + + if (pMod) + cxxScopePop(); + } + + cxxTokenChainClear(g_cxx.pTokenChain); + CXX_DEBUG_LEAVE(); + return true; +} + +bool cxxParserParseImport(void) +{ + CXX_DEBUG_ENTER(); + + unsigned int uProperties = 0; + + if(cxxTagFieldEnabled(CXXTagFieldProperties)) + { + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + } + + cxxParserNewStatement(); + + if(!cxxParserParseUpToOneOf( + CXXTokenTypeSemicolon | CXXTokenTypeEOF, + false + )) + { + CXX_DEBUG_LEAVE_TEXT("Failed to parse up to the next ;"); + return false; + } + + CXXToken *pFirst = cxxTokenChainFirst(g_cxx.pTokenChain); + if (cxxTokenTypeIs(pFirst, CXXTokenTypeSmallerThanSign)) + { + if (pFirst->pNext == NULL + || cxxTokenTypeIsOneOf(pFirst->pNext, CXXTokenTypeGreaterThanSign + | CXXTokenTypeSemicolon + | CXXTokenTypeEOF)) + { + cxxTokenChainClear(g_cxx.pTokenChain); + CXX_DEBUG_LEAVE_TEXT("Cannot find > on the chain"); + return true; + } + + CXXToken * pHeaderBegin = pFirst->pNext; + CXXToken * pSep = cxxTokenChainNextTokenOfType(pHeaderBegin, + CXXTokenTypeGreaterThanSign + | CXXTokenTypeSemicolon + | CXXTokenTypeEOF); + CXX_DEBUG_ASSERT(pSep,"Cannot find the end of header file"); + CXX_DEBUG_ASSERT(pSep != pHeaderBegin, "Unexpected empty header file"); + + CXXToken * pHeader = cxxTokenModuleTokenCreate(pHeaderBegin, pSep->pPrev); + tagEntryInfo * tag = cxxRefTagBegin(CXXTagKindINCLUDE,CR_HEADER_SYSTEM,pHeader); + if (tag) + { + assignRole(tag, CXXR_HEADER_IMPORTED); + if (uProperties & CXXTagPropertyExport) + assignRole(tag, CXXR_HEADER_EXPORTED); + cxxTagCommit(NULL); + } + cxxTokenDestroy(pHeader); + } + else if (cxxTokenTypeIs(pFirst, CXXTokenTypeStringConstant)) + { + const vString *pHName = cppGetLastCharOrStringContents(); + if (!pHName || vStringIsEmpty(pHName)) + { + cxxTokenChainClear(g_cxx.pTokenChain); + CXX_DEBUG_LEAVE_TEXT("Empty header name"); + return true; + } + + vStringCopy(pFirst->pszWord, pHName); + CXXToken *pHeader = pFirst; + + tagEntryInfo * tag = cxxRefTagBegin(CXXTagKindINCLUDE,CR_HEADER_LOCAL,pHeader); + if (tag) + { + assignRole(tag, CXXR_HEADER_IMPORTED); + if (uProperties & CXXTagPropertyExport) + assignRole(tag, CXXR_HEADER_EXPORTED); + cxxTagCommit(NULL); + } + } + else if (cxxTokenTypeIs(pFirst, CXXTokenTypeSingleColon)) + { + CXXToken * pPartBegin = pFirst->pNext; + + if (pPartBegin == NULL + || pPartBegin == g_cxx.pToken + || cxxTokenTypeIsOneOf(pPartBegin, CXXTokenTypeSemicolon + | CXXTokenTypeEOF)) + { + cxxTokenChainClear(g_cxx.pTokenChain); + CXX_DEBUG_LEAVE_TEXT("Empty partition name"); + return true; + } + + CXXToken * pPart = cxxTokenModuleTokenCreate(pPartBegin, g_cxx.pToken->pPrev); + if (pCurrentModuleToken) + { + CXXToken *pMod = cxxTokenCopy(pCurrentModuleToken); + cxxScopePush(pMod, CXXScopeTypeModule, CXXScopeAccessUnknown); + } + + tagEntryInfo * tag = cxxRefTagBegin(CXXTagCPPKindPARTITION,CXXTagPARTITIONRoleImported,pPart); + if (tag) + { + vString * pszProperties = uProperties ? cxxTagSetProperties(uProperties) : NULL; + cxxTagCommit(NULL); + vStringDelete(pszProperties); /* NULL is acceptable. */ + } + cxxTokenDestroy(pPart); + + if (pCurrentModuleToken) + cxxScopePop(); + } + else if (cxxTokenTypeIsOneOf(pFirst, CXXTokenTypeIdentifier)) + { + CXXToken *pModBegin = pFirst; + CXXToken * pSep = cxxTokenChainNextTokenOfType(pModBegin, + CXXTokenTypeSingleColon + | CXXTokenTypeSemicolon + | CXXTokenTypeEOF); + bool bHasPart = (cxxTokenTypeIs(pSep, CXXTokenTypeSingleColon) + && pSep->pNext && cxxTokenTypeIs(pSep->pNext, CXXTokenTypeIdentifier) + && g_cxx.pToken->pPrev && g_cxx.pToken->pPrev != pSep); + + CXXToken * pMod = NULL; + { + pMod = cxxTokenModuleTokenCreate(pModBegin, pSep->pPrev); + tagEntryInfo * tag = cxxRefTagBegin(CXXTagCPPKindMODULE, + bHasPart? CXXTagMODULERolePartOwner: CXXTagMODULERoleImported, + pMod); + if (tag) + { + vString * pszProperties = (!bHasPart && uProperties) ? cxxTagSetProperties(uProperties) : NULL; + + cxxTagCommit(NULL); + cxxScopePush(pMod, CXXScopeTypeModule, CXXScopeAccessUnknown); + vStringDelete(pszProperties); /* NULL is acceptable. */ + } + else + { + cxxTokenDestroy(pMod); + pMod = NULL; + } + } + + if (pMod && bHasPart) + { + CXXToken * pPart = cxxTokenModuleTokenCreate(pSep->pNext, g_cxx.pToken->pPrev); + + tagEntryInfo * tag = cxxRefTagBegin(CXXTagCPPKindPARTITION, + CXXTagPARTITIONRoleImported, pPart); + if (tag) + { + vString * pszProperties = uProperties ? cxxTagSetProperties(uProperties) : NULL; + + cxxTagCommit(NULL); + vStringDelete(pszProperties); /* NULL is acceptable */ + } + cxxTokenDestroy(pPart); + } + + if (pMod) + cxxScopePop(); + } + + cxxTokenChainClear(g_cxx.pTokenChain); + CXX_DEBUG_LEAVE(); + return true; +} diff --git a/ctags/parsers/cxx/cxx_parser_namespace.c b/ctags/parsers/cxx/cxx_parser_namespace.c index 0eb4bdcfd4..b79e379d21 100644 --- a/ctags/parsers/cxx/cxx_parser_namespace.c +++ b/ctags/parsers/cxx/cxx_parser_namespace.c @@ -56,6 +56,8 @@ bool cxxParserParseNamespace(void) { if(g_cxx.uKeywordState & CXXParserKeywordStateSeenInline) uProperties |= CXXTagPropertyInline; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; } cxxParserNewStatement(); // always a new statement @@ -278,7 +280,7 @@ bool cxxParserParseNamespace(void) // anonymous namespace CXX_DEBUG_PRINT("Found anonymous namespace start"); - CXXToken * t = cxxTokenCreateAnonymousIdentifier(CXXTagCPPKindNAMESPACE); + CXXToken * t = cxxTokenCreateAnonymousIdentifier(CXXTagCPPKindNAMESPACE, NULL); tagEntryInfo * tag = cxxTagBegin(CXXTagCPPKindNAMESPACE,t); if(tag) { diff --git a/ctags/parsers/cxx/cxx_parser_template.c b/ctags/parsers/cxx/cxx_parser_template.c index 02ecbca2a2..71bbd67845 100644 --- a/ctags/parsers/cxx/cxx_parser_template.c +++ b/ctags/parsers/cxx/cxx_parser_template.c @@ -269,7 +269,10 @@ cxxParserParseTemplateAngleBracketsInternal(bool bCaptureTypeParameters,int iNes // around but without proper state (include headers, macro expansion, full type // database etc) we simply can't do the same. However, we try to recover if we // figure out we (or the programmer?) screwed up. - // + // For 'greater-than' operators, the first non-nested operator is taken as the + // end of the template parameter list rather than a 'greater-than' operator. + // Treating non-nested operators differently is a syntax error at least since C++03 + // onwards according to https://en.cppreference.com/w/cpp/language/template_parameters. // // Like gcc, if this function knows identifiers in a template prefix more, // the quality of parsing becomes better. @@ -354,7 +357,10 @@ cxxParserParseTemplateAngleBracketsInternal(bool bCaptureTypeParameters,int iNes // ... 1 < whatever ... cxxTokenTypeIs(pSmallerThan->pPrev,CXXTokenTypeNumber) || // ... nonTypeParam < whatever ... - cxxTokenIsPresentInTemplateParametersAsNonType(pSmallerThan->pPrev) + ( + cxxTokenTypeIsOneOf(pSmallerThan->pPrev,CXXTokenTypeIdentifier) && + cxxTokenIsPresentInTemplateParametersAsNonType(pSmallerThan->pPrev) + ) ) { CXX_DEBUG_PRINT("Treating < as less-than operator"); @@ -418,35 +424,8 @@ cxxParserParseTemplateAngleBracketsInternal(bool bCaptureTypeParameters,int iNes CXX_DEBUG_PRINT("Found %d greater-than signs",iGreaterThanCount); - // check greater than operator: very narrow conditions - if( - (iGreaterThanCount == 1) && - ( - // whatever op 2 [C++03 allows this without parens] - // whatever op (...) [C++03 allows this without parens] - cxxTokenTypeIsOneOf( - g_cxx.pToken, - CXXTokenTypeNumber | CXXTokenTypeOpeningParenthesis - ) || - // whatever op nonTypeParameter [C++03 allows this without parens] - ( - cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeIdentifier) && - cxxTokenIsPresentInTemplateParametersAsNonType(g_cxx.pToken) - ) - // WARNING: don't be tempted to add a loose condition that has - // (!cxxTokenIsPresentInTemplateParametersAsType()) on the right. - // It's unsafe. - ) - ) - { - CXX_DEBUG_PRINT("Treating as greater-than sign"); - - if(cxxTokenTypeIsOneOf(g_cxx.pToken,CXXTokenTypeOpeningParenthesis)) - cxxParserUngetCurrentToken(); // needs to be condensed - - continue; - } - + // We do not check for the greater than operator, as it is only valid + // inside parentheses in a template parameter list (see i#3388). // check right shift operator: a bit broader conditions if( @@ -844,8 +823,6 @@ void cxxParserEmitTemplateParameterTags(void) if(!tag) continue; - tag->extensionFields.nth = (short)i; - CXXToken * pTypeToken = cxxTagCheckAndSetTypeField( g_cxx.oTemplateParameters.aTypeStarts[i], g_cxx.oTemplateParameters.aTypeEnds[i] diff --git a/ctags/parsers/cxx/cxx_parser_tokenizer.c b/ctags/parsers/cxx/cxx_parser_tokenizer.c index 056537d447..320236540a 100644 --- a/ctags/parsers/cxx/cxx_parser_tokenizer.c +++ b/ctags/parsers/cxx/cxx_parser_tokenizer.c @@ -857,7 +857,7 @@ static CXXCharTypeData g_aCharTable[128] = 0 , 0 }, - // 127 (0x7f) '' + // 127 (0x7f) { 0, 0, 0 } }; @@ -898,7 +898,11 @@ static void cxxParserAnalyzeAttributeChain(CXXTokenChain * pChain) // // The __attribute__((...)) sequence complicates parsing quite a lot. // For this reason we attempt to "hide" it from the rest of the parser -// at tokenizer level. +// at tokenizer level. However, we will not kill it. For extracting interesting +// information from the sequence in upper layers, attach the token chain +// built from the sequence to the token AROUND the sequence. +// In this function, we call the token "attributes owner" token. +// CXXToken::pSideChain is the member for attaching. // // Returns false if it finds an EOF. This is an important invariant required by // cxxParserParseNextToken(), the only caller. @@ -912,23 +916,25 @@ static bool cxxParserParseNextTokenCondenseAttribute(void) CXX_DEBUG_ENTER(); CXX_DEBUG_ASSERT( - cxxTokenIsKeyword(g_cxx.pToken,CXXKeyword__ATTRIBUTE__), - "This function should be called only after we have parsed __attribute__" + cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeKeyword) && + (cxxKeywordMayDropInTokenizer(g_cxx.pToken->eKeyword)), + "This function should be called only after we have parsed __attribute__ or __declspec" ); - // Kill it - cxxTokenChainDestroyLast(g_cxx.pTokenChain); + CXXToken * pAttrHead = cxxTokenChainTakeLast(g_cxx.pTokenChain); // And go ahead. if(!cxxParserParseNextToken()) { + cxxTokenDestroy(pAttrHead); CXX_DEBUG_LEAVE_TEXT("No next token after __attribute__"); return false; } if(!cxxTokenTypeIs(g_cxx.pToken,CXXTokenTypeOpeningParenthesis)) { + cxxTokenDestroy(pAttrHead); CXX_DEBUG_LEAVE_TEXT("Something that is not an opening parenthesis"); return true; } @@ -947,6 +953,8 @@ static bool cxxParserParseNextTokenCondenseAttribute(void) CXX_DEBUG_LEAVE_TEXT("Failed to parse subchains. The input is broken..."); + cxxTokenDestroy(pAttrHead); + // However our invariant (see comment at the beginning of the function) // forbids us to return false if we didn't find an EOF. So we attempt // to resume parsing anyway. If there is an EOF, cxxParserParseNextToken() @@ -973,12 +981,47 @@ static bool cxxParserParseNextTokenCondenseAttribute(void) cxxParserAnalyzeAttributeChain(pInner->pNext->pChain); } - // Now just kill the chain. - cxxTokenChainDestroyLast(g_cxx.pTokenChain); + CXXToken * pAttrArgs = cxxTokenChainTakeLast(g_cxx.pTokenChain); + CXXToken * pAttrOwner = cxxTokenChainLast(g_cxx.pTokenChain); // And finally extract yet another token. bool bRet = cxxParserParseNextToken(); + if(pAttrOwner == NULL + || cxxTokenTypeIs(pAttrOwner, CXXTokenTypeComma)) { + // If __attribute__ was at the beginning of the chain, + // we cannot attach the __attribute__ side chain to + // the previous token. + // In that case, we attach the side chain to the + // next token. + pAttrOwner = g_cxx.pToken; + } else { + // Look up a previous identifier token. + CXXToken * p = cxxTokenChainPreviousTokenOfType(pAttrOwner, + CXXTokenTypeIdentifier); + if(p) + pAttrOwner = p; + } + + if(pAttrOwner) + { + if(!pAttrOwner->pSideChain) + pAttrOwner->pSideChain = cxxTokenChainCreate(); + cxxTokenChainAppend(pAttrOwner->pSideChain, pAttrHead); + cxxTokenChainAppend(pAttrOwner->pSideChain, pAttrArgs); +#if 0 + fprintf(stderr, "pAttrOwner(%s#%d): ", + pAttrOwner == g_cxx.pToken? "next": "prev", + pAttrOwner->iLineNumber); + CXX_DEBUG_TOKEN(pAttrOwner); + fprintf(stderr, "Side chain: "); + if(pAttrOwner->pSideChain) + CXX_DEBUG_CHAIN(pAttrOwner->pSideChain); + else + CXX_DEBUG_PRINT("NULL\n"); +#endif + } + CXX_DEBUG_LEAVE(); return bRet; } @@ -1326,9 +1369,10 @@ bool cxxParserParseNextToken(void) t->eType = CXXTokenTypeKeyword; t->eKeyword = (CXXKeyword)iCXXKeyword; - if(iCXXKeyword == CXXKeyword__ATTRIBUTE__) + + if(cxxKeywordMayDropInTokenizer(iCXXKeyword)) { - // special handling for __attribute__ + // special handling for __attribute__ and __declspec return cxxParserParseNextTokenCondenseAttribute(); } } @@ -1341,16 +1385,16 @@ bool cxxParserParseNextToken(void) { /* If the macro is overly used, report it here. */ CXX_DEBUG_PRINT("Overly uesd macro %s <%p> useCount: %d (> %d)", - vStringValue(t->pszWord), - pMacro, pMacro? pMacro->useCount: -1, + pMacro->name, + pMacro, pMacro->useCount, CXX_PARSER_MAXIMUM_MACRO_USE_COUNT); } #endif if(pMacro && (pMacro->useCount < CXX_PARSER_MAXIMUM_MACRO_USE_COUNT)) { - CXX_DEBUG_PRINT("Macro %s <%p> useCount: %d", vStringValue(t->pszWord), - pMacro, pMacro? pMacro->useCount: -1); + CXX_DEBUG_PRINT("Macro %s <%p> useCount: %d", pMacro->name, + pMacro, pMacro->useCount); cxxTokenChainDestroyLast(g_cxx.pTokenChain); @@ -1486,12 +1530,10 @@ bool cxxParserParseNextToken(void) return true; } #else - if(g_cxx.iChar == STRING_SYMBOL) + if(g_cxx.iChar == CPP_STRING_SYMBOL) { t->eType = CXXTokenTypeStringConstant; - vStringPut(t->pszWord,'"'); - vStringCat(t->pszWord,cppGetLastCharOrStringContents()); - vStringPut(t->pszWord,'"'); + cppVStringPut(t->pszWord,g_cxx.iChar); g_cxx.iChar = cppGetc(); t->bFollowedBySpace = cppIsspace(g_cxx.iChar); return true; @@ -1534,12 +1576,10 @@ bool cxxParserParseNextToken(void) return true; } #else - if(g_cxx.iChar == CHAR_SYMBOL) + if(g_cxx.iChar == CPP_CHAR_SYMBOL) { t->eType = CXXTokenTypeCharacterConstant; - vStringPut(t->pszWord,'\''); - vStringCat(t->pszWord,cppGetLastCharOrStringContents()); - vStringPut(t->pszWord,'\''); + cppVStringPut(t->pszWord,g_cxx.iChar); g_cxx.iChar = cppGetc(); t->bFollowedBySpace = cppIsspace(g_cxx.iChar); return true; @@ -1681,7 +1721,7 @@ bool cxxParserParseNextToken(void) } t->eType = CXXTokenTypeUnknown; - vStringPut(t->pszWord,g_cxx.iChar); + cppVStringPut(t->pszWord,g_cxx.iChar); g_cxx.iChar = cppGetc(); t->bFollowedBySpace = cppIsspace(g_cxx.iChar); diff --git a/ctags/parsers/cxx/cxx_parser_typedef.c b/ctags/parsers/cxx/cxx_parser_typedef.c index 61fa5ba225..9f542c147a 100644 --- a/ctags/parsers/cxx/cxx_parser_typedef.c +++ b/ctags/parsers/cxx/cxx_parser_typedef.c @@ -174,6 +174,10 @@ void cxxParserExtractTypedef( return; } + unsigned int uProperties = 0; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + CXXToken * t; if(bExpectTerminatorAtEnd) @@ -442,7 +446,14 @@ void cxxParserExtractTypedef( if(bGotTemplate) cxxTagHandleTemplateFields(); - cxxTagCommit(NULL); + vString *pszProperties = NULL; + if(uProperties) + pszProperties = cxxTagSetProperties(uProperties); + + int iCorkQueueIndex = cxxTagCommit(NULL); + cxxTagUseTokenAsPartOfDefTag(iCorkQueueIndex, t); + + vStringDelete(pszProperties); /* NULL is acceptable. */ if ( bGotTemplate && diff --git a/ctags/parsers/cxx/cxx_parser_using.c b/ctags/parsers/cxx/cxx_parser_using.c index dd133fd2e0..4fa9dfdb0e 100644 --- a/ctags/parsers/cxx/cxx_parser_using.c +++ b/ctags/parsers/cxx/cxx_parser_using.c @@ -18,6 +18,7 @@ #include "parse.h" #include "vstring.h" #include "read.h" +#include "trashbox.h" bool cxxParserParseUsingClause(void) { @@ -117,7 +118,7 @@ bool cxxParserParseUsingClause(void) if(g_cxx.pTokenChain->iCount > 0) { - tagEntryInfo * tag; + tagEntryInfo * tag = NULL; if(bUsingNamespace) { @@ -139,11 +140,28 @@ bool cxxParserParseUsingClause(void) t = cxxTokenChainLast(g_cxx.pTokenChain); - CXX_DEBUG_PRINT( + if (cxxTokenTypeIs(t,CXXTokenTypeIdentifier)) { + CXX_DEBUG_PRINT( "Found using clause '%s' which imports a name", vStringValue(t->pszWord) - ); - tag = cxxTagBegin(CXXTagCPPKindNAME,t); + ); + tag = cxxTagBegin(CXXTagCPPKindNAME,t); + } else { + CXXToken * pOp = cxxTokenChainFirstKeyword( + g_cxx.pTokenChain, + CXXKeywordOPERATOR + ); + + if (pOp) { + vString *pOpStr = cxxTokenChainJoinRange (pOp, t, " ", + CXXTokenChainJoinNoTrailingSpaces); + PARSER_TRASH_BOX(pOpStr, vStringDelete); + CXXToken * pOpToken = cxxTokenCopy(pOp); + PARSER_TRASH_BOX(pOpToken, cxxTokenDestroy); + vStringCopy(pOpToken->pszWord, pOpStr); + tag = cxxTagBegin(CXXTagCPPKindNAME,pOpToken); + } + } // FIXME: We need something like "nameref:" here! } diff --git a/ctags/parsers/cxx/cxx_parser_variable.c b/ctags/parsers/cxx/cxx_parser_variable.c index 54c4975aa8..208dd6bffe 100644 --- a/ctags/parsers/cxx/cxx_parser_variable.c +++ b/ctags/parsers/cxx/cxx_parser_variable.c @@ -14,6 +14,7 @@ #include "cxx_token.h" #include "cxx_token_chain.h" #include "cxx_scope.h" +#include "cxx_side_chain.h" #include "vstring.h" #include "read.h" @@ -87,6 +88,8 @@ CXXToken * cxxParserFindFirstPossiblyNestedAndQualifiedIdentifier( // type var: range declaration <-- (FIXME: this is only inside for!) // very complex type with modifiers() namespace::namespace::var = ...; // type namespace::var[] = { +// type var = ...; +// type var[] = ...; // ... // // Assumptions: @@ -162,8 +165,10 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF return false; } - bool bGotVariable = false; + // For tracking a specialization in a variable template + CXXToken * pTemplateSpecializationStart = NULL, * pTemplateSpecializationEnd = NULL; + bool bGotVariable = false; // Loop over the whole statement. while(t) @@ -206,8 +211,9 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF return bGotVariable; } - if(t->eType == CXXTokenTypeSmallerThanSign) + if(cxxTokenTypeIs(t, CXXTokenTypeSmallerThanSign)) { + pTemplateSpecializationStart = t; // Must be part of template type name (so properly balanced). t = cxxTokenChainSkipToEndOfTemplateAngleBracket(t); if(!t) @@ -215,6 +221,7 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF CXX_DEBUG_LEAVE_TEXT("Failed to skip past angle bracket chain"); return bGotVariable; } + pTemplateSpecializationEnd = t; } t = t->pNext; @@ -228,6 +235,7 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF return bGotVariable; } + CXXToken * pEndScanningAttrs = t; CXX_DEBUG_PRINT( "Found notable token '%s' of type 0x%02x(%s)", vStringValue(t->pszWord), @@ -245,6 +253,7 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF CXXToken * pIdentifier = NULL; CXXToken * pTokenBefore = NULL; + bool bSpecializedAsVarTemplate = false; // If we have to continue scanning we'll remove the tokens from here // so they don't end up being part of the type name. @@ -339,7 +348,7 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF cxxTokenTypeIs(t->pPrev,CXXTokenTypeKeyword) && cxxKeywordMayBePartOfTypeName(t->pPrev->eKeyword) && // but not decltype(var)! - (t->pPrev->eKeyword != CXXKeywordDECLTYPE) + !cxxKeywordIsDecltype(t->pPrev->eKeyword) ) ) && ( @@ -348,19 +357,20 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF ) ) { - CXX_DEBUG_LEAVE_TEXT("Parenthesis seems to surround a variable definition"); + CXX_DEBUG_PRINT("Parenthesis seems to surround a variable definition"); pTokenBefore = t->pPrev; t = t->pNext; goto got_identifier; } if( - cxxTokenIsKeyword(t->pPrev,CXXKeywordDECLTYPE) && + cxxTokenTypeIs(t->pPrev,CXXTokenTypeKeyword) && + cxxKeywordIsDecltype(t->pPrev->eKeyword) && t->pNext ) { // part of typename -> skip ahead - CXX_DEBUG_LEAVE_TEXT("Parenthesis follows decltype(), skipping"); + CXX_DEBUG_PRINT("Parenthesis follows decltype(), skipping"); t = t->pNext; continue; } @@ -423,7 +433,17 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF // check for array // Keep the array specifier as part of type - pIdentifier = t->pPrev; + // pTemplateSpecializationStart + // | pTemplateSpecializationEnd + // --------V-V + // type var[] = ... + // -----------^------- t + if (t->pPrev && t->pPrev == pTemplateSpecializationEnd) + bSpecializedAsVarTemplate = true; + + pIdentifier = bSpecializedAsVarTemplate + ? pTemplateSpecializationStart->pPrev + : t->pPrev; pTokenBefore = pIdentifier->pPrev; while(t->pNext && cxxTokenTypeIs(t->pNext,CXXTokenTypeSquareParenthesisChain)) @@ -465,18 +485,68 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF t = t->pNext; break; default: + // pTemplateSpecializationStart + // | pTemplateSpecializationEnd + // --------V-V + // type var = ... + // ------------^------ t + + if (t->pPrev && t->pPrev == pTemplateSpecializationEnd) + bSpecializedAsVarTemplate = true; + // Must be identifier - if(t->pPrev->eType != CXXTokenTypeIdentifier) + if (bSpecializedAsVarTemplate && + !cxxTokenTypeIs(pTemplateSpecializationStart->pPrev, CXXTokenTypeIdentifier)) + { + CXX_DEBUG_LEAVE_TEXT("No identifier before the <> specializer"); + return bGotVariable; + } + else if (!bSpecializedAsVarTemplate && + !cxxTokenTypeIs(t->pPrev, CXXTokenTypeIdentifier)) { CXX_DEBUG_LEAVE_TEXT("No identifier before the notable token"); return bGotVariable; } - pIdentifier = t->pPrev; + pIdentifier = bSpecializedAsVarTemplate + ? pTemplateSpecializationStart->pPrev + : t->pPrev; pTokenBefore = pIdentifier->pPrev; + + // Skip the specializer. + // When extracting the typeref for the variable, the specializer + // becomes noise. + if (bSpecializedAsVarTemplate) + t = pTemplateSpecializationEnd->pNext; + + // The final states: + // pTokenBefore + // | pIdentifier + // V V + // type var ... = + // -------------^- t break; } + if (bSpecializedAsVarTemplate) { + pTemplateSpecializationEnd->bFollowedBySpace = false; + CXXToken * pTemplateSpecialization = cxxTokenChainExtractRange( + pTemplateSpecializationStart, + pTemplateSpecializationEnd, + 0 + ); + if(g_cxx.pTemplateSpecializationTokenChain) + cxxTokenChainClear(g_cxx.pTemplateSpecializationTokenChain); + else + g_cxx.pTemplateSpecializationTokenChain = cxxTokenChainCreate(); + cxxTokenChainAppend(g_cxx.pTemplateSpecializationTokenChain, + pTemplateSpecialization); + cxxTokenChainDestroyRange(pChain, + pTemplateSpecializationStart, + pTemplateSpecializationEnd); + pTemplateSpecializationStart = pTemplateSpecializationEnd = NULL; + } + got_identifier: CXX_DEBUG_ASSERT(pIdentifier,"We should have found an identifier here"); @@ -508,7 +578,7 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF CXXToken * pScopeStart = NULL; // Skip back to the beginning of the scope, if any - while(pTokenBefore->eType == CXXTokenTypeMultipleColons) + while(cxxTokenTypeIs(pTokenBefore, CXXTokenTypeMultipleColons)) { if(!cxxParserCurrentLanguageIsCPP()) { @@ -598,14 +668,25 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF // Possibly one of: // MACRO(whatever) variable; // decltype(whatever) variable; + // __typeof(whatever) variable; + // __typeof__(whatever) variable; + // typeof(whatever) variable; cxxTokenTypeIs(pTokenBefore,CXXTokenTypeParenthesisChain) && pTokenBefore->pPrev && - !pTokenBefore->pPrev->pPrev && + (!pTokenBefore->pPrev->pPrev || + ( + cxxTokenTypeIs(pTokenBefore->pPrev->pPrev,CXXTokenTypeKeyword) && + cxxKeywordMayAppearInVariableDeclaration(pTokenBefore->pPrev->pPrev->eKeyword) + ) + ) && ( // macro cxxTokenTypeIs(pTokenBefore->pPrev,CXXTokenTypeIdentifier) || - // decltype - cxxTokenIsKeyword(pTokenBefore->pPrev,CXXKeywordDECLTYPE) + // decltype or typeof + ( + cxxTokenTypeIs(pTokenBefore->pPrev,CXXTokenTypeKeyword) && + cxxKeywordIsDecltype(pTokenBefore->pPrev->eKeyword) + ) ) ) { @@ -757,6 +838,17 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF // Volatile is part of the type, so we don't mark it as a property //if(g_cxx.uKeywordState & CXXParserKeywordStateSeenVolatile) // uProperties |= CXXTagPropertyVolatile; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenConstexpr) + uProperties |= CXXTagPropertyConstexpr; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenConstinit) + uProperties |= CXXTagPropertyConstinit; + // consteval is not here; it is for functions. + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenThreadLocal) + uProperties |= CXXTagPropertyThreadLocal; + if(g_cxx.uKeywordState & CXXParserKeywordStateSeenExport) + uProperties |= CXXTagPropertyExport; + if(bSpecializedAsVarTemplate) + uProperties |= CXXTagPropertyTemplateSpecialization; pszProperties = cxxTagSetProperties(uProperties); } @@ -764,7 +856,12 @@ bool cxxParserExtractVariableDeclarations(CXXTokenChain * pChain,unsigned int uF if(bGotTemplate) cxxTagHandleTemplateFields(); + cxxSideChainCollectInRange(cxxTokenChainFirst(pChain), pEndScanningAttrs, + pIdentifier); + cxxSideChainScan(pIdentifier->pSideChain); + iCorkIndex = cxxTagCommit(&iCorkIndexFQ); + cxxTagUseTokenAsPartOfDefTag(iCorkIndexFQ, pIdentifier); if(pTypeToken) cxxTokenDestroy(pTypeToken); diff --git a/ctags/parsers/cxx/cxx_qtmoc.c b/ctags/parsers/cxx/cxx_qtmoc.c index d02f227e29..02f512a2c8 100644 --- a/ctags/parsers/cxx/cxx_qtmoc.c +++ b/ctags/parsers/cxx/cxx_qtmoc.c @@ -89,8 +89,7 @@ static void qtMocMakeTagForProperty (CXXToken * pToken, const char *pszType) initTagEntry(&tag, vStringValue(pToken->pszWord), K_PROPERTY); - tag.lineNumber = pToken->iLineNumber; - tag.filePosition = pToken->oFilePosition; + updateTagLine (&tag, pToken->iLineNumber, pToken->oFilePosition); tag.isFileScope = false; if(!cxxScopeIsGlobal()) diff --git a/ctags/parsers/cxx/cxx_scope.c b/ctags/parsers/cxx/cxx_scope.c index f370c6efce..4993da6902 100644 --- a/ctags/parsers/cxx/cxx_scope.c +++ b/ctags/parsers/cxx/cxx_scope.c @@ -112,6 +112,8 @@ unsigned int cxxScopeGetKind(void) return CXXTagKindVARIABLE; case CXXScopeTypeTypedef: return CXXTagKindTYPEDEF; + case CXXScopeTypeModule: + return CXXTagCPPKindMODULE; default: CXX_DEBUG_ASSERT(false,"Unhandled scope type!"); break; @@ -135,6 +137,13 @@ const char * cxxScopeGetName(void) return vStringValue(g_pScope->pTail->pszWord); } +int cxxScopeGetDefTag(void) +{ + if(g_pScope->iCount < 1) + return CORK_NIL; + return g_pScope->pTail->iCorkIndex; +} + int cxxScopeGetSize(void) { return g_pScope->iCount; @@ -143,7 +152,7 @@ int cxxScopeGetSize(void) const char * cxxScopeGetFullName(void) { if(!g_bScopeNameDirty) - return g_szScopeName ? g_szScopeName->buffer : NULL; + return g_szScopeName ? vStringValue(g_szScopeName): NULL; if(g_pScope->iCount < 1) { @@ -151,10 +160,7 @@ const char * cxxScopeGetFullName(void) return NULL; } - if(g_szScopeName) - vStringClear(g_szScopeName); - else - g_szScopeName = vStringNew(); + g_szScopeName = vStringNewOrClear(g_szScopeName); cxxTokenChainJoinInString( g_pScope, @@ -164,7 +170,7 @@ const char * cxxScopeGetFullName(void) ); g_bScopeNameDirty = false; - return g_szScopeName->buffer; + return vStringValue(g_szScopeName); } vString * cxxScopeGetFullNameAsString(void) @@ -182,10 +188,7 @@ vString * cxxScopeGetFullNameAsString(void) if(g_pScope->iCount < 1) return NULL; - if(g_szScopeName) - vStringClear(g_szScopeName); - else - g_szScopeName = vStringNew(); + g_szScopeName = vStringNewOrClear(g_szScopeName); cxxTokenChainJoinInString( g_pScope, @@ -223,7 +226,7 @@ void cxxScopeSetAccess(enum CXXScopeAccess eAccess) void cxxScopePushTop(CXXToken * t) { CXX_DEBUG_ASSERT( - t->eType == CXXTokenTypeIdentifier, + cxxTokenTypeIs(t, CXXTokenTypeIdentifier), "The scope name must be an identifier" ); CXX_DEBUG_ASSERT( diff --git a/ctags/parsers/cxx/cxx_scope.h b/ctags/parsers/cxx/cxx_scope.h index dc7cddd20b..cc186dfd00 100644 --- a/ctags/parsers/cxx/cxx_scope.h +++ b/ctags/parsers/cxx/cxx_scope.h @@ -32,6 +32,7 @@ enum CXXScopeType CXXScopeTypeVariable, // template variables, mainly CXXScopeTypePrototype, CXXScopeTypeTypedef, // template variables used in "using A = B" + CXXScopeTypeModule, // Just for filling the scope field of partitions CXXScopeTypeLAST }; @@ -48,6 +49,10 @@ const char * cxxScopeGetFullName(void); // it is always a single identifier. const char * cxxScopeGetName(void); +// Return the corkIndex of the token representing currently scope. +// This can be CORK_NIL. +int cxxScopeGetDefTag(void); + // Return the number of components of the scope name. int cxxScopeGetSize(void); diff --git a/ctags/parsers/cxx/cxx_side_chain.c b/ctags/parsers/cxx/cxx_side_chain.c new file mode 100644 index 0000000000..03f32a062a --- /dev/null +++ b/ctags/parsers/cxx/cxx_side_chain.c @@ -0,0 +1,230 @@ +/* +* Copyright (c) 2020, Masatake YAMATO +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for extracting information __attribute__ +* lists. +*/ +#include "cxx_side_chain.h" + +#include "cxx_debug.h" +#include "cxx_tag.h" +#include "cxx_token_chain.h" +#include "cxx_parser_internal.h" + +#include "entry.h" + +static const CXXToken *cxxExtractFirstArgumentInAttrs(const CXXToken * pToken) +{ + pToken = pToken->pNext; + + if(!pToken) + return NULL; + if(!pToken->pChain) + return NULL; + + pToken = pToken->pChain->pHead; + if(!pToken) + return NULL; + + if(!cxxTokenTypeIs(pToken,CXXTokenTypeOpeningParenthesis)) + return NULL; + + pToken = pToken->pNext; + if(!pToken) + return NULL; + + if(!cxxTokenTypeIs(pToken,CXXTokenTypeStringConstant)) + return NULL; + + return pToken; +} + +static void cxxScanAttrExtractSection(const CXXToken * pToken) +{ + const CXXToken *pArgToken = cxxExtractFirstArgumentInAttrs(pToken); + + if(pArgToken == NULL) + return; + + Assert(vStringLength(pArgToken->pszWord)); + + vStringChop(pArgToken->pszWord); + cxxTagSetField(CXXTagFieldSection, vStringValue(pArgToken->pszWord)+1, true); + + tagEntryInfo e; + static langType lang = LANG_AUTO; + + if(lang == LANG_AUTO) + lang = getNamedLanguage("LdScript", 0); + if(lang == LANG_IGNORE) + goto out; + + static kindDefinition * kdef = NULL; + if(kdef == NULL) + kdef = getLanguageKindForName (lang, "inputSection"); + if(kdef == NULL) + goto out; + + static roleDefinition *rdef = NULL; + if(rdef == NULL) + rdef = getLanguageRoleForName (lang, kdef->id, "destination"); + if(rdef == NULL) + goto out; + + initForeignRefTagEntry(&e, vStringValue(pArgToken->pszWord)+1, + lang, kdef->id, rdef->id); + makeTagEntry(&e); + + out: + vStringPut(pArgToken->pszWord, '"'); +} + +static void cxxScanAttrExtractAlias(const CXXToken * pToken) +{ + const CXXToken *pArgToken = cxxExtractFirstArgumentInAttrs(pToken); + if(pArgToken == NULL) + return; + + Assert(vStringLength(pArgToken->pszWord)); + + // Remve doubule quote characters surrounding the constant string. + vStringChop(pArgToken->pszWord); + cxxTagSetField(CXXTagFieldAlias, vStringValue(pArgToken->pszWord)+1, true); + + tagEntryInfo e; + static langType lang = LANG_AUTO; + + if(lang == LANG_AUTO) + lang = getNamedLanguage("LdScript", 0); + if(lang == LANG_IGNORE) + goto out; + + static kindDefinition * kdef = NULL; + if(kdef == NULL) + kdef = getLanguageKindForName (lang, "symbol"); + if(kdef == NULL) + goto out; + + static roleDefinition *rdef = NULL; + if(rdef == NULL) + rdef = getLanguageRoleForName (lang, kdef->id, "aliased"); + if(rdef == NULL) + goto out; + + initForeignRefTagEntry(&e, vStringValue(pArgToken->pszWord)+1, + lang, kdef->id, rdef->id); + makeTagEntry(&e); + + out: + vStringPut(pArgToken->pszWord, '"'); +} + +static void cxxScanAttributes(const CXXTokenChain * pAttrChain) +{ + if(pAttrChain == NULL) + return; + + CXXToken * t = pAttrChain->pHead; + bool bSection = false; + bool bAlias = false; + + if((cxxParserCurrentLanguageIsC() || cxxParserCurrentLanguageIsCPP())) + { + bSection = cxxTagFieldEnabled(CXXTagFieldSection); + bAlias = cxxTagFieldEnabled(CXXTagFieldAlias); + } + + while(t) + { + if(cxxTokenTypeIs(t,CXXTokenTypeParenthesisChain) + && t->pChain && t->pChain->pHead) + { + if(bSection) + { + CXXToken * s = cxxTokenChainNextIdentifier(t->pChain->pHead, "section"); + if(!s) + s = cxxTokenChainNextIdentifier(t->pChain->pHead, "__section__"); + if(s) + cxxScanAttrExtractSection(s); + } + if(bAlias) + { + CXXToken * s = cxxTokenChainNextIdentifier(t->pChain->pHead, "alias"); + if(s) + cxxScanAttrExtractAlias(s); + } + } + if(t == pAttrChain->pTail) + break; + t = t->pNext; + } +} + +void cxxSideChainScan(const CXXTokenChain * pSideChain) +{ + bool bAttr = false; + if(pSideChain == NULL) + return; + + CXXToken * t = pSideChain->pHead; + while(t) + { + if(bAttr) + { + bAttr = false; + if(cxxTokenTypeIs(t,CXXTokenTypeParenthesisChain)) + cxxScanAttributes(t->pChain); + } + if(cxxTokenIsKeyword(t,CXXKeyword__ATTRIBUTE__)) + bAttr = true; + + if(t == pSideChain->pTail) + break; + t = t->pNext; + } +} + +void cxxSideChainCollectInRange(CXXToken *pStart, CXXToken *pEnd, CXXToken * pDest) +{ + do + { + if(pStart != pDest) + cxxSideChainAppend(pStart, pDest); + if(pStart == pEnd) + break; + pStart = pStart->pNext; + } + while(true); +} + +void cxxSideChainAppendChain(CXXTokenChain * pSideChain, CXXToken * dest) +{ + if(!pSideChain) + return; + + if(dest->pSideChain) + { + cxxTokenChainAppendEntries(pSideChain, dest->pSideChain); + cxxTokenChainDestroy(pSideChain); + } else + dest->pSideChain = pSideChain; +} + +void cxxSideChainAppend(CXXToken * src, CXXToken * dest) +{ + cxxSideChainAppendChain(src->pSideChain, dest); + src->pSideChain = NULL; +} + +CXXTokenChain * cxxSideChainEject(CXXToken * pToken) +{ + if(!pToken) + return NULL; + + CXXTokenChain *pSideChain = pToken->pSideChain; + pToken->pSideChain = NULL; + return pSideChain; +} diff --git a/ctags/parsers/cxx/cxx_side_chain.h b/ctags/parsers/cxx/cxx_side_chain.h new file mode 100644 index 0000000000..f03abd39bc --- /dev/null +++ b/ctags/parsers/cxx/cxx_side_chain.h @@ -0,0 +1,33 @@ +#ifndef ctags_cxx_side_chain_h_ +#define ctags_cxx_side_chain_h_ +/* +* Copyright (c) 2020-2022 Masatake YAMATO +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for extracting information __attribute__ +* lists. +*/ + +#include "general.h" + +#include "cxx_token_chain.h" + +// cxxSideChainScan and cxxSideChainScanInRange are assumed to be called +// after calling cxxTagBegin and before calling cxxTagCommit. +// cxxSideChainScan scans PSIDECHAIN where __attribute__ sequence are stored +// to find something interesting. +void cxxSideChainScan(const CXXTokenChain * pSideChain); + +// Collect all side chanins in token between PSTART to PEND, and append them to +// the side chain of PDEST. +void cxxSideChainCollectInRange(CXXToken *pStart, CXXToken *pEnd, CXXToken * pDest); + +void cxxSideChainAppend(CXXToken * src, CXXToken * dest); +void cxxSideChainAppendChain(CXXTokenChain * pSideChain, CXXToken * dest); + +// Return the side chain of PTOKEN, and fill NULL the side chain member of PTOKEN. +CXXTokenChain * cxxSideChainEject(CXXToken * pToken); + +#endif //!ctags_cxx_side_chain_h_ diff --git a/ctags/parsers/cxx/cxx_tag.c b/ctags/parsers/cxx/cxx_tag.c index 40275da659..47d656ce57 100644 --- a/ctags/parsers/cxx/cxx_tag.c +++ b/ctags/parsers/cxx/cxx_tag.c @@ -22,6 +22,7 @@ #define CXX_COMMON_MACRO_ROLES(__langPrefix) \ static roleDefinition __langPrefix##MacroRoles [] = { \ RoleTemplateUndef, \ + RoleTemplateCondition, \ } CXX_COMMON_MACRO_ROLES(C); @@ -35,16 +36,48 @@ CXX_COMMON_MACRO_ROLES(CUDA); } CXX_COMMON_HEADER_ROLES(C); -CXX_COMMON_HEADER_ROLES(CXX); +static roleDefinition CXXHeaderRoles [] = { + RoleTemplateSystem, + RoleTemplateLocal, + { true, "imported", "imported with \"imported ...\"" }, + { true, "exported", "exported with \"exported imported ...\"" }, +}; CXX_COMMON_HEADER_ROLES(CUDA); +/* Currently V parser wants these items. */ +#define RoleTemplateForeignDecl { true, "foreigndecl", "declared in foreign languages" } + +#define CXX_COMMON_FUNCTION_ROLES(__langPrefix) \ + static roleDefinition __langPrefix##FunctionRoles [] = { \ + RoleTemplateForeignDecl, \ + } + +CXX_COMMON_FUNCTION_ROLES(C); + +#define CXX_COMMON_STRUCT_ROLES(__langPrefix) \ + static roleDefinition __langPrefix##StructRoles [] = { \ + RoleTemplateForeignDecl, \ + } + +CXX_COMMON_STRUCT_ROLES(C); + +static roleDefinition CXXModuleRoles [] = { + + { true, "partOwner", "used for specifying a partition" }, + { true, "imported", "imported with \"imported ...\"" }, +}; + +static roleDefinition CXXPartitionRoles [] = { + { true, "imported", "imported with \"imported ...\"" }, +}; -#define CXX_COMMON_KINDS(_langPrefix, _szMemberDescription, _syncWith) \ +#define CXX_COMMON_KINDS(_langPrefix, _szMemberDescription, _syncWith, FUNC_ROLES, STRUCT_ROLES) \ { true, 'd', "macro", "macro definitions", \ .referenceOnly = false, ATTACH_ROLES(_langPrefix##MacroRoles), .syncWith = _syncWith \ }, \ { true, 'e', "enumerator", "enumerators (values inside an enumeration)", .syncWith = _syncWith }, \ - { true, 'f', "function", "function definitions", .syncWith = _syncWith }, \ + { true, 'f', "function", "function definitions", \ + .referenceOnly = false, FUNC_ROLES, .syncWith = _syncWith }, \ { true, 'g', "enum", "enumeration names", .syncWith = _syncWith }, \ { true, 'h', "header", "included header files", \ .referenceOnly = true, ATTACH_ROLES(_langPrefix##HeaderRoles), .syncWith = _syncWith \ @@ -52,7 +85,8 @@ CXX_COMMON_HEADER_ROLES(CUDA); { false, 'l', "local", "local variables", .syncWith = _syncWith }, \ { true, 'm', "member", _szMemberDescription, .syncWith = _syncWith }, \ { false, 'p', "prototype", "function prototypes", .syncWith = _syncWith }, \ - { true, 's', "struct", "structure names", .syncWith = _syncWith }, \ + { true, 's', "struct", "structure names", \ + .referenceOnly = false, STRUCT_ROLES, .syncWith = _syncWith }, \ { true, 't', "typedef", "typedefs", .syncWith = _syncWith }, \ { true, 'u', "union", "union names", .syncWith = _syncWith }, \ { true, 'v', "variable", "variable definitions", .syncWith = _syncWith }, \ @@ -65,21 +99,25 @@ static kindDefinition g_aCXXCKinds [] = { /* All other than LANG_AUTO are ignored. LANG_IGNORE is specified as a just placeholder for the macro, and is not needed. */ - CXX_COMMON_KINDS(C,"struct, and union members", LANG_IGNORE) + CXX_COMMON_KINDS(C,"struct, and union members", LANG_IGNORE, ATTACH_ROLES(CFunctionRoles), ATTACH_ROLES(CStructRoles)) }; static kindDefinition g_aCXXCPPKinds [] = { - CXX_COMMON_KINDS(CXX,"class, struct, and union members", LANG_AUTO), + CXX_COMMON_KINDS(CXX,"class, struct, and union members", LANG_AUTO, .nRoles = 0, .nRoles = 0), { true, 'c', "class", "classes" }, { true, 'n', "namespace", "namespaces" }, { false, 'A', "alias", "namespace aliases" }, { false, 'N', "name", "names imported via using scope::symbol" }, { false, 'U', "using", "using namespace statements" }, { false, 'Z', "tparam", "template parameters" }, + { true, 'M', "module", "modules", + .referenceOnly = false, ATTACH_ROLES(CXXModuleRoles) }, + { true, 'P', "partition", "partitions", + .referenceOnly = false, ATTACH_ROLES(CXXPartitionRoles) }, }; static kindDefinition g_aCXXCUDAKinds [] = { - CXX_COMMON_KINDS(CUDA,"struct, and union members", LANG_IGNORE) + CXX_COMMON_KINDS(CUDA,"struct, and union members", LANG_IGNORE, .nRoles = 0, .nRoles = 0) }; static const char * g_aCXXAccessStrings [] = { @@ -92,12 +130,20 @@ static const char * g_aCXXAccessStrings [] = { #define CXX_COMMON_FIELDS \ { \ .name = "properties", \ - .description = "properties (static, inline, mutable,...)", \ + .description = "properties (static, inline, mutable, export,...)", \ .enabled = false \ }, { \ .name = "macrodef", \ .description = "macro definition", \ .enabled = false \ + }, { \ + .name = "section", \ + .description = "the place where the object is placed", \ + .enabled = false \ + }, { \ + .name = "alias", \ + .description = "the name of the alias target specified in __attribute__((alias(...)))", \ + .enabled = false \ } static fieldDefinition g_aCXXCFields [] = { @@ -201,6 +247,21 @@ bool cxxTagKindEnabled(unsigned int uKind) return g_cxx.pKindDefinitions[uKind].enabled; } +bool cxxTagRoleEnabled(unsigned int uKind, int iRole) +{ + if(!cxxTagKindEnabled(uKind)) + return true; + if(iRole == ROLE_DEFINITION_INDEX) + return true; + + CXX_DEBUG_ASSERT( + (ROLE_DEFINITION_INDEX < iRole + && iRole < g_cxx.pKindDefinitions[uKind].nRoles), + "The role must be associated to the kind (%u)", uKind + ); + return g_cxx.pKindDefinitions[uKind].roles[iRole].enabled; +} + fieldDefinition * cxxTagGetCPPFieldDefinitionifiers(void) { return g_aCXXCPPFields; @@ -243,8 +304,47 @@ bool cxxTagFieldEnabled(unsigned int uField) static tagEntryInfo g_oCXXTag; +void cxxTagUseTokenAsPartOfDefTag(int iCorkIndex, CXXToken * pToken) +{ + Assert (pToken->iCorkIndex == CORK_NIL); + pToken->iCorkIndex = iCorkIndex; +} -tagEntryInfo * cxxTagBegin(unsigned int uKind,CXXToken * pToken) +void cxxTagUseTokensInRangeAsPartOfDefTags(int iCorkIndex, CXXToken * pFrom, CXXToken * pTo) +{ + cxxTagUseTokenAsPartOfDefTag(iCorkIndex, pFrom); + while (pFrom != pTo) + { + pFrom = pFrom->pNext; + cxxTagUseTokenAsPartOfDefTag(iCorkIndex, pFrom); + } +} + +static short cxxTagLookBackLastNth(langType iLangType, int iScopeIndex, unsigned int uKind) +{ + for (size_t uCount = countEntryInCorkQueue (); uCount > (CORK_NIL + 1); uCount--) + { + int iCorkIndex = (int)(uCount - 1); + tagEntryInfo *pTag = getEntryInCorkQueue(iCorkIndex); + if (iCorkIndex == iScopeIndex) + return 0; + else if (pTag->extensionFields.scopeIndex == iScopeIndex + && pTag->langType == iLangType + && pTag->kindIndex == uKind) + { + return pTag->extensionFields.nth + ( + /* Over-wrapped; if the value is too large for sizeof(nth), + * Don't increment more. */ + ((short)(pTag->extensionFields.nth + 1) > 0) + ? 1 + : 0 + ); + } + } + return NO_NTH_FIELD; +} + +tagEntryInfo * cxxRefTagBegin(unsigned int uKind, int iRole, CXXToken * pToken) { kindDefinition * pKindDefinitions = g_cxx.pKindDefinitions; @@ -254,20 +354,32 @@ tagEntryInfo * cxxTagBegin(unsigned int uKind,CXXToken * pToken) return NULL; } - initTagEntry( + initRefTagEntry( &g_oCXXTag, vStringValue(pToken->pszWord), - uKind + uKind, + iRole ); - g_oCXXTag.lineNumber = pToken->iLineNumber; - g_oCXXTag.filePosition = pToken->oFilePosition; + updateTagLine (&g_oCXXTag, pToken->iLineNumber, pToken->oFilePosition); g_oCXXTag.isFileScope = false; if(!cxxScopeIsGlobal()) { + // scopeKindIndex and scopeName are used for printing the scope field + // in a tags file. g_oCXXTag.extensionFields.scopeKindIndex = cxxScopeGetKind(); g_oCXXTag.extensionFields.scopeName = cxxScopeGetFullName(); + // scopeIndex is used in the parser internally. + g_oCXXTag.extensionFields.scopeIndex = cxxScopeGetDefTag(); + if (isFieldEnabled(FIELD_NTH) && g_oCXXTag.extensionFields.scopeIndex != CORK_NIL) + { + if (uKind == CXXTagKindMEMBER || uKind == CXXTagKindENUMERATOR + || uKind == CXXTagKindPARAMETER || uKind == CXXTagCPPKindTEMPLATEPARAM) + g_oCXXTag.extensionFields.nth = cxxTagLookBackLastNth(g_oCXXTag.langType, + g_oCXXTag.extensionFields.scopeIndex, + uKind); + } } // FIXME: meaning of "is file scope" is quite debatable... @@ -276,6 +388,11 @@ tagEntryInfo * cxxTagBegin(unsigned int uKind,CXXToken * pToken) return &g_oCXXTag; } +tagEntryInfo * cxxTagBegin(unsigned int uKind,CXXToken * pToken) +{ + return cxxRefTagBegin(uKind, ROLE_DEFINITION_INDEX, pToken); +} + vString * cxxTagSetProperties(unsigned int uProperties) { if(uProperties == 0) @@ -333,6 +450,16 @@ vString * cxxTagSetProperties(unsigned int uProperties) ADD_PROPERTY("scopedenum"); if(uProperties & CXXTagPropertyFunctionTryBlock) ADD_PROPERTY("fntryblock"); + if (uProperties & CXXTagPropertyConstexpr) + ADD_PROPERTY("constexpr"); + if (uProperties & CXXTagPropertyConsteval) + ADD_PROPERTY("consteval"); + if (uProperties & CXXTagPropertyConstinit) + ADD_PROPERTY("constinit"); + if (uProperties & CXXTagPropertyThreadLocal) + ADD_PROPERTY("thread_local"); + if (uProperties & CXXTagPropertyExport) + ADD_PROPERTY("export"); cxxTagSetField(CXXTagFieldProperties,vStringValue(pszProperties),false); @@ -530,7 +657,7 @@ void cxxTagSetField(unsigned int uField,const char * szValue,bool bCopyValue) /* If we make a copy for the value, the copy must be freed after * calling cxxTagCommit() for g_oCXXTag. The parser trash box * allows us to delay freeing the copy. */ - attachParserField(&g_oCXXTag,false,g_cxx.pFieldOptions[uField].ftype, + attachParserField(&g_oCXXTag,g_cxx.pFieldOptions[uField].ftype, bCopyValue?parserTrashBoxPut(eStrdup(szValue),eFree):szValue); } @@ -632,6 +759,8 @@ int cxxTagCommit(int *piCorkQueueIndexFQ) #endif int iCorkQueueIndex = makeTagEntry(&g_oCXXTag); + if (iCorkQueueIndex != CORK_NIL) + registerEntry(iCorkQueueIndex); // Handle --extra=+q if(!isXtagEnabled(XTAG_QUALIFIED_TAGS)) @@ -671,7 +800,7 @@ int cxxTagCommit(int *piCorkQueueIndexFQ) x = vStringNewInit(g_oCXXTag.extensionFields.scopeName); } - vStringCatS(x,"::"); + vStringCatS(x, (eScopeType == CXXScopeTypeModule)? ":": "::"); vStringCatS(x,g_oCXXTag.name); g_oCXXTag.name = vStringValue(x); diff --git a/ctags/parsers/cxx/cxx_tag.h b/ctags/parsers/cxx/cxx_tag.h index 78f89e91ef..33e9a732aa 100644 --- a/ctags/parsers/cxx/cxx_tag.h +++ b/ctags/parsers/cxx/cxx_tag.h @@ -47,7 +47,9 @@ enum CXXTagCPPKind CXXTagCPPKindALIAS, CXXTagCPPKindNAME, CXXTagCPPKindUSING, - CXXTagCPPKindTEMPLATEPARAM + CXXTagCPPKindTEMPLATEPARAM, + CXXTagCPPKindMODULE, + CXXTagCPPKindPARTITION, }; // The fields common to all (sub)languages this parser supports. @@ -55,6 +57,8 @@ enum CXXTagCommonField { CXXTagFieldProperties, CXXTagFieldMacrodef, + CXXTagFieldSection, + CXXTagFieldAlias, CXXTagCommonFieldCount }; @@ -92,12 +96,16 @@ int cxxTagGetCPPKindDefinitionCount(void); // Returns true if the specified tag kind is enabled in the current language bool cxxTagKindEnabled(unsigned int uTagKind); +// Returns true if the specified tag role is enabled in the current language +bool cxxTagRoleEnabled(unsigned int uTagKind, int iTagRole); + // Begin composing a tag. The tag kind must correspond to the current language. // Returns NULL if the tag should *not* be included in the output // or the tag entry info that can be filled up with extension fields. // Must be followed by cxxTagCommit() if it returns a non-NULL value. // The pToken ownership is NOT transferred. tagEntryInfo * cxxTagBegin(unsigned int uKind,CXXToken * pToken); +tagEntryInfo * cxxRefTagBegin(unsigned int uKind, int iRole, CXXToken * pToken); // Set the type of the current tag from the specified token sequence // (which must belong to the same chain!). @@ -150,7 +158,17 @@ typedef enum _CXXTagProperty // scoped enum (C++11) CXXTagPropertyScopedEnum = (1 << 16), // function-try-block: int f() try { ... } catch { ... } - CXXTagPropertyFunctionTryBlock = (1 << 17) + CXXTagPropertyFunctionTryBlock = (1 << 17), + // constexpr has been seen. + CXXTagPropertyConstexpr = (1 << 18), + // consteval has been seen. + CXXTagPropertyConsteval = (1 << 19), + // constinit has been seen. + CXXTagPropertyConstinit = (1 << 20), + // thread_local has been seen. + CXXTagPropertyThreadLocal = (1 << 21), + // export has been seen, + CXXTagPropertyExport = (1 << 22), } CXXTagProperty; // Set the modifiers field of the tag. @@ -177,7 +195,7 @@ void cxxTagSetCorkQueueField( ); // Handle the template-related parts of the tag (class, function, variable) -void cxxTagHandleTemplateFields(); +void cxxTagHandleTemplateFields(void); // Commit the composed tag. Must follow a successful cxxTagBegin() call. // Returns the index of the tag in the cork queue. @@ -188,6 +206,7 @@ void cxxTag(unsigned int uKind,CXXToken * pToken); typedef enum { CR_MACRO_UNDEF, + CR_MACRO_CONDITION, } cMacroRole; typedef enum { @@ -195,8 +214,33 @@ typedef enum { CR_HEADER_LOCAL, } cHeaderRole; +typedef enum { + CXXR_HEADER_IMPORTED = CR_HEADER_LOCAL + 1, + CXXR_HEADER_EXPORTED +} cxxHeaderRole; + +typedef enum { + CXXTagFUNCTIONRoleFOREIGNDECL, +} CXXTagCFunctionRole; + +typedef enum { + CXXTagSTRUCTRoleFOREIGNDECL, +} CXXTagCStructRole; + +typedef enum { + CXXTagMODULERolePartOwner, + CXXTagMODULERoleImported, +} cxxModuleRole; + +typedef enum { + CXXTagPARTITIONRoleImported, +} cxxPartitionRole; + // Initialize the parser state for the specified language. // Must be called before attempting to access the kind options. void cxxTagInitForLanguage(langType eLangType); +// Functions for filling iCorkIndex field of tokens. +void cxxTagUseTokensInRangeAsPartOfDefTags(int iCorkIndex, CXXToken * pFrom, CXXToken * pTo); +void cxxTagUseTokenAsPartOfDefTag(int iCorkIndex, CXXToken * pToken); #endif //!_cxxTag_h_ diff --git a/ctags/parsers/cxx/cxx_token.c b/ctags/parsers/cxx/cxx_token.c index 547dc15073..f2ee525a1d 100644 --- a/ctags/parsers/cxx/cxx_token.c +++ b/ctags/parsers/cxx/cxx_token.c @@ -31,11 +31,14 @@ static CXXToken *createToken(void *createArg CTAGS_ATTR_UNUSED) // we almost always want a string, and since this token // is being reused..well.. we always want it t->pszWord = vStringNew(); + t->iCorkIndex = CORK_NIL; + t->pSideChain = NULL; return t; } static void deleteToken(CXXToken *token) { + cxxTokenChainDestroy(token->pSideChain); vStringDelete(token->pszWord); eFree(token); } @@ -53,6 +56,14 @@ static void clearToken(CXXToken *t) t->pChain = NULL; t->pNext = NULL; t->pPrev = NULL; + + t->iCorkIndex = CORK_NIL; + + if(t->pSideChain) + { + cxxTokenChainDestroy(t->pSideChain); + t->pSideChain = NULL; + } } void cxxTokenAPIInit(void) @@ -117,8 +128,9 @@ CXXToken * cxxTokenCopy(CXXToken * pToken) pRetToken->oFilePosition = pToken->oFilePosition; pRetToken->eType = pToken->eType; pRetToken->eKeyword = pToken->eKeyword; - pToken->bFollowedBySpace = pToken->bFollowedBySpace; + pRetToken->bFollowedBySpace = pToken->bFollowedBySpace; vStringCat(pRetToken->pszWord,pToken->pszWord); + pRetToken->iCorkIndex = pToken->iCorkIndex; return pRetToken; } @@ -137,11 +149,11 @@ CXXToken * cxxTokenCreateKeyword(int iLineNumber,MIOPos oFilePosition,CXXKeyword } -CXXToken * cxxTokenCreateAnonymousIdentifier(unsigned int uTagKind) +CXXToken * cxxTokenCreateAnonymousIdentifier(unsigned int uTagKind, const char *szPrefix) { CXXToken * t = cxxTokenCreate(); - anonGenerate (t->pszWord, "__anon", uTagKind); + anonGenerate (t->pszWord, szPrefix? szPrefix: "__anon", uTagKind); t->eType = CXXTokenTypeIdentifier; t->bFollowedBySpace = true; t->iLineNumber = getInputLineNumber(); diff --git a/ctags/parsers/cxx/cxx_token.h b/ctags/parsers/cxx/cxx_token.h index b320f09735..da02083ebd 100644 --- a/ctags/parsers/cxx/cxx_token.h +++ b/ctags/parsers/cxx/cxx_token.h @@ -10,6 +10,7 @@ */ #include "general.h" +#include "mio.h" #include "vstring.h" #include "cxx_keyword.h" @@ -72,7 +73,7 @@ typedef struct _CXXToken vString * pszWord; CXXKeyword eKeyword; CXXTokenChain * pChain; // this is NOT the parent chain! - bool bFollowedBySpace; + unsigned int bFollowedBySpace: 1; int iLineNumber; MIOPos oFilePosition; @@ -86,6 +87,11 @@ typedef struct _CXXToken // uninitialized and must be treated as undefined. unsigned char uInternalScopeType; unsigned char uInternalScopeAccess; + + int iCorkIndex; + + // The member keeps tokens started from __attribute__ and __declspec. + CXXTokenChain * pSideChain; } CXXToken; CXXToken * cxxTokenCreate(void); @@ -97,7 +103,8 @@ CXXToken * cxxTokenCopy(CXXToken *pToken); // A shortcut for quickly creating keyword tokens. CXXToken * cxxTokenCreateKeyword(int iLineNumber,MIOPos oFilePosition,CXXKeyword eKeyword); -CXXToken * cxxTokenCreateAnonymousIdentifier(unsigned int uTagKind); +CXXToken * cxxTokenCreateAnonymousIdentifier(unsigned int uTagKind, + const char *szPrefix); #define cxxTokenTypeIsOneOf(_pToken,_uTypes) (_pToken->eType & (_uTypes)) #define cxxTokenTypeIs(_pToken,_eType) (_pToken->eType == _eType) diff --git a/ctags/parsers/cxx/cxx_token_chain.c b/ctags/parsers/cxx/cxx_token_chain.c index 7cbe7055f4..ed50a185f3 100644 --- a/ctags/parsers/cxx/cxx_token_chain.c +++ b/ctags/parsers/cxx/cxx_token_chain.c @@ -385,6 +385,22 @@ vString * cxxTokenChainJoin( return s; } +void cxxTokenChainAppendEntries(CXXTokenChain * src, CXXTokenChain * dest) +{ + CXXToken * pDestLast = cxxTokenChainLast(dest); + CXXToken * pSrcFirst = cxxTokenChainFirst(src); + + pDestLast->pNext = pSrcFirst; + pSrcFirst->pPrev = pDestLast; + + dest->iCount += src->iCount; + dest->pTail = src->pTail; + + src->iCount = 0; + src->pHead = NULL; + src->pTail = NULL; +} + #if 0 // currently unused void cxxTokenChainMoveEntries(CXXTokenChain * src,CXXTokenChain * dest) @@ -441,6 +457,17 @@ void cxxTokenChainMoveEntryRange( } #endif +static CXXToken * cxxTokenCreatePlaceholder(CXXToken * pToken) +{ + CXXToken * pPlaceholder = cxxTokenCreate(); + + pPlaceholder->iLineNumber = pToken->iLineNumber; + pPlaceholder->oFilePosition = pToken->oFilePosition; + pPlaceholder->eType = CXXTokenTypeUnknown; + + return pPlaceholder; +} + CXXTokenChain * cxxTokenChainSplitOnComma(CXXTokenChain * tc) { if(!tc) @@ -457,14 +484,31 @@ CXXTokenChain * cxxTokenChainSplitOnComma(CXXTokenChain * tc) while(pStart && pToken->pNext) { - while(pToken->pNext && (!cxxTokenTypeIs(pToken->pNext,CXXTokenTypeComma))) - pToken = pToken->pNext; + CXXToken * pNew = NULL; + + if (cxxTokenTypeIs(pToken,CXXTokenTypeComma)) + { + // If nothing is passed as an argument like + // + // macro(,b), + // macro(a,), or + // macro(,) + // + // , we must inject a dummy token to the chain. + pNew = cxxTokenCreatePlaceholder(pToken); + // we will not update pToken in this case. + } + else + { + while(pToken->pNext && (!cxxTokenTypeIs(pToken->pNext,CXXTokenTypeComma))) + pToken = pToken->pNext; - CXXToken * pNew = cxxTokenChainExtractRange(pStart,pToken,0); + pNew = cxxTokenChainExtractRange(pStart,pToken,0); + pToken = pToken->pNext; // comma or nothing + } if(pNew) cxxTokenChainAppend(pRet,pNew); - pToken = pToken->pNext; // comma or nothing if(pToken) pToken = pToken->pNext; // after comma pStart = pToken; @@ -577,7 +621,7 @@ CXXToken * cxxTokenChainSkipBackToStartOfTemplateAngleBracket(CXXToken * t) if(!t) return NULL; CXX_DEBUG_ASSERT( - t->eType == CXXTokenTypeGreaterThanSign, + cxxTokenTypeIs(t, CXXTokenTypeGreaterThanSign), "This function must be called when pointing to a >" ); int iLevel = 1; @@ -609,7 +653,7 @@ CXXToken * cxxTokenChainFirstTokenOfType( CXXToken * t = tc->pHead; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) return t; t = t->pNext; } @@ -626,7 +670,7 @@ CXXToken * cxxTokenChainNextTokenOfType( t = t->pNext; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) return t; t = t->pNext; } @@ -643,7 +687,7 @@ CXXToken * cxxTokenChainPreviousTokenOfType( t = t->pPrev; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) return t; t = t->pPrev; } @@ -660,7 +704,7 @@ CXXToken * cxxTokenChainPreviousTokenNotOfType( t = t->pPrev; while(t) { - if(!(t->eType & uTokenTypes)) + if(!(cxxTokenTypeIsOneOf(t, uTokenTypes))) return t; t = t->pPrev; } @@ -677,7 +721,7 @@ CXXToken * cxxTokenChainLastTokenOfType( CXXToken * t = tc->pTail; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) return t; t = t->pPrev; } @@ -695,13 +739,13 @@ CXXToken * cxxTokenChainLastPossiblyNestedTokenOfType( CXXToken * t = tc->pTail; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) { if(ppParentChain) *ppParentChain = tc; return t; } - if(t->eType == CXXTokenTypeParenthesisChain) + if(cxxTokenTypeIs(t, CXXTokenTypeParenthesisChain)) { CXXToken * tmp = cxxTokenChainLastPossiblyNestedTokenOfType( t->pChain, @@ -728,13 +772,13 @@ CXXToken * cxxTokenChainFirstPossiblyNestedTokenOfType( CXXToken * t = tc->pHead; while(t) { - if(t->eType & uTokenTypes) + if(cxxTokenTypeIsOneOf(t, uTokenTypes)) { if(ppParentChain) *ppParentChain = tc; return t; } - if(t->eType == CXXTokenTypeParenthesisChain) + if(cxxTokenTypeIs(t, CXXTokenTypeParenthesisChain)) { CXXToken * tmp = cxxTokenChainFirstPossiblyNestedTokenOfType( t->pChain, @@ -761,7 +805,7 @@ CXXToken * cxxTokenChainFirstTokenNotOfType( CXXToken * t = tc->pHead; while(t) { - if(!(t->eType & uTokenTypes)) + if(!(cxxTokenTypeIsOneOf(t, uTokenTypes))) return t; t = t->pNext; } @@ -796,7 +840,7 @@ CXXToken * cxxTokenChainNextTokenNotOfType( t = t->pNext; while(t) { - if(!(t->eType & uTokenTypes)) + if(!(cxxTokenTypeIsOneOf(t, uTokenTypes))) return t; t = t->pNext; } @@ -813,7 +857,7 @@ CXXToken * cxxTokenChainLastTokenNotOfType( CXXToken * t = tc->pTail; while(t) { - if(!(t->eType & uTokenTypes)) + if(!(cxxTokenTypeIsOneOf(t, uTokenTypes))) return t; t = t->pPrev; } @@ -905,7 +949,6 @@ int cxxTokenChainFirstKeywordIndex( return -1; } -#if 0 // This is working code but it's unused and coveralls complains.. sigh. // Remove the #if above if needed. CXXToken * cxxTokenChainFirstKeyword( @@ -928,7 +971,6 @@ CXXToken * cxxTokenChainFirstKeyword( return NULL; } -#endif CXXToken * cxxTokenChainNextIdentifier( CXXToken * from, @@ -1172,12 +1214,20 @@ void cxxTokenChainNormalizeTypeNameSpacingInRange(CXXToken * pFrom,CXXToken * pT CXXTokenTypeParenthesisChain | CXXTokenTypeSquareParenthesisChain )) { + // decltype(a) const + // -----------^ + // In this case, a space is needed. + bool bFollowedBySpace = ( + t->pPrev && + cxxTokenTypeIs(t->pPrev,CXXTokenTypeKeyword) && + cxxKeywordIsDecltype(t->pPrev->eKeyword) + ); cxxTokenChainNormalizeTypeNameSpacing(t->pChain); - t->bFollowedBySpace = false; + t->bFollowedBySpace = bFollowedBySpace; } else if(cxxTokenTypeIs(t,CXXTokenTypeKeyword)) { t->bFollowedBySpace = t->pNext && - (t->eKeyword != CXXKeywordDECLTYPE) && + (!cxxKeywordIsDecltype(t->eKeyword)) && cxxTokenTypeIsOneOf( t->pNext, CXXTokenTypeParenthesisChain | CXXTokenTypeIdentifier | diff --git a/ctags/parsers/cxx/cxx_token_chain.h b/ctags/parsers/cxx/cxx_token_chain.h index 899e30c75e..325f19532c 100644 --- a/ctags/parsers/cxx/cxx_token_chain.h +++ b/ctags/parsers/cxx/cxx_token_chain.h @@ -155,6 +155,12 @@ void cxxTokenChainAppend(CXXTokenChain * tc,CXXToken * t); void cxxTokenChainPrepend(CXXTokenChain * tc,CXXToken * t); void cxxTokenChainInsertAfter(CXXTokenChain * tc,CXXToken * before,CXXToken * t); +// Move tokens in SRC to the end of DEST. +// Unlike cxxTokenChainMoveEntries, cxxTokenChainAppendEntries keeps +// tokens in DEST. +// SRC becomes empty. +void cxxTokenChainAppendEntries(CXXTokenChain * src, CXXTokenChain * dest); + #if 0 // currently unused void cxxTokenChainMoveEntries( @@ -263,14 +269,10 @@ int cxxTokenChainFirstKeywordIndex( CXXKeyword eKeyword ); -#if 0 -// This is working code but it's unused and coveralls complains.. sigh. -// Remove the #if above if needed. CXXToken * cxxTokenChainFirstKeyword( CXXTokenChain * tc, CXXKeyword eKeyword ); -#endif // Assuming that pChain contains a type name, attempt to normalize the // spacing within the whole chain. diff --git a/ctags/parsers/diff.c b/ctags/parsers/diff.c index 27e79982d1..9e6ccde71f 100644 --- a/ctags/parsers/diff.c +++ b/ctags/parsers/diff.c @@ -149,7 +149,7 @@ static void findDiffTags (void) { scope_index = CORK_NIL; cp += 4; - if (isspace ((int) *cp)) continue; + if (isspace (*cp)) continue; /* when original filename is /dev/null use the new one instead */ if (delim == DIFF_DELIM_MINUS && strncmp ((const char*) cp, "/dev/null", 9u) == 0 && @@ -184,7 +184,7 @@ static void findDiffTags (void) && (strncmp ((const char*) cp, DiffDelims[1], 4u) == 0)) { cp += 4; - if (isspace ((int) *cp)) continue; + if (isspace (*cp)) continue; /* when modified filename is /dev/null, the original name is deleted. */ if (strncmp ((const char*) cp, "/dev/null", 9u) == 0 && (cp[9] == 0 || isspace (cp[9]))) diff --git a/ctags/parsers/erlang.c b/ctags/parsers/erlang.c index 961ab6ed29..381dcfa6ff 100644 --- a/ctags/parsers/erlang.c +++ b/ctags/parsers/erlang.c @@ -55,7 +55,7 @@ static bool isIdentifierCharacter (int c) static const unsigned char *skipSpace (const unsigned char *cp) { - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; return cp; } @@ -64,9 +64,9 @@ static const unsigned char *parseIdentifier ( const unsigned char *cp, vString *const identifier) { vStringClear (identifier); - while (isIdentifierCharacter ((int) *cp)) + while (isIdentifierCharacter (*cp)) { - vStringPut (identifier, (int) *cp); + vStringPut (identifier, *cp); ++cp; } return cp; @@ -131,12 +131,12 @@ static void parseDirective (const unsigned char *cp, vString *const module) * Record definitions are handled separately */ vString *const directive = vStringNew (); - const char *const drtv = vStringValue (directive); cp = parseIdentifier (cp, directive); cp = skipSpace (cp); if (*cp == '(') ++cp; + const char *const drtv = vStringValue (directive); if (strcmp (drtv, "record") == 0) parseSimpleTag (cp, K_RECORD); else if (strcmp (drtv, "define") == 0) @@ -171,7 +171,7 @@ static void findErlangTags (void) ++cp; /* Move off of the '-' */ parseDirective(cp, module); } - else if (isIdentifierFirstCharacter ((int) *cp)) + else if (isIdentifierFirstCharacter (*cp)) parseFunctionTag (cp, module); } vStringDelete (module); diff --git a/ctags/parsers/flex.c b/ctags/parsers/flex.c index 59f48cdc9d..4c4047a46d 100644 --- a/ctags/parsers/flex.c +++ b/ctags/parsers/flex.c @@ -327,8 +327,7 @@ static void makeConstTag (tokenInfo *const token, const flexKind kind) initRefTagEntry (&e, name, kind, role); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if ( vStringLength(token->scope) > 0 ) { @@ -464,7 +463,6 @@ static void parseIdentifier (vString *const string, const int firstChar) static void readTokenFull (tokenInfo *const token, bool include_newlines) { int c; - int i; bool newline_encountered = false; /* if we've got a token held back, emit it */ @@ -481,13 +479,11 @@ static void readTokenFull (tokenInfo *const token, bool include_newlines) vStringClear (token->string); getNextChar: - i = 0; do { c = getcFromInputFile (); if (include_newlines && (c == '\r' || c == '\n')) newline_encountered = true; - i++; } while (c == '\t' || c == ' ' || c == '\r' || c == '\n'); @@ -927,20 +923,12 @@ static void skipArrayList (tokenInfo *const token, bool include_newlines) static void addContext (tokenInfo* const parent, const tokenInfo* const child) { - if (vStringLength (parent->string) > 0) - { - vStringPut (parent->string, '.'); - } - vStringCat (parent->string, child->string); + vStringJoin (parent->string, '.', child->string); } static void addToScope (tokenInfo* const token, const vString* const extra) { - if (vStringLength (token->scope) > 0) - { - vStringPut (token->scope, '.'); - } - vStringCat (token->scope, extra); + vStringJoin (token->scope, '.', extra); } /* diff --git a/ctags/parsers/fortran.c b/ctags/parsers/fortran.c index 4d5005f559..1c1175b3f6 100644 --- a/ctags/parsers/fortran.c +++ b/ctags/parsers/fortran.c @@ -24,6 +24,7 @@ #include "parse.h" #include "read.h" #include "routines.h" +#include "selectors.h" #include "vstring.h" #include "xtag.h" @@ -40,6 +41,14 @@ /* * DATA DECLARATIONS */ + +typedef enum eFortranPass { + INIT_PASS = 1, + PASS_FIXED_FORM = INIT_PASS, + PASS_FREE_FORM, + MAX_PASS = PASS_FREE_FORM +} fortranPass; + /* Used to designate type of line read in fixed source form. */ typedef enum eFortranLineType { @@ -220,10 +229,21 @@ typedef struct sTokenInfo { static langType Lang_fortran; static int Ungetc; -static unsigned int Column; -static bool FreeSourceForm; -static bool FreeSourceFormFound = false; -static bool ParsingString; + +static fortranPass currentPass; +#define inFreeSourceForm ((currentPass) == PASS_FREE_FORM) +#define inFixedSourceForm ((currentPass) == PASS_FIXED_FORM) + +/* State used only in FixedSourceForm pass */ +static struct { + unsigned int column; + bool freeSourceFormFound; +} Fixed; + +/* State used only in FreeSourceForm pass */ +static struct { + bool newline; +} Free; /* indexed by tagType */ static kindDefinition FortranKinds [] = { @@ -349,6 +369,18 @@ static const keywordTable FortranKeywordTable [] = { { "while", KEYWORD_while } }; +typedef enum { + X_LINK_NAME, +} fortranXtag; + +static xtagDefinition FortranXtagTable [] = { + { + .enabled = false, + .name = "linkName", + .description = "Linking name used in foreign languages", + }, +}; + static struct { unsigned int count; unsigned int max; @@ -535,6 +567,46 @@ static const char *implementationString (const impType imp) return names [(int) imp]; } +static bool hasLinkName(tagEntryInfo *e) +{ + switch (e->kindIndex) + { + case TAG_FUNCTION: + case TAG_SUBROUTINE: + case TAG_BLOCK_DATA: + case TAG_COMMON_BLOCK: + case TAG_ENTRY_POINT: + return true; + default: + return false; + } +} + +/* ref. + * * https://gcc.gnu.org/onlinedocs/gfortran/Code-Gen-Options.html + * - -fno-underscoring + * - -fsecond-underscore + * * https://docs.oracle.com/cd/E19957-01/805-4940/z400091044a7/index.html + */ +static void makeFortranLinkNameTag(tagEntryInfo *e) +{ + vString *ln = vStringNewInit (e->name); + vStringLower(ln); + +#if 0 + if (strchr(vStringValue (ln), '_')) + vStringPut(ln, '_'); +#endif + + vStringPut(ln, '_'); + + tagEntryInfo ln_e = *e; + ln_e.name = vStringValue (ln); + markTagExtraBit (&ln_e, FortranXtagTable[X_LINK_NAME].xtype); + makeTagEntry (&ln_e); + vStringDelete (ln); +} + static void makeFortranTag (tokenInfo *const token, tagType tag) { token->tag = tag; @@ -551,8 +623,7 @@ static void makeFortranTag (tokenInfo *const token, tagType tag) if (token->anonymous) markTagExtraBit (&e, XTAG_ANONYMOUS); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); e.isFileScope = isFileScope (token->tag); if (e.isFileScope) markTagExtraBit (&e, XTAG_FILE_SCOPE); @@ -581,6 +652,9 @@ static void makeFortranTag (tokenInfo *const token, tagType tag) token->tag == TAG_PROTOTYPE)) e.extensionFields.signature = vStringValue (token->signature); makeTagEntry (&e); + if (isXtagEnabled (FortranXtagTable[X_LINK_NAME].xtype) + && hasLinkName(&e)) + makeFortranLinkNameTag(&e); } } @@ -679,61 +753,61 @@ static lineType getLineType (void) return type; } -static int getFixedFormChar (void) +static int getFixedFormChar (bool parsingString, bool *freeSourceFormFound) { bool newline = false; lineType type; int c = '\0'; - if (Column > 0) + if (Fixed.column > 0) { #ifdef STRICT_FIXED_FORM /* EXCEPTION! Some compilers permit more than 72 characters per line. */ - if (Column > 71) + if (Fixed.column > 71) c = skipLine (); else #endif { c = getcFromInputFile (); - ++Column; + ++Fixed.column; } if (c == '\n') { newline = true; /* need to check for continuation line */ - Column = 0; + Fixed.column = 0; } - else if (c == '!' && ! ParsingString) + else if (c == '!' && ! parsingString) { c = skipLine (); newline = true; /* need to check for continuation line */ - Column = 0; + Fixed.column = 0; } else if (c == '&') /* check for free source form */ { const int c2 = getcFromInputFile (); if (c2 == '\n') - FreeSourceFormFound = true; + *freeSourceFormFound = true; else ungetcToInputFile (c2); } } - while (Column == 0) + while (Fixed.column == 0) { type = getLineType (); switch (type) { case LTYPE_UNDETERMINED: case LTYPE_INVALID: - FreeSourceFormFound = true; - if (! FreeSourceForm) + *freeSourceFormFound = true; + if (inFixedSourceForm) return EOF; case LTYPE_SHORT: break; case LTYPE_COMMENT: skipLine (); break; case LTYPE_EOF: - Column = 6; + Fixed.column = 6; if (newline) c = '\n'; else @@ -744,20 +818,20 @@ static int getFixedFormChar (void) if (newline) { c = '\n'; - Column = 6; + Fixed.column = 6; break; } /* fall through to next case */ case LTYPE_CONTINUATION: - Column = 5; + Fixed.column = 5; do { c = getcFromInputFile (); - ++Column; + ++Fixed.column; } while (isBlank (c)); if (c == '\n') - Column = 0; - else if (Column > 6) + Fixed.column = 0; + else if (Fixed.column > 6) { ungetcToInputFile (c); c = ' '; @@ -781,7 +855,6 @@ static int skipToNextLine (void) static int getFreeFormChar (void) { - static bool newline = true; bool advanceLine = false; int c = getcFromInputFile (); @@ -796,7 +869,7 @@ static int getFreeFormChar (void) while (isspace (c) && c != '\n'); if (c == '\n') { - newline = true; + Free.newline = true; advanceLine = true; } else if (c == '!') @@ -807,16 +880,16 @@ static int getFreeFormChar (void) c = '&'; } } - else if (newline && (c == '!' || c == '#')) + else if (Free.newline && (c == '!' || c == '#')) advanceLine = true; while (advanceLine) { while (isspace (c)) c = getcFromInputFile (); - if (c == '!' || (newline && c == '#')) + if (c == '!' || (Free.newline && c == '#')) { c = skipToNextLine (); - newline = true; + Free.newline = true; continue; } if (c == '&') @@ -824,11 +897,11 @@ static int getFreeFormChar (void) else advanceLine = false; } - newline = (bool) (c == '\n'); + Free.newline = (bool) (c == '\n'); return c; } -static int getChar (void) +static int getCharFull (bool parsingString, bool *freeSourceFormFound) { int c; @@ -837,13 +910,19 @@ static int getChar (void) c = Ungetc; Ungetc = '\0'; } - else if (FreeSourceForm) + else if (inFreeSourceForm) c = getFreeFormChar (); else - c = getFixedFormChar (); + c = getFixedFormChar (parsingString, + freeSourceFormFound); return c; } +static int getChar (void) +{ + return getCharFull (false, &Fixed.freeSourceFormFound); +} + static void ungetChar (const int c) { Ungetc = c; @@ -909,22 +988,20 @@ static vString *parseNumeric (int c) static void parseString (vString *const string, const int delimiter) { const unsigned long inputLineNumber = getInputLineNumber (); - int c; - ParsingString = true; - c = getChar (); + int c = getCharFull (true, &Fixed.freeSourceFormFound); + while (c != delimiter && c != '\n' && c != EOF) { vStringPut (string, c); - c = getChar (); + c = getCharFull (true, &Fixed.freeSourceFormFound); } if (c == '\n' || c == EOF) { verbose ("%s: unterminated character string at line %lu\n", getInputFileName (), inputLineNumber); - if (c != EOF && ! FreeSourceForm) - FreeSourceFormFound = true; + if (c != EOF && inFixedSourceForm) + Fixed.freeSourceFormFound = true; } - ParsingString = false; } /* Read a C identifier beginning with "firstChar" and places it into "name". @@ -1049,7 +1126,7 @@ static void readToken (tokenInfo *const token) } case '!': - if (FreeSourceForm) + if (inFreeSourceForm) { do c = getChar (); @@ -1058,12 +1135,12 @@ static void readToken (tokenInfo *const token) else { skipLine (); - Column = 0; + Fixed.column = 0; } /* fall through to newline case */ case '\n': token->type = TOKEN_STATEMENT_END; - if (FreeSourceForm) + if (inFreeSourceForm) checkForLabel (); break; @@ -2649,13 +2726,22 @@ static rescanReason findFortranTags (const unsigned int passCount) tokenInfo *token; rescanReason rescan; - Assert (passCount < 3); + Assert (passCount <= MAX_PASS); token = newToken (); - FreeSourceForm = (bool) (passCount > 1); - Column = 0; + currentPass = (fortranPass)passCount; + if (currentPass == INIT_PASS) + Ungetc = '\0'; + if (inFreeSourceForm) + Free.newline = true; + if (inFixedSourceForm) + { + Fixed.column = 0; + Fixed.freeSourceFormFound = false; + } + parseProgramUnit (token); - if (FreeSourceFormFound && ! FreeSourceForm) + if (inFixedSourceForm && Fixed.freeSourceFormFound) { verbose ("%s: not fixed source form; retry as free source form\n", getInputFileName ()); @@ -2685,6 +2771,9 @@ extern parserDefinition* FortranParser (void) #endif NULL }; + static selectLanguage selectors[] = { selectFortranOrForthByForthMarker, + NULL }; + parserDefinition* def = parserNew ("Fortran"); def->kindTable = FortranKinds; def->kindCount = ARRAY_SIZE (FortranKinds); @@ -2693,5 +2782,12 @@ extern parserDefinition* FortranParser (void) def->initialize = initialize; def->keywordTable = FortranKeywordTable; def->keywordCount = ARRAY_SIZE (FortranKeywordTable); + def->xtagTable = FortranXtagTable; + def->xtagCount = ARRAY_SIZE(FortranXtagTable); + + def->versionCurrent = 1; + def->versionAge = 1; + def->selectLanguage = selectors; + return def; } diff --git a/ctags/parsers/gdscript.c b/ctags/parsers/gdscript.c index 2de92d9f26..599d2ef5fe 100644 --- a/ctags/parsers/gdscript.c +++ b/ctags/parsers/gdscript.c @@ -139,7 +139,7 @@ static const keywordTable GDScriptKeywordTable[] = { }; -const static struct keywordGroup modifierKeywords = { +static const struct keywordGroup modifierKeywords = { .value = KEYWORD_modifier, .addingUnlessExisting = false, .keywords = { @@ -210,9 +210,7 @@ static void initGDScriptEntry (tagEntryInfo *const e, const tokenInfo *const tok NestingLevel *nl; initTagEntry (e, vStringValue (token->string), kind); - - e->lineNumber = token->lineNumber; - e->filePosition = token->filePosition; + updateTagLine (e, token->lineNumber, token->filePosition); nl = nestingLevelsGetCurrent (GDScriptNestingLevels); if (nl) @@ -286,7 +284,7 @@ static int makeFunctionTag (const tokenInfo *const token, if (decorators && stringListCount (decorators) > 0) { vstr = makeDecoratorString (decorators); - attachParserField (&e, false, GDScriptFields[F_ANNOTATIONS].ftype, + attachParserField (&e, GDScriptFields[F_ANNOTATIONS].ftype, vStringValue (vstr)); } @@ -322,9 +320,7 @@ static int makeSimpleGDScriptRefTag (const tokenInfo *const token, initRefTagEntry (&e, vStringValue (token->string), kind, roleIndex); - - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if (xtag != XTAG_UNKNOWN) markTagExtraBit (&e, xtag); @@ -368,7 +364,7 @@ static void copyToken (tokenInfo *const dest, const tokenInfo *const src) dest->type = src->type; dest->keyword = src->keyword; dest->indent = src->indent; - vStringCopy(dest->string, src->string); + vStringCopy (dest->string, src->string); } /* Skip a single or double quoted string. */ @@ -822,12 +818,12 @@ static vString *parseReturnTypeAnnotation (tokenInfo *const token) static bool parseClassOrDef (tokenInfo *const token, const stringList *const decorators, - gdscriptKind kind, bool isCDef) + gdscriptKind kind) { vString *arglist = NULL; tokenInfo *name = NULL; tokenInfo *parameterTokens[16] = { NULL }; - vString *parameterTypes [ARRAY_SIZE(parameterTokens)] = { NULL }; + vString *parameterTypes [ARRAY_SIZE (parameterTokens)] = { NULL }; unsigned int parameterCount = 0; NestingLevel *lv; int corkIndex; @@ -967,7 +963,7 @@ static bool parseEnum (tokenInfo *const token) } else if (token->type == TOKEN_IDENTIFIER) { - corkIndex = makeSimpleGDScriptTag(token, K_ENUM); + corkIndex = makeSimpleGDScriptTag (token, K_ENUM); readToken (token); } else @@ -982,7 +978,7 @@ static bool parseEnum (tokenInfo *const token) while (token->type != '}' && token->type != TOKEN_EOF) { if (token->type == TOKEN_IDENTIFIER) - makeSimpleGDScriptTag(token, K_ENUMERATOR); + makeSimpleGDScriptTag (token, K_ENUMERATOR); else if (token->type == '=') { /* Skip the right value. */ @@ -1030,7 +1026,8 @@ static bool parseClassName (tokenInfo *const token) eFree ((void *)klass->name); klass->name = name; name = NULL; - unmarkTagExtraBit(klass, XTAG_ANONYMOUS); + unmarkTagExtraBit (klass, XTAG_ANONYMOUS); + unmarkTagExtraBit (klass, GDScriptXtagTable[X_IMPLICIT_CLASS].xtype); /* Adjust the position. */ setTagPositionFromTag (klass, &e); @@ -1054,7 +1051,7 @@ static bool parseClassName (tokenInfo *const token) klass->extensionFields.inheritance = vStringStrdup (token->string); } else - e.extensionFields.inheritance = vStringValue(token->string); + e.extensionFields.inheritance = vStringValue (token->string); } } @@ -1089,7 +1086,7 @@ static bool parseExtends (tokenInfo *const token) { if (klass->extensionFields.inheritance) eFree ((void *)klass->extensionFields.inheritance); - klass->extensionFields.inheritance = vStringStrdup(token->string); + klass->extensionFields.inheritance = vStringStrdup (token->string); } } } @@ -1148,8 +1145,8 @@ static bool parseVariable (tokenInfo *const token, const gdscriptKind kind, const stringList *const decorators, const int keyword) { - readToken(token); - vString *type = vStringNew(); + readToken (token); + vString *type = vStringNew (); tokenInfo *name = newToken (); copyToken (name, token); if (!name) @@ -1166,33 +1163,33 @@ static bool parseVariable (tokenInfo *const token, const gdscriptKind kind, readToken (token); int index = makeSimpleGDScriptTag (name, kind); - deleteToken(name); + deleteToken (name); tagEntryInfo *e = getEntryInCorkQueue (index); if (e && decorators && stringListCount (decorators) > 0) { vString *vstr = makeDecoratorString (decorators); - attachParserField (e, true, GDScriptFields[F_ANNOTATIONS].ftype, + attachParserField (e, GDScriptFields[F_ANNOTATIONS].ftype, vStringValue (vstr)); vStringDelete (vstr); } - vString *vtype = vStringNew(); + vString *vtype = vStringNew (); char * stype = vStringValue (type); - if (strcmp(stype, "=") && strcmp(stype, "")) + if (strcmp (stype, "=") && strcmp (stype, "")) { - vStringCatS(vtype, stype); + vStringCatS (vtype, stype); } - vStringDelete(type); + vStringDelete (type); - if (e && vStringLength(vtype) > 0) /// TODO: Fix types away + if (e && vStringLength (vtype) > 0) /// TODO: Fix types away { e->extensionFields.typeRef [0] = eStrdup ("typename"); e->extensionFields.typeRef [1] = vStringDeleteUnwrap (vtype); } else { - vStringDelete(vtype); + vStringDelete (vtype); } @@ -1215,9 +1212,7 @@ static void setIndent (tokenInfo *const token) while (lv && GDS_NL (lv)->indentation >= token->indent) { - tagEntryInfo *e = getEntryInCorkQueue (lv->corkIndex); - if (e) - e->extensionFields.endLine = token->lineNumber; + setTagEndLineToCorkEntry (lv->corkIndex, token->lineNumber); nestingLevelsPop (GDScriptNestingLevels); lv = nestingLevelsGetCurrent (GDScriptNestingLevels); @@ -1255,7 +1250,7 @@ static int prepareUnnamedClass (struct NestingLevels *nls) static void findGDScriptTags (void) { tokenInfo *const token = newToken (); - stringList *decorators = stringListNew(); + stringList *decorators = stringListNew (); bool atStatementStart = true; TokenContinuationDepth = 0; @@ -1263,7 +1258,12 @@ static void findGDScriptTags (void) GDScriptNestingLevels = nestingLevelsNew (sizeof (struct gdscriptNestingLevelUserData)); if (isXtagEnabled (GDScriptXtagTable[X_IMPLICIT_CLASS].xtype)) - prepareUnnamedClass (GDScriptNestingLevels); + { + int index = prepareUnnamedClass (GDScriptNestingLevels); + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e) + markTagExtraBit (e, GDScriptXtagTable[X_IMPLICIT_CLASS].xtype); + } readToken (token); while (token->type != TOKEN_EOF) @@ -1285,9 +1285,9 @@ static void findGDScriptTags (void) case KEYWORD_func: kind = K_METHOD; break; case KEYWORD_signal: kind = K_SIGNAL; break; default: - AssertNotReached(); + AssertNotReached (); } - readNext = parseClassOrDef (token, decorators, kind, false); + readNext = parseClassOrDef (token, decorators, kind); } else if (token->keyword == KEYWORD_extends) { @@ -1325,7 +1325,7 @@ static void findGDScriptTags (void) else if (token->type == TOKEN_KEYWORD && token->keyword == KEYWORD_modifier) { - stringListAdd (decorators, vStringNewCopy(token->string)); + stringListAdd (decorators, vStringNewCopy (token->string)); } else if (token->type == '@' && atStatementStart && GDScriptFields[F_ANNOTATIONS].enabled) @@ -1337,7 +1337,7 @@ static void findGDScriptTags (void) readNext = false; else { - stringListAdd (decorators, vStringNewCopy(token->string)); + stringListAdd (decorators, vStringNewCopy (token->string)); readToken (token); vString *d = vStringNew (); @@ -1402,8 +1402,8 @@ extern parserDefinition* GDScriptParser (void) def->keywordCount = ARRAY_SIZE (GDScriptKeywordTable); def->fieldTable = GDScriptFields; def->fieldCount = ARRAY_SIZE (GDScriptFields); - def->xtagTable = GDScriptXtagTable; - def->xtagCount = ARRAY_SIZE(GDScriptXtagTable); + def->xtagTable = GDScriptXtagTable; + def->xtagCount = ARRAY_SIZE (GDScriptXtagTable); def->useCork = CORK_QUEUE; def->requestAutomaticFQTag = true; return def; diff --git a/ctags/parsers/geany_lcpp.h b/ctags/parsers/geany_lcpp.h index a60210d541..fcf5052ca2 100644 --- a/ctags/parsers/geany_lcpp.h +++ b/ctags/parsers/geany_lcpp.h @@ -15,6 +15,29 @@ #include "general.h" /* must always come first */ #include "types.h" +/* + * DATA DECLARATIONS + */ + +enum eCharacters { + /* white space characters */ + SPACE = ' ', + NEWLINE = '\n', + CRETURN = '\r', + FORMFEED = '\f', + TAB = '\t', + VTAB = '\v', + + /* some hard to read characters */ + DOUBLE_QUOTE = '"', + SINGLE_QUOTE = '\'', + BACKSLASH = '\\', + + /* symbolic representations, above 0xFF not to conflict with any byte */ + STRING_SYMBOL = ('S' + 0xff), + CHAR_SYMBOL = ('C' + 0xff) +}; + /* * MACROS */ diff --git a/ctags/parsers/go.c b/ctags/parsers/go.c index d642d24b44..6d3cc672e4 100644 --- a/ctags/parsers/go.c +++ b/ctags/parsers/go.c @@ -146,7 +146,7 @@ static kindDefinition GoKinds[] = { {true, 'm', "member", "struct members"}, {true, 'M', "anonMember", "struct anonymous members"}, {true, 'n', "methodSpec", "interface method specification"}, - {true, 'u', "unknown", "unknown", + {true, 'Y', "unknown", "unknown", .referenceOnly = true, ATTACH_ROLES (GoUnknownRoles)}, {true, 'P', "packageName", "name for specifying imported package"}, {true, 'a', "talias", "type aliases"}, @@ -693,8 +693,7 @@ static int makeTagFull (tokenInfo *const token, const goKind kind, initRefTagEntry (&e, name, kind, role); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if (argList) e.extensionFields.signature = argList; if (typeref) @@ -814,7 +813,7 @@ static void parseFunctionOrMethod (tokenInfo *const token, const int scope) collectorTruncate (&collector, false); if (receiver_type_token) { - func_scope = anyEntryInScope (scope, vStringValue (receiver_type_token->string)); + func_scope = anyEntryInScope (scope, vStringValue (receiver_type_token->string), false); if (func_scope == CORK_NIL) func_scope = makeTagFull(receiver_type_token, GOTAG_UNKNOWN, scope, NULL, NULL, @@ -862,7 +861,7 @@ static void parseFunctionOrMethod (tokenInfo *const token, const int scope) { skipToMatched (token, NULL); if (e) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine (e, getInputLineNumber()); } } @@ -872,7 +871,7 @@ static void parseFunctionOrMethod (tokenInfo *const token, const int scope) static void attachTypeRefField (int scope, intArray *corks, const char *const type) { - int type_cork = anyEntryInScope (scope, type); + int type_cork = anyEntryInScope (scope, type, false); tagEntryInfo *type_e = getEntryInCorkQueue (type_cork); for (unsigned int i = 0; i < intArrayCount (corks); i++) @@ -1256,7 +1255,7 @@ static void parseConstTypeVar (tokenInfo *const token, goKind kind, const int sc { tagEntryInfo *e = getEntryInCorkQueue (member_scope); if (e) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine(e, getInputLineNumber ()); } if (usesParens && !isType (token, TOKEN_CLOSE_PAREN)) diff --git a/ctags/parsers/haskell.c b/ctags/parsers/haskell.c index f870ea48eb..46b30126a9 100644 --- a/ctags/parsers/haskell.c +++ b/ctags/parsers/haskell.c @@ -105,7 +105,7 @@ static void add_tag(const char *token, haskellKind kind, vString *name) vStringClear(name); } -static int isident(char c) +static int isident(int c) { return isalnum(c) || c == '_' || c == '\'' || c == '$'; } diff --git a/ctags/parsers/haxe.c b/ctags/parsers/haxe.c index 930c36dc2e..c07721370d 100644 --- a/ctags/parsers/haxe.c +++ b/ctags/parsers/haxe.c @@ -15,7 +15,6 @@ */ #include "general.h" /* must always come first */ #include /* to define isalpha () */ -#include #ifdef DEBUG #include #endif @@ -79,17 +78,17 @@ static void findHxTags (void) vStringCopyS(laccess,priv); if (strncmp ((const char*) cp, "var", (size_t) 3) == 0 && - isspace ((int) cp [3])) + isspace (cp [3])) { cp += 3; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') + while (isalnum (*cp) || *cp == '_') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_VARIABLE); @@ -97,17 +96,17 @@ static void findHxTags (void) vStringClear (name); } else if (strncmp ((const char*) cp, "function", (size_t) 8) == 0 && - isspace ((int) cp [8])) + isspace (cp [8])) { cp += 8; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') + while (isalnum (*cp) || *cp == '_') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_METHODS); @@ -115,16 +114,16 @@ static void findHxTags (void) vStringClear (name); } else if (strncmp ((const char*) cp, "class", (size_t) 5) == 0 && - isspace ((int) cp [5])) + isspace (cp [5])) { cp += 5; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') + while (isalnum (*cp) || *cp == '_') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_CLASS); @@ -132,57 +131,57 @@ static void findHxTags (void) vStringClear (name); } else if (strncmp ((const char*) cp, "enum", (size_t) 4) == 0 && - isspace ((int) cp [4])) + isspace (cp [4])) { cp += 4; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') + while (isalnum (*cp) || *cp == '_') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_ENUM); vStringClear (name); } else if (strncmp ((const char*) cp, "public", (size_t) 6) == 0 && - isspace((int) cp [6])) + isspace(cp [6])) { cp += 6; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringCopyS(laccess,pub); goto another; } else if (strncmp ((const char*) cp, "static", (size_t) 6) == 0 && - isspace((int) cp [6])) + isspace(cp [6])) { cp += 6; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; goto another; } else if (strncmp ((const char*) cp, "interface", (size_t) 9) == 0 && - isspace((int) cp [9])) + isspace(cp [9])) { cp += 9; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') { - vStringPut (name, (int) *cp); + while (isalnum (*cp) || *cp == '_') { + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_INTERFACE); vStringClear (name); - } else if (strncmp ((const char *) cp,"typedef",(size_t) 7) == 0 && isspace(((int) cp[7]))) { + } else if (strncmp ((const char *) cp,"typedef",(size_t) 7) == 0 && isspace((cp[7]))) { cp += 7; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; vStringClear (name); - while (isalnum ((int) *cp) || *cp == '_') { - vStringPut (name, (int) *cp); + while (isalnum (*cp) || *cp == '_') { + vStringPut (name, *cp); ++cp; } makeSimpleTag (name, HXTAG_TYPEDEF); diff --git a/ctags/parsers/html.c b/ctags/parsers/html.c index 56b3b3f332..1b6c0c0915 100644 --- a/ctags/parsers/html.c +++ b/ctags/parsers/html.c @@ -19,6 +19,7 @@ #include "routines.h" #include "keyword.h" #include "promise.h" +#include "trace.h" /* The max. number of nested elements - prevents further recursion if the limit * is exceeded and avoids stack overflow for invalid input containing too many @@ -29,6 +30,7 @@ typedef enum { K_ANCHOR, K_CLASS, + K_TITLE, K_HEADING1, K_HEADING2, K_HEADING3, @@ -66,6 +68,7 @@ static kindDefinition HtmlKinds [] = { { true, 'a', "anchor", "named anchors" }, { true, 'c', "class", "classes", .referenceOnly = true, ATTACH_ROLES (ClassRoles)}, + { true, 't', "title", "titles" }, { true, 'h', "heading1", "H1 headings" }, { true, 'i', "heading2", "H2 headings" }, { true, 'j', "heading3", "H3 headings" }, @@ -77,9 +80,16 @@ static kindDefinition HtmlKinds [] = { }; typedef enum { + /* The order starting from "title" to "h3" should + * not be changed. + * + */ + KEYWORD_heading_start, + KEYWORD_title = KEYWORD_heading_start, KEYWORD_h1, KEYWORD_h2, KEYWORD_h3, + KEYWORD_heading_end = KEYWORD_h3, KEYWORD_a, KEYWORD_script, KEYWORD_style, @@ -110,6 +120,7 @@ typedef enum { } keywordId; static const keywordTable HtmlKeywordTable[] = { + {"title", KEYWORD_title}, {"h1", KEYWORD_h1}, {"h2", KEYWORD_h2}, {"h3", KEYWORD_h3}, @@ -147,8 +158,8 @@ typedef enum { TOKEN_NAME, /* tag and attribute names */ TOKEN_STRING, /* single- or double-quoted attribute value */ TOKEN_TEXT, - TOKEN_TAG_START, /* < */ - TOKEN_TAG_START2, /* */ TOKEN_TAG_END2, /* /> */ TOKEN_EQUAL, @@ -157,14 +168,14 @@ typedef enum { } tokenType; #ifdef DEBUG -const char *tokenTypes[] = { +static const char *tokenTypes[] = { #define E(X) [TOKEN_##X] = #X E(EOF), E(NAME), E(STRING), E(TEXT), - E(TAG_START), - E(TAG_START2), + E(OPEN_TAG_START), + E(CLOSE_TAG_START), E(TAG_END), E(TAG_END2), E(EQUAL), @@ -185,16 +196,7 @@ static int Lang_html; static void readTag (tokenInfo *token, vString *text, int depth); -#ifdef DEBUG -#if 0 -static void dumpToken (tokenInfo *token, const char *context, const char* extra_context) -{ - fprintf (stderr, "[%7s] %-20s@%s.%s\n", - tokenTypes[token->type], vStringValue(token->string), - context, extra_context? extra_context: "_"); -} -#endif -#endif +static void skipOtherScriptContent (const int delimiter); static void readTokenText (tokenInfo *const token, bool collectText) { @@ -234,6 +236,58 @@ static void readTokenText (tokenInfo *const token, bool collectText) } } +static void readTokenInScript (tokenInfo *const token) +{ + int c; + + vStringClear (token->string); + + c = getcFromInputFile (); + while (isspace (c)) + c = getcFromInputFile (); + + switch (c) + { + case EOF: + token->type = TOKEN_EOF; + break; + + case '<': + { + int d = getcFromInputFile (); + if (d == '/') + token->type = TOKEN_CLOSE_TAG_START; + else + { + ungetcToInputFile (d); + token->type = TOKEN_OTHER; + } + break; + } + default: + { + while (!isspace (c) && c != '<' && c != '>' && c != '/' && + c != '=' && c != '\'' && c != '"' && c != EOF) + { + vStringPut (token->string, tolower (c)); + c = getcFromInputFile (); + } + + if (vStringLength (token->string) == 0) + token->type = TOKEN_OTHER; + else + { + token->type = TOKEN_NAME; + if (c != EOF) + ungetcToInputFile (c); + } + break; + } + } + + TRACE_PRINT("token (in script): %s (%s)", tokenTypes[token->type], vStringValue (token->string)); +} + static void readToken (tokenInfo *const token, bool skipComments) { int c; @@ -255,7 +309,6 @@ static void readToken (tokenInfo *const token, bool skipComments) case '<': { int d = getcFromInputFile (); - if (d == '!') { d = getcFromInputFile (); @@ -286,14 +339,17 @@ static void readToken (tokenInfo *const token, bool skipComments) ungetcToInputFile (d); token->type = TOKEN_OTHER; } - else if (d == '?') - token->type = TOKEN_OTHER; + else if (d == '?' || d == '%') + { + skipOtherScriptContent(d); + goto getNextChar; + } else if (d == '/') - token->type = TOKEN_TAG_START2; + token->type = TOKEN_CLOSE_TAG_START; else { ungetcToInputFile (d); - token->type = TOKEN_TAG_START; + token->type = TOKEN_OPEN_TAG_START; } break; } @@ -346,6 +402,8 @@ static void readToken (tokenInfo *const token, bool skipComments) break; } } + + TRACE_PRINT("token: %s (%s)", tokenTypes[token->type], vStringValue (token->string)); } static void appendText (vString *text, vString *appendedText) @@ -363,6 +421,8 @@ static void appendText (vString *text, vString *appendedText) static bool readTagContent (tokenInfo *token, vString *text, long *line, long *lineOffset, int depth) { + TRACE_ENTER(); + tokenType type; readTokenText (token, text != NULL); @@ -374,21 +434,25 @@ static bool readTagContent (tokenInfo *token, vString *text, long *line, long *l *lineOffset = getInputLineOffset (); readToken (token, false); type = token->type; - if (type == TOKEN_TAG_START) + if (type == TOKEN_OPEN_TAG_START) readTag (token, text, depth + 1); - if (type == TOKEN_COMMENT || type == TOKEN_TAG_START) + if (type == TOKEN_COMMENT || type == TOKEN_OPEN_TAG_START) { readTokenText (token, text != NULL); appendText (text, token->string); } } - while (type == TOKEN_COMMENT || type == TOKEN_TAG_START); + while (type == TOKEN_COMMENT || type == TOKEN_OPEN_TAG_START); - return type == TOKEN_TAG_START2; + TRACE_LEAVE_TEXT("is_close_tag? %d", type == TOKEN_CLOSE_TAG_START); + + return type == TOKEN_CLOSE_TAG_START; } static bool skipScriptContent (tokenInfo *token, long *line, long *lineOffset) { + TRACE_ENTER(); + bool found_start = false; bool found_script = false; @@ -402,10 +466,10 @@ static bool skipScriptContent (tokenInfo *token, long *line, long *lineOffset) line_tmp[0] = getInputLineNumber (); lineOffset_tmp[0] = getInputLineOffset (); - readToken (token, false); + readTokenInScript (token); type = token->type; - if (type == TOKEN_TAG_START2) + if (type == TOKEN_CLOSE_TAG_START) { found_start = true; line_tmp[1] = line_tmp[0]; @@ -424,16 +488,65 @@ static bool skipScriptContent (tokenInfo *token, long *line, long *lineOffset) } while ((type != TOKEN_EOF) && (!found_script)); + TRACE_LEAVE_TEXT("found_script? %d", found_script); + return found_script; } +static void skipOtherScriptContent (const int delimiter) +{ + TRACE_ENTER(); + + const long startSourceLineNumber = getSourceLineNumber (); + const long startLineNumber = getInputLineNumber (); + const long startLineOffset = getInputLineOffset () - 2; + + vString *script_name = vStringNew (); + bool reading_script_name = true; + while (1) + { + int c = getcFromInputFile (); + if (c == EOF) + { + break; + } + else if (reading_script_name && !isspace(c)) + { + vStringPut (script_name, c); + } + else if (reading_script_name) + { + reading_script_name = false; + } + else if (c == delimiter) + { + c = getcFromInputFile (); + if (c == '>') + { + break; + } + ungetcToInputFile (c); + } + } + + if (strcasecmp ("php", vStringValue (script_name)) == 0 + || strcmp ("=", vStringValue (script_name)) == 0) + makePromise ("PHP", startLineNumber, startLineOffset, + getInputLineNumber (), getInputLineOffset (), + startSourceLineNumber); + + vStringDelete (script_name); + + TRACE_LEAVE(); +} + static void makeClassRefTags (const char *classes) { vString *klass = vStringNew (); do { - if (*classes && !isspace (*classes)) + if (*classes && !isspace ((unsigned char) *classes)) vStringPut (klass, *classes); else if (!vStringIsEmpty (klass)) { @@ -453,6 +566,8 @@ static void makeClassRefTags (const char *classes) static void readTag (tokenInfo *token, vString *text, int depth) { + TRACE_ENTER(); + bool textCreated = false; readToken (token, true); @@ -465,7 +580,7 @@ static void readTag (tokenInfo *token, vString *text, int depth) bool stylesheet_expectation = false; startTag = lookupKeyword (vStringValue (token->string), Lang_html); - isHeading = (startTag == KEYWORD_h1 || startTag == KEYWORD_h2 || startTag == KEYWORD_h3); + isHeading = (KEYWORD_heading_start <= startTag && startTag <= KEYWORD_heading_end); isVoid = (startTag >= KEYWORD_area && startTag <= KEYWORD_wbr); if (text == NULL && isHeading) { @@ -600,6 +715,8 @@ static void readTag (tokenInfo *token, vString *text, int depth) { htmlKind headingKind; + if (startTag == KEYWORD_title) + headingKind = K_TITLE; if (startTag == KEYWORD_h1) headingKind = K_HEADING1; else if (startTag == KEYWORD_h2) @@ -628,10 +745,14 @@ static void readTag (tokenInfo *token, vString *text, int depth) out: if (textCreated) vStringDelete (text); + + TRACE_LEAVE(); } static void findHtmlTags (void) { + TRACE_ENTER(); + tokenInfo token; token.string = vStringNew (); @@ -639,12 +760,14 @@ static void findHtmlTags (void) do { readToken (&token, true); - if (token.type == TOKEN_TAG_START) + if (token.type == TOKEN_OPEN_TAG_START) readTag (&token, NULL, 0); } while (token.type != TOKEN_EOF); vStringDelete (token.string); + + TRACE_LEAVE(); } static void initialize (const langType language) diff --git a/ctags/parsers/iniconf.c b/ctags/parsers/iniconf.c index 830d427479..6ce23280a0 100644 --- a/ctags/parsers/iniconf.c +++ b/ctags/parsers/iniconf.c @@ -96,9 +96,7 @@ static void makeIniconfTagMaybe (const char *section, const char *key, const cha } else { - tagEntryInfo *last = getEntryInCorkQueue (*index); - if (last) - last->extensionFields.endLine = getInputLineNumber (); + setTagEndLineToCorkEntry (*index, getInputLineNumber ()); initTagEntry (&e, section, K_SECTION); *index = makeTagEntry (&e); @@ -124,7 +122,7 @@ static void findIniconfTags (void) const unsigned char* cp = line; bool possible = true; - if (isspace ((int) *cp) || *cp == '#' || *cp == ';' || *cp == '\0' + if (isspace (*cp) || *cp == '#' || *cp == ';' || *cp == '\0' || (*cp == '/' && *(cp+1) == '/')) continue; @@ -134,7 +132,7 @@ static void findIniconfTags (void) ++cp; while (*cp != '\0' && *cp != ']') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } @@ -160,25 +158,25 @@ static void findIniconfTags (void) while (*cp != '\0') { /* We look for any sequence of identifier characters following a white space */ - if (possible && isIdentifier ((int) *cp)) + if (possible && isIdentifier (*cp)) { - while (isIdentifier ((int) *cp)) + while (isIdentifier (*cp)) { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } vStringStripTrailing (name); - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; if (*cp == '=' || *cp == ':') { cp++; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; - while (isValue ((int) *cp)) + while (isValue (*cp)) { - vStringPut (val, (int) *cp); + vStringPut (val, *cp); ++cp; } vStringStripTrailing (val); @@ -211,7 +209,7 @@ static void findIniconfTags (void) vStringClear (name); } else - possible = !!(isspace ((int) *cp)); + possible = !!(isspace (*cp)); if (*cp != '\0') ++cp; diff --git a/ctags/parsers/jscript.c b/ctags/parsers/jscript.c index df572df1cf..3788cc0907 100644 --- a/ctags/parsers/jscript.c +++ b/ctags/parsers/jscript.c @@ -1,22 +1,22 @@ /* - * Copyright (c) 2003, Darren Hiebert + * Copyright (c) 2003, Darren Hiebert * - * This source code is released for free distribution under the terms of the - * GNU General Public License version 2 or (at your option) any later version. + * This source code is released for free distribution under the terms of the + * GNU General Public License version 2 or (at your option) any later version. * - * This module contains functions for generating tags for JavaScript language - * files. + * This module contains functions for generating tags for JavaScript language + * files. * - * Reference: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf + * Reference: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf * - * This is a good reference for different forms of the function statement: - * http://www.permadi.com/tutorial/jsFunc/ - * Another good reference: - * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide + * This is a good reference for different forms of the function statement: + * http://www.permadi.com/tutorial/jsFunc/ + * Another good reference: + * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide */ /* - * INCLUDE FILES + * INCLUDE FILES */ #include "general.h" /* must always come first */ #include /* to define isalpha () */ @@ -38,6 +38,7 @@ #include "debug.h" #include "entry.h" #include "keyword.h" +#include "numarray.h" #include "parse.h" #include "read.h" #include "routines.h" @@ -46,31 +47,22 @@ #include "options.h" #include "mbcs.h" #include "trace.h" -#include "strlist.h" + +#include "jscript.h" /* - * MACROS + * MACROS */ #define isType(token,t) (bool) ((token)->type == (t)) #define isKeyword(token,k) (bool) ((token)->keyword == (k)) -#define isIdentChar(c) \ - (isalpha (c) || isdigit (c) || (c) == '$' || \ - (c) == '@' || (c) == '_' || (c) == '#' || \ - (c) >= 0x80) #define newToken() (objPoolGet (TokenPool)) #define deleteToken(t) (objPoolPut (TokenPool, (t))) /* - * DATA DECLARATIONS - */ - -/* - * Tracks class and function names already created + * DATA DECLARATIONS */ -static stringList *ClassNames; -static stringList *FunctionNames; -/* Used to specify type of keyword. +/* Used to specify type of keyword. */ enum eKeywordId { KEYWORD_function, @@ -129,22 +121,24 @@ typedef enum eTokenType { /* To handle Babel's decorators. * Used only in readTokenFull or lower functions. */ TOKEN_ATMARK, - TOKEN_BINARY_OPERATOR + TOKEN_BINARY_OPERATOR, + TOKEN_ARROW, + TOKEN_DOTS, /* ... */ } tokenType; typedef struct sTokenInfo { tokenType type; keywordId keyword; vString * string; - vString * scope; - unsigned long lineNumber; - MIOPos filePosition; + int scope; + unsigned long lineNumber; + MIOPos filePosition; int nestLevel; bool dynamicProp; } tokenInfo; /* - * DATA DEFINITIONS + * DATA DEFINITIONS */ static tokenType LastTokenType; @@ -158,31 +152,124 @@ static objPool *TokenPool = NULL; static iconv_t JSUnicodeConverter = (iconv_t) -2; #endif +/* + * "chain element" role is introduced when adapting the JavaScript parser + * to corkAPI. + * + * In the corkAPI, a cork index returned from makeTagEntry() can + * represent a scope of another tag. Let's think about `input-0.js' that + * the node command accepts as an input for ctags. + * + +---+ input-0.js ------------------------------------------------------ + | 1 | class A { + | 2 | f = function(x) { + | 3 | return x + | 4 | } + | 5 | } + +---+------------------------------------------------------------------ + * + * The following pseudo C code illustrate the code for + * tagging `A' and `f' in input-0.js: + +---+------------------------------------------------------------------ + | |... + | | tagEntryFor e_for_A, e_for_f; + | | ... + | | int index_for_A = makeTagEntry (&e_for_A); + | | ... + |>>>| e_for_f.extensionFields.scopeIndex = index_for_A; + | | ... + | | makeTagEntry (&e_for_f); + | | ... + +---+------------------------------------------------------------------ + * + * `index_for_A' represents "A" in "class A". + * `f' is defined in `A'. To fill the scope field of the tag for `f', + * `scopeIndex' member of the tag is filled with `index_for_A' at line |>>>|. + * + * If `A' is defined in the input source file, this technique based on + * the cork API works fine. However, if `A' is not defined in the input + * source file, the technique doesn't work well. + +---+ input-1.js ------------------------------------------------------- + | 1 | import {A} from 'input-0.js'; + | 2 | A.g = function(x) { + | 3 | return x + | 4 | } + +---+------------------------------------------------------------------ + * + * In this case, `A' may be defined in input-0.js. + * The current implementation of ctags processes file by file; it doesn't + * use the knowledge in other input source files than current input source + * file. ctags processing input-1.js doesn't know the cork index for `A'. + * + * When tagging `g' with "function" kind, how can we fill the scope field + * of the tag for `g'? + * + * Here the "chain element" role comes. + * This role is used for tagging `z' in "x.y.z" in the case when ctags + * doesn't see the definitions for `x' and `y'. + * The JavaScript parser makes reference tags for `x' and `'y' with + * "chain element" role. makeTagEntry() returns a cork index regardless the + * type of tags (definition or reference). + * The index for the reference tag for `y' can be used to fill the scope + * field of the tag for `z'. The index for `x' can be used to fill the + * field for `y'. + * + * With these trick and technique, the scope field for `g' is filled: + +---+ tags for input-1.js --------------------------------------------- + | 1 | A input-1.js /^A.g = function(x) {$/;" f roles:chainElt extras:reference + | 2 | g input-1.js /^A.g = function(x) {$/;" f scope:function:A signature:(x) roles:def + +---+------------------------------------------------------------------ + * + * By default, reference tags are not emitted. So non-ctags-expert users may + * not see the tag entry for `A'. + * + * makeJsRefTagsForNameChain() and makeJsTagCommon() implement the trick + * and technique. + * + * Arguable points: + * + * Is "chain element(chainElt)" suitable name for people familier with JavaScript? + * + * Kinds assigned to the tag having chainElt role must revised. Eventually + * we may need to introduce "unknown" kind like the Python parser. Assigning + * "function" kind to `A' in input-1.js is obviously wrong. + */ + +typedef enum { + JS_VARIABLE_CHAINELT, +} jsVariableRole; + typedef enum { - JSTAG_FUNCTION, - JSTAG_CLASS, - JSTAG_METHOD, - JSTAG_PROPERTY, - JSTAG_CONSTANT, - JSTAG_VARIABLE, - JSTAG_GENERATOR, - JSTAG_GETTER, - JSTAG_SETTER, - JSTAG_FIELD, - JSTAG_COUNT -} jsKind; + JS_CLASS_CHAINELT, +} jsClassRole; + +static roleDefinition JsFunctionRoles [] = { + /* Currently V parser wants this items. */ + { true, "foreigndecl", "declared in foreign languages" }, +}; + +static roleDefinition JsVariableRoles [] = { + { false, "chainElt", "(EXPERIMENTAL)used as an element in a name chain like a.b.c" }, +}; + +static roleDefinition JsClassRoles [] = { + { false, "chainElt", "(EXPERIMENTAL)used as an element in a name chain like a.b.c" }, +}; static kindDefinition JsKinds [] = { - { true, 'f', "function", "functions" }, - { true, 'c', "class", "classes" }, - { true, 'm', "method", "methods" }, - { true, 'p', "property", "properties" }, - { true, 'C', "constant", "constants" }, - { true, 'v', "variable", "global variables" }, - { true, 'g', "generator", "generators" }, - { true, 'G', "getter", "getters" }, - { true, 'S', "setter", "setters" }, - { true, 'M', "field", "fields" }, + { true, 'f', "function", "functions", + .referenceOnly = false, ATTACH_ROLES(JsFunctionRoles) }, + { true, 'c', "class", "classes", + .referenceOnly = false, ATTACH_ROLES(JsClassRoles) }, + { true, 'm', "method", "methods" }, + { true, 'p', "property", "properties" }, + { true, 'C', "constant", "constants" }, + { true, 'v', "variable", "global variables", + .referenceOnly = false, ATTACH_ROLES(JsVariableRoles) }, + { true, 'g', "generator", "generators" }, + { true, 'G', "getter", "getters" }, + { true, 'S', "setter", "setters" }, + { true, 'M', "field", "fields" }, }; static const keywordTable JsKeywordTable [] = { @@ -205,7 +292,7 @@ static const keywordTable JsKeywordTable [] = { { "try", KEYWORD_try }, { "catch", KEYWORD_catch }, { "finally", KEYWORD_finally }, - { "sap", KEYWORD_sap }, + { "sap", KEYWORD_sap }, { "return", KEYWORD_return }, { "class", KEYWORD_class }, { "extends", KEYWORD_extends }, @@ -218,29 +305,35 @@ static const keywordTable JsKeywordTable [] = { }; /* - * FUNCTION DEFINITIONS + * FUNCTION DEFINITIONS */ /* Recursive functions */ static void readTokenFull (tokenInfo *const token, bool include_newlines, vString *const repr); static void skipArgumentList (tokenInfo *const token, bool include_newlines, vString *const repr); -static void parseFunction (tokenInfo *const token); -static bool parseBlock (tokenInfo *const token, const vString *const parentScope); +static bool parseFunction (tokenInfo *const token, tokenInfo *const name, const bool is_inside_class); +static bool parseBlock (tokenInfo *const token, int parent_scope); +static bool parseMethods (tokenInfo *const token, int class_index, const bool is_es6_class); static bool parseLine (tokenInfo *const token, bool is_inside_class); static void parseUI5 (tokenInfo *const token); #ifdef DO_TRACING -//static void dumpToken (const tokenInfo *const token); static const char *tokenTypeName(enum eTokenType e); -//static const char *keywordName(enum eKeywordId e); +static const char* getNameStringForCorkIndex(int index); +static const char* getKindStringForCorkIndex(int index); +static const char *kindName(jsKind kind); +// #define DO_TRACING_USE_DUMP_TOKEN +#ifdef DO_TRACING_USE_DUMP_TOKEN +static void dumpToken (const tokenInfo *const token); +#endif #endif static void *newPoolToken (void *createArg CTAGS_ATTR_UNUSED) { tokenInfo *token = xMalloc (1, tokenInfo); - token->string = vStringNew (); - token->scope = vStringNew (); + token->string = vStringNew (); + token->scope = CORK_NIL; return token; } @@ -249,26 +342,25 @@ static void clearPoolToken (void *data) { tokenInfo *token = data; - token->type = TOKEN_UNDEFINED; - token->keyword = KEYWORD_NONE; - token->nestLevel = 0; - token->dynamicProp = false; - token->lineNumber = getInputLineNumber (); + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->nestLevel = 0; + token->dynamicProp = false; + token->lineNumber = getInputLineNumber (); token->filePosition = getInputFilePosition (); vStringClear (token->string); - vStringClear (token->scope); + token->scope = CORK_NIL; } static void deletePoolToken (void *data) { tokenInfo *token = data; vStringDelete (token->string); - vStringDelete (token->scope); eFree (token); } static void copyToken (tokenInfo *const dest, const tokenInfo *const src, - bool const include_non_read_info) + bool const include_non_read_info) { dest->lineNumber = src->lineNumber; dest->filePosition = src->filePosition; @@ -279,7 +371,7 @@ static void copyToken (tokenInfo *const dest, const tokenInfo *const src, if (include_non_read_info) { dest->nestLevel = src->nestLevel; - vStringCopy(dest->scope, src->scope); + dest->scope = src->scope; } } @@ -291,150 +383,224 @@ static void injectDynamicName (tokenInfo *const token, vString *newName) } /* - * Tag generation functions + * Tag generation functions */ -static void makeJsTagCommon (const tokenInfo *const token, const jsKind kind, - vString *const signature, vString *const inheritance, - bool anonymous) +struct bestJSEntryInScopeData { + int index; +}; + +static bool findBestJSEntry (int corkIndex, tagEntryInfo *entry, void *cb_data) { - if (JsKinds [kind].enabled ) + struct bestJSEntryInScopeData *data = cb_data; + + if (isRoleAssigned (entry, ROLE_DEFINITION_INDEX)) { - const char *name = vStringValue (token->string); - vString *fullscope = vStringNewCopy (token->scope); - const char *p; - tagEntryInfo e; + data->index = corkIndex; + return false; + } - if (!token->dynamicProp && kind != JSTAG_PROPERTY && (p = strrchr (name, '.')) != NULL ) - { - if (vStringLength (fullscope) > 0) - vStringPut (fullscope, '.'); - vStringNCatS (fullscope, name, (size_t) (p - name)); - name = p + 1; - } + if (data->index == CORK_NIL || data->index > corkIndex) + data->index = corkIndex; - initTagEntry (&e, name, kind); + return true; +} - TRACE_PRINT("Emitting tag for symbol '%s' of kind %02x with scope '%s'",name,kind,vStringValue(fullscope)); +static int bestJSEntryInScope(int scope, const char *name) +{ + /* If the SCOPE has a tag entry having NAME, the tag is the best + * even if there are reference tag entries having NAME. + * If the scope has only reference tag entries having NAME, the + * tag having smallest cork index is the best. + */ - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + struct bestJSEntryInScopeData data = { + .index = CORK_NIL, + }; + foreachEntriesInScope (scope, name, findBestJSEntry, &data); + return data.index; +} - if ( vStringLength(fullscope) > 0 ) - { - /* FIXME: proper parent type */ - jsKind parent_kind = JSTAG_CLASS; +static int makeJsRefTagsForNameChain (char *name_chain, const tokenInfo *token, int leaf_kind, int scope) +{ + /* To fill the scope field of "d" of "a.b.c.d", + * "c" must be tagged if the cork API is used. + * ---------------------------------------------------------- + * How the fields for "a", "b", and "c" are filled. + * a kind:class scope: roles:chainElt + * b kind:class scope:class:a roles:chainElt + * + * The fields of c depends on LEAF_KIND that is passed to this functions. + * + * if (LEAF_KIND == FUNCTION) + * c kind:function scope:class:b roles:chainElt + * else + * c kind:class scope:class:b roles:chainElt + */ + const char *name = name_chain; + char *next = strchr(name_chain, '.'); + if (next) + *next = '\0'; + int index = bestJSEntryInScope (scope, name); + + if (index == CORK_NIL) + { + tagEntryInfo e; + int kind = JSTAG_CLASS; + int role = JS_CLASS_CHAINELT; + if (next == NULL && leaf_kind == JSTAG_FUNCTION) + { /* * If we're creating a function (and not a method), - * guess we're inside another function + * assume the parent is a plain variable. */ - if (kind == JSTAG_FUNCTION) - parent_kind = JSTAG_FUNCTION; - - e.extensionFields.scopeKindIndex = parent_kind; - e.extensionFields.scopeName = vStringValue (fullscope); - } - - if (signature && vStringLength(signature)) - { - size_t i; - /* sanitize signature by replacing all control characters with a - * space (because it's simple). - * there should never be any junk in a valid signature, but who - * knows what the user wrote and CTags doesn't cope well with weird - * characters. */ - for (i = 0; i < signature->length; i++) - { - unsigned char c = (unsigned char) vStringChar (signature, i); - if (c < 0x20 /* below space */ || c == 0x7F /* DEL */) - vStringChar (signature, i) = ' '; - } - e.extensionFields.signature = vStringValue(signature); + kind = JSTAG_VARIABLE; + role = JS_VARIABLE_CHAINELT; } - if (inheritance) - e.extensionFields.inheritance = vStringValue(inheritance); + initRefTagEntry (&e, name, kind, role); + updateTagLine (&e, token->lineNumber, token->filePosition); + e.extensionFields.scopeIndex = scope; - if (anonymous) - markTagExtraBit (&e, XTAG_ANONYMOUS); - - makeTagEntry (&e); - vStringDelete (fullscope); + index = makeTagEntry (&e); + /* We shold remove This condition. We should fix the callers passing + * an empty name instead. makeTagEntry() returns CORK_NIL if the tag + * name is empty. */ + if (index != CORK_NIL) + registerEntry (index); } -} -static void makeJsTag (const tokenInfo *const token, const jsKind kind, - vString *const signature, vString *const inheritance) -{ - makeJsTagCommon (token, kind, signature, inheritance, false); + return next + ? makeJsRefTagsForNameChain (next + 1, token, leaf_kind, index) + : index; } -static void makeClassTagCommon (tokenInfo *const token, vString *const signature, - vString *const inheritance, bool anonymous) +static int makeJsTagCommon (const tokenInfo *const token, const jsKind kind, + vString *const signature, vString *const inheritance, + bool anonymous) { + int index = CORK_NIL; + const char *name = vStringValue (token->string); + const char *p; + char *name_chain = NULL; + if (!token->dynamicProp && kind != JSTAG_PROPERTY && (p = strrchr (name, '.')) != NULL ) + { + if ((p - name) != 0) + name_chain = eStrndup (name, (size_t) (p - name)); + name = p + 1; + if (name[0] == '\0') + return CORK_NIL; + } + int scope = token->scope; + if (name_chain) { - vString * fulltag = vStringNew (); - if (vStringLength (token->scope) > 0) - { - vStringCopy(fulltag, token->scope); - vStringPut (fulltag, '.'); - vStringCat (fulltag, token->string); - } - else - { - vStringCopy(fulltag, token->string); - } - if ( ! stringListHas(ClassNames, vStringValue (fulltag)) ) + scope = makeJsRefTagsForNameChain (name_chain, token, kind, scope); + eFree (name_chain); + } + + /* + * Check whether NAME is already defined in SCOPE. + * If the NAME is already defined, return the cork index for the NAME. + */ + if (kind == JSTAG_FUNCTION || kind == JSTAG_CLASS) + { + index = anyKindEntryInScope (scope, name, kind, true); + if (index != CORK_NIL) + return index; + } + + tagEntryInfo e; + initTagEntry (&e, name, kind); + updateTagLine (&e, token->lineNumber, token->filePosition); + e.extensionFields.scopeIndex = scope; + +#ifdef DO_TRACING + { + const char *scope_str = getNameStringForCorkIndex (scope); + const char *scope_kind_str = getKindStringForCorkIndex (scope); + TRACE_PRINT("Emitting tag for symbol '%s' of kind %s with scope '%s:%s'", name, kindName(kind), scope_kind_str, scope_str); + } +#endif + + if (signature && vStringLength(signature)) + { + size_t i; + /* sanitize signature by replacing all control characters with a + * space (because it's simple). + * there should never be any junk in a valid signature, but who + * knows what the user wrote and CTags doesn't cope well with weird + * characters. */ + for (i = 0; i < signature->length; i++) { - stringListAdd (ClassNames, vStringNewCopy (fulltag)); - makeJsTagCommon (token, JSTAG_CLASS, signature, inheritance, - anonymous); + unsigned char c = (unsigned char) vStringChar (signature, i); + if (c < 0x20 /* below space */ || c == 0x7F /* DEL */) + vStringChar (signature, i) = ' '; } - vStringDelete (fulltag); + e.extensionFields.signature = vStringValue(signature); } + + if (inheritance) + e.extensionFields.inheritance = vStringValue(inheritance); + + if (anonymous) + markTagExtraBit (&e, XTAG_ANONYMOUS); + + index = makeTagEntry (&e); + /* We shold remove This condition. We should fix the callers passing + * an empty name instead. makeTagEntry() returns CORK_NIL if the tag + * name is empty. */ + if (index != CORK_NIL) + registerEntry (index); + + return index; +} + +static int makeJsTag (const tokenInfo *const token, const jsKind kind, + vString *const signature, vString *const inheritance) +{ + return makeJsTagCommon (token, kind, signature, inheritance, false); +} + +static int makeClassTagCommon (tokenInfo *const token, vString *const signature, + vString *const inheritance, bool anonymous) +{ + return makeJsTagCommon (token, JSTAG_CLASS, signature, inheritance, anonymous); } -static void makeClassTag (tokenInfo *const token, vString *const signature, +static int makeClassTag (tokenInfo *const token, vString *const signature, vString *const inheritance) { - makeClassTagCommon (token, signature, inheritance, false); + return makeClassTagCommon (token, signature, inheritance, false); } -static void makeFunctionTagCommon (tokenInfo *const token, vString *const signature, bool generator, - bool anonymous) +static int makeFunctionTagCommon (tokenInfo *const token, vString *const signature, + bool generator, bool anonymous) { - { - vString * fulltag = vStringNew (); - if (vStringLength (token->scope) > 0) - { - vStringCopy(fulltag, token->scope); - vStringPut (fulltag, '.'); - vStringCat (fulltag, token->string); - } - else - { - vStringCopy(fulltag, token->string); - } - if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) ) - { - stringListAdd (FunctionNames, vStringNewCopy (fulltag)); - makeJsTagCommon (token, generator ? JSTAG_GENERATOR : JSTAG_FUNCTION, signature, NULL, - anonymous); - } - vStringDelete (fulltag); - } + return makeJsTagCommon (token, generator ? JSTAG_GENERATOR : JSTAG_FUNCTION, signature, NULL, + anonymous); +} + +static int makeFunctionTag (tokenInfo *const token, vString *const signature, bool generator) +{ + return makeFunctionTagCommon (token, signature, generator, false); } -static void makeFunctionTag (tokenInfo *const token, vString *const signature, bool generator) +static bool isClassName (tokenInfo *const name) { - makeFunctionTagCommon (token, signature, generator, false); + char * p = strrchr(vStringValue (name->string), '.'); + if (p == NULL) + p = vStringValue (name->string); + else + p++; + + return isupper((unsigned char) *p); } /* - * Parsing functions + * Parsing functions */ /* given @p point, returns the first byte of the encoded output sequence, and @@ -528,7 +694,7 @@ static int handleUnicodeCodePoint (uint32_t point) * @param isUTF16 Location to store whether @param value is an UTF-16 word. * @returns Whether a valid sequence was read. */ static bool readUnicodeEscapeSequenceValue (uint32_t *const value, - bool *const isUTF16) + bool *const isUTF16) { bool valid = false; int d = getcFromInputFile (); @@ -547,25 +713,27 @@ static bool readUnicodeEscapeSequenceValue (uint32_t *const value, * We skip the leading 0s because there can be any number of them * and they don't change any meaning. */ bool has_leading_zero = false; + int l; - while ((cp[cp_len] = (char) getcFromInputFile ()) == '0') + while ((cp[cp_len] = (char) (l = getcFromInputFile ())) == '0') has_leading_zero = true; - while (isxdigit (cp[cp_len]) && ++cp_len < ARRAY_SIZE (cp)) - cp[cp_len] = (char) getcFromInputFile (); + while (isxdigit (l) && ++cp_len < ARRAY_SIZE (cp)) + cp[cp_len] = (char) (l = getcFromInputFile ()); valid = ((cp_len > 0 || has_leading_zero) && cp_len < ARRAY_SIZE (cp) && cp[cp_len] == '}' && /* also check if it's a valid Unicode code point */ (cp_len < 6 || (cp_len == 6 && strncmp (cp, "110000", 6) < 0))); if (! valid) /* put back the last (likely invalid) character */ - ungetcToInputFile (cp[cp_len]); + ungetcToInputFile (l); } else { /* Handles Unicode escape sequences: \u Hex4Digits */ + int l; do - cp[cp_len] = (char) ((cp_len == 0) ? e : getcFromInputFile ()); - while (isxdigit (cp[cp_len]) && ++cp_len < 4); + cp[cp_len] = (char) (l = ((cp_len == 0) ? e : getcFromInputFile ())); + while (isxdigit (l) && ++cp_len < 4); valid = (cp_len == 4); } @@ -739,12 +907,34 @@ static void parseRegExp (void) } while (c != EOF); } -/* Read a C identifier beginning with "firstChar" and places it into - * "name". +/* Read a C identifier beginning with "first_char" and places it into + * "name". */ -static void parseIdentifier (vString *const string, const int firstChar) + +static int include_period_in_identifier = 0; + +static void accept_period_in_identifier(bool incl) +{ + if (incl) + { + include_period_in_identifier++; + } + else if (!incl && include_period_in_identifier > 0) + { + include_period_in_identifier--; + } +} + +static bool isIdentChar(const int c) { - int c = firstChar; + return (isalpha (c) || isdigit (c) || c == '$' || \ + c == '@' || c == '_' || c == '#' || \ + c >= 0x80 || (include_period_in_identifier > 0 && c == '.')); +} + +static void parseIdentifier (vString *const string, const int first_char) +{ + int c = first_char; Assert (isIdentChar (c)); do { @@ -848,32 +1038,73 @@ static void readTokenFullRaw (tokenInfo *const token, bool include_newlines, vSt switch (c) { - case EOF: token->type = TOKEN_EOF; break; - case '(': token->type = TOKEN_OPEN_PAREN; break; - case ')': token->type = TOKEN_CLOSE_PAREN; break; - case ';': token->type = TOKEN_SEMICOLON; break; - case ',': token->type = TOKEN_COMMA; break; - case '.': token->type = TOKEN_PERIOD; break; - case ':': token->type = TOKEN_COLON; break; - case '{': token->type = TOKEN_OPEN_CURLY; break; - case '}': token->type = TOKEN_CLOSE_CURLY; break; - case '=': token->type = TOKEN_EQUAL_SIGN; break; - case '[': token->type = TOKEN_OPEN_SQUARE; break; - case ']': token->type = TOKEN_CLOSE_SQUARE; break; + case EOF: token->type = TOKEN_EOF; break; + case '(': token->type = TOKEN_OPEN_PAREN; break; + case ')': token->type = TOKEN_CLOSE_PAREN; break; + case ';': token->type = TOKEN_SEMICOLON; break; + case ',': token->type = TOKEN_COMMA; break; + case '.': + { + token->type = TOKEN_PERIOD; + + int d = getcFromInputFile (); + if (d != '.') + { + ungetcToInputFile (d); + break; + } + + d = getcFromInputFile (); + if (d != '.') + { + ungetcToInputFile (d); + ungetcToInputFile ('.'); + break; + } + + token->type = TOKEN_DOTS; + if (repr) + { + /* Adding two dots is enough here. + * The first one is already added with + * vStringPut (repr, c). + */ + vStringCatS (repr, ".."); + } + break; + } + case ':': token->type = TOKEN_COLON; break; + case '{': token->type = TOKEN_OPEN_CURLY; break; + case '}': token->type = TOKEN_CLOSE_CURLY; break; + case '[': token->type = TOKEN_OPEN_SQUARE; break; + case ']': token->type = TOKEN_CLOSE_SQUARE; break; + + case '=': + { + int d = getcFromInputFile (); + if (d == '>') + token->type = TOKEN_ARROW; + else + { + ungetcToInputFile (d); + token->type = TOKEN_EQUAL_SIGN; + } + break; + } case '+': case '-': + { + int d = getcFromInputFile (); + if (d == c) /* ++ or -- */ + token->type = TOKEN_POSTFIX_OPERATOR; + else { - int d = getcFromInputFile (); - if (d == c) /* ++ or -- */ - token->type = TOKEN_POSTFIX_OPERATOR; - else - { - ungetcToInputFile (d); - token->type = TOKEN_BINARY_OPERATOR; - } - break; + ungetcToInputFile (d); + token->type = TOKEN_BINARY_OPERATOR; } + break; + } case '*': token->type = TOKEN_STAR; @@ -890,117 +1121,117 @@ static void readTokenFullRaw (tokenInfo *const token, bool include_newlines, vSt case '\'': case '"': - token->type = TOKEN_STRING; - parseString (token->string, c); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - if (repr) - { - vStringCat (repr, token->string); - vStringPut (repr, c); - } - break; + token->type = TOKEN_STRING; + parseString (token->string, c); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + if (repr) + { + vStringCat (repr, token->string); + vStringPut (repr, c); + } + break; case '`': - token->type = TOKEN_TEMPLATE_STRING; - parseTemplateString (token->string); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - if (repr) - { - vStringCat (repr, token->string); - vStringPut (repr, c); - } - break; + token->type = TOKEN_TEMPLATE_STRING; + parseTemplateString (token->string); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + if (repr) + { + vStringCat (repr, token->string); + vStringPut (repr, c); + } + break; case '/': - { - int d = getcFromInputFile (); - if ( (d != '*') && /* is this the start of a comment? */ - (d != '/') ) /* is a one line comment? */ - { - ungetcToInputFile (d); - switch (LastTokenType) - { - case TOKEN_CHARACTER: - case TOKEN_IDENTIFIER: - case TOKEN_STRING: - case TOKEN_TEMPLATE_STRING: - case TOKEN_CLOSE_CURLY: - case TOKEN_CLOSE_PAREN: - case TOKEN_CLOSE_SQUARE: - token->type = TOKEN_BINARY_OPERATOR; - break; - - default: - token->type = TOKEN_REGEXP; - parseRegExp (); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - break; - } - } - else - { - if (repr) /* remove the / we added */ - vStringChop(repr); - if (d == '*') - { - skipToCharacterInInputFile2('*', '/'); - goto getNextChar; - } - else if (d == '/') /* is this the start of a comment? */ - { - skipToCharacterInInputFile ('\n'); - /* if we care about newlines, put it back so it is seen */ - if (include_newlines) - ungetcToInputFile ('\n'); - goto getNextChar; - } - } - break; - } + { + int d = getcFromInputFile (); + if ( (d != '*') && /* is this the start of a comment? */ + (d != '/') ) /* is a one line comment? */ + { + ungetcToInputFile (d); + switch (LastTokenType) + { + case TOKEN_CHARACTER: + case TOKEN_IDENTIFIER: + case TOKEN_STRING: + case TOKEN_TEMPLATE_STRING: + case TOKEN_CLOSE_CURLY: + case TOKEN_CLOSE_PAREN: + case TOKEN_CLOSE_SQUARE: + token->type = TOKEN_BINARY_OPERATOR; + break; + + default: + token->type = TOKEN_REGEXP; + parseRegExp (); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + } + else + { + if (repr) /* remove the / we added */ + vStringChop(repr); + if (d == '*') + { + skipToCharacterInInputFile2('*', '/'); + goto getNextChar; + } + else if (d == '/') /* is this the start of a comment? */ + { + skipToCharacterInInputFile ('\n'); + /* if we care about newlines, put it back so it is seen */ + if (include_newlines) + ungetcToInputFile ('\n'); + goto getNextChar; + } + } + break; + } case '#': - /* skip shebang in case of e.g. Node.js scripts */ - if (token->lineNumber > 1) - token->type = TOKEN_UNDEFINED; - else if ((c = getcFromInputFile ()) != '!') - { - ungetcToInputFile (c); - token->type = TOKEN_UNDEFINED; - } - else - { - skipToCharacterInInputFile ('\n'); - goto getNextChar; - } - break; + /* skip shebang in case of e.g. Node.js scripts */ + if (token->lineNumber > 1) + token->type = TOKEN_UNDEFINED; + else if ((c = getcFromInputFile ()) != '!') + { + ungetcToInputFile (c); + token->type = TOKEN_UNDEFINED; + } + else + { + skipToCharacterInInputFile ('\n'); + goto getNextChar; + } + break; case '@': - token->type = TOKEN_ATMARK; - break; + token->type = TOKEN_ATMARK; + break; case '\\': - c = readUnicodeEscapeSequence (c); - /* fallthrough */ + c = readUnicodeEscapeSequence (c); + /* fallthrough */ default: - if (! isIdentChar (c)) - token->type = TOKEN_UNDEFINED; - else - { - parseIdentifier (token->string, c); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - token->keyword = lookupKeyword (vStringValue (token->string), Lang_js); - if (isKeyword (token, KEYWORD_NONE)) - token->type = TOKEN_IDENTIFIER; - else - token->type = TOKEN_KEYWORD; - if (repr && vStringLength (token->string) > 1) - vStringCatS (repr, vStringValue (token->string) + 1); - } - break; + if (! isIdentChar (c)) + token->type = TOKEN_UNDEFINED; + else + { + parseIdentifier (token->string, c); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + token->keyword = lookupKeyword (vStringValue (token->string), Lang_js); + if (isKeyword (token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + if (repr && vStringLength (token->string) > 1) + vStringCatS (repr, vStringValue (token->string) + 1); + } + break; } if (include_newlines && newline_encountered) @@ -1019,23 +1250,24 @@ static void readTokenFullRaw (tokenInfo *const token, bool include_newlines, vSt /* these already end a statement, so no need to duplicate it */ #define IS_STMT_SEPARATOR(t) ((t) == TOKEN_SEMICOLON || \ - (t) == TOKEN_EOF || \ - (t) == TOKEN_COMMA || \ - (t) == TOKEN_OPEN_CURLY) + (t) == TOKEN_EOF || \ + (t) == TOKEN_COMMA || \ + (t) == TOKEN_OPEN_CURLY) /* these cannot be the start or end of a statement */ #define IS_BINARY_OPERATOR(t) ((t) == TOKEN_EQUAL_SIGN || \ - (t) == TOKEN_COLON || \ - (t) == TOKEN_PERIOD || \ - (t) == TOKEN_STAR || \ - (t) == TOKEN_BINARY_OPERATOR) + (t) == TOKEN_ARROW || \ + (t) == TOKEN_COLON || \ + (t) == TOKEN_PERIOD || \ + (t) == TOKEN_STAR || \ + (t) == TOKEN_BINARY_OPERATOR) if (! IS_STMT_SEPARATOR(LastTokenType) && - ! IS_STMT_SEPARATOR(token->type) && - ! IS_BINARY_OPERATOR(LastTokenType) && - ! IS_BINARY_OPERATOR(token->type) && - /* these cannot be followed by a semicolon */ - ! (LastTokenType == TOKEN_OPEN_PAREN || - LastTokenType == TOKEN_OPEN_SQUARE)) + ! IS_STMT_SEPARATOR(token->type) && + ! IS_BINARY_OPERATOR(LastTokenType) && + ! IS_BINARY_OPERATOR(token->type) && + /* these cannot be followed by a semicolon */ + ! (LastTokenType == TOKEN_OPEN_PAREN || + LastTokenType == TOKEN_OPEN_SQUARE)) { /* hold the token... */ Assert (NextToken == NULL); @@ -1043,8 +1275,8 @@ static void readTokenFullRaw (tokenInfo *const token, bool include_newlines, vSt copyToken (NextToken, token, false); /* ...and emit a semicolon instead */ - token->type = TOKEN_SEMICOLON; - token->keyword = KEYWORD_NONE; + token->type = TOKEN_SEMICOLON; + token->keyword = KEYWORD_NONE; vStringClear (token->string); if (repr) vStringPut (token->string, '\n'); @@ -1116,12 +1348,12 @@ static void readTokenFull (tokenInfo *const token, bool include_newlines, vStrin } } -#ifdef JSCRIPT_DO_DEBUGGING +#ifdef DO_TRACING_USE_DUMP_TOKEN /* trace readTokenFull() */ static void readTokenFullDebug (tokenInfo *const token, bool include_newlines, vString *const repr) { readTokenFull (token, include_newlines, repr); - TRACE_PRINT("token '%s' of type %02x with scope '%s'",vStringValue(token->string),token->type, vStringValue(token->scope)); + dumpToken (token); } # define readTokenFull readTokenFullDebug #endif @@ -1132,18 +1364,44 @@ static void readToken (tokenInfo *const token) } /* - * Token parsing functions + * Token parsing functions */ -static void skipArgumentList (tokenInfo *const token, bool include_newlines, vString *const repr) +static int parseMethodsInAnonymousObject (tokenInfo *const token) { - int nest_level = 0; + int index = CORK_NIL; + + tokenInfo *const anon_object = newToken (); + copyToken (anon_object, token, true); + anonGenerate (anon_object->string, "anonymousObject", JSTAG_VARIABLE); + anon_object->type = TOKEN_IDENTIFIER; + + index = makeJsTagCommon (anon_object, JSTAG_VARIABLE, NULL, NULL, true); + if (! parseMethods (token, index, false)) + { + /* If no method is found, the anonymous object + * should not be tagged. + */ + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e) + markTagAsPlaceholder (e, true); + index = CORK_NIL; + } + + deleteToken (anon_object); + + return index; +} +static void skipArgumentList (tokenInfo *const token, bool include_newlines, vString *const repr) +{ if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */ { - nest_level++; + int nest_level = 1; if (repr) vStringPut (repr, '('); + + tokenType prev_token_type = token->type; while (nest_level > 0 && ! isType (token, TOKEN_EOF)) { readTokenFull (token, false, repr); @@ -1151,8 +1409,17 @@ static void skipArgumentList (tokenInfo *const token, bool include_newlines, vSt nest_level++; else if (isType (token, TOKEN_CLOSE_PAREN)) nest_level--; + else if (isType (token, TOKEN_OPEN_CURLY)) + { + if (prev_token_type == TOKEN_ARROW) + parseBlock (token, CORK_NIL); + else + parseMethodsInAnonymousObject (token); + } else if (isKeyword (token, KEYWORD_function)) - parseFunction (token); + parseFunction (token, NULL, false); + + prev_token_type = token->type; } readTokenFull (token, include_newlines, NULL); } @@ -1160,17 +1427,16 @@ static void skipArgumentList (tokenInfo *const token, bool include_newlines, vSt static void skipArrayList (tokenInfo *const token, bool include_newlines) { - int nest_level = 0; - /* * Handle square brackets - * var name[1] + * var name[1] * So we must check for nested open and closing square brackets */ if (isType (token, TOKEN_OPEN_SQUARE)) /* arguments? */ { - nest_level++; + int nest_level = 1; + tokenType prev_token_type = token->type; while (nest_level > 0 && ! isType (token, TOKEN_EOF)) { readToken (token); @@ -1178,6 +1444,15 @@ static void skipArrayList (tokenInfo *const token, bool include_newlines) nest_level++; else if (isType (token, TOKEN_CLOSE_SQUARE)) nest_level--; + else if (isType (token, TOKEN_OPEN_CURLY)) + { + if (prev_token_type == TOKEN_ARROW) + parseBlock (token, CORK_NIL); + else + parseMethodsInAnonymousObject (token); + } + + prev_token_type = token->type; } readTokenFull (token, include_newlines, NULL); } @@ -1198,28 +1473,14 @@ static void skipQualifiedIdentifier (tokenInfo *const token) static void addContext (tokenInfo* const parent, const tokenInfo* const child) { - if (vStringLength (parent->string) > 0) - { - vStringPut (parent->string, '.'); - } - vStringCat (parent->string, child->string); -} - -static void addToScope (tokenInfo* const token, const vString* const extra) -{ - if (vStringLength (token->scope) > 0) - { - vStringPut (token->scope, '.'); - } - vStringCat (token->scope, extra); + vStringJoin (parent->string, '.', child->string); } /* - * Scanning functions + * Scanning functions */ -static bool findCmdTerm (tokenInfo *const token, bool include_newlines, - bool include_commas) +static bool findCmdTerm (tokenInfo *const token, bool include_newlines, bool include_commas) { /* * Read until we find either a semicolon or closing brace. @@ -1233,21 +1494,15 @@ static bool findCmdTerm (tokenInfo *const token, bool include_newlines, /* Handle nested blocks */ if ( isType (token, TOKEN_OPEN_CURLY)) { - parseBlock (token, NULL); + parseBlock (token, CORK_NIL); readTokenFull (token, include_newlines, NULL); } else if ( isType (token, TOKEN_OPEN_PAREN) ) - { skipArgumentList(token, include_newlines, NULL); - } else if ( isType (token, TOKEN_OPEN_SQUARE) ) - { skipArrayList(token, include_newlines); - } else - { readTokenFull (token, include_newlines, NULL); - } } return isType (token, TOKEN_SEMICOLON); @@ -1258,11 +1513,11 @@ static void parseSwitch (tokenInfo *const token) /* * switch (expression) { * case value1: - * statement; - * break; + * statement; + * break; * case value2: - * statement; - * break; + * statement; + * break; * default : statement; * } */ @@ -1276,7 +1531,7 @@ static void parseSwitch (tokenInfo *const token) if (isType (token, TOKEN_OPEN_CURLY)) { - parseBlock (token, NULL); + parseBlock (token, CORK_NIL); } } @@ -1284,24 +1539,24 @@ static bool parseLoop (tokenInfo *const token) { /* * Handles these statements - * for (x=0; x<3; x++) - * document.write("This text is repeated three times
"); + * for (x=0; x<3; x++) + * document.write("This text is repeated three times
"); * - * for (x=0; x<3; x++) - * { - * document.write("This text is repeated three times
"); - * } + * for (x=0; x<3; x++) + * { + * document.write("This text is repeated three times
"); + * } * - * while (number<5){ - * document.write(number+"
"); - * number++; - * } + * while (number<5){ + * document.write(number+"
"); + * number++; + * } * - * do{ - * document.write(number+"
"); - * number++; - * } - * while (number<5); + * do{ + * document.write(number+"
"); + * number++; + * } + * while (number<5); */ bool is_terminated = true; @@ -1310,31 +1565,21 @@ static bool parseLoop (tokenInfo *const token) readToken(token); if (isType (token, TOKEN_OPEN_PAREN)) - { skipArgumentList(token, false, NULL); - } if (isType (token, TOKEN_OPEN_CURLY)) - { - parseBlock (token, NULL); - } + parseBlock (token, CORK_NIL); else - { is_terminated = parseLine(token, false); - } } else if (isKeyword (token, KEYWORD_do)) { readToken(token); if (isType (token, TOKEN_OPEN_CURLY)) - { - parseBlock (token, NULL); - } + parseBlock (token, CORK_NIL); else - { is_terminated = parseLine(token, false); - } if (is_terminated) readToken(token); @@ -1344,9 +1589,8 @@ static bool parseLoop (tokenInfo *const token) readToken(token); if (isType (token, TOKEN_OPEN_PAREN)) - { skipArgumentList(token, true, NULL); - } + if (! isType (token, TOKEN_SEMICOLON)) { /* oddly enough, `do {} while (0) var foo = 42` is perfectly @@ -1366,41 +1610,41 @@ static bool parseIf (tokenInfo *const token) bool read_next_token = true; /* * If statements have two forms - * if ( ... ) - * one line; + * if ( ... ) + * one line; * - * if ( ... ) - * statement; - * else - * statement + * if ( ... ) + * statement; + * else + * statement * - * if ( ... ) { - * multiple; - * statements; - * } + * if ( ... ) { + * multiple; + * statements; + * } * * - * if ( ... ) { - * return elem - * } + * if ( ... ) { + * return elem + * } * * This example if correctly written, but the * else contains only 1 statement without a terminator * since the function finishes with the closing brace. * - * function a(flag){ - * if(flag) - * test(1); - * else - * test(2) - * } + * function a(flag){ + * if(flag) + * test(1); + * else + * test(2) + * } * * TODO: Deal with statements that can optional end - * without a semi-colon. Currently this messes up - * the parsing of blocks. - * Need to somehow detect this has happened, and either - * backup a token, or skip reading the next token if - * that is possible from all code locations. + * without a semi-colon. Currently this messes up + * the parsing of blocks. + * Need to somehow detect this has happened, and either + * backup a token, or skip reading the next token if + * that is possible from all code locations. * */ @@ -1415,14 +1659,10 @@ static bool parseIf (tokenInfo *const token) } if (isType (token, TOKEN_OPEN_PAREN)) - { skipArgumentList(token, false, NULL); - } if (isType (token, TOKEN_OPEN_CURLY)) - { - parseBlock (token, NULL); - } + parseBlock (token, CORK_NIL); else { /* The next token should only be read if this statement had its own @@ -1432,23 +1672,99 @@ static bool parseIf (tokenInfo *const token) return read_next_token; } -static void parseFunction (tokenInfo *const token) +static bool collectChildren (int corkIndex, tagEntryInfo *entry, void *data) { - TRACE_ENTER(); + intArray *children = (intArray *)data; + + Assert (entry->extensionFields.scopeIndex != CORK_NIL); + intArrayAdd (children, corkIndex); + + return true; +} + +/* During parsing, there is a case that a language object (parent) + * should be tagged only when there are language objects (children) + * are defined in the parent; if the parent has no child, the parser + * should not make a tag for the parent. + * + * Handling the this case was not easy because the parser must fill + * the scope field of children with the cork index of parent. + * However, the parser can decide whether the parent should be tagged + * or not after parsing inside the parent where the children are + * defined. + * + * "class" is an example of the language object of the parent. + * "methods" are examples of the language object of the children. + * "class" is tagged as a class only when methods are found in it. + * + * + * The parser handles this case with the following steps: + * + * 1. make a dummy tag entry for the candidate of parent with + * + * > int dummyIndex = makeSimplePlaceholder(). + * + * ctags doesn't emit this dummy tag entry. + * + * 2. parse inside the candidate of parent and count children. + * If a child is found, make a tag for it with filling its + * scope field with the dummyIndex. + * + * 3. make a true tag entry for the parent if a child is found: + * + * > int trueIdex = makeTagEntry (...); + * + * 4. update the scope fields of children with the trueIdex. + * + * moveChildren (dummyIndex, trueIdex); + * + */ +static void moveChildren (int old_parent, int new_parent) +{ + intArray *children = intArrayNew (); + foreachEntriesInScope (old_parent, NULL, collectChildren, children); + for (unsigned int i = 0; i < intArrayCount (children); i++) + { + int c = intArrayItem (children, i); + + unregisterEntry (c); + tagEntryInfo *e = getEntryInCorkQueue (c); + Assert (e); + e->extensionFields.scopeIndex = new_parent; + registerEntry (c); + } + intArrayDelete (children); +} + +static bool parseFunction (tokenInfo *const token, tokenInfo *const lhs_name, const bool is_inside_class) +{ +#ifdef DO_TRACING + { + const char *scope_str = getNameStringForCorkIndex (token->scope); + const char *scope_kind_str = getKindStringForCorkIndex (token->scope); + TRACE_ENTER_TEXT("token has scope '%s' of kind %s", scope_str, scope_kind_str); + } +#endif tokenInfo *const name = newToken (); vString *const signature = vStringNew (); - bool is_class = false; bool is_generator = false; bool is_anonymous = false; + int index_for_name = CORK_NIL; /* * This deals with these formats - * function validFunctionTwo(a,b) {} - * function * generator(a,b) {} + * function validFunctionTwo(a,b) {} + * function * generator(a,b) {} */ copyToken (name, token, true); readToken (name); + if (isType (name, TOKEN_KEYWORD) && + (isKeyword (name, KEYWORD_get) || isKeyword (name, KEYWORD_set))) + { + name->type = TOKEN_IDENTIFIER; // treat as function name + } + if (isType (name, TOKEN_STAR)) { is_generator = true; @@ -1458,7 +1774,7 @@ static void parseFunction (tokenInfo *const token) { /* anonymous function */ copyToken (token, name, false); - anonGenerate (name->string, "AnonymousFunction", JSTAG_FUNCTION); + anonGenerate (name->string, "anonymousFunction", JSTAG_FUNCTION); is_anonymous = true; } else if (!isType (name, TOKEN_IDENTIFIER)) @@ -1466,51 +1782,59 @@ static void parseFunction (tokenInfo *const token) else readToken (token); - while (isType (token, TOKEN_PERIOD)) - { - readToken (token); - if (! isType(token, TOKEN_KEYWORD)) - { - addContext (name, token); - readToken (token); - } - } - if ( isType (token, TOKEN_OPEN_PAREN) ) skipArgumentList(token, false, signature); if ( isType (token, TOKEN_OPEN_CURLY) ) { - is_class = parseBlock (token, name->string); - if ( is_class ) - makeClassTagCommon (name, signature, NULL, is_anonymous); - else - makeFunctionTagCommon (name, signature, is_generator, is_anonymous); + if ( lhs_name != NULL && is_inside_class ) + { + index_for_name = makeJsTag (lhs_name, is_generator ? JSTAG_GENERATOR : JSTAG_METHOD, signature, NULL); + } + else if ( lhs_name != NULL ) + { + index_for_name = isClassName (lhs_name) ? + makeClassTag (lhs_name, signature, NULL): + makeFunctionTag (lhs_name, signature, is_generator); + } + + int f = index_for_name, + p = CORK_NIL; + if ( f == CORK_NIL || !is_anonymous ) + p = isClassName (name) ? + makeClassTagCommon (name, signature, NULL, is_anonymous) : + makeFunctionTagCommon (name, signature, is_generator, is_anonymous); + + if (f == CORK_NIL) + f = p; + + parseBlock (token, f); } - findCmdTerm (token, false, false); + if ( lhs_name == NULL ) + findCmdTerm (token, false, false); cleanUp: vStringDelete (signature); deleteToken (name); TRACE_LEAVE(); + return index_for_name; } /* Parses a block surrounded by curly braces. - * @p parentScope is the scope name for this block, or NULL for unnamed scopes */ -static bool parseBlock (tokenInfo *const token, const vString *const parentScope) + * @p parent_scope is the scope name for this block, or NULL for unnamed scopes */ +static bool parseBlock (tokenInfo *const token, int parent_scope) { TRACE_ENTER(); bool is_class = false; bool read_next_token = true; - vString * saveScope = vStringNew (); + int save_scope = token->scope; - vStringCopy(saveScope, token->scope); - if (parentScope) + if (parent_scope != CORK_NIL) { - addToScope (token, parentScope); + token->scope = parent_scope; token->nestLevel++; } @@ -1557,7 +1881,7 @@ static bool parseBlock (tokenInfo *const token, const vString *const parentScope else if (isType (token, TOKEN_OPEN_CURLY)) { /* Handle nested blocks */ - parseBlock (token, NULL); + parseBlock (token, CORK_NIL); } else { @@ -1585,55 +1909,71 @@ static bool parseBlock (tokenInfo *const token, const vString *const parentScope ! isType (token, TOKEN_CLOSE_CURLY) && read_next_token); } - vStringCopy(token->scope, saveScope); - vStringDelete(saveScope); - if (parentScope) + token->scope = save_scope; + if (parent_scope != CORK_NIL) token->nestLevel--; TRACE_LEAVE(); - return is_class; } -static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, - const bool is_es6_class) +static bool parseMethods (tokenInfo *const token, int class_index, + const bool is_es6_class) { - TRACE_ENTER_TEXT("token is '%s' of type %s in classToken '%s' of type %s (es6: %s)", + TRACE_ENTER_TEXT("token is '%s' of type %s in parentToken '%s' of kind %s (es6: %s)", vStringValue(token->string), tokenTypeName (token->type), - vStringValue(class->string), tokenTypeName (class->type), + class_index == CORK_NIL ? "none" : getNameStringForCorkIndex (class_index), + class_index == CORK_NIL ? "none" : getKindStringForCorkIndex (class_index), is_es6_class? "yes": "no"); + /* + * When making a tag for `name', its core index is stored to + * `indexForName'. The value stored to `indexForName' is valid + * till the value for `name' is updated. If the value for `name' + * is changed, `indexForName' is reset to CORK_NIL. + */ tokenInfo *const name = newToken (); + int index_for_name = CORK_NIL; bool has_methods = false; - vString *saveScope = vStringNew (); + int save_scope = token->scope; - vStringCopy (saveScope, token->scope); - addToScope (token, class->string); + if (class_index != CORK_NIL) + token->scope = class_index; /* * This deals with these formats - * validProperty : 2, - * validMethod : function(a,b) {} - * 'validMethod2' : function(a,b) {} - * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false} + * validProperty : 2, + * validMethod : function(a,b) {} + * 'validMethod2' : function(a,b) {} + * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false} * get prop() {} * set prop(val) {} - * - * ES6 methods: - * property(...) {} - * *generator() {} - * - * ES6 computed name: - * [property]() {} - * get [property]() {} - * set [property]() {} - * *[generator]() {} + * get(...) {} + * set(...) {} + * + * ES6 methods: + * property(...) {} + * *generator() {} + * + * ES6 computed name: + * [property]() {} + * get [property]() {} + * set [property]() {} + * *[generator]() {} * * tc39/proposal-class-fields * field0 = function(a,b) {} * field1 = 1 * The parser extracts field0 as a method because the left value * is a function (kind propagation), and field1 as a field. + * + * static methods and static initialization blocks + * - ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks + * + * static func() {} + * static {} + * static prop; + * static prop = val; */ bool dont_read = false; @@ -1641,6 +1981,7 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, { bool is_setter = false; bool is_getter = false; + bool is_static = false; /* For recognizing static {...} block. */ if (!dont_read) readToken (token); @@ -1653,19 +1994,40 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, if (isKeyword (token, KEYWORD_async)) readToken (token); - else if (isType(token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_get)) + else if (isKeyword (token, KEYWORD_static)) + is_static = true; + else if (isType (token, TOKEN_KEYWORD) && + (isKeyword (token, KEYWORD_get) || isKeyword (token, KEYWORD_set))) { - is_getter = true; + tokenInfo *saved_token = newToken (); + copyToken (saved_token, token, true); readToken (token); + if (isType(token, TOKEN_OPEN_PAREN) || isType(token, TOKEN_COLON)) + { + Assert (NextToken == NULL); + NextToken = newToken (); + copyToken (NextToken, token, false); /* save token for next read */ + copyToken (token, saved_token, true); /* restore token to process */ + token->type = TOKEN_IDENTIFIER; /* process as identifier */ + token->keyword = KEYWORD_NONE; + } + else if (isKeyword (saved_token, KEYWORD_get)) + is_getter = true; + else + is_setter = true; + + deleteToken (saved_token); } - else if (isType(token, TOKEN_KEYWORD) && isKeyword (token, KEYWORD_set)) + else if (isType (token, TOKEN_DOTS)) { - is_setter = true; - readToken (token); + /* maybe spread operator. Just skip the next expression. */ + findCmdTerm(token, true, true); + continue; } - if (! isType (token, TOKEN_KEYWORD) && - ! isType (token, TOKEN_SEMICOLON)) + if ((! isType (token, TOKEN_KEYWORD) && + ! isType (token, TOKEN_SEMICOLON)) + || is_static) { bool is_generator = false; bool is_shorthand = false; /* ES6 shorthand syntax */ @@ -1690,6 +2052,7 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, } copyToken(name, token, true); + index_for_name = CORK_NIL; if (is_computed_name && ! isType (token, TOKEN_STRING)) is_dynamic_prop = true; @@ -1715,6 +2078,7 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, if (is_dynamic_prop) { injectDynamicName (name, dprop); + index_for_name = CORK_NIL; dprop = NULL; } else @@ -1730,10 +2094,11 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, if (isKeyword (token, KEYWORD_async)) readToken (token); } + + vString * signature = vStringNew (); if ( is_shorthand || isKeyword (token, KEYWORD_function) ) { TRACE_PRINT("Seems to be a function or shorthand"); - vString *const signature = vStringNew (); if (! is_shorthand) { @@ -1750,6 +2115,7 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, skipArgumentList(token, false, signature); } +function: if (isType (token, TOKEN_OPEN_CURLY)) { has_methods = true; @@ -1762,8 +2128,8 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, else if (is_setter) kind = JSTAG_SETTER; - makeJsTag (name, kind, signature, NULL); - parseBlock (token, name->string); + index_for_name = makeJsTag (name, kind, signature, NULL); + parseBlock (token, index_for_name); /* * If we aren't parsing an ES6 class (for which there @@ -1774,730 +2140,876 @@ static bool parseMethods (tokenInfo *const token, const tokenInfo *const class, if (! is_es6_class) readToken (token); } - - vStringDelete (signature); } else if (! is_es6_class) { - bool has_child_methods = false; + int p = CORK_NIL; + tokenInfo *saved_token = newToken (); - /* skip whatever is the value */ - while (! isType (token, TOKEN_COMMA) && - ! isType (token, TOKEN_CLOSE_CURLY) && - ! isType (token, TOKEN_EOF)) + /* skip whatever is the value */ + while (! isType (token, TOKEN_COMMA) && + ! isType (token, TOKEN_CLOSE_CURLY) && + ! isType (token, TOKEN_EOF)) + { + if (isType (token, TOKEN_OPEN_CURLY)) { - if (isType (token, TOKEN_OPEN_CURLY)) - { - /* Recurse to find child properties/methods */ - has_child_methods = parseMethods (token, name, false); - readToken (token); - } - else if (isType (token, TOKEN_OPEN_PAREN)) - { - skipArgumentList (token, false, NULL); - } - else if (isType (token, TOKEN_OPEN_SQUARE)) - { - skipArrayList (token, false); - } - else + /* Recurse to find child properties/methods */ + p = makeSimplePlaceholder (name->string); + parseMethods (token, p, false); + readToken (token); + } + else if (isType (token, TOKEN_OPEN_PAREN)) + { + vStringClear (signature); + skipArgumentList (token, false, signature); + } + else if (isType (token, TOKEN_OPEN_SQUARE)) + { + skipArrayList (token, false); + } + else if (isType (token, TOKEN_ARROW)) + { + TRACE_PRINT("Seems to be an anonymous function"); + if (vStringIsEmpty (signature) && + isType (saved_token, TOKEN_IDENTIFIER)) { - readToken (token); + vStringPut (signature, '('); + vStringCat (signature, saved_token->string); + vStringPut (signature, ')'); } + readToken (token); + deleteToken (saved_token); + goto function; } - - has_methods = true; - if (has_child_methods) - makeJsTag (name, JSTAG_CLASS, NULL, NULL); else - makeJsTag (name, JSTAG_PROPERTY, NULL, NULL); + { + copyToken (saved_token, token, true); + readToken (token); + } + } + deleteToken (saved_token); + + has_methods = true; + index_for_name = makeJsTag (name, JSTAG_PROPERTY, NULL, NULL); + if (p != CORK_NIL) + moveChildren (p, index_for_name); } else if (can_be_field) { makeJsTag (name, JSTAG_FIELD, NULL, NULL); parseLine (token, true); } + + vStringDelete (signature); + } + else if (is_static) + { + if (isType (token, TOKEN_OPEN_CURLY)) + /* static initialization block */ + parseBlock (token, class_index); + else + dont_read = true; + continue; } else { - makeJsTag (name, JSTAG_FIELD, NULL, NULL); - if (!isType (token, TOKEN_SEMICOLON)) + bool is_property = isType (token, TOKEN_COMMA); + makeJsTag (name, is_property ? JSTAG_PROPERTY : JSTAG_FIELD, NULL, NULL); + if (!isType (token, TOKEN_SEMICOLON) && !is_property) dont_read = true; } } } while ( isType(token, TOKEN_COMMA) || - ( is_es6_class && ! isType(token, TOKEN_EOF) ) ); + ( is_es6_class && ! isType(token, TOKEN_EOF) ) ); TRACE_PRINT("Finished parsing methods"); findCmdTerm (token, false, false); cleanUp: - vStringCopy (token->scope, saveScope); - vStringDelete (saveScope); + token->scope = save_scope; deleteToken (name); TRACE_LEAVE_TEXT("found method(s): %s", has_methods? "yes": "no"); - return has_methods; } -static bool parseES6Class (tokenInfo *const token, const tokenInfo *targetName) +static bool parseES6Class (tokenInfo *const token, const tokenInfo *target_name) { TRACE_ENTER(); - tokenInfo * className = newToken (); + tokenInfo * class_name = newToken (); vString *inheritance = NULL; bool is_anonymous = true; - copyToken (className, token, true); - readToken (className); + copyToken (class_name, token, true); + readToken (class_name); /* optional name */ - if (isType (className, TOKEN_IDENTIFIER)) + if (isType (class_name, TOKEN_IDENTIFIER)) { readToken (token); is_anonymous = false; } else { - copyToken (token, className, true); + copyToken (token, class_name, true); /* We create a fake name so we have a scope for the members */ - if (! targetName) - anonGenerate (className->string, "AnonymousClass", JSTAG_CLASS); + if (! target_name) + anonGenerate (class_name->string, "AnonymousClass", JSTAG_CLASS); } - if (! targetName) - targetName = className; + if (! target_name) + target_name = class_name; if (isKeyword (token, KEYWORD_extends)) inheritance = vStringNew (); /* skip inheritance info */ while (! isType (token, TOKEN_OPEN_CURLY) && - ! isType (token, TOKEN_EOF) && - ! isType (token, TOKEN_SEMICOLON)) + ! isType (token, TOKEN_EOF) && + ! isType (token, TOKEN_SEMICOLON)) readTokenFull (token, false, inheritance); /* remove the last added token (here we assume it's one char, "{" or ";" */ if (inheritance && vStringLength (inheritance) > 0 && - ! isType (token, TOKEN_EOF)) + ! isType (token, TOKEN_EOF)) { vStringChop (inheritance); vStringStripTrailing (inheritance); vStringStripLeading (inheritance); } - TRACE_PRINT("Emitting tag for class '%s'", vStringValue(targetName->string)); + TRACE_PRINT("Emitting tag for class '%s'", vStringValue(target_name->string)); - makeJsTagCommon (targetName, JSTAG_CLASS, NULL, inheritance, - (is_anonymous && (targetName == className))); + int r = makeJsTagCommon (target_name, JSTAG_CLASS, NULL, inheritance, + (is_anonymous && (target_name == class_name))); - if (! is_anonymous && targetName != className) + if (! is_anonymous && target_name != class_name) { /* FIXME: what to do with the secondary name? It's local to the * class itself, so not very useful... let's hope people * don't give it another name than the target in case of - * var MyClass = class MyClassSecondaryName { ... } + * var MyClass = class MyClassSecondaryName { ... } * I guess it could be an alias to MyClass, or duplicate it * altogether, not sure. */ - makeJsTag (className, JSTAG_CLASS, NULL, inheritance); + makeJsTag (class_name, JSTAG_CLASS, NULL, inheritance); } if (inheritance) vStringDelete (inheritance); if (isType (token, TOKEN_OPEN_CURLY)) - parseMethods (token, targetName, true); + parseMethods (token, r, true); - deleteToken (className); + deleteToken (class_name); TRACE_LEAVE(); return true; } -static bool parseStatement (tokenInfo *const token, bool is_inside_class) +static void convertToFunction (int index, const char *signature) { - TRACE_ENTER_TEXT("is_inside_class: %s", is_inside_class? "yes": "no"); + tagEntryInfo *e = getEntryInCorkQueue(index); + if (e && e->kindIndex != JSTAG_FUNCTION + && ( signature == NULL || e->extensionFields.signature == NULL)) + { + e->kindIndex = JSTAG_FUNCTION; + if (signature) + e->extensionFields.signature = eStrdup (signature); + } +} - tokenInfo *const name = newToken (); - tokenInfo *const secondary_name = newToken (); - tokenInfo *const method_body_token = newToken (); - vString * saveScope = vStringNew (); - bool is_class = false; - bool is_var = false; - bool is_const = false; - bool is_terminated = true; - bool is_global = false; - bool has_methods = false; - vString * fulltag; +static vString *trimGarbageInSignature (vString *sig) +{ + /* Drop "=>" at the end. */ + const char *sigstr = vStringValue (sig); + char *last = strrchr (sigstr, ')'); + Assert (last); + vStringTruncate (sig, last - sigstr + 1); + return sig; +} + +static vString *makeVStringForSignature (tokenInfo *const token) +{ + vString * sig = vStringNewInit ("("); + + if (isType (token, TOKEN_IDENTIFIER)) + vStringCat (sig, token->string); + else if (isType (token, TOKEN_CLOSE_PAREN)) + vStringPut (sig, ')'); + else if (isType (token, TOKEN_DOTS)) + vStringCatS (sig, "..."); + + return sig; +} + +typedef struct sStatementState { + int indexForName; + bool isClass; + bool isConst; + bool isTerminated; + bool isGlobal; + bool foundThis; +} statementState; + +static void deleteTokenFn(void *token) { deleteToken(token); } + +static bool parsePrototype (tokenInfo *const name, tokenInfo *const token, statementState *const state) +{ + TRACE_ENTER(); - vStringCopy (saveScope, token->scope); /* - * Functions can be named or unnamed. - * This deals with these formats: - * Function - * validFunctionOne = function(a,b) {} - * testlib.validFunctionFive = function(a,b) {} - * var innerThree = function(a,b) {} - * var innerFour = (a,b) {} - * var D2 = secondary_fcn_name(a,b) {} - * var D3 = new Function("a", "b", "return a+b;"); - * Class - * testlib.extras.ValidClassOne = function(a,b) { - * this.a = a; - * } - * Class Methods - * testlib.extras.ValidClassOne.prototype = { - * 'validMethodOne' : function(a,b) {}, - * 'validMethodTwo' : function(a,b) {} - * } - * ValidClassTwo = function () - * { - * this.validMethodThree = function() {} - * // unnamed method - * this.validMethodFour = () {} - * } - * Database.prototype.validMethodThree = Database_getTodaysDate; + * When we reach the "prototype" tag, we infer: + * "BindAgent" is a class + * "build" is a method + * + * function BindAgent( repeatableIdName, newParentIdName ) { + * } + * + * CASE 1 + * Specified function name: "build" + * BindAgent.prototype.build = + * BondAgent.prototype.crush = function( mode ) { + * maybe parse nested functions + * } + * + * CASE 2 + * Prototype listing + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * */ + if (! ( isType (name, TOKEN_IDENTIFIER) + || isType (name, TOKEN_STRING) ) ) + /* + * Unexpected input. Try to reset the parsing. + * + * TOKEN_STRING is acceptable. e.g.: + * ----------------------------------- + * "a".prototype = function( mode ) {} + */ + { + TRACE_LEAVE_TEXT("bad input"); + return false; + } + + state->indexForName = makeClassTag (name, NULL, NULL); + state->isClass = true; - if ( is_inside_class ) - is_class = true; /* - * var can precede an inner function + * There should a ".function_name" next. */ - if ( isKeyword(token, KEYWORD_var) || - isKeyword(token, KEYWORD_let) || - isKeyword(token, KEYWORD_const) ) + readToken (token); + if (isType (token, TOKEN_PERIOD)) { - TRACE_PRINT("var/let/const case"); - is_const = isKeyword(token, KEYWORD_const); /* - * Only create variables for global scope + * Handle CASE 1 */ - if ( token->nestLevel == 0 ) + readToken (token); + if (isType (token, TOKEN_KEYWORD) && + (isKeyword (token, KEYWORD_get) || isKeyword (token, KEYWORD_set))) { - is_global = true; + token->type = TOKEN_IDENTIFIER; // treat as function name } - readToken(token); - } - -nextVar: - if ( isKeyword(token, KEYWORD_this) ) - { - TRACE_PRINT("found 'this' keyword"); - readToken(token); - if (isType (token, TOKEN_PERIOD)) + if (! isType(token, TOKEN_KEYWORD)) { - readToken(token); - } - } + vString *const signature = vStringNew (); - copyToken(name, token, true); - TRACE_PRINT("name becomes '%s' of type %s", - vStringValue(token->string), tokenTypeName (token->type)); + token->scope = state->indexForName; - while (! isType (token, TOKEN_CLOSE_CURLY) && - ! isType (token, TOKEN_SEMICOLON) && - ! isType (token, TOKEN_EQUAL_SIGN) && - ! isType (token, TOKEN_COMMA) && - ! isType (token, TOKEN_EOF)) - { - if (isType (token, TOKEN_OPEN_CURLY)) - parseBlock (token, NULL); + tokenInfo *identifier_token = newToken (); + ptrArray *prototype_tokens = NULL; + accept_period_in_identifier(true); - /* Potentially the name of the function */ - if (isType (token, TOKEN_PERIOD)) - { - /* - * Cannot be a global variable is it has dot references in the name - */ - is_global = false; - /* Assume it's an assignment to a global name (e.g. a class) using - * its fully qualified name, so strip the scope. - * FIXME: resolve the scope so we can make more than an assumption. */ - vStringClear (token->scope); - vStringClear (name->scope); - do - { - readToken (token); - if (! isType(token, TOKEN_KEYWORD)) - { - if ( is_class ) - { - addToScope(token, name->string); - } - else - addContext (name, token); + tokenInfo *const method_body_token = newToken (); + copyToken (method_body_token, token, true); + readToken (method_body_token); - readToken (token); - } - else if ( isKeyword(token, KEYWORD_prototype) ) + while (! isType (method_body_token, TOKEN_SEMICOLON) && + ! isType (method_body_token, TOKEN_CLOSE_CURLY) && + ! isType (method_body_token, TOKEN_OPEN_CURLY) && + ! isType (method_body_token, TOKEN_EOF)) + { + if ( isType (method_body_token, TOKEN_OPEN_PAREN) ) + skipArgumentList(method_body_token, false, + vStringLength (signature) == 0 ? signature : NULL); + else { - /* - * When we reach the "prototype" tag, we infer: - * "BindAgent" is a class - * "build" is a method - * - * function BindAgent( repeatableIdName, newParentIdName ) { - * } - * - * CASE 1 - * Specified function name: "build" - * BindAgent.prototype.build = function( mode ) { - * maybe parse nested functions - * } - * - * CASE 2 - * Prototype listing - * ValidClassOne.prototype = { - * 'validMethodOne' : function(a,b) {}, - * 'validMethodTwo' : function(a,b) {} - * } - * - */ - if (! ( isType (name, TOKEN_IDENTIFIER) - || isType (name, TOKEN_STRING) ) ) - /* - * Unexpected input. Try to reset the parsing. - * - * TOKEN_STRING is acceptable. e.g.: - * ----------------------------------- - * "a".prototype = function( mode ) {} - */ - goto cleanUp; - - makeClassTag (name, NULL, NULL); - is_class = true; - - /* - * There should a ".function_name" next. - */ - readToken (token); - if (isType (token, TOKEN_PERIOD)) + char* s1 = vStringValue (identifier_token->string); + char* s2 = NULL; + if ( isType (method_body_token, TOKEN_EQUAL_SIGN) && + ! isType (identifier_token, TOKEN_UNDEFINED) && + (s2 = strstr (s1, ".prototype."))) { - /* - * Handle CASE 1 - */ - readToken (token); - if (! isType(token, TOKEN_KEYWORD)) - { - vString *const signature = vStringNew (); + if (prototype_tokens == NULL) + prototype_tokens = ptrArrayNew (deleteTokenFn); - addToScope(token, name->string); + memmove (s2, s2+10, strlen (s2+10) + 1); + vStringSetLength (identifier_token->string); - copyToken (method_body_token, token, true); - readToken (method_body_token); + tokenInfo *const save_token = newToken (); + copyToken (save_token, identifier_token, true); + ptrArrayAdd (prototype_tokens, save_token); + identifier_token->type = TOKEN_UNDEFINED; + } + else if ( isType(method_body_token, TOKEN_IDENTIFIER)) + copyToken (identifier_token, method_body_token, false); - while (! isType (method_body_token, TOKEN_SEMICOLON) && - ! isType (method_body_token, TOKEN_CLOSE_CURLY) && - ! isType (method_body_token, TOKEN_OPEN_CURLY) && - ! isType (method_body_token, TOKEN_EOF)) - { - if ( isType (method_body_token, TOKEN_OPEN_PAREN) ) - skipArgumentList(method_body_token, false, - vStringLength (signature) == 0 ? signature : NULL); - else - readToken (method_body_token); - } + readToken (method_body_token); + } + } + deleteToken (identifier_token); + accept_period_in_identifier(false); - makeJsTag (token, JSTAG_METHOD, signature, NULL); - vStringDelete (signature); + int index = makeJsTag (token, JSTAG_METHOD, signature, NULL); - if ( isType (method_body_token, TOKEN_OPEN_CURLY)) - { - parseBlock (method_body_token, token->string); - is_terminated = true; - } - else - is_terminated = isType (method_body_token, TOKEN_SEMICOLON); - goto cleanUp; - } - } - else if (isType (token, TOKEN_EQUAL_SIGN)) - { - readToken (token); - if (isType (token, TOKEN_OPEN_CURLY)) - { - /* - * Handle CASE 2 - * - * Creates tags for each of these class methods - * ValidClassOne.prototype = { - * 'validMethodOne' : function(a,b) {}, - * 'validMethodTwo' : function(a,b) {} - * } - */ - parseMethods(token, name, false); - /* - * Find to the end of the statement - */ - findCmdTerm (token, false, false); - is_terminated = true; - goto cleanUp; - } - } + if (prototype_tokens != NULL) + { + for (int i=0; iisTerminated = true; + } + else + state->isTerminated = isType (method_body_token, TOKEN_SEMICOLON); - /* - if ( isType (token, TOKEN_OPEN_CURLY) ) - { - is_class = parseBlock (token, name->string); + deleteToken (method_body_token); + TRACE_LEAVE_TEXT("done: single"); + return false; } - */ - } - - if ( isType (token, TOKEN_CLOSE_CURLY) ) - { - /* - * Reaching this section without having - * processed an open curly brace indicates - * the statement is most likely not terminated. - */ - is_terminated = false; - goto cleanUp; } - - if ( isType (token, TOKEN_SEMICOLON) || - isType (token, TOKEN_EOF) || - isType (token, TOKEN_COMMA) ) + else if (isType (token, TOKEN_EQUAL_SIGN)) { - /* - * Only create variables for global scope - */ - if ( token->nestLevel == 0 && is_global ) + readToken (token); + if (isType (token, TOKEN_OPEN_CURLY)) { /* - * Handles this syntax: - * var g_var2; + * Handle CASE 2 + * + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, state->indexForName, false); + /* + * Find to the end of the statement */ - makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + findCmdTerm (token, false, false); + state->isTerminated = true; + TRACE_LEAVE_TEXT("done: multiple"); + return false; } - /* - * Statement has ended. - * This deals with calls to functions, like: - * alert(..); - */ - if (isType (token, TOKEN_COMMA)) + } + + TRACE_LEAVE_TEXT("done: not found"); + return true; +} + +static bool parseStatementLHS (tokenInfo *const name, tokenInfo *const token, statementState *const state) +{ + TRACE_ENTER(); + + do + { + readToken (token); + if (! isType(token, TOKEN_KEYWORD)) { + if ( state->isClass ) + token->scope = state->indexForName; + else + { + addContext (name, token); + state->indexForName = CORK_NIL; + } + readToken (token); - goto nextVar; } - goto cleanUp; - } + else if ( isKeyword(token, KEYWORD_prototype) ) + { + if (! parsePrototype (name, token, state) ) + { + TRACE_LEAVE_TEXT("done: prototype"); + return false; + } + } + else + readToken (token); + } while (isType (token, TOKEN_PERIOD)); + + TRACE_LEAVE(); + return true; +} - if ( isType (token, TOKEN_EQUAL_SIGN) ) +static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, statementState *const state, bool is_inside_class) +{ + TRACE_ENTER(); + + int paren_depth = 0; + int arrowfun_paren_depth = 0; + bool canbe_arrowfun = false; + + readToken (token); + + /* rvalue might be surrounded with parentheses */ + while (isType (token, TOKEN_OPEN_PAREN)) { - int parenDepth = 0; + paren_depth++; + arrowfun_paren_depth++; + readToken (token); + } + if (isKeyword (token, KEYWORD_async)) + { + arrowfun_paren_depth = 0; readToken (token); - /* rvalue might be surrounded with parentheses */ + /* check for function signature */ while (isType (token, TOKEN_OPEN_PAREN)) { - parenDepth++; + paren_depth++; + arrowfun_paren_depth++; readToken (token); } + } - if (isKeyword (token, KEYWORD_async)) - readToken (token); - - if ( isKeyword (token, KEYWORD_function) ) + if ( isKeyword (token, KEYWORD_function) ) + { + state->indexForName = parseFunction (token, name, is_inside_class); + } + else if (isKeyword (token, KEYWORD_class)) + { + state->isTerminated = parseES6Class (token, name); + } + else if (isType (token, TOKEN_OPEN_CURLY)) + { + /* + * Creates tags for each of these class methods + * objectOne = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * Or checks if this is a hash variable. + * var z = {}; + */ + bool anon_object = vStringIsEmpty (name->string); + if (anon_object) { - vString *const signature = vStringNew (); - bool is_generator = false; + anonGenerate (name->string, "anonymousObject", JSTAG_VARIABLE); + state->indexForName = CORK_NIL; + } + int p = makeSimplePlaceholder (name->string); + if ( parseMethods(token, p, false) ) + { + jsKind kind = state->foundThis || strchr (vStringValue(name->string), '.') != NULL ? JSTAG_PROPERTY : JSTAG_VARIABLE; + state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, anon_object); + moveChildren (p, state->indexForName); + } + else if ( token->nestLevel == 0 && state->isGlobal ) + { + /* + * Only create variables for global scope + * + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + state->indexForName = anyKindsEntryInScope (name->scope, vStringValue (name->string), + (int[]){JSTAG_VARIABLE, JSTAG_FUNCTION, JSTAG_CLASS}, 3, true); - readToken (token); - if (isType (token, TOKEN_STAR)) - { - is_generator = true; - readToken (token); - } + if (state->indexForName == CORK_NIL) + state->indexForName = makeJsTag (name, state->isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + } + /* Here we should be at the end of the block, on the close curly. + * If so, read the next token not to confuse that close curly with + * the end of the current statement. */ + if (isType (token, TOKEN_CLOSE_CURLY)) + { + readTokenFull(token, true, NULL); + state->isTerminated = isType (token, TOKEN_SEMICOLON); + } + } + else if (isType (token, TOKEN_OPEN_SQUARE) && !vStringIsEmpty (name->string)) + { + /* + * Creates tag for an array + */ + skipArrayList(token, true); + jsKind kind = state->foundThis || strchr (vStringValue(name->string), '.') != NULL ? JSTAG_PROPERTY : JSTAG_VARIABLE; + /* + * Only create variables for global scope or class/object properties + */ + if ( ( token->nestLevel == 0 && state->isGlobal ) || kind == JSTAG_PROPERTY ) + { + state->indexForName = makeJsTagCommon (name, kind, NULL, NULL, false); + } + } + else if (isKeyword (token, KEYWORD_new)) + { + readToken (token); + bool is_var = isType (token, TOKEN_IDENTIFIER) || isKeyword (token, KEYWORD_capital_object); + if ( isKeyword (token, KEYWORD_function) || + isKeyword (token, KEYWORD_capital_function) || + is_var ) + { + if ( isKeyword (token, KEYWORD_capital_function) && isClassName (name) ) + state->isClass = true; - if (! isType (token, TOKEN_KEYWORD) && - ! isType (token, TOKEN_OPEN_PAREN)) - { - /* - * Functions of this format: - * var D2A = function theAdd(a, b) - * { - * return a+b; - * } - * Are really two separate defined functions and - * can be referenced in two ways: - * alert( D2A(1,2) ); // produces 3 - * alert( theAdd(1,2) ); // also produces 3 - * So it must have two tags: - * D2A - * theAdd - * Save the reference to the name for later use, once - * we have established this is a valid function we will - * create the secondary reference to it. - */ - copyToken(secondary_name, token, true); + if ( isType (token, TOKEN_IDENTIFIER) ) + skipQualifiedIdentifier (token); + else readToken (token); - } if ( isType (token, TOKEN_OPEN_PAREN) ) - skipArgumentList(token, false, signature); + skipArgumentList(token, true, NULL); - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_SEMICOLON) && token->nestLevel == 0) { - /* - * This will be either a function or a class. - * We can only determine this by checking the body - * of the function. If we find a "this." we know - * it is a class, otherwise it is a function. - */ - if ( is_inside_class ) - { - makeJsTag (name, is_generator ? JSTAG_GENERATOR : JSTAG_METHOD, signature, NULL); - if ( vStringLength(secondary_name->string) > 0 ) - makeFunctionTag (secondary_name, signature, is_generator); - parseBlock (token, name->string); - } + if ( is_var ) + state->indexForName = makeJsTag (name, state->isConst ? JSTAG_CONSTANT : state->foundThis ? JSTAG_PROPERTY : JSTAG_VARIABLE, NULL, NULL); + else if ( state->isClass ) + state->indexForName = makeClassTag (name, NULL, NULL); else { - if (! ( isType (name, TOKEN_IDENTIFIER) - || isType (name, TOKEN_STRING) - || isType (name, TOKEN_KEYWORD) ) ) - { - /* Unexpected input. Try to reset the parsing. */ - TRACE_PRINT("Unexpected input, trying to reset"); - vStringDelete (signature); - goto cleanUp; - } - - is_class = parseBlock (token, name->string); - if ( is_class ) - makeClassTag (name, signature, NULL); - else - makeFunctionTag (name, signature, is_generator); - - if ( vStringLength(secondary_name->string) > 0 ) - makeFunctionTag (secondary_name, signature, is_generator); + /* FIXME: we cannot really get a meaningful + * signature from a `new Function()` call, + * so for now just don't set any */ + state->indexForName = makeFunctionTag (name, NULL, false); } } - - vStringDelete (signature); + else if (isType (token, TOKEN_CLOSE_CURLY)) + state->isTerminated = false; } - else if (isKeyword (token, KEYWORD_class)) + } + else if (! isType (token, TOKEN_KEYWORD) && + token->nestLevel == 0 && state->isGlobal ) + { + /* + * Only create variables for global scope + * + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + state->indexForName = anyKindsEntryInScope (name->scope, vStringValue (name->string), + (int[]){JSTAG_VARIABLE, JSTAG_FUNCTION, JSTAG_CLASS}, 3, true); + + if (state->indexForName == CORK_NIL) { - is_terminated = parseES6Class (token, name); + state->indexForName = makeJsTag (name, state->isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + if (isType (token, TOKEN_IDENTIFIER)) + canbe_arrowfun = true; } - else if (isType (token, TOKEN_OPEN_CURLY)) + } + else if ( isType (token, TOKEN_IDENTIFIER) ) + { + canbe_arrowfun = true; + } + + if (arrowfun_paren_depth == 0 && canbe_arrowfun) + { + /* var v = a => { ... } */ + vString *sig = vStringNewInit ("("); + vStringCat (sig, token->string); + vStringPut (sig, ')'); + readTokenFull (token, true, NULL); + if (isType (token, TOKEN_ARROW)) { - /* - * Creates tags for each of these class methods - * ValidClassOne.prototype = { - * 'validMethodOne' : function(a,b) {}, - * 'validMethodTwo' : function(a,b) {} - * } - * Or checks if this is a hash variable. - * var z = {}; - */ - bool anonClass = vStringIsEmpty (name->string); - if (anonClass) - anonGenerate (name->string, "AnonymousClass", JSTAG_CLASS); - has_methods = parseMethods(token, name, false); - if (has_methods) - makeJsTagCommon (name, JSTAG_CLASS, NULL, NULL, anonClass); + if (state->indexForName == CORK_NIL) // was not a global variable + state->indexForName = makeFunctionTag (name, sig, false); else + convertToFunction (state->indexForName, vStringValue (sig)); + } + vStringDelete (sig); + } + + if (paren_depth > 0) + { + /* Collect parameters for arrow function. */ + vString *sig = (arrowfun_paren_depth == 1)? makeVStringForSignature (token): NULL; + + while (paren_depth > 0 && ! isType (token, TOKEN_EOF)) + { + if (isType (token, TOKEN_OPEN_PAREN)) { - /* - * Only create variables for global scope - */ - if ( token->nestLevel == 0 && is_global ) - { - /* - * A pointer can be created to the function. - * If we recognize the function/class name ignore the variable. - * This format looks identical to a variable definition. - * A variable defined outside of a block is considered - * a global variable: - * var g_var1 = 1; - * var g_var2; - * This is not a global variable: - * var g_var = function; - * This is a global variable: - * var g_var = different_var_name; - */ - fulltag = vStringNew (); - if (vStringLength (token->scope) > 0) - { - vStringCopy(fulltag, token->scope); - vStringPut (fulltag, '.'); - vStringCat (fulltag, token->string); - } - else - { - vStringCopy(fulltag, token->string); - } - if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) && - ! stringListHas(ClassNames, vStringValue (fulltag)) ) - { - makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); - } - vStringDelete (fulltag); - } + paren_depth++; + arrowfun_paren_depth++; + } + else if (isType (token, TOKEN_CLOSE_PAREN)) + { + paren_depth--; + arrowfun_paren_depth--; } - /* Here we should be at the end of the block, on the close curly. - * If so, read the next token not to confuse that close curly with - * the end of the current statement. */ - if (isType (token, TOKEN_CLOSE_CURLY)) + readTokenFull (token, true, sig); + + /* var f = (a, b) => { ... } */ + if (arrowfun_paren_depth == 0 && isType (token, TOKEN_ARROW) && sig) { - readTokenFull(token, true, NULL); - is_terminated = isType (token, TOKEN_SEMICOLON); + if (state->indexForName == CORK_NIL) // was not a global variable + state->indexForName = makeFunctionTag (name, trimGarbageInSignature (sig), false); + else + convertToFunction (state->indexForName, + vStringValue (trimGarbageInSignature (sig))); + + vStringDelete (sig); + sig = NULL; } } - else if (isKeyword (token, KEYWORD_new)) + if (isType (token, TOKEN_CLOSE_CURLY)) + state->isTerminated = false; + + vStringDelete (sig); /* NULL is acceptable. */ + } + + TRACE_LEAVE(); + return true; +} + +static bool parseStatement (tokenInfo *const token, bool is_inside_class) +{ + TRACE_ENTER_TEXT("is_inside_class: %s", is_inside_class? "yes": "no"); + + /* + * When making a tag for `name', its core index is stored to + * `indexForName'. The value stored to `indexForName' is valid + * till the value for `name' is updated. If the value for `name' + * is changed, `indexForName' is reset to CORK_NIL. + */ + tokenInfo *const name = newToken (); + int save_scope = token->scope; + bool found_lhs = false; + statementState state = { + .indexForName = CORK_NIL, + .isClass = is_inside_class, + .isConst = false, + .isTerminated = true, + .isGlobal = false, + .foundThis = false + }; + + /* + * Functions can be named or unnamed. + * This deals with these formats: + * Function + * validFunctionOne = function(a,b) {} + * testlib.validFunctionFive = function(a,b) {} + * var innerThree = function(a,b) {} + * var innerFour = (a,b) {} + * var D2 = secondary_fcn_name(a,b) {} + * var D3 = new Function("a", "b", "return a+b;"); + * Class + * testlib.extras.ValidClassOne = function(a,b) { + * this.a = a; + * } + * Class Methods + * testlib.extras.ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * ValidClassTwo = function () + * { + * this.validMethodThree = function() {} + * // unnamed method + * this.validMethodFour = () {} + * } + * Database.prototype.validMethodThree = Database_getTodaysDate; + */ + + /* + * var can precede an inner function + */ + if ( isKeyword(token, KEYWORD_var) || + isKeyword(token, KEYWORD_let) || + isKeyword(token, KEYWORD_const) ) + { + TRACE_PRINT("var/let/const case"); + state.isConst = isKeyword(token, KEYWORD_const); + /* + * Only create variables for global scope + */ + if ( token->nestLevel == 0 ) { - readToken (token); - is_var = isType (token, TOKEN_IDENTIFIER); - if ( isKeyword (token, KEYWORD_function) || - isKeyword (token, KEYWORD_capital_function) || - isKeyword (token, KEYWORD_capital_object) || - is_var ) - { - if ( isKeyword (token, KEYWORD_capital_object) ) - is_class = true; + state.isGlobal = true; + } + readToken(token); + } - if (is_var) - skipQualifiedIdentifier (token); - else - readToken (token); +nextVar: + state.indexForName = CORK_NIL; + state.foundThis = false; + if ( isKeyword(token, KEYWORD_this) ) + { + TRACE_PRINT("found 'this' keyword"); + state.foundThis = true; - if ( isType (token, TOKEN_OPEN_PAREN) ) - skipArgumentList(token, true, NULL); + readToken(token); + if (isType (token, TOKEN_PERIOD)) + { + readToken(token); + } + else if (isType (token, TOKEN_OPEN_SQUARE)) + { + skipArrayList (token, false); + } + } - if (isType (token, TOKEN_SEMICOLON)) - { - if ( token->nestLevel == 0 ) - { - if ( is_var ) - { - makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); - } - else if ( is_class ) - { - makeClassTag (name, NULL, NULL); - } - else - { - /* FIXME: we cannot really get a meaningful - * signature from a `new Function()` call, - * so for now just don't set any */ - makeFunctionTag (name, NULL, false); - } - } - } - else if (isType (token, TOKEN_CLOSE_CURLY)) - is_terminated = false; - } + copyToken(name, token, true); + TRACE_PRINT("name becomes '%s' of type %s", + vStringValue(token->string), tokenTypeName (token->type)); + + while (! isType (token, TOKEN_CLOSE_CURLY) && + ! isType (token, TOKEN_SEMICOLON) && + ! isType (token, TOKEN_EQUAL_SIGN) && + ! isType (token, TOKEN_COMMA) && + ! isType (token, TOKEN_EOF)) + { + found_lhs = true; + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, CORK_NIL); + readTokenFull (token, true, NULL); } - else if (! isType (token, TOKEN_KEYWORD)) + else if (isKeyword (token, KEYWORD_function)) { - /* - * Only create variables for global scope - */ - if ( token->nestLevel == 0 && is_global ) - { - /* - * A pointer can be created to the function. - * If we recognize the function/class name ignore the variable. - * This format looks identical to a variable definition. - * A variable defined outside of a block is considered - * a global variable: - * var g_var1 = 1; - * var g_var2; - * This is not a global variable: - * var g_var = function; - * This is a global variable: - * var g_var = different_var_name; - */ - fulltag = vStringNew (); - if (vStringLength (token->scope) > 0) - { - vStringCopy(fulltag, token->scope); - vStringPut (fulltag, '.'); - vStringCat (fulltag, token->string); - } - else - { - vStringCopy(fulltag, token->string); - } - if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) && - ! stringListHas(ClassNames, vStringValue (fulltag)) ) - { - makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); - } - vStringDelete (fulltag); - } + parseFunction (token, NULL, false); + readTokenFull (token, true, NULL); } - if (parenDepth > 0) + /* Potentially the name of the function */ + else if (isType (token, TOKEN_PERIOD)) { - while (parenDepth > 0 && ! isType (token, TOKEN_EOF)) - { - if (isType (token, TOKEN_OPEN_PAREN)) - parenDepth++; - else if (isType (token, TOKEN_CLOSE_PAREN)) - parenDepth--; - readTokenFull (token, true, NULL); - } - if (isType (token, TOKEN_CLOSE_CURLY)) - is_terminated = false; + /* + * Cannot be a global variable is it has dot references in the name + */ + state.isGlobal = false; + /* Assume it's an assignment to a global name (e.g. a class) using + * its fully qualified name, so strip the scope. + * FIXME: resolve the scope so we can make more than an assumption. */ + token->scope = CORK_NIL; + name->scope = CORK_NIL; + if ( ! parseStatementLHS (name, token, &state) ) + goto cleanUp; } + else + readTokenFull (token, true, NULL); + + if ( isType (token, TOKEN_OPEN_PAREN) ) + skipArgumentList(token, false, NULL); + + if ( isType (token, TOKEN_OPEN_SQUARE) ) + skipArrayList(token, false); } - /* if we aren't already at the cmd end, advance to it and check whether - * the statement was terminated */ - if (! isType (token, TOKEN_CLOSE_CURLY) && - ! isType (token, TOKEN_SEMICOLON)) + + if ( isType (token, TOKEN_CLOSE_CURLY) ) { /* - * Statements can be optionally terminated in the case of - * statement prior to a close curly brace as in the - * document.write line below: - * - * function checkForUpdate() { - * if( 1==1 ) { - * document.write("hello from checkForUpdate
") - * } - * return 1; - * } + * Reaching this section without having + * processed an open curly brace indicates + * the statement is most likely not terminated. + */ + state.isTerminated = false; + } + else if ( isType (token, TOKEN_SEMICOLON) || + isType (token, TOKEN_EOF) || + isType (token, TOKEN_COMMA) ) + { + /* + * Only create variables for global scope + */ + if ( token->nestLevel == 0 && state.isGlobal ) + { + /* + * Handles this syntax: + * var g_var2; + */ + state.indexForName = makeJsTag (name, state.isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + } + /* + * Statement has ended. + * This deals with calls to functions, like: + * alert(..); */ - is_terminated = findCmdTerm (token, true, true); - /* if we're at a comma, try and read a second var */ if (isType (token, TOKEN_COMMA)) { readToken (token); + state.isClass = false; goto nextVar; } } + else + { + bool ok = found_lhs; + if ( ok && isType (token, TOKEN_EQUAL_SIGN) ) + { + ok = parseStatementRHS (name, token, &state, is_inside_class); + } + /* if we aren't already at the cmd end, advance to it and check whether + * the statement was terminated */ + if (ok && + ! isType (token, TOKEN_CLOSE_CURLY) && + ! isType (token, TOKEN_SEMICOLON)) + { + /* + * Statements can be optionally terminated in the case of + * statement prior to a close curly brace as in the + * document.write line below: + * + * function checkForUpdate() { + * if( 1==1 ) { + * document.write("hello from checkForUpdate
") + * } + * return 1; + * } + */ + state.isTerminated = findCmdTerm (token, true, true); + /* if we're at a comma, try and read a second var */ + if (isType (token, TOKEN_COMMA)) + { + readToken (token); + state.isClass = false; + goto nextVar; + } + } + else if (ok && isType (token, TOKEN_SEMICOLON)) + state.isTerminated = true; + } cleanUp: - vStringCopy(token->scope, saveScope); + token->scope = save_scope; deleteToken (name); - deleteToken (secondary_name); - deleteToken (method_body_token); - vStringDelete(saveScope); - - TRACE_LEAVE(); - return is_terminated; + TRACE_LEAVE_TEXT("is terminated: %d", (int) state.isTerminated); + return state.isTerminated; } static void parseUI5 (tokenInfo *const token) @@ -2523,6 +3035,8 @@ static void parseUI5 (tokenInfo *const token) if (isType (token, TOKEN_PERIOD)) { + int r = CORK_NIL; + readToken (token); while (! isType (token, TOKEN_OPEN_PAREN) && ! isType (token, TOKEN_EOF)) @@ -2540,9 +3054,29 @@ static void parseUI5 (tokenInfo *const token) if (isType (token, TOKEN_COMMA)) readToken (token); + if (isType(name, TOKEN_STRING)) + { + /* + * `name' can include '.'. + * Setting dynamicProp to true can prohibit + * that makeClassTag ispects the inside + * of `name'. + */ + name->dynamicProp = true; + r = makeClassTag (name, NULL, NULL); + /* + * TODO + * `name' specifies a class of OpenUI5. + * So tagging it as a language object of + * JavaScript is incorrect. We have to introduce + * OpenUI5 language as a subparser of JavaScript + * to fix this situation. + */ + } + do { - parseMethods (token, name, false); + parseMethods (token, r, false); } while (! isType (token, TOKEN_CLOSE_CURLY) && ! isType (token, TOKEN_EOF)); } @@ -2592,7 +3126,7 @@ static bool parseLine (tokenInfo *const token, bool is_inside_class) is_terminated = parseLine (token, is_inside_class); break; case KEYWORD_function: - parseFunction (token); + parseFunction (token, NULL, false); break; case KEYWORD_class: is_terminated = parseES6Class (token, NULL); @@ -2613,7 +3147,6 @@ static bool parseLine (tokenInfo *const token, bool is_inside_class) } TRACE_LEAVE(); - return is_terminated; } @@ -2628,7 +3161,7 @@ static void parseJsFile (tokenInfo *const token) if (isType (token, TOKEN_KEYWORD) && token->keyword == KEYWORD_sap) parseUI5 (token); else if (isType (token, TOKEN_KEYWORD) && (token->keyword == KEYWORD_export || - token->keyword == KEYWORD_default)) + token->keyword == KEYWORD_default)) /* skip those at top-level */; else parseLine (token, false); @@ -2638,86 +3171,100 @@ static void parseJsFile (tokenInfo *const token) } #ifdef DO_TRACING -#if 0 +#ifdef DO_TRACING_USE_DUMP_TOKEN static void dumpToken (const tokenInfo *const token) { - fprintf(stderr, "Token <%p>: %s: %s\n", - token, + const char *scope_str = getNameStringForCorkIndex (token->scope); + const char *scope_kind_str = getKindStringForCorkIndex (token->scope); + + if (strcmp(scope_str, "placeholder") == 0) + { + TRACE_PRINT("%s: %s", + tokenTypeName (token->type), + vStringValue (token->string)); + } + else + { + TRACE_PRINT("%s: %s (scope '%s' of kind %s)", tokenTypeName (token->type), - (token->type == TOKEN_KEYWORD ? keywordName (token->keyword): - token->type == TOKEN_IDENTIFIER? vStringValue (token->string): - "")); + vStringValue (token->string), + scope_str, scope_kind_str); + } } #endif +static const char* +getNameStringForCorkIndex(int index) +{ + if (index == CORK_NIL) + return "none"; + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e == NULL) + return "ghost"; /* Can this happen? */ + + if (e->placeholder) + return "placeholder"; + + return e->name; +} + +static const char* +getKindStringForCorkIndex(int index) +{ + if (index == CORK_NIL) + return "none"; + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e == NULL) + return "ghost"; /* Can this happen? */ + + if (e->placeholder) + return "placeholder"; + + if (e->kindIndex == KIND_GHOST_INDEX) + return "ghost"; + + return JsKinds [e->kindIndex].name; +} + +static const char *kindName(jsKind kind) +{ + return ((int)kind) >= 0 ? JsKinds[kind].name : "none"; +} + static const char *tokenTypeName(enum eTokenType e) -{ /* Generated by misc/enumstr.sh with cmdline "parsers/jscript.c" "eTokenType" "tokenTypeName" */ - switch (e) - { - case TOKEN_BINARY_OPERATOR: return "TOKEN_BINARY_OPERATOR"; - case TOKEN_CHARACTER: return "TOKEN_CHARACTER"; - case TOKEN_CLOSE_CURLY: return "TOKEN_CLOSE_CURLY"; - case TOKEN_CLOSE_PAREN: return "TOKEN_CLOSE_PAREN"; - case TOKEN_CLOSE_SQUARE: return "TOKEN_CLOSE_SQUARE"; - case TOKEN_COLON: return "TOKEN_COLON"; - case TOKEN_COMMA: return "TOKEN_COMMA"; - case TOKEN_EOF: return "TOKEN_EOF"; - case TOKEN_EQUAL_SIGN: return "TOKEN_EQUAL_SIGN"; - case TOKEN_IDENTIFIER: return "TOKEN_IDENTIFIER"; - case TOKEN_KEYWORD: return "TOKEN_KEYWORD"; - case TOKEN_OPEN_CURLY: return "TOKEN_OPEN_CURLY"; - case TOKEN_OPEN_PAREN: return "TOKEN_OPEN_PAREN"; - case TOKEN_OPEN_SQUARE: return "TOKEN_OPEN_SQUARE"; - case TOKEN_PERIOD: return "TOKEN_PERIOD"; - case TOKEN_POSTFIX_OPERATOR: return "TOKEN_POSTFIX_OPERATOR"; - case TOKEN_REGEXP: return "TOKEN_REGEXP"; - case TOKEN_SEMICOLON: return "TOKEN_SEMICOLON"; - case TOKEN_STAR: return "TOKEN_STAR"; - case TOKEN_STRING: return "TOKEN_STRING"; - case TOKEN_TEMPLATE_STRING: return "TOKEN_TEMPLATE_STRING"; - case TOKEN_UNDEFINED: return "TOKEN_UNDEFINED"; - default: return "UNKNOWN"; - } -} - -#if 0 -static const char *keywordName(enum eKeywordId e) -{ /* Generated by misc/enumstr.sh with cmdline "parsers/jscript.c" "eKeywordId" "keywordName" */ +{ /* Generated by misc/enumstr.sh with cmdline: + parsers/jscript.c eTokenType tokenTypeName */ switch (e) { - case KEYWORD_async: return "KEYWORD_async"; - case KEYWORD_capital_function: return "KEYWORD_capital_function"; - case KEYWORD_capital_object: return "KEYWORD_capital_object"; - case KEYWORD_catch: return "KEYWORD_catch"; - case KEYWORD_class: return "KEYWORD_class"; - case KEYWORD_const: return "KEYWORD_const"; - case KEYWORD_default: return "KEYWORD_default"; - case KEYWORD_do: return "KEYWORD_do"; - case KEYWORD_else: return "KEYWORD_else"; - case KEYWORD_export: return "KEYWORD_export"; - case KEYWORD_extends: return "KEYWORD_extends"; - case KEYWORD_finally: return "KEYWORD_finally"; - case KEYWORD_for: return "KEYWORD_for"; - case KEYWORD_function: return "KEYWORD_function"; - case KEYWORD_get: return "KEYWORD_get"; - case KEYWORD_if: return "KEYWORD_if"; - case KEYWORD_let: return "KEYWORD_let"; - case KEYWORD_new: return "KEYWORD_new"; - case KEYWORD_prototype: return "KEYWORD_prototype"; - case KEYWORD_return: return "KEYWORD_return"; - case KEYWORD_sap: return "KEYWORD_sap"; - case KEYWORD_set: return "KEYWORD_set"; - case KEYWORD_static: return "KEYWORD_static"; - case KEYWORD_switch: return "KEYWORD_switch"; - case KEYWORD_this: return "KEYWORD_this"; - case KEYWORD_try: return "KEYWORD_try"; - case KEYWORD_var: return "KEYWORD_var"; - case KEYWORD_while: return "KEYWORD_while"; - default: return "UNKNOWN"; + case TOKEN_UNDEFINED: return "TOKEN_UNDEFINED"; + case TOKEN_EOF: return "TOKEN_EOF"; + case TOKEN_CHARACTER: return "TOKEN_CHARACTER"; + case TOKEN_CLOSE_PAREN: return "TOKEN_CLOSE_PAREN"; + case TOKEN_SEMICOLON: return "TOKEN_SEMICOLON"; + case TOKEN_COLON: return "TOKEN_COLON"; + case TOKEN_COMMA: return "TOKEN_COMMA"; + case TOKEN_KEYWORD: return "TOKEN_KEYWORD"; + case TOKEN_OPEN_PAREN: return "TOKEN_OPEN_PAREN"; + case TOKEN_IDENTIFIER: return "TOKEN_IDENTIFIER"; + case TOKEN_STRING: return "TOKEN_STRING"; + case TOKEN_TEMPLATE_STRING: return "TOKEN_TEMPLATE_STRING"; + case TOKEN_PERIOD: return "TOKEN_PERIOD"; + case TOKEN_OPEN_CURLY: return "TOKEN_OPEN_CURLY"; + case TOKEN_CLOSE_CURLY: return "TOKEN_CLOSE_CURLY"; + case TOKEN_EQUAL_SIGN: return "TOKEN_EQUAL_SIGN"; + case TOKEN_OPEN_SQUARE: return "TOKEN_OPEN_SQUARE"; + case TOKEN_CLOSE_SQUARE: return "TOKEN_CLOSE_SQUARE"; + case TOKEN_REGEXP: return "TOKEN_REGEXP"; + case TOKEN_POSTFIX_OPERATOR: return "TOKEN_POSTFIX_OPERATOR"; + case TOKEN_STAR: return "TOKEN_STAR"; + case TOKEN_ATMARK: return "TOKEN_ATMARK"; + case TOKEN_BINARY_OPERATOR: return "TOKEN_BINARY_OPERATOR"; + case TOKEN_ARROW: return "TOKEN_ARROW"; + case TOKEN_DOTS: return "TOKEN_DOTS"; + default: return "UNKNOWN"; } } #endif -#endif static void initialize (const langType language) { @@ -2740,21 +3287,15 @@ static void findJsTags (void) tokenInfo *const token = newToken (); NextToken = NULL; - ClassNames = stringListNew (); - FunctionNames = stringListNew (); LastTokenType = TOKEN_UNDEFINED; parseJsFile (token); - stringListDelete (ClassNames); - stringListDelete (FunctionNames); - ClassNames = NULL; - FunctionNames = NULL; deleteToken (token); #ifdef HAVE_ICONV if (JSUnicodeConverter != (iconv_t) -2 && /* not created */ - JSUnicodeConverter != (iconv_t) -1 /* creation failed */) + JSUnicodeConverter != (iconv_t) -1 /* creation failed */) { iconv_close (JSUnicodeConverter); JSUnicodeConverter = (iconv_t) -2; @@ -2771,7 +3312,7 @@ extern parserDefinition* JavaScriptParser (void) // which have JS function definitions, so we just use the JS parser static const char *const extensions [] = { "js", "jsx", "mjs", NULL }; static const char *const aliases [] = { "js", "node", "nodejs", - "seed", "gjs", + "seed", "gjs", /* Used in PostgreSQL * https://github.com/plv8/plv8 */ "v8", @@ -2789,6 +3330,11 @@ extern parserDefinition* JavaScriptParser (void) def->finalize = finalize; def->keywordTable = JsKeywordTable; def->keywordCount = ARRAY_SIZE (JsKeywordTable); + def->useCork = CORK_QUEUE|CORK_SYMTAB; + def->requestAutomaticFQTag = true; + + def->versionCurrent = 1; + def->versionAge = 1; return def; } diff --git a/ctags/parsers/jscript.h b/ctags/parsers/jscript.h new file mode 100644 index 0000000000..0dfb435900 --- /dev/null +++ b/ctags/parsers/jscript.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License version 2 or (at your option) any later version. + * + * This module contains functions for generating tags for JavaScript language + * files. + */ + +#ifndef CTAGS_JSCRIPT_H +#define CTAGS_JSCRIPT_H + +typedef enum { + JSTAG_FUNCTION, + JSTAG_CLASS, + JSTAG_METHOD, + JSTAG_PROPERTY, + JSTAG_CONSTANT, + JSTAG_VARIABLE, + JSTAG_GENERATOR, + JSTAG_GETTER, + JSTAG_SETTER, + JSTAG_FIELD, + JSTAG_COUNT +} jsKind; + +typedef enum { + JSTAG_FUNCTIONRoleFOREIGNDECL +} JSTAGFunctionRole; + +#endif diff --git a/ctags/parsers/json.c b/ctags/parsers/json.c index fef9790bed..09c3e426b9 100644 --- a/ctags/parsers/json.c +++ b/ctags/parsers/json.c @@ -128,8 +128,7 @@ static void makeJsonTag (tokenInfo *const token, const jsonKind kind) initTagEntry (&e, vStringValue (token->string), kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if (vStringLength (token->scope) > 0) { @@ -244,9 +243,7 @@ static void pushScope (tokenInfo *const token, const tokenInfo *const parent, const jsonKind parentKind) { - if (vStringLength (token->scope) > 0) - vStringPut (token->scope, '.'); - vStringCat (token->scope, parent->string); + vStringJoin(token->scope, '.', parent->string); token->scopeKind = parentKind; } diff --git a/ctags/parsers/julia.c b/ctags/parsers/julia.c index 3f433e6a15..f14b9a8b20 100644 --- a/ctags/parsers/julia.c +++ b/ctags/parsers/julia.c @@ -92,7 +92,7 @@ static kindDefinition JuliaKinds [] = { ATTACH_ROLES(JuliaModuleRoles) }, { true, 's', "struct", "Structures" }, { true, 't', "type", "Types" }, - { true, 'x', "unknown", "name defined in other modules", + { true, 'Y', "unknown", "name defined in other modules", .referenceOnly = true, ATTACH_ROLES(JuliaUnknownRoles) }, }; @@ -205,11 +205,7 @@ static void resetScope (vString *scope, size_t old_len) /* Adds a name to the end of the scope string */ static void addToScope (vString *scope, vString *name) { - if (vStringLength(scope) > 0) - { - vStringPut(scope, '.'); - } - vStringCat(scope, name); + vStringJoin(scope, '.', name); } /* Reads a character from the file */ @@ -235,7 +231,7 @@ static void advanceAndStoreChar (lexerState *lexer) { if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH) { - vStringPut(lexer->token_str, (char) lexer->cur_c); + vStringPut(lexer->token_str, lexer->cur_c); } advanceChar(lexer); } @@ -874,8 +870,7 @@ static void addTag (vString* ident, const char* type, const char* arg_list, int tagEntryInfo tag; initTagEntry(&tag, vStringValue(ident), kind); - tag.lineNumber = line; - tag.filePosition = pos; + updateTagLine(&tag, line, pos); tag.sourceFileName = getInputFileName(); tag.extensionFields.signature = arg_list; @@ -896,8 +891,7 @@ static void addReferenceTag (vString* ident, int kind, int role, unsigned long l } tagEntryInfo tag; initRefTagEntry(&tag, vStringValue(ident), kind, role); - tag.lineNumber = line; - tag.filePosition = pos; + updateTagLine(&tag, line, pos); if (parent_kind != K_NONE) { tag.extensionFields.scopeKindIndex = parent_kind; diff --git a/ctags/parsers/lisp.c b/ctags/parsers/lisp.c index d4d219d776..01ddef31cb 100644 --- a/ctags/parsers/lisp.c +++ b/ctags/parsers/lisp.c @@ -32,7 +32,7 @@ typedef enum { } lispKind; static kindDefinition LispKinds [] = { - { true, 'u', "unknown", "unknown type of definitions" }, + { true, 'Y', "unknown", "unknown type of definitions" }, { true, 'f', "function", "functions" }, { true, 'v', "variable", "variables" }, { true, 'm', "macro", "macros" }, @@ -67,7 +67,7 @@ typedef enum { * defvar-mode-local (=> cadr) */ static kindDefinition EmacsLispKinds [] = { - { true, 'u', "unknown", "unknown type of definitions" }, + { true, 'Y', "unknown", "unknown type of definitions" }, { true, 'f', "function", "functions" }, { true, 'v', "variable", "variables" }, { true, 'c', "const", "constants" }, @@ -266,7 +266,7 @@ static void L_getit (vString *const name, const unsigned char *dbp, while (isspace (*dbp)) dbp++; } - for (p=dbp ; *p!='\0' && *p!='(' && !isspace ((int) *p) && *p!=')' ; p++) + for (p=dbp ; *p!='\0' && *p!='(' && !isspace (*p) && *p!=')' ; p++) vStringPut (name, *p); if (vStringLength (name) > 0) @@ -296,13 +296,13 @@ static void findLispTagsCommon (bool case_insensitive, if (L_isdef (p, case_insensitive)) { vStringClear (kind_hint); - while (*p != '\0' && !isspace ((int) *p)) + while (*p != '\0' && !isspace (*p)) { vStringPut (kind_hint, - case_insensitive? toupper((int)*p): *p); + case_insensitive? toupper(*p): *p); p++; } - while (isspace ((int) *p)) + while (isspace (*p)) p++; L_getit (name, p, case_insensitive, hint2kind, kind_hint); } @@ -310,7 +310,7 @@ static void findLispTagsCommon (bool case_insensitive, { do p++; - while (*p != '\0' && !isspace ((int) *p) + while (*p != '\0' && !isspace (*p) && *p != ':' && *p != '(' && *p != ')'); if (*p == ':') { @@ -321,10 +321,10 @@ static void findLispTagsCommon (bool case_insensitive, if (L_isdef (p - 1, case_insensitive)) { vStringClear (kind_hint); - while (*p != '\0' && !isspace ((int) *p)) + while (*p != '\0' && !isspace (*p)) { vStringPut (kind_hint, - case_insensitive? toupper((int)*p): *p); + case_insensitive? toupper(*p): *p); p++; } while (isspace (*p)) diff --git a/ctags/parsers/lua.c b/ctags/parsers/lua.c index 2d7efed950..865379085b 100644 --- a/ctags/parsers/lua.c +++ b/ctags/parsers/lua.c @@ -41,7 +41,7 @@ static kindDefinition LuaKinds [] = { { true, 'f', "function", "functions" }, /* `unknown' is a kind just for making FQ tag for functions. */ - { false, 'X', "unknown", "unknown language object", + { false, 'Y', "unknown", "unknown language object", .referenceOnly = true, ATTACH_ROLES(LuaUnknownRoles) }, }; @@ -61,7 +61,7 @@ static bool is_a_code_line (const unsigned char *line) { bool result; const unsigned char *p = line; - while (isspace ((int) *p)) + while (isspace (*p)) p++; if (p [0] == '\0') result = false; @@ -74,7 +74,9 @@ static bool is_a_code_line (const unsigned char *line) static bool isLuaIdentifier (char c) { - return (bool) !(isspace(c) || c == '(' || c == ')' || c == '=' || c == '.' || c == ':'); + return (bool) !(isspace((unsigned char) c) || c == '(' || c == ')' + || c == '=' || c == '.' || c == ':' || c == '{' + || c == '}'); } static void set_scope (int child, int parent) @@ -101,7 +103,7 @@ static void extract_next_token (const char *begin, const char *end_sentinel, vSt return; /* Trim prefixed white spaces */ - while (isspace ((int) *begin)) + while (isspace ((unsigned char) *begin)) begin++; /* Both on '(' */ @@ -111,7 +113,7 @@ static void extract_next_token (const char *begin, const char *end_sentinel, vSt const char *end = end_sentinel - 1; /* Trim suffixed white spaces */ - while (isspace ((int) *end)) + while (isspace ((unsigned char) *end)) end--; Assert (begin <= end); @@ -130,7 +132,7 @@ static void extract_next_token (const char *begin, const char *end_sentinel, vSt vStringClear (name); } else if (isLuaIdentifier (*c)) - vStringPut (name, (int) *c); + vStringPut (name, *c); else { /* An unexpected character is found @@ -155,7 +157,7 @@ static void extract_prev_token (const char *end, const char *begin_sentinel, vSt if (! (begin_sentinel <= end)) return; - while (isspace ((int) *end)) + while (isspace ((unsigned char) *end)) { end--; if (! (begin_sentinel <= end)) @@ -226,7 +228,7 @@ static void findLuaTags (void) p = p + 8; /* skip the `function' word */ /* We expect [ \t(] */ - if (! (*p == '(' || isspace ((int)*p))) + if (! (*p == '(' || isspace ((unsigned char) *p))) continue; q = strchr ((const char*) p, '('); if (q) diff --git a/ctags/parsers/make.c b/ctags/parsers/make.c index 8f3180495f..57e19dec41 100644 --- a/ctags/parsers/make.c +++ b/ctags/parsers/make.c @@ -17,7 +17,9 @@ #include "make.h" +#include "entry.h" #include "kind.h" +#include "numarray.h" #include "parse.h" #include "read.h" #include "routines.h" @@ -98,7 +100,7 @@ static bool isSpecialTarget (vString *const name) } while (i < vStringLength (name)) { char ch = vStringChar (name, i++); - if (ch != '_' && !isupper (ch)) + if (ch != '_' && !isupper ((unsigned char) ch)) { return false; } @@ -106,12 +108,12 @@ static bool isSpecialTarget (vString *const name) return true; } -static void makeSimpleMakeTag (vString *const name, makeKind kind) +static int makeSimpleMakeTag (vString *const name, makeKind kind) { if (!isLanguageEnabled (getInputLanguage ())) - return; + return CORK_NIL; - makeSimpleTag (name, kind); + return makeSimpleTag (name, kind); } static void makeSimpleMakeRefTag (const vString* const name, const int kind, @@ -123,22 +125,23 @@ static void makeSimpleMakeRefTag (const vString* const name, const int kind, makeSimpleRefTag (name, kind, roleIndex); } -static void newTarget (vString *const name) +static int newTarget (vString *const name) { /* Ignore GNU Make's "special targets". */ if (isSpecialTarget (name)) { - return; + return CORK_NIL; } - makeSimpleMakeTag (name, K_TARGET); + return makeSimpleMakeTag (name, K_TARGET); } -static void newMacro (vString *const name, bool with_define_directive, bool appending) +static int newMacro (vString *const name, bool with_define_directive, bool appending) { + int r = CORK_NIL; subparser *s; if (!appending) - makeSimpleMakeTag (name, K_MACRO); + r = makeSimpleMakeTag (name, K_MACRO); foreachSubparser(s, false) { @@ -148,6 +151,8 @@ static void newMacro (vString *const name, bool with_define_directive, bool appe m->newMacroNotify (m, vStringValue(name), with_define_directive, appending); leaveSubparser(); } + + return r; } static void valueFound (vString *const name) @@ -206,13 +211,37 @@ static void readIdentifier (const int first, vString *const id) ungetcToInputFile (c); } +static void endTargets (intArray *targets, unsigned long lnum) +{ + for (unsigned int i = 0; i < intArrayCount (targets); i++) + { + int cork_index = intArrayItem (targets, i); + setTagEndLineToCorkEntry (cork_index, lnum); + } + intArrayClear (targets); +} + +static bool isTheLastTargetOnTheSameLine (intArray *current_targets, + unsigned long line) +{ + if (!intArrayIsEmpty (current_targets)) + { + int r = intArrayLast (current_targets); + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e && e->lineNumber == line) + return true; + } + + return false; +} + static void findMakeTags (void) { stringList *identifiers = stringListNew (); bool newline = true; - bool in_define = false; + int current_macro = CORK_NIL; bool in_value = false; - bool in_rule = false; + intArray *current_targets = intArrayNew (); bool variable_possible = true; bool appending = false; int c; @@ -226,7 +255,7 @@ static void findMakeTags (void) { if (newline) { - if (in_rule) + if (!intArrayIsEmpty (current_targets)) { if (c == '\t' || (c = skipToNonWhite (c)) == '#') { @@ -234,13 +263,13 @@ static void findMakeTags (void) c = nextChar (); } else if (c != '\n') - in_rule = false; + endTargets (current_targets, getInputLineNumber () - 1); } else if (in_value) in_value = false; stringListClear (identifiers); - variable_possible = (bool)(!in_rule); + variable_possible = intArrayIsEmpty (current_targets); newline = false; } if (c == '\n') @@ -271,9 +300,12 @@ static void findMakeTags (void) { unsigned int i; for (i = 0; i < stringListCount (identifiers); i++) - newTarget (stringListItem (identifiers, i)); + { + int r = newTarget (stringListItem (identifiers, i)); + if (r != CORK_NIL) + intArrayAdd (current_targets, r); + } stringListClear (identifiers); - in_rule = true; } } else if (variable_possible && c == '=' && @@ -282,7 +314,10 @@ static void findMakeTags (void) newMacro (stringListItem (identifiers, 0), false, appending); in_value = true; - in_rule = false; + unsigned long curline = getInputLineNumber (); + unsigned long adj = isTheLastTargetOnTheSameLine (current_targets, + curline)? 0: 1; + endTargets (current_targets, curline - adj); appending = false; } else if (variable_possible && isIdentifier (c)) @@ -296,13 +331,15 @@ static void findMakeTags (void) if (stringListCount (identifiers) == 1) { - if (in_define && ! strcmp (vStringValue (name), "endef")) - in_define = false; - else if (in_define) + if ((current_macro != CORK_NIL) && ! strcmp (vStringValue (name), "endef")) + { + setTagEndLineToCorkEntry (current_macro, getInputLineNumber ()); + current_macro = CORK_NIL; + } + else if (current_macro != CORK_NIL) skipLine (); else if (! strcmp (vStringValue (name), "define")) { - in_define = true; c = skipToNonWhite (nextChar ()); vStringClear (name); /* all remaining characters on the line are the name -- even spaces */ @@ -315,7 +352,7 @@ static void findMakeTags (void) ungetcToInputFile (c); vStringStripTrailing (name); - newMacro (name, true, false); + current_macro = newMacro (name, true, false); } else if (! strcmp (vStringValue (name), "export")) stringListClear (identifiers); @@ -357,6 +394,9 @@ static void findMakeTags (void) variable_possible = false; } + endTargets (current_targets, getInputLineNumber ()); + + intArrayDelete (current_targets); stringListDelete (identifiers); } @@ -376,5 +416,6 @@ extern parserDefinition* MakefileParser (void) def->extensions = extensions; def->aliases = aliases; def->parser = findMakeTags; + def->useCork = CORK_QUEUE; return def; } diff --git a/ctags/parsers/markdown.c b/ctags/parsers/markdown.c index 3510d756d3..0df878ea7c 100644 --- a/ctags/parsers/markdown.c +++ b/ctags/parsers/markdown.c @@ -28,6 +28,7 @@ #include "parse.h" #include "read.h" #include "vstring.h" +#include "utf8_str.h" #include "nestlevel.h" #include "routines.h" #include "promise.h" @@ -47,6 +48,7 @@ typedef enum { K_LEVEL5SECTION, K_SECTION_COUNT, K_FOOTNOTE = K_SECTION_COUNT, + K_HASHTAG, } markdownKind; static kindDefinition MarkdownKinds[] = { @@ -57,6 +59,7 @@ static kindDefinition MarkdownKinds[] = { { true, 'T', "l4subsection", "level 4 sections" }, { true, 'u', "l5subsection", "level 5 sections" }, { true, 'n', "footnote", "footnotes" }, + { true, 'h', "hashtag", "hashtags"}, }; static fieldDefinition MarkdownFields [] = { @@ -90,7 +93,7 @@ static NestingLevel *getNestingLevel (const int kind, unsigned long adjustmentWh nl = nestingLevelsGetCurrent (nestingLevels); e = getEntryOfNestingLevel (nl); if ((nl && (e == NULL)) || (e && (e->kindIndex >= kind))) - nestingLevelsPop (nestingLevels); + nestingLevelsPopFull (nestingLevels, HT_UINT_TO_PTR ((unsigned int)line)); else break; } @@ -113,12 +116,11 @@ static int makeMarkdownTag (const vString* const name, const int kind, const boo if (twoLine) { /* we want the line before the '---' underline chars */ - const unsigned long line = getInputLineNumber (); - Assert (line > 0); - if (line > 0) + Assert (e.lineNumber > 1); + if (e.lineNumber > 1) { - e.lineNumber--; - e.filePosition = getInputFilePositionForLine (line - 1); + unsigned long lineNumber = e.lineNumber - 1; + updateTagLine (&e, lineNumber, getInputFilePositionForLine (lineNumber)); } } @@ -180,6 +182,16 @@ static int getFirstCharPos (const unsigned char *line, int lineLen, bool *indent } +static void fillEndField (NestingLevel *nl, void *ctxData) +{ + tagEntryInfo *e = getEntryOfNestingLevel (nl); + if (e) + { + unsigned long line = (unsigned long)(HT_PTR_TO_UINT (ctxData)); + setTagEndLine (e, line); + } +} + static void getFootnoteMaybe (const char *line) { const char *start = strstr (line, "[^"); @@ -202,26 +214,140 @@ static void getFootnoteMaybe (const char *line) vStringDelete (footnote); } -static bool extractLanguageForCodeBlock (const char *langMarker, - vString *codeLang) +static markdownSubparser * extractLanguageForCodeBlock (const char *langMarker, + vString *codeLang) { - subparser *s; - bool r = false; + subparser *s = NULL; + bool b = false; + markdownSubparser *r = NULL; foreachSubparser (s, false) { markdownSubparser *m = (markdownSubparser *)s; enterSubparser(s); if (m->extractLanguageForCodeBlock) - r = m->extractLanguageForCodeBlock (m, langMarker, codeLang); + b = m->extractLanguageForCodeBlock (m, langMarker, codeLang); leaveSubparser(); - if (r) + if (b) + { + r = m; break; + } } return r; } +static void notifyCodeBlockLine (markdownSubparser *m, const unsigned char *line) +{ + subparser *s = (subparser *)m; + if (m->notifyCodeBlockLine) + { + enterSubparser(s); + m->notifyCodeBlockLine (m, line); + leaveSubparser(); + } +} + +static void notifyEndOfCodeBlock (markdownSubparser *m) +{ + subparser *s = (subparser *)m; + + if (m->notifyEndOfCodeBlock) + { + enterSubparser(s); + m->notifyEndOfCodeBlock (m); + leaveSubparser(); + } +} + +typedef enum { + HTAG_SPACE_FOUND, + HTAG_HASHTAG_FOUND, + HTAG_TEXT, +} hashtagState; + +/* + State machine to find all hashtags in a line. + */ +static void getAllHashTagsInLineMaybe (const unsigned char *line, int pos, + int lineLen, hashtagState state) +{ + while (pos < lineLen) + { + switch (state) + { + case HTAG_SPACE_FOUND: + if (line[pos] == '#') + { + state = HTAG_HASHTAG_FOUND; + } + else if (!isspace (line[pos])) + state = HTAG_TEXT; + pos++; + break; + case HTAG_HASHTAG_FOUND: + { + /* `#123` is invalid */ + bool hasNonNumericalChar = false; + + const int hashtag_start = pos; + while (pos < lineLen) + { + int utf8_len; + if (isalpha (line[pos]) + || line[pos] == '_' || line[pos] == '-' || line[pos] == '/') + { + hasNonNumericalChar = true; + pos++; + } + else if (isdigit (line[pos])) + { + pos++; + } + else if ((utf8_len = + utf8_raw_strlen ((const char *) &line[pos], + lineLen - pos)) > 0) + { + hasNonNumericalChar = true; + pos += utf8_len; + Assert (pos <= lineLen); + } + else + { + break; + } + } + + int hashtag_length = pos - hashtag_start; + if (hasNonNumericalChar && hashtag_length > 0) + { + vString *tag = + vStringNewNInit ((const char *) (&(line[hashtag_start])), hashtag_length); + makeMarkdownTag (tag, K_HASHTAG, false); + vStringDelete (tag); + } + + if (pos < lineLen) + { + if (isspace (line[pos])) + state = HTAG_SPACE_FOUND; + else + state = HTAG_TEXT; + } + } + break; + case HTAG_TEXT: + while (pos < lineLen && !isspace (line[pos])) + pos++; + state = HTAG_SPACE_FOUND; + break; + default: + break; + } + } +} + static void findMarkdownTags (void) { vString *prevLine = vStringNew (); @@ -237,8 +363,9 @@ static void findMarkdownTags (void) if (sub) chooseExclusiveSubparser (sub, NULL); - nestingLevels = nestingLevelsNew (0); + nestingLevels = nestingLevelsNewFull (0, fillEndField); + markdownSubparser *marksub = NULL; while ((line = readLineFromInputFile ()) != NULL) { int lineLen = strlen ((const char*) line); @@ -249,7 +376,11 @@ static void findMarkdownTags (void) if (lineNum == 1 || inPreambule) { - if (line[pos] == '-' && line[pos + 1] == '-' && line[pos + 2] == '-') + if ((line[pos] == '-' && line[pos + 1] == '-' && line[pos + 2] == '-') + || ( /* Yaml uses "..." as the end of a document. + * See https://yaml.org/spec/1.2.2/#22-structures */ + inPreambule && + (line[pos] == '.' && line[pos + 1] == '.' && line[pos + 2] == '.'))) { if (inPreambule) { @@ -273,7 +404,7 @@ static void findMarkdownTags (void) char c = line[pos]; char otherC = c == '`' ? '~' : '`'; int nSame; - for (nSame = 1; line[nSame] == line[pos]; ++nSame); + for (nSame = 1; line[nSame + pos] == line[pos]; ++nSame); if (inCodeChar != otherC && nSame >= 3) { @@ -286,7 +417,8 @@ static void findMarkdownTags (void) startLineNumber = startSourceLineNumber = lineNum + 1; vStringClear (codeLang); - if (! extractLanguageForCodeBlock (langMarker, codeLang)) + marksub = extractLanguageForCodeBlock (langMarker, codeLang); + if (! marksub) { vStringCopyS (codeLang, langMarker); vStringStripLeading (codeLang); @@ -299,7 +431,12 @@ static void findMarkdownTags (void) if (vStringLength (codeLang) > 0 && startLineNumber < endLineNumber) makePromise (vStringValue (codeLang), startLineNumber, 0, - endLineNumber, 0, startSourceLineNumber); + endLineNumber, 0, startSourceLineNumber); + if (marksub) + { + notifyEndOfCodeBlock(marksub); + marksub = NULL; + } } lineProcessed = true; @@ -320,6 +457,9 @@ static void findMarkdownTags (void) lineProcessed = true; } + if (marksub) + notifyCodeBlockLine (marksub, line); + /* code block or comment */ if (inCodeChar || inComment) lineProcessed = true; @@ -331,8 +471,10 @@ static void findMarkdownTags (void) /* if it's a title underline, or a delimited block marking character */ else if (line[pos] == '=' || line[pos] == '-' || line[pos] == '#' || line[pos] == '>') { + /* hashtags may follow the title or quote */ + getAllHashTagsInLineMaybe(line, pos, lineLen, line[pos] == '#' ? HTAG_SPACE_FOUND :HTAG_TEXT); int nSame; - for (nSame = 1; line[nSame] == line[pos]; ++nSame); + for (nSame = 1; line[pos + nSame] == line[pos]; ++nSame); /* quote */ if (line[pos] == '>') @@ -359,11 +501,11 @@ static void findMarkdownTags (void) makeSectionMarkdownTag (prevLine, kind, marker); } /* otherwise is it a one line title */ - else if (line[pos] == '#' && nSame <= K_SECTION_COUNT && isspace (line[nSame])) + else if (line[pos] == '#' && nSame <= K_SECTION_COUNT && isspace (line[pos + nSame])) { int kind = nSame - 1; bool delimited = false; - vString *name = getHeading (kind, line, lineLen, &delimited); + vString *name = getHeading (kind, line + pos, lineLen - pos, &delimited); if (vStringLength (name) > 0) makeSectionMarkdownTag (name, kind, delimited ? "##" : "#"); vStringDelete (name); @@ -375,6 +517,7 @@ static void findMarkdownTags (void) vStringClear (prevLine); if (!lineProcessed) { + getAllHashTagsInLineMaybe(line, pos, lineLen, HTAG_SPACE_FOUND); getFootnoteMaybe ((const char *)line); vStringCatS (prevLine, (const char*) line); } @@ -383,7 +526,7 @@ static void findMarkdownTags (void) vStringDelete (codeLang); { unsigned int line = (unsigned int)getInputLineNumber (); - nestingLevelsFree (nestingLevels); + nestingLevelsFreeFull (nestingLevels, HT_UINT_TO_PTR (line)); } } @@ -392,6 +535,9 @@ extern parserDefinition* MarkdownParser (void) parserDefinition* const def = parserNew ("Markdown"); static const char *const extensions [] = { "md", "markdown", NULL }; + def->versionCurrent = 1; + def->versionAge = 1; + def->enabled = true; def->extensions = extensions; def->useCork = CORK_QUEUE; @@ -405,7 +551,7 @@ extern parserDefinition* MarkdownParser (void) /* * This setting (useMemoryStreamInput) is for running * Yaml parser from YamlFrontMatter as subparser. - * YamlFrontMatter is run from FrontMatter as a gust parser. + * YamlFrontMatter is run from FrontMatter as a geust parser. * FrontMatter is run from Markdown as a guest parser. * This stacked structure hits the limitation of the main * part: subparser's requirement for memory based input stream diff --git a/ctags/parsers/markdown.h b/ctags/parsers/markdown.h index 2af2372aca..4c711b2ee5 100644 --- a/ctags/parsers/markdown.h +++ b/ctags/parsers/markdown.h @@ -21,9 +21,46 @@ typedef struct sMarkdownSubparser markdownSubparser; struct sMarkdownSubparser { subparser subparser; + /* ```something + ---^ The rest of string is passed as LANGMARKER. + + A sub parser analyses LANGMARKER. + If the sub parser can extract a name of language from LANGMARKER, + the parser puts the name to LANGNAME, and returns true. + If not, return false. + + e.g. + + ```{python} + + Fot this input, ctags pases "{python}" as LANGMARKER. */ bool (* extractLanguageForCodeBlock) (markdownSubparser *s, const char *langMarker, vString *langName); + + /* ```something + ... + + ... + ``` + + ctags passes each LINE in the code block to the sub parser + that returns true when ctags calls extractLanguageForCodeBlock() + with the sub parser. */ + void (* notifyCodeBlockLine) (markdownSubparser *s, + const unsigned char *line); + + /* ```something + ... + codeblock + ... + ``` + ^ The end of code block. + + ctags notifies the sub parser that the end of code block + is found. + */ + void (* notifyEndOfCodeBlock) (markdownSubparser *s); }; #endif diff --git a/ctags/parsers/nsis.c b/ctags/parsers/nsis.c index 8e64196b30..54634ea44e 100644 --- a/ctags/parsers/nsis.c +++ b/ctags/parsers/nsis.c @@ -76,7 +76,7 @@ static fieldDefinition NsisFields[] = { static const unsigned char* skipWhitespace (const unsigned char* cp) { - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; return cp; } @@ -86,9 +86,9 @@ static const unsigned char* skipFlags (const unsigned char* cp) while (*cp == '/') { ++cp; - while (! isspace ((int) *cp)) + while (! isspace (*cp)) ++cp; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; } return cp; @@ -104,13 +104,13 @@ static int makeSimpleTagWithScope(vString *name, int kindIndex, int parentCorkIn #define lineStartingWith(CP,EXPECTED,EOL) \ (strncasecmp ((const char*) CP, EXPECTED, strlen(EXPECTED)) == 0 \ - && (EOL ? (isspace ((int) CP [strlen(EXPECTED)]) || CP [strlen(EXPECTED)] == '\0') \ - : isspace ((int) CP [strlen(EXPECTED)]))) + && (EOL ? (isspace ((unsigned char) CP [strlen(EXPECTED)]) || CP [strlen(EXPECTED)] == '\0') \ + : isspace ((unsigned char) CP [strlen(EXPECTED)]))) #define fillName(NAME,CP,CONDITION) \ while (CONDITION) \ { \ - vStringPut ((NAME), (int) *(CP)); \ + vStringPut ((NAME), *(CP)); \ ++(CP); \ } \ do {} while (0) @@ -149,7 +149,7 @@ static const unsigned char* parseSection (const unsigned char* cp, vString *name int in_escape = 0; do { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; if (*cp == '\0') @@ -182,11 +182,11 @@ static const unsigned char* parseSection (const unsigned char* cp, vString *name } else { - while (isalnum ((int) *cp) + while (isalnum (*cp) || *cp == '_' || *cp == '-' || *cp == '.' || *cp == '!' || *cp == '$' || *cp == '{' || *cp == '}' || *cp == '(' || *cp == ')') { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); ++cp; } } @@ -201,7 +201,7 @@ static const unsigned char* parseSection (const unsigned char* cp, vString *name vStringClear (name); cp = skipWhitespace (cp); - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_')); + fillName (name, cp, (isalnum (*cp) || *cp == '_')); if (vStringLength (name) > 0) { @@ -221,7 +221,7 @@ static const unsigned char* parseLangString (const unsigned char* cp, vString *n * e.g. * https://github.com/vim/vim/blob/3dabd718f4b2d8e09de9e2ec73832620b91c2f79/nsis/lang/english.nsi */ - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_' || *cp == '^')); + fillName (name, cp, (isalnum (*cp) || *cp == '_' || *cp == '^')); if (vStringLength (name) > 0) { @@ -231,7 +231,7 @@ static const unsigned char* parseLangString (const unsigned char* cp, vString *n vStringClear (name); cp = skipWhitespace (cp); - fillName (name, cp, ((*cp != '\0') && (!isspace ((int) *cp)))); + fillName (name, cp, ((*cp != '\0') && (!isspace (*cp)))); if (vStringLength (name) > 0) { attachParserFieldToCorkEntry (r, NsisFields[F_LANGID].ftype, @@ -266,7 +266,7 @@ static void findNsisTags (void) cp = skipWhitespace (cp); fillName (name, cp, - (isalnum ((int) *cp) || *cp == '_' || *cp == '-' || *cp == '.' || *cp == '!')); + (isalnum (*cp) || *cp == '_' || *cp == '-' || *cp == '.' || *cp == '!')); makeSimpleTag (name, K_FUNCTION); vStringClear (name); @@ -278,7 +278,7 @@ static void findNsisTags (void) cp = skipWhitespace (cp); cp = skipFlags (cp); - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_')); + fillName (name, cp, (isalnum (*cp) || *cp == '_')); makeSimpleTag (name, K_VARIABLE); vStringClear (name); @@ -319,7 +319,7 @@ static void findNsisTags (void) cp = skipWhitespace (cp); cp = skipFlags (cp); - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_')); + fillName (name, cp, (isalnum (*cp) || *cp == '_')); makeSimpleTag (name, K_DEFINITION); vStringClear (name); @@ -331,7 +331,7 @@ static void findNsisTags (void) cp = skipWhitespace (cp); cp = skipFlags (cp); - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_')); + fillName (name, cp, (isalnum (*cp) || *cp == '_')); int index = makeSimpleTag (name, K_MACRO); if (vStringLength (name) > 0) @@ -340,7 +340,7 @@ static void findNsisTags (void) { vStringClear (name); cp = skipWhitespace (cp); - fillName (name, cp, (isalnum ((int) *cp) || *cp == '_')); + fillName (name, cp, (isalnum (*cp) || *cp == '_')); if (vStringLength (name) == 0) break; makeSimpleTagWithScope (name, K_MACRO_PARAM, index); diff --git a/ctags/parsers/objc.c b/ctags/parsers/objc.c index 5efd852bfd..3538b19ccc 100644 --- a/ctags/parsers/objc.c +++ b/ctags/parsers/objc.c @@ -1,4 +1,3 @@ - /* * Copyright (c) 2010, Vincent Berthoux * @@ -223,7 +222,7 @@ static void readCString (lexingState * st) else { lastIsBackSlash = *c == '\\'; - vStringPut (st->name, (int) *c); + vStringPut (st->name, *c); } c++; @@ -272,11 +271,11 @@ static void readIdentifier (lexingState * st) /* first char is a simple letter */ if (isAlpha (*st->cp) || *st->cp == '_') - vStringPut (st->name, (int) *st->cp); + vStringPut (st->name, *st->cp); /* Go till you get identifier chars */ for (p = st->cp + 1; isIdent (*p); p++) - vStringPut (st->name, (int) *p); + vStringPut (st->name, *p); st->cp = p; } @@ -289,11 +288,11 @@ static void readIdentifierObjcDirective (lexingState * st) /* first char is a simple letter */ if (*st->cp == '@') - vStringPut (st->name, (int) *st->cp); + vStringPut (st->name, *st->cp); /* Go till you get identifier chars */ for (p = st->cp + 1; isIdent (*p); p++) - vStringPut (st->name, (int) *p); + vStringPut (st->name, *p); st->cp = p; } @@ -514,6 +513,9 @@ static int addTag (vString * const ident, int kind) if (! ObjcKinds[kind].enabled) return CORK_NIL; + if (vStringIsEmpty (ident)) + return CORK_NIL; + prepareTag (&toCreate, ident, kind); return makeTagEntry (&toCreate); } diff --git a/ctags/parsers/ocaml.c b/ctags/parsers/ocaml.c index d4af643d96..59d5d73651 100644 --- a/ctags/parsers/ocaml.c +++ b/ctags/parsers/ocaml.c @@ -337,11 +337,11 @@ static void readIdentifier (lexingState * st) /* first char is a simple letter */ if (isAlpha (*st->cp) || *st->cp == '_') - vStringPut (st->name, (int) *st->cp); + vStringPut (st->name, *st->cp); /* Go till you get identifier chars */ for (p = st->cp + 1; isIdent (*p); p++) - vStringPut (st->name, (int) *p); + vStringPut (st->name, *p); st->cp = p; } @@ -898,8 +898,7 @@ static void prepareTag (tagEntryInfo * tag, vString const *name, int kind) /* Ripped out of read.h initTagEntry, because of line number * shenanigans. * Ugh. Lookahead is harder than I expected. */ - tag->lineNumber = ocaLineNumber; - tag->filePosition = ocaFilePosition; + updateTagLine(tag, ocaLineNumber, ocaFilePosition); parentIndex = getLastNamedIndex (); if (parentIndex >= 0) @@ -1338,12 +1337,6 @@ static void localLet (vString * const ident, ocaToken what, ocaToken whatNext) * because their syntax is similar. */ static void matchPattern (vString * const ident, ocaToken what, ocaToken whatNext) { - /* keep track of [], as it - * can be used in patterns and can - * mean the end of match expression in - * revised syntax */ - static int braceCount = 0; - switch (what) { case Tok_To: @@ -1351,12 +1344,9 @@ static void matchPattern (vString * const ident, ocaToken what, ocaToken whatNex toDoNext = &mayRedeclare; break; - case Tok_BRL: - braceCount++; - break; - case OcaKEYWORD_value: popLastNamed (); + // fall through case OcaKEYWORD_and: case OcaKEYWORD_end: // why was this global? matches only make sense in local scope diff --git a/ctags/parsers/pascal.c b/ctags/parsers/pascal.c index bc526ecc63..1afcf78bbe 100644 --- a/ctags/parsers/pascal.c +++ b/ctags/parsers/pascal.c @@ -67,16 +67,16 @@ static void makePascalTag (const tagEntryInfo* const tag) static const unsigned char* dbp; -#define starttoken(c) (isalpha ((int) c) || (int) c == '_') -#define intoken(c) (isalnum ((int) c) || (int) c == '_' || (int) c == '.') -#define endtoken(c) (! intoken (c) && ! isdigit ((int) c)) +#define starttoken(c) (isalpha ((unsigned char) c) || (int) c == '_') +#define intoken(c) (isalnum ((unsigned char) c) || (int) c == '_' || (int) c == '.') +#define endtoken(c) (! intoken (c) && ! isdigit ((unsigned char) c)) static bool tail (const char *cp) { bool result = false; register int len = 0; - while (*cp != '\0' && tolower ((int) *cp) == tolower ((int) dbp [len])) + while (*cp != '\0' && tolower ((unsigned char) *cp) == tolower (dbp [len])) cp++, len++; if (*cp == '\0' && !intoken (dbp [len])) { @@ -121,7 +121,7 @@ static void parseArglist (const char *buf, vString *arglist, vString *vartype) if (NULL != (var = strchr (end, ':'))) { var++; /* skip ':' */ - while (isspace ((int) *var)) + while (isspace ((unsigned char) *var)) ++var; if (starttoken (*var)) @@ -240,7 +240,7 @@ static void findPascalTags (void) /* check if this is an "extern" declaration */ if (*dbp == '\0') continue; - if (tolower ((int) *dbp == 'e')) + if (tolower (*dbp == 'e')) { if (tail ("extern")) /* superfluous, really! */ { @@ -248,7 +248,7 @@ static void findPascalTags (void) verify_tag = false; } } - else if (tolower ((int) *dbp) == 'f') + else if (tolower (*dbp) == 'f') { if (tail ("forward")) /* check for forward reference */ { @@ -272,7 +272,7 @@ static void findPascalTags (void) continue; /* grab block name */ - while (isspace ((int) *dbp)) + while (isspace (*dbp)) ++dbp; if (!starttoken(*dbp)) continue; @@ -292,7 +292,7 @@ static void findPascalTags (void) } else if (!incomment && !inquote && !found_tag) { - switch (tolower ((int) c)) + switch (tolower (c)) { case 'c': if (tail ("onstructor")) diff --git a/ctags/parsers/perl.c b/ctags/parsers/perl.c index 9f51aa6138..1bda7362cf 100644 --- a/ctags/parsers/perl.c +++ b/ctags/parsers/perl.c @@ -22,12 +22,10 @@ #include "routines.h" #include "selectors.h" #include "subparser.h" +#include "trace.h" #include "vstring.h" #include "xtag.h" -#define TRACE_PERL_C 0 -#define TRACE if (TRACE_PERL_C) printf("perl.c:%d: ", __LINE__), printf - /* * DATA DEFINITIONS */ @@ -39,6 +37,14 @@ static roleDefinition PerlModuleRoles [] = { { true, "unused", "specified in `no' built-in function" }, }; +typedef enum { + R_HEREDOC_ENDLABEL, +} perlHeredocRole; + +static roleDefinition PerlHeredocRoles [] = { + { true, "endmarker", "end marker" }, +}; + static kindDefinition PerlKinds [] = { { true, 'c', "constant", "constants" }, { true, 'f', "format", "formats" }, @@ -48,13 +54,26 @@ static kindDefinition PerlKinds [] = { { false, 'd', "subroutineDeclaration", "subroutine declarations" }, { false, 'M', "module", "modules", .referenceOnly = true, ATTACH_ROLES(PerlModuleRoles)}, + { false, 'h', "heredoc", "marker for here document", + .referenceOnly = false, ATTACH_ROLES (PerlHeredocRoles) }, +}; + +struct hereDocMarker { + vString *marker; + bool indented; + int corkIndex; +}; + +struct hereDocMarkerManager { + ptrArray *markers; + size_t current; }; /* * FUNCTION DEFINITIONS */ -static void notifyEnteringPod () +static void notifyEnteringPod (void) { subparser *sub; @@ -70,7 +89,7 @@ static void notifyEnteringPod () } } -static void notifyLeavingPod () +static void notifyLeavingPod (void) { subparser *sub; @@ -233,16 +252,16 @@ static void makeTagFromLeftSide (const char *begin, const char *end, const char *b, *e; if (! PerlKinds[KIND_PERL_CONSTANT].enabled) return; - for (e = end - 1; e > begin && isspace(*e); --e) + for (e = end - 1; e > begin && isspace((unsigned char) *e); --e) ; if (e < begin) return; - for (b = e; b >= begin && isIdentifier(*b); --b) + for (b = e; b >= begin && isIdentifier((unsigned char) *b); --b) ; /* Identifier must be either beginning of line of have some whitespace * on its left: */ - if (b < begin || isspace(*b) || ',' == *b) + if (b < begin || isspace((unsigned char) *b) || ',' == *b) ++b; else if (b != begin) return; @@ -363,6 +382,276 @@ static void parseQuotedWords(const unsigned char *cp, } while ((cp = readLineFromInputFile()) != NULL); } +/* + * Extract heredoc markers and skip the heredoc areas. + * + * - https://perldoc.perl.org/perlop#%3C%3CEOF + */ +static struct hereDocMarker *hereDocMarkerNew (bool indented) +{ + struct hereDocMarker *marker = xMalloc(1, struct hereDocMarker); + + marker->indented = indented; + marker->marker = vStringNew(); + marker->corkIndex = CORK_NIL; + + return marker; +} + +static void hereDocMarkerDelete (struct hereDocMarker *marker) +{ + vStringDelete (marker->marker); + eFree (marker); +} + +static unsigned char *readHereDocMarker (unsigned char *line, + vString *marker, + unsigned char quote_char) +{ + unsigned char *cp = line; + bool backslash = false; + + for (cp = line; *cp != '\0'; cp++) + { + if (backslash) + { + vStringPut (marker, *cp); + backslash = false; + continue; + } + + if (quote_char == '"' && (*cp == '\\')) + { + backslash = true; + continue; + } + + if (quote_char && *cp == quote_char) + { + cp++; + break; + } + + if (!quote_char && !isIdentifier(*cp)) + break; + + vStringPut (marker, *cp); + } + + return cp; +} + +enum stringType { + STRING_TYPE_NONE = '\0', + STRING_TYPE_SINGLEQ = '\'', + STRING_TYPE_DOUBLEQ = '"', + STRING_TYPE_BACKQ = '`', +}; + + +static const unsigned char *escapeFromString (const unsigned char *line, + const unsigned char *end, + enum stringType stype) +{ + bool in_escape = false; + const unsigned char *cp = line; + + switch (stype) + { + case STRING_TYPE_NONE: + return line; + case STRING_TYPE_SINGLEQ: + case STRING_TYPE_DOUBLEQ: + case STRING_TYPE_BACKQ: + while ((end && cp < end) || (end == NULL && *cp != '\0')) + { + if (in_escape) + { + cp++; + in_escape = false; + } + else if (*cp == '\\') + { + cp++; + in_escape = true; + } + else if (*cp == (unsigned char)stype) + { + cp++; + return cp; + } + else + cp++; + } + return NULL; + default: + AssertNotReached (); + return NULL; + } +} + +static enum stringType isInString (const unsigned char *line, + const unsigned char *end) +{ + const unsigned char *cp = line; + enum stringType t = STRING_TYPE_NONE; + + while (cp && cp < end) + { + switch (*cp) + { + case '\'': + case '\"': + case '`': + t = *cp; + break; + default: + t = STRING_TYPE_NONE; + break; + } + + cp++; + if (t != STRING_TYPE_NONE) + cp = escapeFromString (cp, end, t); + } + + return (cp == NULL)? t: STRING_TYPE_NONE; +} + + +static const unsigned char *collectHereDocMarker (struct hereDocMarkerManager *mgr, + const unsigned char *line) +{ + unsigned char *starter = (unsigned char*)strstr((char *)line, "<<"); + unsigned char *cp = NULL; + bool indented = false; + unsigned char quote_char = 0; + bool space_seen = false; + + if (starter == NULL) + return NULL; + + enum stringType stype; + if ((stype = isInString(line, starter)) != STRING_TYPE_NONE) + return escapeFromString (starter + 2, NULL, stype); + + cp = starter + 2; + while (isspace (*cp)) + { + /* To avoid confusing with a shift operator, we track + * spaces after the starter (<<). */ + space_seen = true; + cp++; + } + + if (*cp == '\0') + return NULL; + + if (*cp == '~') { + if (space_seen) + return cp + 1; + indented = true; + cp++; + if (*cp == '\0') + return NULL; + while (isspace (*cp)) + cp++; + if (*cp == '\0') + return NULL; + } + + switch (*cp) + { + case '\'': + case '"': + case '`': + quote_char = *cp; + /* Fall through */ + case '\\': + cp++; + if (*cp == '\0') + return NULL; + break; + default: + if (!isIdentifier1(*cp)) + return cp; + if (space_seen) + return cp; + break; + } + + struct hereDocMarker *marker = hereDocMarkerNew (indented); + const unsigned char *last_cp = cp; + cp = readHereDocMarker(cp, marker->marker, quote_char); + if (vStringLength (marker->marker) > 0) + { + marker->corkIndex = makeSimpleTag (marker->marker, + KIND_PERL_HEREDOCMARKER); + ptrArrayAdd (mgr->markers, marker); + } + else + hereDocMarkerDelete (marker); + + if (*cp != '\0' && cp != last_cp) + return cp; + return NULL; +} + +static void collectHereDocMarkers (struct hereDocMarkerManager *mgr, + const unsigned char *line) +{ + const unsigned char *cp = line; +#ifdef DEBUG + const unsigned char *last = cp; +#endif + while ((cp = collectHereDocMarker(mgr, cp)) != NULL) + Assert(last < cp); +} + +static bool isInHereDoc (struct hereDocMarkerManager *mgr, + const unsigned char *line) +{ + if (ptrArrayCount (mgr->markers) == 0) + return false; + + const unsigned char *cp = line; + struct hereDocMarker *current = ptrArrayItem (mgr->markers, mgr->current); + if (current->indented) + { + while (isspace(*cp)) + cp++; + } + if (strncmp((const char *)cp, vStringValue (current->marker), vStringLength (current->marker)) == 0 + && (cp [vStringLength (current->marker)] == '\0' + || (!isIdentifier (cp [vStringLength (current->marker)])))) + { + setTagEndLineToCorkEntry (current->corkIndex, getInputLineNumber()); + + makeSimpleRefTag (current->marker, KIND_PERL_HEREDOCMARKER, R_HEREDOC_ENDLABEL); + + mgr->current++; + if (mgr->current == ptrArrayCount (mgr->markers)) + { + ptrArrayClear (mgr->markers); + mgr->current = 0; + } + } + return true; +} + +static void initHereDocMarkerManager(struct hereDocMarkerManager *mgr) +{ + mgr->markers = ptrArrayNew((ptrArrayDeleteFunc)hereDocMarkerDelete); + mgr->current = 0; +} + +static void finiHereDocMarkerManager(struct hereDocMarkerManager *mgr) +{ + ptrArrayDelete (mgr->markers); + mgr->markers = NULL; + mgr->current = 0; +} + /* Algorithm adapted from from GNU etags. * Perl support by Bart Robinson * Perl sub names: look for /^ [ \t\n]sub [ \t\n]+ [^ \t\n{ (]+/ @@ -395,6 +684,9 @@ static void findPerlTags (void) RESPECT_DATA = (1 << 1), } respect_token = RESPECT_END | RESPECT_DATA; + struct hereDocMarkerManager hdoc_mgr; + initHereDocMarkerManager (&hdoc_mgr); + while ((line = readLineFromInputFile ()) != NULL) { bool spaceRequired = false; @@ -403,6 +695,9 @@ static void findPerlTags (void) perlKind kind = KIND_PERL_NONE; tagEntryInfo e; + if (isInHereDoc (&hdoc_mgr, line)) + continue; + if (skipPodDoc) { if (strncmp ((const char*) line, "=cut", (size_t) 4) == 0) @@ -460,12 +755,12 @@ static void findPerlTags (void) if (parse_only_pod_area) continue; - while (isspace (*cp)) + while (isspace (*cp) || *cp == '{' || *cp == '}') cp++; if (strncmp((const char*) cp, "sub", (size_t) 3) == 0) { - TRACE("this looks like a sub\n"); + TRACE_PRINT("this looks like a sub"); cp += 3; kind = KIND_PERL_SUBROUTINE; spaceRequired = true; @@ -490,7 +785,7 @@ static void findPerlTags (void) } vString *module = NULL; - while (isalnum(*cp) || *cp == ':' || *cp == '.') { + while (isalnum(*cp) || *cp == ':' || *cp == '.' || *cp == '_') { if (!module) module = vStringNew(); vStringPut(module, *cp); @@ -550,7 +845,7 @@ static void findPerlTags (void) while (isspace (*cp)) cp++; vString *module = NULL; - while (isalnum(*cp) || *cp == ':' || *cp == '.') { + while (isalnum(*cp) || *cp == ':' || *cp == '.' || *cp == '_') { if (!module) module = vStringNew(); vStringPut(module, *cp); @@ -580,9 +875,9 @@ static void findPerlTags (void) else vStringClear (package); const unsigned char *const first = cp; - while (*cp && (int) *cp != ';' && !isspace ((int) *cp)) + while (*cp && (int) *cp != ';' && !isspace (*cp)) { - vStringPut (package, (int) *cp); + vStringPut (package, *cp); cp++; } vStringCatS (package, "::"); @@ -611,14 +906,16 @@ static void findPerlTags (void) if ((int) *p == ':' && (int) *(p + 1) != ':') kind = KIND_PERL_LABEL; } + if (kind != KIND_PERL_LABEL) + collectHereDocMarkers (&hdoc_mgr, cp); } if (kind != KIND_PERL_NONE) { - TRACE("cp0: %s\n", (const char *) cp); + TRACE_PRINT("cp0: %s", (const char *) cp); if (spaceRequired && *cp && !isspace (*cp)) continue; - TRACE("cp1: %s\n", (const char *) cp); + TRACE_PRINT("cp1: %s", (const char *) cp); while (isspace (*cp)) cp++; @@ -633,7 +930,7 @@ static void findPerlTags (void) while (isIdentifier (*cp) || (KIND_PERL_PACKAGE == kind && ':' == *cp)) { - vStringPut (name, (int) *cp); + vStringPut (name, *cp); cp++; } @@ -646,7 +943,7 @@ static void findPerlTags (void) vStringCatS (name, "STDOUT"); } - TRACE("name: %s\n", vStringValue (name)); + TRACE_PRINT("name: %s", vStringValue (name)); if (0 == vStringLength(name)) { vStringClear(name); @@ -710,6 +1007,7 @@ static void findPerlTags (void) END_MAIN_WHILE: vStringDelete (name); + finiHereDocMarkerManager (&hdoc_mgr); if (package != NULL) vStringDelete (package); } diff --git a/ctags/parsers/perl.h b/ctags/parsers/perl.h index a100d54dad..3406530aa8 100644 --- a/ctags/parsers/perl.h +++ b/ctags/parsers/perl.h @@ -30,6 +30,7 @@ enum PerlKindType { KIND_PERL_SUBROUTINE, KIND_PERL_SUBROUTINE_DECLARATION, KIND_PERL_MODULE, + KIND_PERL_HEREDOCMARKER, }; struct sPerlSubparser { diff --git a/ctags/parsers/php.c b/ctags/parsers/php.c index dd754913e0..8c045cec3d 100644 --- a/ctags/parsers/php.c +++ b/ctags/parsers/php.c @@ -25,6 +25,8 @@ #include "routines.h" #include "debug.h" #include "objpool.h" +#include "promise.h" +#include "trace.h" #define isIdentChar(c) (isalnum (c) || (c) == '_' || (c) >= 0x80) #define newToken() (objPoolGet (TokenPool)) @@ -237,6 +239,35 @@ typedef enum eTokenType { TOKEN_QMARK, } tokenType; +#ifdef DEBUG +static const char *tokenTypes[] = { +#define E(X) [TOKEN_##X] = #X + E(UNDEFINED), + E(EOF), + E(CHARACTER), + E(CLOSE_PAREN), + E(SEMICOLON), + E(COLON), + E(COMMA), + E(KEYWORD), + E(OPEN_PAREN), + E(OPERATOR), + E(IDENTIFIER), + E(STRING), + E(PERIOD), + E(OPEN_CURLY), + E(CLOSE_CURLY), + E(EQUAL_SIGN), + E(OPEN_SQUARE), + E(CLOSE_SQUARE), + E(VARIABLE), + E(AMPERSAND), + E(BACKSLASH), + E(QMARK), +#undef E +}; +#endif + typedef struct { tokenType type; keywordId keyword; @@ -320,8 +351,7 @@ static void initPhpEntry (tagEntryInfo *const e, const tokenInfo *const token, initTagEntry (e, vStringValue (token->string), kind); - e->lineNumber = token->lineNumber; - e->filePosition = token->filePosition; + updateTagLine (e, token->lineNumber, token->filePosition); if (access != ACCESS_UNDEFINED) e->extensionFields.access = accessToString (access); @@ -412,8 +442,7 @@ static void makeNamespacePhpTag (const tokenInfo *const token, const vString *co initTagEntry (&e, vStringValue (name), K_NAMESPACE); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); makePhpTagEntry (&e); } @@ -602,11 +631,11 @@ static void parseString (vString *const string, const int delimiter) int c = getcFromInputFile (); if (c == '\\' && (c = getcFromInputFile ()) != EOF) - vStringPut (string, (char) c); + vStringPut (string, c); else if (c == EOF || c == delimiter) break; else - vStringPut (string, (char) c); + vStringPut (string, c); } } @@ -716,7 +745,7 @@ static void parseHeredoc (vString *const string) { c = getcFromInputFile (); - vStringPut (string, (char) c); + vStringPut (string, c); if (c == '\r' || c == '\n') { /* new line, check for a delimiter right after. No need to handle @@ -727,7 +756,7 @@ static void parseHeredoc (vString *const string) c = getcFromInputFile (); while (c == ' ' || c == '\t') { - vStringPut (string, (char) c); + vStringPut (string, c); c = getcFromInputFile (); indent_len++; } @@ -768,7 +797,7 @@ static void parseIdentifier (vString *const string, const int firstChar) int c = firstChar; do { - vStringPut (string, (char) c); + vStringPut (string, c); c = getcFromInputFile (); } while (isIdentChar (c)); ungetcToInputFile (c); @@ -901,9 +930,19 @@ static void readToken (tokenInfo *const token) if (! InPhp) { + unsigned long startSourceLineNumber = getSourceLineNumber (); + unsigned long startLineNumber = getInputLineNumber (); + int startLineOffset = getInputLineOffset (); + c = findPhpStart (); if (c != EOF) InPhp = true; + + unsigned long endLineNumber = getInputLineNumber (); + int endLineOffset = getInputLineOffset (); + + makePromise ("HTML", startLineNumber, startLineOffset, + endLineNumber, endLineOffset, startSourceLineNumber); } else c = getcFromInputFile (); @@ -1117,6 +1156,8 @@ static void readToken (tokenInfo *const token) } MayBeKeyword = nextMayBeKeyword; + + TRACE_PRINT("token: %s (%s)", tokenTypes[token->type], vStringValue (token->string)); } static void readQualifiedName (tokenInfo *const token, vString *name, @@ -1218,9 +1259,7 @@ static bool parseClassOrIface (tokenInfo *const token, const phpKind kind, vString *qualifiedName = vStringNew (); readQualifiedName (token, qualifiedName, NULL); - if (vStringLength (inheritance) > 0) - vStringPut (inheritance, ','); - vStringCat (inheritance, qualifiedName); + vStringJoin(inheritance, ',', qualifiedName); if (istat == inheritance_extends && !parent) parent = qualifiedName; else @@ -1701,6 +1740,8 @@ static void enterScope (tokenInfo *const parentToken, const vString *const extraScope, const int parentKind) { + TRACE_ENTER(); + tokenInfo *token = newToken (); vString *typeName = vStringNew (); int origParentKind = parentToken->parentKind; @@ -1800,10 +1841,14 @@ static void enterScope (tokenInfo *const parentToken, parentToken->parentKind = origParentKind; vStringDelete (typeName); deleteToken (token); + + TRACE_LEAVE(); } static void findTags (bool startsInPhpMode) { + TRACE_ENTER(); + tokenInfo *const token = newToken (); InPhp = startsInPhpMode; @@ -1823,16 +1868,22 @@ static void findTags (bool startsInPhpMode) vStringDelete (FullScope); vStringDelete (CurrentNamesapce); deleteToken (token); + + TRACE_LEAVE(); } static void findPhpTags (void) { + TRACE_ENTER(); findTags (false); + TRACE_LEAVE(); } static void findZephirTags (void) { + TRACE_ENTER(); findTags (true); + TRACE_LEAVE(); } static void initializePool (void) diff --git a/ctags/parsers/powershell.c b/ctags/parsers/powershell.c index fad342b364..eb7239640f 100644 --- a/ctags/parsers/powershell.c +++ b/ctags/parsers/powershell.c @@ -39,12 +39,18 @@ static const char *const accessTypes[] = { typedef enum { K_FUNCTION, K_VARIABLE, + K_CLASS, + K_FILTER, + K_ENUM, COUNT_KIND } powerShellKind; static kindDefinition PowerShellKinds[COUNT_KIND] = { { true, 'f', "function", "functions" }, - { true, 'v', "variable", "variables" } + { true, 'v', "variable", "variables" }, + { true, 'c', "class", "classes" }, + { true, 'i', "filter", "filter" }, + { true, 'g', "enum", "enum names" }, }; @@ -69,13 +75,31 @@ typedef enum eTokenType { TOKEN_VARIABLE } tokenType; +enum { + KEYWORD_function, + KEYWORD_filter, + KEYWORD_class, + KEYWORD_enum, +}; + +/* We need an integer that is not an unsigned to allow KEYWORD_NONE. */ +typedef int keywordId; + +static const keywordTable PowerShellKeywordTable[] = { + { "function", KEYWORD_function }, + { "filter", KEYWORD_filter }, + { "class", KEYWORD_class }, + { "enum", KEYWORD_enum }, +}; + typedef struct { tokenType type; + keywordId keyword; vString * string; vString * scope; unsigned long lineNumber; MIOPos filePosition; - int parentKind; /* KIND_GHOST_INDEX if none */ + int parentKind; /* KIND_GHOST_INDEX if none */ } tokenInfo; @@ -100,8 +124,7 @@ static void initPowerShellEntry (tagEntryInfo *const e, const tokenInfo *const t { initTagEntry (e, vStringValue (token->string), kind); - e->lineNumber = token->lineNumber; - e->filePosition = token->filePosition; + updateTagLine (e, token->lineNumber, token->filePosition); if (access != NULL) e->extensionFields.access = access; @@ -128,13 +151,13 @@ static void makeSimplePowerShellTag (const tokenInfo *const token, const powerSh } static void makeFunctionTag (const tokenInfo *const token, const vString *const arglist, - const char *const access) + const char *const access, int kind) { - if (PowerShellKinds[K_FUNCTION].enabled) + if (PowerShellKinds[kind].enabled) { tagEntryInfo e; - initPowerShellEntry (&e, token, K_FUNCTION, access); + initPowerShellEntry (&e, token, kind, access); if (arglist) e.extensionFields.signature = vStringValue (arglist); @@ -143,11 +166,36 @@ static void makeFunctionTag (const tokenInfo *const token, const vString *const } } +static void makeClassTag (const tokenInfo *const token) +{ + if (PowerShellKinds[K_CLASS].enabled) + { + tagEntryInfo e; + + initPowerShellEntry (&e, token, K_CLASS, NULL); + + makeTagEntry (&e); + } +} + +static void makeEnumTag (const tokenInfo *const token) +{ + if (PowerShellKinds[K_ENUM].enabled) + { + tagEntryInfo e; + + initPowerShellEntry (&e, token, K_ENUM, NULL); + + makeTagEntry (&e); + } +} + static tokenInfo *newToken (void) { tokenInfo *const token = xMalloc (1, tokenInfo); token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; token->string = vStringNew (); token->scope = vStringNew (); token->lineNumber = getInputLineNumber (); @@ -185,7 +233,12 @@ static void addToScope (tokenInfo *const token, const vString *const extra) static bool isIdentChar (const int c) { - return (isalnum (c) || c == ':' || c == '_' || c == '-' || c >= 0x80); + return (isalnum (c) || c == '_' || c == '-' || c >= 0x80); +} + +static bool isScopeIdentifierChar (const int c) +{ + return (isIdentChar (c) || c == ':'); } static void parseString (vString *const string, const int delimiter) @@ -194,30 +247,44 @@ static void parseString (vString *const string, const int delimiter) { int c = getcFromInputFile (); - if (c == '\\' && (c = getcFromInputFile ()) != EOF) - vStringPut (string, (char) c); + if (delimiter == '"' && c == '`' && (c = getcFromInputFile ()) != EOF) + vStringPut (string, c); else if (c == EOF || c == delimiter) break; else - vStringPut (string, (char) c); + vStringPut (string, c); } } -static void parseIdentifier (vString *const string, const int firstChar) +/* parse a identifier that may contain scopes, such as function names and + * variable names. + * + * VariableName + * FunctionName + * local:VariableName + * private:FunctionName + */ +static void parseScopeIdentifier (vString *const string, const int firstChar) { int c = firstChar; do { - vStringPut (string, (char) c); + vStringPut (string, c); c = getcFromInputFile (); - } while (isIdentChar (c)); + } while (isScopeIdentifierChar (c)); ungetcToInputFile (c); } -static bool isTokenFunction (vString *const name) +/* parse a identifier that do not contain scope, such as class names. */ +static void parseIdentifier (vString *const string, const int firstChar) { - return (strcasecmp (vStringValue (name), "function") == 0 || - strcasecmp (vStringValue (name), "filter") == 0); + int c = firstChar; + do + { + vStringPut (string, c); + c = getcFromInputFile (); + } while (isIdentChar (c)); + ungetcToInputFile (c); } static bool isSpace (int c) @@ -251,7 +318,7 @@ static int skipSingleComment (void) return c; } -static void readToken (tokenInfo *const token) +static void readTokenFull (tokenInfo *const token, bool includingScope) { int c; @@ -338,14 +405,14 @@ static void readToken (tokenInfo *const token) case '$': /* variable start */ { int d = getcFromInputFile (); - if (! isIdentChar (d)) + if (! isScopeIdentifierChar (d)) { ungetcToInputFile (d); token->type = TOKEN_UNDEFINED; } else { - parseIdentifier (token->string, d); + parseScopeIdentifier (token->string, d); token->type = TOKEN_VARIABLE; } break; @@ -356,16 +423,26 @@ static void readToken (tokenInfo *const token) token->type = TOKEN_UNDEFINED; else { - parseIdentifier (token->string, c); - if (isTokenFunction (token->string)) - token->type = TOKEN_KEYWORD; + if (includingScope) + parseScopeIdentifier (token->string, c); else + parseIdentifier (token->string, c); + token->keyword = lookupCaseKeyword ( + vStringValue (token->string), getInputLanguage ()); + if (token->keyword == KEYWORD_NONE) token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; } break; } } +static void readToken (tokenInfo *const token) +{ + readTokenFull (token, false); +} + static void enterScope (tokenInfo *const parentToken, const vString *const extraScope, const int parentKind); @@ -406,13 +483,13 @@ static const char *parsePowerShellScope (tokenInfo *const token) * * function myfunc($foo, $bar) {} */ -static bool parseFunction (tokenInfo *const token) +static bool parseFunction (tokenInfo *const token, int kind) { bool readNext = true; tokenInfo *nameFree = NULL; const char *access; - readToken (token); + readTokenFull (token, true); if (token->type != TOKEN_IDENTIFIER) return false; @@ -456,7 +533,6 @@ static bool parseFunction (tokenInfo *const token) case TOKEN_STRING: vStringCatS (arglist, "'...'"); break; case TOKEN_IDENTIFIER: - case TOKEN_KEYWORD: case TOKEN_VARIABLE: { switch (vStringLast (arglist)) @@ -485,23 +561,89 @@ static bool parseFunction (tokenInfo *const token) } while (token->type != TOKEN_EOF && depth > 0); - makeFunctionTag (nameFree, arglist, access); + makeFunctionTag (nameFree, arglist, access, kind); vStringDelete (arglist); readToken (token); } else if (token->type == TOKEN_OPEN_CURLY) { /* filters doesn't need to have an arglist */ - makeFunctionTag (nameFree, NULL, access); + makeFunctionTag (nameFree, NULL, access, kind); + } + + if (token->type == TOKEN_OPEN_CURLY) + enterScope (token, nameFree->string, kind); + else + readNext = false; + + deleteToken (nameFree); + + return readNext; +} + +/* parse a class + * + * class MyClass {} + * class Derived : Base {} + */ +static bool parseClass (tokenInfo *const token) +{ + bool readNext = true; + vString *nameFree = NULL; + + readToken (token); + + if (token->type != TOKEN_IDENTIFIER) + return false; + + makeClassTag (token); + nameFree = vStringNewCopy (token->string); + readToken (token); + + while (token->type != TOKEN_OPEN_CURLY && token->type != TOKEN_EOF) + { + readToken (token); } if (token->type == TOKEN_OPEN_CURLY) - enterScope (token, nameFree->string, K_FUNCTION); + enterScope (token, nameFree, K_CLASS); else readNext = false; - if (nameFree) - deleteToken (nameFree); + vStringDelete (nameFree); + + return readNext; +} + +/* parse a enum + * + * enum EnumName {} + */ +static bool parseEnum (tokenInfo *const token) +{ + bool readNext = true; + vString *nameFree = NULL; + + readToken (token); + + if (token->type != TOKEN_IDENTIFIER) + return false; + + makeEnumTag (token); + nameFree = vStringNewCopy (token->string); + readToken (token); + + while (token->type != TOKEN_OPEN_CURLY && token->type != TOKEN_EOF) + { + readToken (token); + } + + if (token->type == TOKEN_OPEN_CURLY) + enterScope (token, nameFree, K_ENUM); + else + readNext = false; + + vStringDelete (nameFree); return readNext; } @@ -521,8 +663,11 @@ static bool parseVariable (tokenInfo *const token) readToken (token); if (token->type == TOKEN_EQUAL_SIGN) { - if (token->parentKind != K_FUNCTION) - { /* ignore local variables (i.e. within a function) */ + if (token->parentKind != K_FUNCTION && + token->parentKind != K_FILTER && + token->parentKind != K_CLASS) + { /* ignore local variables (i.e. within a function) + * TODO: Parses class properties to make tags. */ access = parsePowerShellScope (name); makeSimplePowerShellTag (name, K_VARIABLE, access); readNext = true; @@ -564,7 +709,26 @@ static void enterScope (tokenInfo *const parentToken, break; case TOKEN_KEYWORD: - readNext = parseFunction (token); + switch (token->keyword) + { + case KEYWORD_function: + readNext = parseFunction (token, K_FUNCTION); + break; + + case KEYWORD_filter: + readNext = parseFunction (token, K_FILTER); + break; + + case KEYWORD_class: + readNext = parseClass (token); + break; + + case KEYWORD_enum: + readNext = parseEnum (token); + break; + + default: break; + } break; case TOKEN_VARIABLE: @@ -604,5 +768,7 @@ extern parserDefinition* PowerShellParser (void) def->kindCount = ARRAY_SIZE (PowerShellKinds); def->extensions = extensions; def->parser = findPowerShellTags; + def->keywordTable = PowerShellKeywordTable; + def->keywordCount = ARRAY_SIZE (PowerShellKeywordTable); return def; } diff --git a/ctags/parsers/python.c b/ctags/parsers/python.c index 1f6300424e..0403fcc430 100644 --- a/ctags/parsers/python.c +++ b/ctags/parsers/python.c @@ -24,6 +24,7 @@ #include "xtag.h" #include "objpool.h" #include "ptrarray.h" +#include "trace.h" #define isIdentifierChar(c) \ (isalnum (c) || (c) == '_' || (c) >= 0x80) @@ -44,6 +45,7 @@ enum { KEYWORD_lambda, KEYWORD_pass, KEYWORD_return, + KEYWORD_REST }; typedef int keywordId; /* to allow KEYWORD_NONE */ @@ -140,7 +142,7 @@ static kindDefinition PythonKinds[COUNT_KIND] = { {true, 'I', "namespace", "name referring a module defined in other file"}, {true, 'i', "module", "modules", .referenceOnly = true, ATTACH_ROLES(PythonModuleRoles)}, - {true, 'x', "unknown", "name referring a class/variable/function/module defined in other module", + {true, 'Y', "unknown", "name referring a class/variable/function/module defined in other module", .referenceOnly = false, ATTACH_ROLES(PythonUnknownRoles)}, {false, 'z', "parameter", "function parameters" }, {false, 'l', "local", "local variables" }, @@ -164,6 +166,22 @@ static const keywordTable PythonKeywordTable[] = { { "return", KEYWORD_return }, }; +/* Taken from https://docs.python.org/3/reference/lexical_analysis.html#keywords */ +static const struct keywordGroup PythonRestKeywords = { + .value = KEYWORD_REST, + .addingUnlessExisting = true, + .keywords = { + "False", "await", "else", "import", "pass", + "None", "break", "except", "in", "raise", + "True", "class", "finally", "is", "return", + "and", "continue", "for", "lambda", "try", + "as", "def", "from", "nonlocal", "while", + "assert", "del", "global", "not", "with", + "async", "elif", "if", "or", "yield", + NULL + }, +}; + typedef enum eTokenType { /* 0..255 are the byte's value */ TOKEN_EOF = 256, @@ -236,8 +254,7 @@ static void initPythonEntry (tagEntryInfo *const e, const tokenInfo *const token initTagEntry (e, vStringValue (token->string), kind); - e->lineNumber = token->lineNumber; - e->filePosition = token->filePosition; + updateTagLine (e, token->lineNumber, token->filePosition); nl = nestingLevelsGetCurrent (PythonNestingLevels); if (nl) @@ -280,7 +297,7 @@ static int makeClassTag (const tokenInfo *const token, e.extensionFields.inheritance = inheritance ? vStringValue (inheritance) : ""; if (decorators && vStringLength (decorators) > 0) { - attachParserField (&e, false, PythonFields[F_DECORATORS].ftype, + attachParserField (&e, PythonFields[F_DECORATORS].ftype, vStringValue (decorators)); } @@ -304,7 +321,7 @@ static int makeFunctionTag (const tokenInfo *const token, e.extensionFields.signature = vStringValue (arglist); if (decorators && vStringLength (decorators) > 0) { - attachParserField (&e, false, PythonFields[F_DECORATORS].ftype, + attachParserField (&e, PythonFields[F_DECORATORS].ftype, vStringValue (decorators)); } @@ -340,8 +357,7 @@ static int makeSimplePythonRefTag (const tokenInfo *const token, initRefTagEntry (&e, vStringValue (altName ? altName : token->string), kind, roleIndex); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if (xtag != XTAG_UNKNOWN) markTagExtraBit (&e, xtag); @@ -448,7 +464,7 @@ static void readIdentifier (vString *const string, const int firstChar) int c = firstChar; do { - vStringPut (string, (char) c); + vStringPut (string, c); c = getcFromInputFile (); } while (isIdentifierChar (c)); @@ -855,10 +871,12 @@ static bool readCDefName (tokenInfo *const token, pythonKind *kind) static vString *parseParamTypeAnnotation (tokenInfo *const token, vString *arglist) { + TRACE_ENTER(); readToken (token); if (token->type != ':') { ungetToken (token); + TRACE_LEAVE_TEXT("type != :"); return NULL; } @@ -890,12 +908,14 @@ static vString *parseParamTypeAnnotation (tokenInfo *const token, || token->type == ','))) { ungetToken (token); + TRACE_LEAVE_TEXT("= or ,"); return t; } reprCat (arglist, token); reprCat (t, token); } vStringDelete (t); + TRACE_LEAVE_TEXT("return NULL"); return NULL; } @@ -969,6 +989,7 @@ static void deleteTypedParam (struct typedParam *p) static void parseArglist (tokenInfo *const token, const int kind, vString *const arglist, ptrArray *const parameters) { + TRACE_ENTER(); int prevTokenType = token->type; int depth = 1; @@ -1015,11 +1036,13 @@ static void parseArglist (tokenInfo *const token, const int kind, } } while (token->type != TOKEN_EOF && depth > 0); + TRACE_LEAVE(); } static void parseCArglist (tokenInfo *const token, const int kind, vString *const arglist, ptrArray *const parameters) { + TRACE_ENTER(); int depth = 1; tokenInfo *pname = newToken (); vString *ptype = vStringNew (); @@ -1125,12 +1148,14 @@ static void parseCArglist (tokenInfo *const token, const int kind, vStringDelete (ptype); deleteToken (pname); + TRACE_LEAVE(); } static bool parseClassOrDef (tokenInfo *const token, const vString *const decorators, pythonKind kind, bool isCDef) { + TRACE_ENTER(); vString *arglist = NULL; tokenInfo *name = NULL; ptrArray *parameters = NULL; @@ -1140,13 +1165,19 @@ static bool parseClassOrDef (tokenInfo *const token, if (isCDef) { if (! readCDefName (token, &kind)) + { + TRACE_LEAVE_TEXT("!readCDefName"); return false; + } } else { readToken (token); if (token->type != TOKEN_IDENTIFIER) + { + TRACE_LEAVE_TEXT("token->type != TOKEN_IDENTIFIER"); return false; + } } name = newToken (); @@ -1205,11 +1236,13 @@ static bool parseClassOrDef (tokenInfo *const token, e->extensionFields.typeRef [1] = vStringDeleteUnwrap (t); } + TRACE_LEAVE_TEXT("return true"); return true; } static bool parseImport (tokenInfo *const token) { + TRACE_ENTER(); tokenInfo *fromModule = NULL; if (token->keyword == KEYWORD_from) @@ -1360,6 +1393,7 @@ static bool parseImport (tokenInfo *const token) if (fromModule) deleteToken (fromModule); + TRACE_LEAVE(); return false; } @@ -1409,8 +1443,100 @@ static bool skipVariableTypeAnnotation (tokenInfo *const token, vString *const r return false; } +static bool isAtSimpleNamespace (tokenInfo *const token) +{ + if (token->type == TOKEN_IDENTIFIER + && vStringEqC (token->string, "SimpleNamespace")) + { + tokenInfo *backup = newToken(); + + copyToken (backup, token); + readToken (token); + if (token->type != '(') + { + ungetToken (token); + copyToken (token, backup); + deleteToken (backup); + return false; + } + deleteToken (backup); + return true; + } + + if (token->type == TOKEN_IDENTIFIER + && vStringEqC (token->string, "types")) + { + readQualifiedName(token); + if (token->type == TOKEN_IDENTIFIER + && vStringEqC (token->string, ".SimpleNamespace")) + { + tokenInfo *backup = newToken(); + + copyToken (backup, token); + readToken (token); + + if (token->type != '(') + { + ungetToken (token); + copyToken (token, backup); + deleteToken (backup); + return false; + } + deleteToken (backup); + return true; + } + } + return false; +} + +static void parseSimpleNamespace (tokenInfo *const token, int namespaceIndex) +{ + TRACE_ENTER(); + + nestingLevelsPush (PythonNestingLevels, namespaceIndex); + int prevTokenType = token->type; + int depth = 1; + int lastIndex = CORK_NIL; + + do + { + if (token->type != TOKEN_WHITESPACE) + prevTokenType = token->type; + + readTokenFull (token, true); + + if (token->type == '(' || + token->type == '[' || + token->type == '{') + depth ++; + else if (token->type == ')' || + token->type == ']' || + token->type == '}') + depth --; + else if (depth == 1 && + token->type == TOKEN_IDENTIFIER && + (prevTokenType == '(' || prevTokenType == ',')) + lastIndex = makeSimplePythonTag (token, K_UNKNOWN); + else if (depth == 1 && + token->keyword == KEYWORD_lambda && + prevTokenType == '=' && + lastIndex != CORK_NIL) + { + tagEntryInfo *e = getEntryInCorkQueue (lastIndex); + if (e) + e->kindIndex = K_FUNCTION; /* K_METHOD? */ + lastIndex = CORK_NIL; + } + } + while (token->type != TOKEN_EOF && depth > 0); + nestingLevelsPop (PythonNestingLevels); + + TRACE_LEAVE(); +} + static bool parseVariable (tokenInfo *const token, const pythonKind kind) { + TRACE_ENTER(); /* In order to support proper tag type for lambdas in multiple * assignations, we first collect all the names, and then try and map * an assignation to it */ @@ -1469,6 +1595,7 @@ static bool parseVariable (tokenInfo *const token, const pythonKind kind) * we catch lambdas and alike */ if (token->type == '=') { + TRACE_PRINT("operator: ="); unsigned int i = 0; do @@ -1480,19 +1607,9 @@ static bool parseVariable (tokenInfo *const token, const pythonKind kind) if (! nameToken) /* nothing */; - else if (token->keyword != KEYWORD_lambda) - { - int index = makeSimplePythonTag (nameToken, kind); - tagEntryInfo *e = getEntryInCorkQueue (index); - if (e && *type) - { - e->extensionFields.typeRef [0] = eStrdup ("typename"); - e->extensionFields.typeRef [1] = vStringDeleteUnwrap (*type); - *type = NULL; - } - } - else + else if (token->keyword == KEYWORD_lambda) { + TRACE_PRINT("keyword: lambda"); tokenInfo *anon = NULL; vString *arglist = vStringNew (); if (*type) @@ -1562,7 +1679,7 @@ static bool parseVariable (tokenInfo *const token, const pythonKind kind) vString *nameref = vStringNewInit (PythonKinds [K_FUNCTION].name); vStringPut (nameref, ':'); vStringCat (nameref, anon->string); - attachParserField (ve, true, PythonFields[F_NAMEREF].ftype, + attachParserField (ve, PythonFields[F_NAMEREF].ftype, vStringValue (nameref)); vStringDelete (nameref); } @@ -1573,6 +1690,24 @@ static bool parseVariable (tokenInfo *const token, const pythonKind kind) makeFunctionTag (nameToken, arglist, NULL); vStringDelete (arglist); } + else + { + TRACE_PRINT("name: %s", vStringValue(nameToken->string)); + bool isAtSN = isAtSimpleNamespace(token); + int index = makeSimplePythonTag (nameToken, + ((kind != K_LOCAL_VARIABLE) && isAtSN) + ? K_NAMESPACE + : kind); + if (isAtSN) + parseSimpleNamespace(token, index); + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e && *type) + { + e->extensionFields.typeRef [0] = eStrdup ("typename"); + e->extensionFields.typeRef [1] = vStringDeleteUnwrap (*type); + *type = NULL; + } + } /* skip until next initializer */ while ((TokenContinuationDepth > 0 || token->type != ',') && @@ -1601,6 +1736,7 @@ static bool parseVariable (tokenInfo *const token, const pythonKind kind) vStringDelete (nameTypes[nameCount]); /* NULL is acceptable. */ } + TRACE_LEAVE_TEXT("return false"); return false; } @@ -1611,9 +1747,7 @@ static void setIndent (tokenInfo *const token) while (lv && PY_NL (lv)->indentation >= token->indent) { - tagEntryInfo *e = getEntryInCorkQueue (lv->corkIndex); - if (e) - e->extensionFields.endLine = token->lineNumber; + setTagEndLineToCorkEntry (lv->corkIndex, token->lineNumber); nestingLevelsPop (PythonNestingLevels); lv = nestingLevelsGetCurrent (PythonNestingLevels); @@ -1622,6 +1756,8 @@ static void setIndent (tokenInfo *const token) static void findPythonTags (void) { + TRACE_ENTER(); + tokenInfo *const token = newToken (); vString *decorators = vStringNew (); bool atStatementStart = true; @@ -1683,9 +1819,7 @@ static void findPythonTags (void) readNext = false; else { - if (vStringLength (decorators) > 0) - vStringPut (decorators, ','); - vStringCat (decorators, token->string); + vStringJoin(decorators, ',', token->string); readToken (token); readNext = skipOverPair (token, '(', ')', decorators, true); } @@ -1711,6 +1845,7 @@ static void findPythonTags (void) vStringDelete (decorators); deleteToken (token); Assert (NextToken == NULL); + TRACE_LEAVE(); } static void initialize (const langType language) @@ -1718,6 +1853,7 @@ static void initialize (const langType language) Lang_python = language; TokenPool = objPoolNew (16, newPoolToken, deletePoolToken, clearPoolToken, NULL); + addKeywordGroup (&PythonRestKeywords, Lang_python); } static void finalize (langType language CTAGS_ATTR_UNUSED, bool initialized) diff --git a/ctags/parsers/r.c b/ctags/parsers/r.c index e66b94faed..90d2706884 100644 --- a/ctags/parsers/r.c +++ b/ctags/parsers/r.c @@ -316,7 +316,8 @@ static int makeSimpleRTagR (tokenInfo *const token, int parent, int kind, (int[]){K_FUNCTION, K_GLOBALVAR, K_FUNCVAR, - K_PARAM}, 4) != CORK_NIL) + K_PARAM}, 4, + false) != CORK_NIL) return CORK_NIL; parent = CORK_NIL; @@ -330,7 +331,7 @@ static int makeSimpleRTagR (tokenInfo *const token, int parent, int kind, if (pe && ((pe->langType == Lang_R && pe->kindIndex == K_FUNCTION) || (pe->langType != Lang_R && askSubparserTagHasFunctionAlikeKind (pe)))) { - cousin = anyEntryInScope (parent, tokenString (token)); + cousin = anyEntryInScope (parent, tokenString (token), false); if (kind == K_GLOBALVAR) kind = K_FUNCVAR; } @@ -340,10 +341,10 @@ static int makeSimpleRTagR (tokenInfo *const token, int parent, int kind, parent = searchScopeOtherThan (pe->extensionFields.scopeIndex, (int[]){K_VECTOR, K_LIST, K_DATAFRAME}, 3); if (parent == CORK_NIL) - cousin = anyKindEntryInScope (parent, tokenString (token), K_GLOBALVAR); + cousin = anyKindEntryInScope (parent, tokenString (token), K_GLOBALVAR, false); else { - cousin = anyKindEntryInScope (parent, tokenString (token), K_FUNCVAR); + cousin = anyKindEntryInScope (parent, tokenString (token), K_FUNCVAR, false); kind = K_FUNCVAR; } } @@ -352,7 +353,7 @@ static int makeSimpleRTagR (tokenInfo *const token, int parent, int kind, /* The condition for tagging is a bit relaxed here. Even if the same name tag is created in the scope, a name is tagged if kinds are different. */ - cousin = anyKindEntryInScope (parent, tokenString (token), kind); + cousin = anyKindEntryInScope (parent, tokenString (token), kind, false); } if (cousin != CORK_NIL) return CORK_NIL; @@ -365,7 +366,7 @@ static int makeSimpleRTagR (tokenInfo *const token, int parent, int kind, if (assignmentOp) { if (strlen (assignmentOp) > 0) - attachParserField (tag, true, + attachParserField (tag, RFields [F_ASSIGNMENT_OPERATOR].ftype, assignmentOp); else @@ -408,7 +409,7 @@ static int makeSimpleRTag (tokenInfo *const token, int parent, bool in_func, int { tagEntryInfo *e = getEntryInCorkQueue (r); if (e) - attachParserField (e, true, + attachParserField (e, RFields [F_CONSTRUCTOR].ftype, ctor); } @@ -943,7 +944,7 @@ static void parseRightSide (tokenInfo *const token, tokenInfo *const symbol, int tagEntryInfo *tag = getEntryInCorkQueue (corkIndex); if (tag) { - tag->extensionFields.endLine = token->lineNumber; + setTagEndLine (tag, token->lineNumber); if (signature) { tag->extensionFields.signature = vStringDeleteUnwrap(signature); @@ -958,7 +959,7 @@ static void parseRightSide (tokenInfo *const token, tokenInfo *const symbol, int foreachEntriesInScope (corkIndex, NULL, findNonPlaceholder, &any_non_placehoders); if (!any_non_placehoders) - tag->placeholder = 1; + markTagAsPlaceholder (tag, true); /* TODO: remove from intervaltab */ } } diff --git a/ctags/parsers/rst.c b/ctags/parsers/rst.c index e708ea5f40..7520227c6f 100644 --- a/ctags/parsers/rst.c +++ b/ctags/parsers/rst.c @@ -8,6 +8,9 @@ * This module contains functions for generating tags for reStructuredText (reST) files. * * This module was ported from geany. +* +* References: +* https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html */ /* @@ -21,27 +24,35 @@ #include "parse.h" #include "read.h" #include "vstring.h" +#include "utf8_str.h" #include "nestlevel.h" #include "entry.h" #include "routines.h" #include "field.h" +#include "htable.h" +#include "debug.h" +#include "promise.h" /* * DATA DEFINITIONS */ typedef enum { K_EOF = -1, - K_CHAPTER = 0, + K_TITLE = 0, + K_SUBTITLE, + K_CHAPTER, K_SECTION, K_SUBSECTION, K_SUBSUBSECTION, - K_CITATION, + SECTION_COUNT, + K_CITATION = SECTION_COUNT, K_TARGET, K_SUBSTDEF, - SECTION_COUNT } rstKind; static kindDefinition RstKinds[] = { + { true, 'H', "title", "titles"}, + { true, 'h', "subtitle", "sub titles" }, { true, 'c', "chapter", "chapters"}, { true, 's', "section", "sections" }, { true, 'S', "subsection", "subsections" }, @@ -53,6 +64,7 @@ static kindDefinition RstKinds[] = { typedef enum { F_SECTION_MARKER, + F_SECTION_OVERLINE, } rstField; static fieldDefinition RstFields [] = { @@ -61,12 +73,36 @@ static fieldDefinition RstFields [] = { .description = "character used for declaring section", .enabled = false, }, + { + .name = "overline", + .description = "whether using overline & underline for declaring section", + .enabled = false, + .dataType = FIELDTYPE_BOOL + }, }; -static char kindchars[SECTION_COUNT]; - static NestingLevels *nestingLevels = NULL; +struct sectionTracker { + char kindchar; + bool overline; + int count; +}; + +struct olineTracker +{ + char c; + size_t len; +}; + +struct codeblockTracker { + size_t blockIndent; + char *language; + unsigned long startLine; + unsigned long endLine; + unsigned long endLineLength; +}; + /* * FUNCTION DEFINITIONS */ @@ -93,7 +129,7 @@ static NestingLevel *getNestingLevel(const int kind) if ((nl && (e == NULL)) || (e && e->kindIndex >= kind)) { if (e) - e->extensionFields.endLine = (getInputLineNumber() - d); + setTagEndLine (e, (getInputLineNumber() - d)); nestingLevelsPop(nestingLevels); } else @@ -109,21 +145,14 @@ static int makeTargetRstTag(const vString* const name, rstKind kindex) initTagEntry (&e, vStringValue (name), kindex); const NestingLevel *nl = nestingLevelsGetCurrent(nestingLevels); - tagEntryInfo *parent = NULL; if (nl) - parent = getEntryOfNestingLevel (nl); - - if (parent) - { - e.extensionFields.scopeKindIndex = parent->kindIndex; - e.extensionFields.scopeName = parent->name; - } + e.extensionFields.scopeIndex = nl->corkIndex; return makeTagEntry (&e); } static void makeSectionRstTag(const vString* const name, const int kind, const MIOPos filepos, - char marker) + char marker, bool overline) { const NestingLevel *const nl = getNestingLevel(kind); tagEntryInfo *parent; @@ -136,35 +165,21 @@ static void makeSectionRstTag(const vString* const name, const int kind, const M char m [2] = { [1] = '\0' }; initTagEntry (&e, vStringValue (name), kind); - - e.lineNumber--; /* we want the line before the '---' underline chars */ - e.filePosition = filepos; + Assert (e.lineNumber > 1); + /* we want the line before the '---' underline chars */ + if (e.lineNumber > 1) + updateTagLine (&e, e.lineNumber - 1, filepos); parent = getEntryOfNestingLevel (nl); if (parent && (parent->kindIndex < kind)) - { -#if 1 - e.extensionFields.scopeKindIndex = parent->kindIndex; - e.extensionFields.scopeName = parent->name; -#else - /* TODO - - Following code makes the scope information full qualified form. - Do users want the full qualified form? - --- ./Units/rst.simple.d/expected.tags 2015-12-18 01:32:35.574255617 +0900 - +++ /home/yamato/var/ctags-github/Units/rst.simple.d/FILTERED.tmp 2016-05-05 03:05:38.165604756 +0900 - @@ -5,2 +5,2 @@ - -Subsection 1.1.1 input.rst /^Subsection 1.1.1$/;" S section:Section 1.1 - -Subsubsection 1.1.1.1 input.rst /^Subsubsection 1.1.1.1$/;" t subsection:Subsection 1.1.1 - +Subsection 1.1.1 input.rst /^Subsection 1.1.1$/;" S section:Chapter 1.Section 1.1 - +Subsubsection 1.1.1.1 input.rst /^Subsubsection 1.1.1.1$/;" t subsection:Chapter 1.Section 1.1.Subsection 1.1.1 - */ - e.extensionFields.scopeIndex = nl->corkIndex; -#endif - } + e.extensionFields.scopeIndex = nl->corkIndex; m[0] = marker; - attachParserField (&e, false, RstFields [F_SECTION_MARKER].ftype, m); + attachParserField (&e, RstFields [F_SECTION_MARKER].ftype, m); + + if (overline) + attachParserField (&e, RstFields [F_SECTION_OVERLINE].ftype, ""); + r = makeTagEntry (&e); } nestingLevelsPush(nestingLevels, r); @@ -189,59 +204,42 @@ static bool issame(const char *str) } -static int get_kind(char c) +static int get_kind(char c, bool overline, struct sectionTracker tracker[]) { int i; for (i = 0; i < SECTION_COUNT; i++) { - if (kindchars[i] == c) + if (tracker[i].kindchar == c && tracker[i].overline == overline) + { + tracker[i].count++; return i; + } - if (kindchars[i] == 0) + if (tracker[i].count == 0) { - kindchars[i] = c; + tracker[i].count = 1; + tracker[i].kindchar = c; + tracker[i].overline = overline; return i; } } return -1; } - -/* computes the length of an UTF-8 string - * if the string doesn't look like UTF-8, return -1 */ -static int utf8_strlen(const char *buf, int buf_len) +static const unsigned char *is_markup_line_with_char (const unsigned char *line, char reftype) { - int len = 0; - const char *end = buf + buf_len; - - for (len = 0; buf < end; len ++) - { - /* perform quick and naive validation (no sub-byte checking) */ - if (! (*buf & 0x80)) - buf ++; - else if ((*buf & 0xe0) == 0xc0) - buf += 2; - else if ((*buf & 0xf0) == 0xe0) - buf += 3; - else if ((*buf & 0xf8) == 0xf0) - buf += 4; - else /* not a valid leading UTF-8 byte, abort */ - return -1; - - if (buf > end) /* incomplete last byte */ - return -1; - } - - return len; + if ((line [0] == '.') && (line [1] == '.') && (line [2] == ' ') + && (line [3] == reftype)) + return line + 4; + return NULL; } - -static const unsigned char *is_markup_line (const unsigned char *line, char reftype) +static const unsigned char *is_markup_line_with_cstr (const unsigned char *line, char *str, size_t len) { if ((line [0] == '.') && (line [1] == '.') && (line [2] == ' ') - && (line [3] == reftype)) - return line + 4; + && (strncmp ((char *)line + 3, str, len) == 0)) + return line + 3 + len; return NULL; } @@ -303,22 +301,211 @@ static int capture_markup (const unsigned char *target_line, char defaultTermina return r; } -/* TODO: parse overlining & underlining as distinct sections. */ +static void overline_clear(struct olineTracker *ol) +{ + ol->c = 0; + ol->len = 0; +} + +static void overline_set(struct olineTracker *ol, char c, size_t len) +{ + ol->c = c; + ol->len = len; +} + +static bool has_overline(struct olineTracker *ol) +{ + return (ol->c != 0); +} + +static void init_codeblock (struct codeblockTracker *codeblock, const char *language, + size_t blockIndent, unsigned long startLine) +{ + codeblock->language = eStrdup (language); + codeblock->blockIndent = blockIndent; + codeblock->startLine = startLine; + codeblock->endLine = 0; + codeblock->endLineLength = 0; +} + +static bool is_in_codeblock (struct codeblockTracker *codeblock) +{ + return (codeblock->language != NULL); +} + +static void reset_codeblock (struct codeblockTracker *codeblock) +{ + eFree (codeblock->language); + codeblock->language = NULL; +} + +static bool does_codeblock_continue (struct codeblockTracker *codeblock, size_t blockOffset, + unsigned char initChar) +{ + if (blockOffset > codeblock->blockIndent) + return true; + + if (blockOffset <= codeblock->blockIndent && initChar == '\0') + return true; + + return false; +} + +static void update_codeblock (struct codeblockTracker *codeblock, unsigned int endLine, unsigned long endLineLength) +{ + codeblock->endLine = endLine; + codeblock->endLineLength = endLineLength; +} + +static void submit_codeblock (struct codeblockTracker *codeblock) +{ + if (codeblock->endLine) + makePromise (codeblock->language, + codeblock->startLine, 0, + codeblock->endLine, codeblock->endLineLength, codeblock->startLine); + reset_codeblock (codeblock); +} + +static int getFosterEntry(tagEntryInfo *e, int shift) +{ + int r = CORK_NIL; + + while (shift-- > 0) + { + r = e->extensionFields.scopeIndex; + Assert(r != CORK_NIL); + e = getEntryInCorkQueue(r); + Assert(e); + } + return r; +} + +static void shiftKinds(int shift, rstKind baseKind) +{ + size_t count = countEntryInCorkQueue(); + hashTable *remapping_table = hashTableNew (count, + hashPtrhash, + hashPtreq, NULL, NULL); + hashTableSetValueForUnknownKey(remapping_table, HT_INT_TO_PTR(CORK_NIL), NULL); + + for (size_t index = 0; index < count; index++) + { + tagEntryInfo *e = getEntryInCorkQueue((int)index); + if (e && (baseKind <= e->kindIndex && e->kindIndex < SECTION_COUNT)) + { + e->kindIndex += shift; + if (e->kindIndex >= SECTION_COUNT) + { + markTagAsPlaceholder(e, true); + + int foster_parent = getFosterEntry(e, shift); + Assert (foster_parent != CORK_NIL); + hashTablePutItem(remapping_table, HT_INT_TO_PTR(index), + HT_INT_TO_PTR(foster_parent)); + } + } + } + + for (size_t index = 0; index < count; index++) + { + tagEntryInfo *e = getEntryInCorkQueue((int)index); + if (e && e->extensionFields.scopeIndex != CORK_NIL) + { + void *remapping_to = hashTableGetItem (remapping_table, + HT_INT_TO_PTR(e->extensionFields.scopeIndex)); + if (HT_PTR_TO_INT(remapping_to) != CORK_NIL) + e->extensionFields.scopeIndex = HT_PTR_TO_INT(remapping_to); + } + } + hashTableDelete(remapping_table); +} + +static void adjustSectionKinds(struct sectionTracker section_tracker[]) +{ + if (section_tracker[K_TITLE].count > 1) + { + shiftKinds(2, K_TITLE); + return; + } + + if (section_tracker[K_TITLE].count == 1 + && section_tracker[K_SUBTITLE].count > 1) + { + shiftKinds(1, K_SUBTITLE); + return; + } +} + +static void inlineTagScope(tagEntryInfo *e, int parent_index) +{ + tagEntryInfo *parent = getEntryInCorkQueue (parent_index); + if (parent) + { + e->extensionFields.scopeKindIndex = parent->kindIndex; + e->extensionFields.scopeName = eStrdup(parent->name); + e->extensionFields.scopeIndex = CORK_NIL; + } +} + +static void inlineScopes (void) +{ + /* TODO + Following code makes the scope information full qualified form. + Do users want the full qualified form? + --- ./Units/rst.simple.d/expected.tags 2015-12-18 01:32:35.574255617 +0900 + +++ /home/yamato/var/ctags-github/Units/rst.simple.d/FILTERED.tmp 2016-05-05 03:05:38.165604756 +0900 + @@ -5,2 +5,2 @@ + -Subsection 1.1.1 input.rst /^Subsection 1.1.1$/;" S section:Section 1.1 + -Subsubsection 1.1.1.1 input.rst /^Subsubsection 1.1.1.1$/;" t subsection:Subsection 1.1.1 + +Subsection 1.1.1 input.rst /^Subsection 1.1.1$/;" S section:Chapter 1.Section 1.1 + +Subsubsection 1.1.1.1 input.rst /^Subsubsection 1.1.1.1$/;" t subsection:Chapter 1.Section 1.1.Subsection 1.1.1 + */ + size_t count = countEntryInCorkQueue(); + for (size_t index = 0; index < count; index++) + { + tagEntryInfo *e = getEntryInCorkQueue((int)index); + + if (e && e->extensionFields.scopeIndex != CORK_NIL) + inlineTagScope(e, e->extensionFields.scopeIndex); + } +} + static void findRstTags (void) { vString *name = vStringNew (); MIOPos filepos; const unsigned char *line; const unsigned char *markup_line; + struct sectionTracker section_tracker[SECTION_COUNT]; + struct olineTracker overline; + struct codeblockTracker codeblock = { .language = NULL }; + const bool run_guest = isXtagEnabled (XTAG_GUEST); memset(&filepos, 0, sizeof(filepos)); - memset(kindchars, 0, sizeof kindchars); + memset(section_tracker, 0, sizeof section_tracker); + overline_clear(&overline); nestingLevels = nestingLevelsNew(0); while ((line = readLineFromInputFile ()) != NULL) { - if ((markup_line = is_markup_line (line, '_')) != NULL) + const unsigned char *line_trimmed = line; + while (isspace(*line_trimmed)) + line_trimmed++; + + if (run_guest && is_in_codeblock (&codeblock)) + { + if (does_codeblock_continue (&codeblock, (line_trimmed - line), *line_trimmed)) + { + update_codeblock(&codeblock, getInputLineNumber(), strlen((const char *)line)); + continue; + } + else + submit_codeblock(&codeblock); + } + + if ((markup_line = is_markup_line_with_char (line_trimmed, '_')) != NULL) { + overline_clear(&overline); /* Handle .. _target: * http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#hyperlink-targets */ @@ -328,8 +515,9 @@ static void findRstTags (void) continue; } } - else if ((markup_line = is_markup_line (line, '[')) != NULL) + else if ((markup_line = is_markup_line_with_char (line_trimmed, '[')) != NULL) { + overline_clear(&overline); /* Handle .. [citation] * https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#citations */ @@ -339,8 +527,9 @@ static void findRstTags (void) continue; } } - else if ((markup_line = is_markup_line (line, '|')) != NULL) + else if ((markup_line = is_markup_line_with_char (line_trimmed, '|')) != NULL) { + overline_clear(&overline); /* Hanle .. |substitute definition| * https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#substitution-definitions */ @@ -350,6 +539,22 @@ static void findRstTags (void) continue; } } + else if (run_guest + && (markup_line = is_markup_line_with_cstr (line_trimmed, "code-block::", 12)) != NULL) + { + if (is_in_codeblock (&codeblock)) + reset_codeblock (&codeblock); + + while (isspace(*markup_line)) + markup_line++; + + if (*markup_line) + { + init_codeblock (&codeblock, (const char *)markup_line, line_trimmed - line, + getInputLineNumber() + 1); + continue; + } + } int line_len = strlen((const char*) line); int name_len_bytes = vStringLength(name); @@ -362,30 +567,75 @@ static void findRstTags (void) if (name_len < 0) name_len = name_len_bytes; + /* overline may come after an empty line (or begging of file). */ + if (name_len_bytes == 0 && line_len > 0 && + ispunct(line[0]) && issame((const char*) line)) + { + overline_set(&overline, *line, line_len); + continue; + } + /* underlines must be the same length or more */ if (line_len >= name_len && name_len > 0 && ispunct(line[0]) && issame((const char*) line)) { char c = line[0]; - int kind = get_kind(c); + bool o = (overline.c == c && overline.len == line_len); + int kind = get_kind(c, o, section_tracker); + + overline_clear(&overline); if (kind >= 0) { - makeSectionRstTag(name, kind, filepos, c); + makeSectionRstTag(name, kind, filepos, c, o); + vStringClear(name); continue; } } + + if (has_overline(&overline)) + { + if (name_len > 0) + { + /* + * Though we saw an overline and a section title text, + * we cannot find the associated underline. + * In that case, we must reset the state of tracking + * overline. + */ + overline_clear(&overline); + } + + /* + * We san an overline. The line is the candidate + * of a section title text. Skip the prefixed whitespaces. + */ + while (isspace(*line)) + line++; + } + vStringClear (name); if (!isspace(*line)) { vStringCatS(name, (const char*)line); + vStringStripTrailing (name); filepos = getInputFilePosition(); } } + + if (run_guest && is_in_codeblock (&codeblock)) + submit_codeblock(&codeblock); + /* Force popping all nesting levels */ getNestingLevel (K_EOF); vStringDelete (name); nestingLevelsFree(nestingLevels); + + adjustSectionKinds(section_tracker); + inlineScopes(); + + if (run_guest && codeblock.language) + eFree (codeblock.language); } extern parserDefinition* RstParser (void) diff --git a/ctags/parsers/ruby.c b/ctags/parsers/ruby.c index fce596c045..860cad3f6b 100644 --- a/ctags/parsers/ruby.c +++ b/ctags/parsers/ruby.c @@ -21,12 +21,16 @@ #include "debug.h" #include "entry.h" #include "parse.h" +#include "promise.h" #include "nestlevel.h" #include "read.h" #include "routines.h" #include "strlist.h" +#include "subparser.h" #include "vstring.h" +#include "ruby.h" + /* * DATA DECLARATIONS */ @@ -82,6 +86,8 @@ static fieldDefinition RubyFields[] = { struct blockData { stringList *mixin; + rubySubparser *subparser; + int subparserCorkIndex; }; static NestingLevels* nesting = NULL; @@ -94,43 +100,16 @@ static NestingLevels* nesting = NULL; static void enterUnnamedScope (void); -/* -* Returns a string describing the scope in 'nls'. -* We record the current scope as a list of entered scopes. -* Scopes corresponding to 'if' statements and the like are -* represented by empty strings. Scopes corresponding to -* modules and classes are represented by the name of the -* module or class. -*/ -static vString* nestingLevelsToScope (const NestingLevels* nls) -{ - int i; - unsigned int chunks_output = 0; - vString* result = vStringNew (); - for (i = 0; i < nls->n; ++i) - { - NestingLevel *nl = nestingLevelsGetNthFromRoot (nls, i); - tagEntryInfo *e = getEntryOfNestingLevel (nl); - if (e && strlen (e->name) > 0 && (!e->placeholder)) - { - if (chunks_output++ > 0) - vStringPut (result, SCOPE_SEPARATOR); - vStringCatS (result, e->name); - } - } - return result; -} - /* * Attempts to advance 's' past 'literal'. * Returns true if it did, false (and leaves 's' where * it was) otherwise. */ -static bool canMatch (const unsigned char** s, const char* literal, - bool (*end_check) (int)) +static bool canMatchWithEnd (const unsigned char** s, const unsigned char *end, + const char* literal, bool (*end_check) (int)) { const int literal_length = strlen (literal); - const int s_length = strlen ((const char *)*s); + const int s_length = end - *s; if (s_length < literal_length) return false; @@ -138,12 +117,12 @@ static bool canMatch (const unsigned char** s, const char* literal, const unsigned char next_char = *(*s + literal_length); if (strncmp ((const char*) *s, literal, literal_length) != 0) { - return false; + return false; } /* Additionally check that we're at the end of a token. */ if (! end_check (next_char)) { - return false; + return false; } *s += literal_length; return true; @@ -154,19 +133,19 @@ static bool isIdentChar (int c) return (isalnum (c) || c == '_'); } -static bool notIdentChar (int c) +static bool notIdentCharButColon (int c) { - return ! isIdentChar (c); + return ! (isIdentChar (c) || c == ':'); } static bool isOperatorChar (int c) { return (c == '[' || c == ']' || - c == '=' || c == '!' || c == '~' || - c == '+' || c == '-' || - c == '@' || c == '*' || c == '/' || c == '%' || - c == '<' || c == '>' || - c == '&' || c == '^' || c == '|'); + c == '=' || c == '!' || c == '~' || + c == '+' || c == '-' || + c == '@' || c == '*' || c == '/' || c == '%' || + c == '<' || c == '>' || + c == '&' || c == '^' || c == '|'); } static bool notOperatorChar (int c) @@ -188,7 +167,7 @@ static bool isWhitespace (int c) * Advance 's' while the passed predicate is true. Returns true if * advanced by at least one position. */ -static bool advanceWhile (const unsigned char** s, bool (*predicate) (int)) +static bool advanceWhileFull (const unsigned char** s, bool (*predicate) (int), vString *repr) { const unsigned char* original_pos = *s; @@ -199,35 +178,65 @@ static bool advanceWhile (const unsigned char** s, bool (*predicate) (int)) return *s != original_pos; } + if (repr) + vStringPut (repr, **s); (*s)++; } return *s != original_pos; } -static bool canMatchKeyword (const unsigned char** s, const char* literal) +static bool advanceWhile (const unsigned char** s, bool (*predicate) (int)) +{ + return advanceWhileFull (s, predicate, NULL); +} + +extern bool rubyCanMatchKeyword (const unsigned char** s, const char* literal) +{ + /* Using notIdentCharButColon() here. + * + * A hash can be defined like {for: nil, foo: 0}. + *"for" in the above example is not a keyword. + */ + size_t s_length = strlen ((const char *)*s); + return canMatchWithEnd (s, *s + s_length, literal, notIdentCharButColon); +} + +extern bool canMatchKeyword (const unsigned char** s, const unsigned char *end, const char* literal) { - return canMatch (s, literal, notIdentChar); + /* Using notIdentCharButColon() here. + * + * A hash can be defined like {for: nil, foo: 0}. + *"for" in the above example is not a keyword. + */ + return canMatchWithEnd (s, end, literal, notIdentCharButColon); } /* * Extends canMatch. Works similarly, but allows assignment to precede * the keyword, as block assignment is a common Ruby idiom. */ -static bool canMatchKeywordWithAssign (const unsigned char** s, const char* literal) +#define vStringTruncateMaybe(VS,LEN) if (VS) vStringTruncate ((VS), (LEN)) + +static bool canMatchKeywordWithAssignFull (const unsigned char** s, const unsigned char *end, + const char* literal, vString *assignee) { const unsigned char* original_pos = *s; + size_t original_len; + if (assignee) + original_len = vStringLength (assignee); - if (canMatchKeyword (s, literal)) + if (canMatchKeyword (s, end, literal)) { return true; } advanceWhile (s, isSigilChar); - if (! advanceWhile (s, isIdentChar)) + if (! advanceWhileFull (s, isIdentChar, assignee)) { *s = original_pos; + vStringTruncateMaybe (assignee, original_len); return false; } @@ -236,48 +245,63 @@ static bool canMatchKeywordWithAssign (const unsigned char** s, const char* lite if (! (advanceWhile (s, isOperatorChar) && *(*s - 1) == '=')) { *s = original_pos; + vStringTruncateMaybe (assignee, original_len); return false; } advanceWhile (s, isWhitespace); - if (canMatchKeyword (s, literal)) + if (canMatchKeyword (s, end, literal)) { return true; } *s = original_pos; + vStringTruncateMaybe (assignee, original_len); return false; } +static bool canMatchKeywordWithAssign (const unsigned char** s, const unsigned char *end, + const char* literal) +{ + return canMatchKeywordWithAssignFull (s, end, literal, NULL); +} + +extern bool rubyCanMatchKeywordWithAssign (const unsigned char** s, const char* literal) +{ + size_t s_length = strlen ((const char *)*s); + return canMatchKeywordWithAssign (s, *s + s_length, literal); +} + /* * Attempts to advance 'cp' past a Ruby operator method name. Returns * true if successful (and copies the name into 'name'), false otherwise. */ -static bool parseRubyOperator (vString* name, const unsigned char** cp) +static bool parseRubyOperator (vString* name, const unsigned char** cp, + const unsigned char *end) { static const char* RUBY_OPERATORS[] = { - "[]", "[]=", - "**", - "!", "~", "+@", "-@", - "*", "/", "%", - "+", "-", - ">>", "<<", - "&", - "^", "|", - "<=", "<", ">", ">=", - "<=>", "==", "===", "!=", "=~", "!~", - "`", - NULL + "[]", "[]=", + "**", + "!", "~", "+@", "-@", + "*", "/", "%", + "+", "-", + ">>", "<<", + "&", + "^", "|", + "<=", "<", ">", ">=", + "<=>", "==", "===", "!=", "=~", "!~", + "`", + NULL }; int i; for (i = 0; RUBY_OPERATORS[i] != NULL; ++i) { - if (canMatch (cp, RUBY_OPERATORS[i], notOperatorChar)) - { - vStringCatS (name, RUBY_OPERATORS[i]); - return true; - } + if (canMatchWithEnd (cp, end, RUBY_OPERATORS[i], notOperatorChar)) + { + vStringCatS (name, RUBY_OPERATORS[i]); + return true; + } } return false; } @@ -295,12 +319,19 @@ static int emitRubyTagFull (vString* name, rubyKind kind, bool pushLevel, bool c const char *unqualified_name; const char *qualified_name; int r; + bool anonymous = false; - if (!RubyKinds[kind].enabled) { - return CORK_NIL; - } + if (!name) + { + name = anonGenerateNew ("__anon", kind == K_UNDEFINED? K_CLASS: kind); + anonymous = true; + } + + if (!RubyKinds[kind].enabled) { + return CORK_NIL; + } - scope = nestingLevelsToScope (nesting); + scope = nestingLevelsToScopeNew (nesting, SCOPE_SEPARATOR); lvl = nestingLevelsGetCurrent (nesting); parent = getEntryOfNestingLevel (lvl); if (parent) @@ -312,10 +343,9 @@ static int emitRubyTagFull (vString* name, rubyKind kind, bool pushLevel, bool c { if (unqualified_name > qualified_name) { - if (vStringLength (scope) > 0) - vStringPut (scope, SCOPE_SEPARATOR); + vStringPutUnlessEmpty (scope, SCOPE_SEPARATOR); vStringNCatS (scope, qualified_name, - unqualified_name - qualified_name); + unqualified_name - qualified_name); /* assume module parent type for a lack of a better option */ parent_kind = K_MODULE; } @@ -331,11 +361,15 @@ static int emitRubyTagFull (vString* name, rubyKind kind, bool pushLevel, bool c if (unqualified_name[0] != '$' && vStringLength (scope) > 0) { Assert (0 <= parent_kind && - (size_t) parent_kind < (ARRAY_SIZE (RubyKinds))); + (size_t) parent_kind < (ARRAY_SIZE (RubyKinds))); tag.extensionFields.scopeKindIndex = parent_kind; tag.extensionFields.scopeName = vStringValue (scope); } + + if (anonymous) + markTagExtraBit (&tag, XTAG_ANONYMOUS); + r = makeTagEntry (&tag); if (pushLevel) @@ -343,6 +377,10 @@ static int emitRubyTagFull (vString* name, rubyKind kind, bool pushLevel, bool c if (clearName) vStringClear (name); + + if (anonymous) + vStringDelete (name); + vStringDelete (scope); return r; @@ -360,12 +398,69 @@ static bool charIsIn (char ch, const char* list) } /* Advances 'cp' over leading whitespace. */ -static void skipWhitespace (const unsigned char** cp) +#define skipWhitespace rubySkipWhitespace +extern bool rubySkipWhitespace (const unsigned char** cp) { + bool r = false; while (isspace (**cp)) { - ++*cp; + ++*cp; + r = true; } + return r; +} + +static void parseString (const unsigned char** cp, unsigned char boundary, vString* vstr) +{ + while (**cp != 0 && **cp != boundary) + { + if (vstr) + vStringPut (vstr, **cp); + ++*cp; + } + + /* skip the last found '"' */ + if (**cp == boundary) + ++*cp; +} + +extern bool rubyParseString (const unsigned char** cp, unsigned char boundary, vString* vstr) +{ + const unsigned char *p = *cp; + parseString (cp, boundary, vstr); + return (p != *cp); +} + +/* If the current scope is A.B, and the name is B.C, B is overlapped. + * In that case, the function returns true. Else false. + */ +static bool doesNameOverlapCurrentScope (vString *name) +{ + bool r = false; + vString *scope = nestingLevelsToScopeNew (nesting, SCOPE_SEPARATOR); + + const char *scope_cstr = vStringValue(scope); + const char *scope_last = strrchr (scope_cstr, SCOPE_SEPARATOR); + size_t scope_last_slen; + + if (scope_last) + { + scope_last++; + scope_last_slen = strlen (scope_last); + } + else + { + scope_last = scope_cstr; + scope_last_slen = vStringLength (scope); + } + + if (strncmp (vStringValue (name), scope_last, scope_last_slen) == 0 + && *(vStringValue (name) + scope_last_slen) == '.') + r = true; + + vStringDelete (scope); + + return r; } /* @@ -373,7 +468,8 @@ static void skipWhitespace (const unsigned char** cp) * name, leaving *cp pointing to the character after the identifier. */ static rubyKind parseIdentifier ( - const unsigned char** cp, vString* name, rubyKind kind) + const unsigned char** cp, const unsigned char *end, + vString* name, rubyKind kind) { /* Method names are slightly different to class and variable names. * A method name may optionally end with a question mark, exclamation @@ -406,13 +502,32 @@ static rubyKind parseIdentifier ( /* Check for operators such as "def []=(key, val)". */ if (kind == K_METHOD || kind == K_SINGLETON) { - if (parseRubyOperator (name, cp)) + if (parseRubyOperator (name, cp, end)) { return kind; } } /* Copy the identifier into 'name'. */ + if (**cp == ':') + { + if (*((*cp) + 1) == '"' || *((*cp) + 1) == '\'') + { + /* The symbol is defined with string literal like: + ---- + :"[]" + :"[]=" + ---- + */ + unsigned char b = *(++*cp); + ++*cp; + parseString (cp, b, name); + return kind; + } + /* May be symbol like :name. Skip the first character. */ + ++*cp; + } + while (**cp != 0 && (**cp == ':' || isIdentChar (**cp) || charIsIn (**cp, also_ok))) { char last_char = **cp; @@ -435,8 +550,10 @@ static rubyKind parseIdentifier ( /* Recognize singleton methods. */ if (last_char == '.') { - vStringClear (name); - return parseIdentifier (cp, name, K_SINGLETON); + if (strcmp (vStringValue (name), "self.") == 0 + || doesNameOverlapCurrentScope (name)) + vStringClear (name); + return parseIdentifier (cp, end, name, K_SINGLETON); } } @@ -452,18 +569,16 @@ static rubyKind parseIdentifier ( return kind; } -static void parseString (const unsigned char** cp, unsigned char boundary, vString* vstr) +extern bool rubyParseMethodName (const unsigned char **cp, vString* vstr) { - while (**cp != 0 && **cp != boundary) - { - if (vstr) - vStringPut (vstr, **cp); - ++*cp; - } + size_t cp_length = strlen ((const char *)*cp); + return (parseIdentifier (cp, *cp + cp_length, vstr, K_METHOD) == K_METHOD); +} - /* skip the last found '"' */ - if (**cp == boundary) - ++*cp; +extern bool rubyParseModuleName (const unsigned char **cp, vString* vstr) +{ + size_t cp_length = strlen ((const char *)*cp); + return (parseIdentifier (cp, *cp + cp_length, vstr, K_MODULE) == K_MODULE); } static void parseSignature (const unsigned char** cp, vString* vstr) @@ -499,7 +614,7 @@ static void parseSignature (const unsigned char** cp, vString* vstr) vStringPut (vstr, b); continue; } - else if (isspace (vStringLast (vstr))) + else if (isspace ((unsigned char) vStringLast (vstr))) { if (! (isspace (**cp))) { @@ -523,14 +638,15 @@ static void parseSignature (const unsigned char** cp, vString* vstr) } } -static int readAndEmitTagFull (const unsigned char** cp, rubyKind expected_kind, +static int readAndEmitTagFull (const unsigned char** cp, const unsigned char *end, + rubyKind expected_kind, bool pushLevel, bool clearName) { int r = CORK_NIL; if (isspace (**cp)) { vString *name = vStringNew (); - rubyKind actual_kind = parseIdentifier (cp, name, expected_kind); + rubyKind actual_kind = parseIdentifier (cp, end, name, expected_kind); if (actual_kind == K_UNDEFINED || vStringLength (name) == 0) { @@ -564,12 +680,12 @@ static int readAndEmitTagFull (const unsigned char** cp, rubyKind expected_kind, return r; } -static int readAndEmitTag (const unsigned char** cp, rubyKind expected_kind) +static int readAndEmitTag (const unsigned char** cp, const unsigned char *end, rubyKind expected_kind) { - return readAndEmitTagFull (cp, expected_kind, expected_kind != K_CONST, true); + return readAndEmitTagFull (cp, end, expected_kind, expected_kind != K_CONST, true); } -static void readAndStoreMixinSpec (const unsigned char** cp, const char *how_mixin) +static void readAndStoreMixinSpec (const unsigned char** cp, const unsigned char *end, const char *how_mixin) { NestingLevel *nl = NULL; @@ -616,7 +732,7 @@ static void readAndStoreMixinSpec (const unsigned char** cp, const char *how_mix vStringPut(spec, ':'); size_t len = vStringLength (spec); - parseIdentifier (cp, spec, K_MODULE); + parseIdentifier (cp, end, spec, K_MODULE); if (len == vStringLength (spec)) { vStringDelete (spec); @@ -640,12 +756,23 @@ static void enterUnnamedScope (void) { tagEntryInfo e; initTagEntry (&e, "", e_parent->kindIndex); - e.placeholder = 1; + markTagAsPlaceholder(&e, true); r = makeTagEntry (&e); } nestingLevelsPush (nesting, r); } +static void parasiteToScope (rubySubparser *subparser, int subparserCorkIndex) +{ + NestingLevel *nl = nestingLevelsGetCurrent (nesting); + struct blockData *bdata = nestingLevelGetUserData (nl); + bdata->subparser = subparser; + bdata->subparserCorkIndex = subparserCorkIndex; + + if (subparser->enterBlockNotify) + subparser->enterBlockNotify (subparser, subparserCorkIndex); +} + static void attachMixinField (int corkIndex, stringList *mixinSpec) { vString *mixinField = stringListItem (mixinSpec, 0); @@ -659,7 +786,7 @@ static void attachMixinField (int corkIndex, stringList *mixinSpec) vStringValue (mixinField)); } -static void deleteBlockData (NestingLevel *nl) +static void deleteBlockData (NestingLevel *nl, void *data CTAGS_ATTR_UNUSED) { struct blockData *bdata = nestingLevelGetUserData (nl); @@ -670,7 +797,17 @@ static void deleteBlockData (NestingLevel *nl) tagEntryInfo *e = getEntryInCorkQueue (nl->corkIndex); if (e && !e->placeholder) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine (e, getInputLineNumber ()); + + tagEntryInfo *sub_e; + if (bdata->subparserCorkIndex != CORK_NIL + && (sub_e = getEntryInCorkQueue (bdata->subparserCorkIndex))) + { + setTagEndLine (sub_e, getInputLineNumber ()); + if (bdata->subparser) + bdata->subparser->leaveBlockNotify (bdata->subparser, + bdata->subparserCorkIndex); + } if (bdata->mixin) stringListDelete (bdata->mixin); @@ -678,24 +815,23 @@ static void deleteBlockData (NestingLevel *nl) static bool doesLineIncludeConstant (const unsigned char **cp, vString *constant) { - const unsigned char **p = cp; + const unsigned char *p = *cp; - if (isspace (**p)) - skipWhitespace (p); + if (isspace (*p)) + skipWhitespace (&p); - if (isupper (**p)) + if (isupper (*p)) { - while (**p != 0 && isIdentChar (**p)) + while (*p != 0 && isIdentChar (*p)) { - vStringPut (constant, **p); - ++*p; + vStringPut (constant, *p); + ++p; } - if (isspace (**p)) - skipWhitespace (p); - - if (**p == '=') + if (isspace (*p)) + skipWhitespace (&p); + if (*p == '=') { - *cp = *p; + *cp = p; return true; } vStringClear (constant); @@ -718,7 +854,8 @@ static void emitRubyAccessorTags (vString *a, bool reader, bool writer) } } -static void readAttrsAndEmitTags (const unsigned char **cp, bool reader, bool writer) +static void readAttrsAndEmitTags (const unsigned char **cp, const unsigned char *end, + bool reader, bool writer) { vString *a = vStringNew (); @@ -730,8 +867,7 @@ static void readAttrsAndEmitTags (const unsigned char **cp, bool reader, bool wr skipWhitespace (cp); if (**cp == ':') { - ++*cp; - if (K_METHOD == parseIdentifier (cp, a, K_METHOD)) + if (K_METHOD == parseIdentifier (cp, end, a, K_METHOD)) { emitRubyAccessorTags (a, reader, writer); skipWhitespace (cp); @@ -762,7 +898,32 @@ static void readAttrsAndEmitTags (const unsigned char **cp, bool reader, bool wr vStringDelete (a); } -static int readAliasMethodAndEmitTags (const unsigned char **cp) +/* + * The following patterns doen't make a scope: + * define_method (:name,... + * define_method ("name",... + * define_method ('name',... + * define_method :name, ... + * define_method "name", ... + * define_method 'name', ... + * + * The following patterns make a scope: + * define_method (:name) do ... + * define_method ("name") do ... + * define_method ('name') do ... + * define_method :name do ... + * define_method "name" do ... + * define_method 'name' do ... + * + * The following patterns may make a scope: + * define_method (:name) ... + * define_method ("name") ... + * define_method ('name') ... + * define_method :name ... + * define_method "name" ... + * define_method 'name' ... + */ +static int readMethodAndEmitTags (const unsigned char **cp, const unsigned char *end, rubyKind kind) { int r = CORK_NIL; vString *a = vStringNew (); @@ -774,8 +935,7 @@ static int readAliasMethodAndEmitTags (const unsigned char **cp) skipWhitespace (cp); if (**cp == ':') { - ++*cp; - if (K_METHOD != parseIdentifier (cp, a, K_METHOD)) + if (K_METHOD != parseIdentifier (cp, end, a, K_METHOD)) vStringClear (a); } else if (**cp == '"' || **cp == '\'') @@ -786,7 +946,27 @@ static int readAliasMethodAndEmitTags (const unsigned char **cp) } if (vStringLength (a) > 0) - r = emitRubyTagFull (a, K_ALIAS, false, false); + { + bool pushLevel = false; + skipWhitespace (cp); + if (kind == K_METHOD + && (**cp == ')' + || strncmp((const char *)*cp, "do", 2) == 0)) { + if (**cp == ')') + ++*cp; + pushLevel = true; + } + + r = emitRubyTagFull (a, kind, pushLevel, false); + + /* If the name doesn't make a scope, fill the end: field of the tag. */ + if (kind == K_METHOD && !pushLevel) + { + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e) + setTagEndLine (e, e->lineNumber); + } + } vStringDelete (a); return r; @@ -817,7 +997,7 @@ static int readStringAndEmitTag (const unsigned char **cp, rubyKind kind, int ro return r; } -static int readAndEmitDef (const unsigned char **cp) +static int readAndEmitDef (const unsigned char **cp, const unsigned char *end) { rubyKind kind = K_METHOD; NestingLevel *nl = nestingLevelsGetCurrent (nesting); @@ -834,9 +1014,20 @@ static int readAndEmitDef (const unsigned char **cp) * end * end */ - if (e_scope && e_scope->kindIndex == K_CLASS && strlen (e_scope->name) == 0) + if (e_scope && (e_scope->kindIndex == K_CLASS || e_scope->kindIndex == K_MODULE) + && ( + /* Class.new do + ... in this case, an anonymous class is created and + ... pushed. */ + isTagExtraBitMarked (e_scope, XTAG_ANONYMOUS) + || + /* class << + ... in this case, a placeholder tag having an empty + ... name is created and pushed. */ + *(e_scope->name) == '\0' + )) kind = K_SINGLETON; - int corkIndex = readAndEmitTag (cp, kind); + int corkIndex = readAndEmitTag (cp, end, kind); tagEntryInfo *e = getEntryInCorkQueue (corkIndex); /* Fill signature: field. */ @@ -857,17 +1048,46 @@ static int readAndEmitDef (const unsigned char **cp) else vStringPut (signature, ')'); e->extensionFields.signature = vStringDeleteUnwrap (signature); - signature = NULL;; + signature = NULL; vStringDelete (signature); } return corkIndex; } +static rubySubparser *notifyLine (const unsigned char **cp) +{ + subparser *sub; + rubySubparser *rubysub = NULL; + + foreachSubparser (sub, false) + { + rubysub = (rubySubparser *)sub; + rubysub->corkIndex = CORK_NIL; + + if (rubysub->lineNotify) + { + enterSubparser(sub); + const unsigned char *base = *cp; + rubysub->corkIndex = rubysub->lineNotify(rubysub, cp); + leaveSubparser(); + if (rubysub->corkIndex != CORK_NIL) + break; + *cp = base; + } + } + + if (rubysub && rubysub->corkIndex != CORK_NIL) + return rubysub; + return NULL; +} + static void findRubyTags (void) { const unsigned char *line; bool inMultiLineComment = false; vString *constant = vStringNew (); + vString *leftSide = vStringNew (); + bool found_rdoc = false; nesting = nestingLevelsNewFull (sizeof (struct blockData), deleteBlockData); @@ -881,19 +1101,34 @@ static void findRubyTags (void) * * if you wished, and this function would fail to recognize anything. */ - while ((line = readLineFromInputFile ()) != NULL) + size_t llen; + while ((line = readLineFromInputFileWithLength (&llen)) != NULL) { + rubySubparser *subparser = CORK_NIL; const unsigned char *cp = line; + const unsigned char *lend = line + llen; /* if we expect a separator after a while, for, or until statement * separators are "do", ";" or newline */ bool expect_separator = false; - if (canMatch (&cp, "=begin", isWhitespace)) + if (found_rdoc == false) + { + if (strncmp ((const char*)cp, "__END__", 7) == 0) + break; + + if (strncmp ((const char*)cp, "# =", 3) == 0) + { + found_rdoc = true; + makePromise ("RDoc", 0, 0, 0, 0, 0); + } + } + + if (canMatchWithEnd (&cp, lend, "=begin", isWhitespace)) { inMultiLineComment = true; continue; } - if (canMatch (&cp, "=end", isWhitespace)) + if (canMatchWithEnd (&cp, lend, "=end", isWhitespace)) { inMultiLineComment = false; continue; @@ -913,16 +1148,16 @@ static void findRubyTags (void) * unless */ - if (canMatchKeywordWithAssign (&cp, "for") || - canMatchKeywordWithAssign (&cp, "until") || - canMatchKeywordWithAssign (&cp, "while")) + if (canMatchKeywordWithAssign (&cp, lend, "for") || + canMatchKeywordWithAssign (&cp, lend, "until") || + canMatchKeywordWithAssign (&cp, lend, "while")) { expect_separator = true; enterUnnamedScope (); } - else if (canMatchKeywordWithAssign (&cp, "case") || - canMatchKeywordWithAssign (&cp, "if") || - canMatchKeywordWithAssign (&cp, "unless")) + else if (canMatchKeywordWithAssign (&cp, lend, "case") || + canMatchKeywordWithAssign (&cp, lend, "if") || + canMatchKeywordWithAssign (&cp, lend, "unless")) { enterUnnamedScope (); } @@ -931,104 +1166,153 @@ static void findRubyTags (void) * "module M", "class C" and "def m" should only be at the beginning * of a line. */ - if (canMatchKeywordWithAssign (&cp, "module")) + if (canMatchKeywordWithAssign (&cp, lend, "class") + || canMatchKeywordWithAssign (&cp, lend, "module") + || (canMatchKeywordWithAssignFull (&cp, lend, "Class.new", leftSide)) + || (canMatchKeywordWithAssignFull (&cp, lend, "Module.new", leftSide))) { - readAndEmitTag (&cp, K_MODULE); - } - else if (canMatchKeywordWithAssign (&cp, "class")) - { - int r = readAndEmitTag (&cp, K_CLASS); + int r; + + int kind = K_UNDEFINED; + if (*(cp - 1) == 's') + /* clas*s* */ + kind = K_CLASS; + else if (*(cp - 1) == 'e') + /* *modul*e* */ + kind = K_MODULE; + else if (*(cp - 5) == 's') + { + /* Clas*s*.new */ + kind = K_CLASS; + expect_separator = true; + } + else if (*(cp - 5) == 'e') + { + /* Modul*e*.new */ + kind = K_MODULE; + expect_separator = true; + } + else + AssertNotReached(); + + if (expect_separator) + { + r = emitRubyTagFull(vStringLength (leftSide) > 0? leftSide: NULL, kind, true, false); + vStringClear (leftSide); + } + else + r = readAndEmitTag (&cp, lend, kind); + tagEntryInfo *e = getEntryInCorkQueue (r); if (e) { skipWhitespace (&cp); - if (*cp == '<' && *(cp + 1) != '<') + if ((*cp == '<' && *(cp + 1) != '<') + || (expect_separator && (*cp == '('))) { cp++; vString *parent = vStringNew (); - parseIdentifier (&cp, parent, K_CLASS); + parseIdentifier (&cp, lend, parent, K_CLASS); if (vStringLength (parent) > 0) e->extensionFields.inheritance = vStringDeleteUnwrap (parent); else vStringDelete (parent); + if (expect_separator) + { + skipWhitespace (&cp); + if (*cp == ')') + cp++; + } } } } - else if (canMatchKeywordWithAssign (&cp, "include")) + else if (canMatchKeywordWithAssign (&cp, lend, "include")) { - readAndStoreMixinSpec (&cp, "include"); + readAndStoreMixinSpec (&cp, lend, "include"); } - else if (canMatchKeywordWithAssign (&cp, "prepend")) + else if (canMatchKeywordWithAssign (&cp, lend, "prepend")) { - readAndStoreMixinSpec (&cp, "prepend"); + readAndStoreMixinSpec (&cp, lend, "prepend"); } - else if (canMatchKeywordWithAssign (&cp, "extend")) + else if (canMatchKeywordWithAssign (&cp, lend, "extend")) { - readAndStoreMixinSpec (&cp, "extend"); + readAndStoreMixinSpec (&cp, lend, "extend"); } - else if (canMatchKeywordWithAssign (&cp, "def")) + else if (canMatchKeywordWithAssign (&cp, lend, "def")) { - readAndEmitDef (&cp); + readAndEmitDef (&cp, lend); } - else if (canMatchKeywordWithAssign (&cp, "attr_reader")) + else if (canMatchKeywordWithAssign (&cp, lend, "attr_reader")) { - readAttrsAndEmitTags (&cp, true, false); + readAttrsAndEmitTags (&cp, lend, true, false); } - else if (canMatchKeywordWithAssign (&cp, "attr_writer")) + else if (canMatchKeywordWithAssign (&cp, lend, "attr_writer")) { - readAttrsAndEmitTags (&cp, false, true); + readAttrsAndEmitTags (&cp, lend, false, true); } - else if (canMatchKeywordWithAssign (&cp, "attr_accessor")) + else if (canMatchKeywordWithAssign (&cp, lend, "attr_accessor")) { - readAttrsAndEmitTags (&cp, true, true); + readAttrsAndEmitTags (&cp, lend, true, true); } else if (doesLineIncludeConstant (&cp, constant)) { emitRubyTag (constant, K_CONST); vStringClear (constant); } - else if (canMatchKeywordWithAssign (&cp, "require")) + else if (canMatchKeywordWithAssign (&cp, lend, "require")) { readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_REQUIRED); } - else if (canMatchKeywordWithAssign (&cp, "require_relative")) + else if (canMatchKeywordWithAssign (&cp, lend, "require_relative")) { readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_REQUIRED_REL); } - else if (canMatchKeywordWithAssign (&cp, "load")) + else if (canMatchKeywordWithAssign (&cp, lend, "load")) { readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_LOADED); } - else if (canMatchKeywordWithAssign (&cp, "alias")) + else if (canMatchKeywordWithAssign (&cp, lend, "alias")) { - if (!readAndEmitTagFull (&cp, K_ALIAS, false, true) + if (!readAndEmitTagFull (&cp, lend, K_ALIAS, false, true) && (*cp == '$')) { /* Alias for a global variable. */ ++cp; vString *alias = vStringNew (); vStringPut (alias, '$'); - if (K_METHOD == parseIdentifier (&cp, alias, K_METHOD) + if (K_METHOD == parseIdentifier (&cp, lend, alias, K_METHOD) && vStringLength (alias) > 0) emitRubyTagFull (alias, K_ALIAS, false, false); vStringDelete (alias); } } - else if (canMatchKeywordWithAssign (&cp, "alias_method")) - readAliasMethodAndEmitTags (&cp); - else if ((canMatchKeywordWithAssign (&cp, "private") - || canMatchKeywordWithAssign (&cp, "protected") - || canMatchKeywordWithAssign (&cp, "public") - || canMatchKeywordWithAssign (&cp, "private_class_method") - || canMatchKeywordWithAssign (&cp, "public_class_method"))) + else if (canMatchKeywordWithAssign (&cp, lend, "alias_method")) + readMethodAndEmitTags (&cp, lend, K_ALIAS); + else if (canMatchKeywordWithAssign (&cp, lend, "define_method")) + { + int r = readMethodAndEmitTags (&cp, lend, K_METHOD); + /* "define_method(m)" makes a scope. + * In that case, we know we will see '{' or 'do' soon. + */ + NestingLevel *nl = nestingLevelsGetCurrent (nesting); + if (nl && r == nl->corkIndex) + expect_separator = true; + } + else if ((canMatchKeywordWithAssign (&cp, lend, "private") + || canMatchKeywordWithAssign (&cp, lend, "protected") + || canMatchKeywordWithAssign (&cp, lend, "public") + || canMatchKeywordWithAssign (&cp, lend, "private_class_method") + || canMatchKeywordWithAssign (&cp, lend, "public_class_method"))) { skipWhitespace (&cp); - if (canMatchKeywordWithAssign (&cp, "def")) - readAndEmitDef (&cp); + if (canMatchKeywordWithAssign (&cp, lend, "def")) + readAndEmitDef (&cp, lend); /* TODO: store the method for controlling visibility * to the "access:" field of the tag.*/ } + else + subparser = notifyLine(&cp); while (*cp != '\0') @@ -1051,19 +1335,36 @@ static void findRubyTags (void) */ break; } - else if (canMatchKeyword (&cp, "begin")) + else if (canMatchKeyword (&cp, lend, "begin")) { enterUnnamedScope (); } - else if (canMatchKeyword (&cp, "do")) + else if (canMatchKeyword (&cp, lend, "do") || (*cp == '{')) { + if (*cp == '{') + ++cp; + if (! expect_separator) + { + /* We saw '{' or 'do' unexpectedly. + * Let's make an placeholder scope for it. + * + * If '{' or 'do' is expected, a scope will be pushed already. + * If they are not expected, a scope is pushed here. + * Either case a scope is pushed. See the code handling "end". + */ enterUnnamedScope (); + if (subparser && subparser->corkIndex) + parasiteToScope (subparser, subparser->corkIndex); + } else expect_separator = false; } - else if (canMatchKeyword (&cp, "end") && nesting->n > 0) + else if ((canMatchKeyword (&cp, lend, "end") || (*cp == '}')) && nesting->n > 0) { + if (*cp == '}') + ++cp; + /* Leave the most recent scope. */ nestingLevelsPop (nesting); } @@ -1088,8 +1389,32 @@ static void findRubyTags (void) while (isIdentChar (*cp)); } } + + if (expect_separator) + { + NestingLevel *nl = nestingLevelsGetCurrent (nesting); + tagEntryInfo *e_scope = getEntryOfNestingLevel (nl); + + if (e_scope && (e_scope->kindIndex == K_CLASS + || e_scope->kindIndex == K_MODULE + /* Though "define_method(m)" is seen, no + * "do" or "{" is found. + * + * If "define_method(m, x)" is seen, neither + * "do" nor "{" is expected; a scope was not + * pushed. + */ + || ((e_scope->kindIndex == K_METHOD) && + !e_scope->placeholder))) + /* Class.new() ... was found but "do" or `{' is not + * found at the end; no block is made. Let's + * pop the nesting level push when Class.new() + * was found. This is applicable to Module.new. */ + nestingLevelsPop (nesting); + } } nestingLevelsFree (nesting); + vStringDelete (leftSide); vStringDelete (constant); } diff --git a/ctags/parsers/ruby.h b/ctags/parsers/ruby.h new file mode 100644 index 0000000000..231ebbfc0a --- /dev/null +++ b/ctags/parsers/ruby.h @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2000-2001, Thaddeus Covert +* Copyright (c) 2002 Matthias Veit +* Copyright (c) 2004 Elliott Hughes +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for generating tags for Ruby language +* files. +*/ + +#ifndef CTAGS_PARSER_RUBY_H +#define CTAGS_PARSER_RUBY_H + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include "subparser.h" + +typedef struct sRubySubparser rubySubparser; + +struct sRubySubparser { + subparser subparser; + /* Returning other than CORK_NIL means the string is consumed. */ + int (* lineNotify) (rubySubparser *s, const unsigned char **cp); + void (* enterBlockNotify) (rubySubparser *s, int corkIndex); + void (* leaveBlockNotify) (rubySubparser *s, int corkIndex); + /* Privately used in Ruby parser side. */ + int corkIndex; +}; + +extern bool rubySkipWhitespace (const unsigned char **cp); +extern bool rubyCanMatchKeyword (const unsigned char** s, const char* literal); +extern bool rubyCanMatchKeywordWithAssign (const unsigned char** s, const char* literal); + +extern bool rubyParseString (const unsigned char** cp, unsigned char boundary, vString* vstr); +extern bool rubyParseMethodName (const unsigned char **cp, vString* vstr); +extern bool rubyParseModuleName (const unsigned char **cp, vString* vstr); + +#endif diff --git a/ctags/parsers/rust.c b/ctags/parsers/rust.c index 648eac36e2..eac20882e2 100644 --- a/ctags/parsers/rust.c +++ b/ctags/parsers/rust.c @@ -43,6 +43,7 @@ typedef enum { K_FIELD, K_VARIANT, K_METHOD, + K_CONST, K_NONE } RustKind; @@ -59,6 +60,7 @@ static kindDefinition rustKinds[] = { {true, 'm', "field", "A struct field"}, {true, 'e', "enumerator", "An enum variant"}, {true, 'P', "method", "A method"}, + {true, 'C', "constant", "A constant"}, }; typedef enum { @@ -131,7 +133,7 @@ static void writeCurTokenToStr (lexerState *lexer, vString *out_str) vStringCatS(out_str, "->"); break; default: - vStringPut(out_str, (char) lexer->cur_token); + vStringPut(out_str, lexer->cur_token); } } @@ -154,7 +156,7 @@ static void advanceNChar (lexerState *lexer, int n) static void advanceAndStoreChar (lexerState *lexer) { if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH) - vStringPut(lexer->token_str, (char) lexer->cur_c); + vStringPut(lexer->token_str, lexer->cur_c); advanceChar(lexer); } @@ -439,8 +441,7 @@ static void addTag (vString* ident, const char* arg_list, int kind, unsigned lon tagEntryInfo tag; initTagEntry(&tag, vStringValue(ident), kind); - tag.lineNumber = line; - tag.filePosition = pos; + updateTagLine (&tag, line, pos); tag.extensionFields.signature = arg_list; /*tag.extensionFields.varType = type;*/ /* FIXME: map to typeRef[1]? */ @@ -725,6 +726,18 @@ static void parseStatic (lexerState *lexer, vString *scope, int parent_kind) addTag(lexer->token_str, NULL, K_STATIC, lexer->line, lexer->pos, scope, parent_kind); } +/* Const format: + * "const" + */ +static void parseConst (lexerState *lexer, vString *scope, int parent_kind) +{ + advanceToken(lexer, true); + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, K_CONST, lexer->line, lexer->pos, scope, parent_kind); +} + /* Type format: * "type" */ @@ -918,6 +931,10 @@ static void parseBlock (lexerState *lexer, bool delim, int kind, vString *scope) { parseStatic(lexer, scope, kind); } + else if(strcmp(vStringValue(lexer->token_str), "const") == 0) + { + parseConst(lexer, scope, kind); + } else if(strcmp(vStringValue(lexer->token_str), "trait") == 0) { parseTrait(lexer, scope, kind); diff --git a/ctags/parsers/sh.c b/ctags/parsers/sh.c index b469bd2ac4..cbb48f2fe5 100644 --- a/ctags/parsers/sh.c +++ b/ctags/parsers/sh.c @@ -15,7 +15,9 @@ #include +#include "debug.h" #include "entry.h" +#include "keyword.h" #include "kind.h" #include "parse.h" #include "read.h" @@ -24,42 +26,105 @@ #include "vstring.h" #include "xtag.h" +#include "sh.h" + /* * DATA DEFINITIONS */ typedef enum { + K_SUBPARSER = -2, K_NOTHING = -1, /* place holder. Never appears on tags file. */ K_ALIAS, K_FUNCTION, - K_SOURCE, + K_SCRIPT, K_HEREDOCLABEL, } shKind; typedef enum { R_SCRIPT_LOADED, + LAST_R_SCRIPT, } shScriptRole; +typedef enum { + R_ZSH_SCRIPT_AUTOLOADED = LAST_R_SCRIPT, +} zshScriptRole; + +#define SH_SCRIPT_ROLES_COMMON { true, "loaded", "loaded" } static roleDefinition ShScriptRoles [] = { - { true, "loaded", "loaded" }, + SH_SCRIPT_ROLES_COMMON, +}; + +static roleDefinition ZshScriptRoles [] = { + SH_SCRIPT_ROLES_COMMON, + { true, "autoloaded", "autoloaded" }, }; typedef enum { R_HEREDOC_ENDMARKER, } shHeredocRole; +#define SH_HEREDOC_ROLES_COMMON { true, "endmarker", "end marker" } static roleDefinition ShHeredocRoles [] = { - { true, "endmarker", "end marker" }, + SH_HEREDOC_ROLES_COMMON, +}; + +static roleDefinition ZshHeredocRoles [] = { + SH_HEREDOC_ROLES_COMMON, +}; + +typedef enum { + R_ZSH_FUNCTION_AUTOLOADED, +} zshFunctionRole; + +static roleDefinition ZshFunctionRoles [] = { + { true, "autoloaded", "function name passed to autoload built-in command" }, }; +#define SH_KINDS_COMMON(SCRIPT_ROLES,HEREDOC_ROLES, FUNCTION_ROLES_SPEC) \ + { true, 'a', "alias", "aliases"}, \ + { true, 'f', "function", "functions", \ + .referenceOnly = false, FUNCTION_ROLES_SPEC }, \ + { true, 's', "script", "script files", \ + .referenceOnly = true, ATTACH_ROLES (SCRIPT_ROLES) }, \ + { true, 'h', "heredoc", "label for here document", \ + .referenceOnly = false, ATTACH_ROLES (HEREDOC_ROLES) } + static kindDefinition ShKinds [] = { - { true, 'a', "alias", "aliases"}, - { true, 'f', "function", "functions"}, - { true, 's', "script", "script files", - .referenceOnly = true, ATTACH_ROLES (ShScriptRoles) }, - { true, 'h', "heredoc", "label for here document", - .referenceOnly = false, ATTACH_ROLES (ShHeredocRoles) }, + SH_KINDS_COMMON(ShScriptRoles, ShHeredocRoles,), +}; + +static kindDefinition ZshKinds [] = { + SH_KINDS_COMMON(ZshScriptRoles, ZshHeredocRoles, + ATTACH_ROLES(ZshFunctionRoles)), +}; + +enum eShKeywordId { + KEYWORD_function, + KEYWORD_alias, + KEYWORD_source, + KEYWORD_autoload, +}; + +const char *dialectMap [] = { + "Sh", "Zsh", +}; + +static const struct dialectalKeyword KeywordTable [] = { + { "function", KEYWORD_function, { 1, 1 } }, + { "alias", KEYWORD_alias, { 1, 1 } }, + { "source", KEYWORD_source, { 1, 1 } }, + { "autoload", KEYWORD_autoload, { 0, 1 } }, }; +/* +* FUNCTION DECLARATIONS +*/ +static subparser *notifyLineToSubparsers (const unsigned char *cp, + int *n); +static int extractNameToSubparser (subparser *sub, const unsigned char *cp, + vString *name); +static int makeTagInSubparser (subparser *sub, vString *name); + /* * FUNCTION DEFINITIONS */ @@ -74,6 +139,11 @@ static bool isFileChar (int c) || c == '~'); } +static bool isIdentChar0 (int c) +{ + return (isalpha (c) || c == '_' || c == '-'); +} + static bool isIdentChar (int c) { return (isalnum (c) || c == '_' || c == '-'); @@ -124,7 +194,7 @@ static int readDestfileName (const unsigned char *cp, vString *destfile) { const unsigned char *origin = cp; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; /* >... */ @@ -135,17 +205,17 @@ static int readDestfileName (const unsigned char *cp, vString *destfile) if (*cp == '>') ++cp; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; - if (!isFileChar ((int) *cp)) + if (!isFileChar (*cp)) return 0; vStringClear(destfile); do { - vStringPut (destfile, (int) *cp); + vStringPut (destfile, *cp); ++cp; - } while (isFileChar ((int) *cp)); + } while (isFileChar (*cp)); if (vStringLength(destfile) > 0) return cp - origin; @@ -258,21 +328,201 @@ static int hdocStateReadDestfileName (struct hereDocParsingState *hstate, static void hdocStateUpdateTag (struct hereDocParsingState *hstate, unsigned long endLine) { - tagEntryInfo *tag = getEntryInCorkQueue (hstate->corkIndex); - if (tag) + setTagEndLineToCorkEntry (hstate->corkIndex, endLine); + hstate->corkIndex = CORK_NIL; +} + +static size_t handleShKeyword (int keyword, + vString *token, + int *kind, + int *role, + bool (** check_char)(int)) +{ + switch (keyword) { - tag->extensionFields.endLine = endLine; - hstate->corkIndex = CORK_NIL; + case KEYWORD_function: + *kind = K_FUNCTION; + break; + case KEYWORD_alias: + *kind = K_ALIAS; + *check_char = isIdentChar; + break; + case KEYWORD_source: + *kind = K_SCRIPT; + *role = R_SCRIPT_LOADED; + *check_char = isFileChar; + break; + default: + AssertNotReached(); + break; } + + return vStringLength(token); } -static void findShTags (void) +static int makeShAliasTag(vString *name, const unsigned char ** cp, const char *options) +{ + const unsigned char *p = *cp; + int r = CORK_NIL; + + const char *opt = vStringValue (name); + if (opt[0] == '-') + { + if (strpbrk(opt, options)) + return CORK_NIL; + + vStringClear(name); + + while (isspace (*p)) + p++; + + while (*p && isIdentChar (*p)) + vStringPut (name, *p++); + } + + if (!vStringIsEmpty(name) && *p == '=') + r = makeSimpleTag (name, K_ALIAS); + *cp = p; + return r; +} + +static int makeShTag (vString *name, const unsigned char ** cp, + int found_kind, int found_role) +{ + if (found_kind == K_ALIAS && found_role == ROLE_DEFINITION_INDEX) + /* -p is for printing defined aliases in bash. */ + return makeShAliasTag (name, cp, "p"); + + return makeSimpleRefTag (name, found_kind, found_role); +} + +static int makeZshAutoloadTag(vString *name, const unsigned char ** cp) +{ + const unsigned char *p = *cp; + + int r = CORK_NIL; + do + { + if (vStringChar(name, 0) != '-' && vStringChar(name, 0) != '+') + { + do + { + r = makeSimpleRefTag (name, K_SCRIPT, R_ZSH_SCRIPT_AUTOLOADED); + + tagEntryInfo e; + const char *func = strrchr(vStringValue (name), '/'); + func = func? func + 1: vStringValue (name); + if (*func != '\0') + { + initRefTagEntry (&e, func, K_FUNCTION, R_ZSH_FUNCTION_AUTOLOADED); + r = makeTagEntry(&e); + } + + while (isspace (*p)) + p++; + + if (*p == '\0') + break; + + vStringClear (name); + while (*p != '\0' && !isspace (*p)) + vStringPut (name, *p++); + } + while (1); + break; + } + + if (*p == '\0') + break; + + vStringClear(name); + while (*p != '\0' && !isspace(*p)) + vStringPut (name, *p++); + + while (isspace (*p)) + ++p; + } + while (1); + + *cp = p; + return r; +} + +static int makeZshFunctionTag(vString *name, const unsigned char ** cp) +{ + const unsigned char *p = *cp; + + int r = CORK_NIL; + + if (strcmp(vStringValue(name), "-T") == 0) + { + vStringClear(name); + + while (isspace (*p)) + p++; + + while (isBashFunctionChar (*p)) + { + vStringPut (name, *p); + ++p; + } + } + + if (!vStringIsEmpty(name)) + r = makeSimpleTag (name, K_FUNCTION); + *cp = p; + return r; +} + +static int makeZshTag (vString *name, const unsigned char ** cp, + int found_kind, int found_role) +{ + if (found_kind == K_SCRIPT && found_role == R_ZSH_SCRIPT_AUTOLOADED) + return makeZshAutoloadTag(name, cp); + + if (found_kind == K_FUNCTION && found_role == ROLE_DEFINITION_INDEX) + return makeZshFunctionTag(name, cp); + + if (found_kind == K_ALIAS && found_role == ROLE_DEFINITION_INDEX) + /* -m, -r, and -L are for printing defined aliases. */ + return makeShAliasTag (name, cp, "mrL"); + + return makeShTag (name, cp, found_kind, found_role); +} + +static size_t handleZshKeyword (int keyword, + vString *token, + int *kind, + int *role, + bool (** check_char)(int)) +{ + switch (keyword) + { + case KEYWORD_autoload: + *kind = K_SCRIPT; + *role = R_ZSH_SCRIPT_AUTOLOADED; + break; + default: + return handleShKeyword (keyword, token, kind, role, check_char); + } + + return vStringLength(token); +} + +typedef bool (* checkCharFunc) (int); +static void findShTagsCommon (size_t (* keyword_handler) (int, + vString *, + int *, + int *, + checkCharFunc *check_char), + int (* make_tag_handler) (vString *, const unsigned char **, int, int)) + { vString *name = vStringNew (); const unsigned char *line; vString *hereDocDelimiter = NULL; bool hereDocIndented = false; - bool (* check_char)(int); + checkCharFunc check_char; struct hereDocParsingState hstate; hdocStateInit (&hstate); @@ -280,7 +530,8 @@ static void findShTags (void) while ((line = readLineFromInputFile ()) != NULL) { const unsigned char* cp = line; - shKind found_kind = K_NOTHING; + int found_kind = K_NOTHING; + int found_role = ROLE_DEFINITION_INDEX; if (hereDocDelimiter) { @@ -307,8 +558,11 @@ static void findShTags (void) hdocStateClear (&hstate); while (*cp != '\0') { + subparser *sub = NULL; + int sub_n = 0; + /* jump over whitespace */ - while (isspace ((int)*cp)) + while (isspace (*cp)) cp++; /* jump over strings */ @@ -355,7 +609,7 @@ static void findShTags (void) } else { - while (isIdentChar ((int) *cp)) + while (isIdentChar (*cp)) cp++; end = cp; } @@ -382,80 +636,115 @@ static void findShTags (void) check_char = isBashFunctionChar; - if (strncmp ((const char*) cp, "function", (size_t) 8) == 0 && - isspace ((int) cp [8])) - { - found_kind = K_FUNCTION; - cp += 8; - } - else if (strncmp ((const char*) cp, "alias", (size_t) 5) == 0 && - isspace ((int) cp [5])) - { - check_char = isIdentChar; - found_kind = K_ALIAS; - cp += 5; - } - else if (cp [0] == '.' - && isspace((int) cp [1])) + if (cp [0] == '.' + && isspace(cp [1])) { - found_kind = K_SOURCE; + found_kind = K_SCRIPT; + found_role = R_SCRIPT_LOADED; ++cp; check_char = isFileChar; } - else if (strncmp ((const char*) cp, "source", (size_t) 6) == 0 - && isspace((int) cp [6])) + else if ((sub = notifyLineToSubparsers (cp, &sub_n))) { - found_kind = K_SOURCE; - cp += 6; - check_char = isFileChar; + found_kind = K_SUBPARSER; + cp += sub_n; + } + else + { + vString *token = vStringNew (); + const unsigned char *tmp = cp; + while (*tmp && (tmp == cp + ? (isIdentChar0 (*tmp)) + : (isIdentChar (*tmp)))) + { + vStringPut (token, *tmp); + tmp++; + } + + int keyword = lookupKeyword (vStringValue (token), + getInputLanguage ()); + if (keyword != KEYWORD_NONE) + cp += (* keyword_handler) (keyword, token, + &found_kind, &found_role, &check_char); + vStringDelete (token); } if (found_kind != K_NOTHING) - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; - // Get the name of the function, alias or file to be read by source - if (! check_char ((int) *cp)) + if (found_kind == K_SUBPARSER) { - found_kind = K_NOTHING; - - int d = hdocStateReadDestfileName (&hstate, cp, - hereDocDelimiter); - if (d > 0) - cp += d; - else if (*cp != '\0') - ++cp; - continue; + sub_n = extractNameToSubparser (sub, cp, name); + if (!vStringIsEmpty (name)) + cp += sub_n; } - while (check_char ((int) *cp)) + else { - vStringPut (name, (int) *cp); - ++cp; + // Get the name of the function, alias or file to be read by source + if (! check_char (*cp)) + { + found_kind = K_NOTHING; + found_role = ROLE_DEFINITION_INDEX; + + int d = hdocStateReadDestfileName (&hstate, cp, + hereDocDelimiter); + if (d > 0) + cp += d; + else if (*cp != '\0') + ++cp; + continue; + } + + while (check_char (*cp)) + { + vStringPut (name, *cp); + ++cp; + } } - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; - if ((found_kind != K_SOURCE) + if ((found_kind != K_SCRIPT) && *cp == '(') { ++cp; - while (isspace ((int) *cp)) + while (isspace (*cp)) ++cp; if (*cp == ')') { - found_kind = K_FUNCTION; + /* A function definiton can look like an array initialization: + * + * ... f=() + * + * We use followng heuristics to distinguish a function definition + * from an array initialization: + * + * preceding "function" keyword: function f=() + * followed by '{': f=() { + * + */ + if (! ((vStringLast(name) == '=') + /* Have we found "function" yet? */ + && (found_kind != K_FUNCTION) + && (strchr ((char *)(cp + 1), '{') == NULL))) + found_kind = K_FUNCTION; ++cp; } } if (found_kind != K_NOTHING) { - if (found_kind == K_SOURCE) - makeSimpleRefTag (name, K_SOURCE, R_SCRIPT_LOADED); + if (found_kind == K_SUBPARSER) + { + if (!vStringIsEmpty (name)) + makeTagInSubparser (sub, name); + } else - makeSimpleTag (name, found_kind); + make_tag_handler (name, &cp, found_kind, found_role); found_kind = K_NOTHING; + found_role = ROLE_DEFINITION_INDEX; } else if (!hereDocDelimiter) hdocStateUpdateArgs (&hstate, name); @@ -468,13 +757,83 @@ static void findShTags (void) vStringDelete (hereDocDelimiter); } +static void findShTags (void) +{ + findShTagsCommon (handleShKeyword, makeShTag); +} + +static void findZshTags (void) +{ + findShTagsCommon (handleZshKeyword, makeZshTag); +} + +static subparser *notifyLineToSubparsers (const unsigned char *cp, + int *n) +{ + int r = 0; + subparser *sub; + + foreachSubparser (sub, false) + { + shSubparser *shsub = (shSubparser *)sub; + + if(shsub->lineNotify) + { + enterSubparser(sub); + r = shsub->lineNotify (shsub, cp); + leaveSubparser(); + if (r > 0) + break; + } + } + + if (r > 0) + { + *n = r; + return sub; + } + return NULL; +} + +static int extractNameToSubparser(subparser *sub, const unsigned char *cp, + vString *name) +{ + int n; + shSubparser *shsub = (shSubparser *)sub; + + enterSubparser(sub); + n = shsub->extractName(shsub, cp, name); + leaveSubparser(); + + return n; +} + +static int makeTagInSubparser (subparser *sub, vString *name) +{ + int r; + shSubparser *shsub = (shSubparser *)sub; + + enterSubparser(sub); + r = shsub->makeTag(shsub, name); + leaveSubparser(); + + return r; +} + +static void initializeSh (const langType language) +{ + addDialectalKeywords (KeywordTable, ARRAY_SIZE (KeywordTable), + language, + dialectMap, ARRAY_SIZE (dialectMap)); +} + extern parserDefinition* ShParser (void) { static const char *const extensions [] = { - "sh", "SH", "bsh", "bash", "ksh", "zsh", "ash", NULL + "sh", "SH", "bsh", "bash", "ksh", "ash", NULL }; static const char *const aliases [] = { - "sh", "bash", "ksh", "zsh", "ash", + "sh", "bash", "ksh", "ash", "dash", /* major mode name in emacs */ "shell-script", NULL @@ -485,6 +844,26 @@ extern parserDefinition* ShParser (void) def->extensions = extensions; def->aliases = aliases; def->parser = findShTags; + def->initialize = initializeSh; + def->useCork = CORK_QUEUE; + return def; +} + +extern parserDefinition* ZshParser (void) +{ + static const char *const extensions [] = { + "zsh", NULL + }; + static const char *const aliases [] = { + "zsh", NULL, + }; + parserDefinition* def = parserNew ("Zsh"); + def->kindTable = ZshKinds; + def->kindCount = ARRAY_SIZE (ZshKinds); + def->extensions = extensions; + def->aliases = aliases; + def->parser = findZshTags; + def->initialize = initializeSh; def->useCork = CORK_QUEUE; return def; } diff --git a/ctags/parsers/sh.h b/ctags/parsers/sh.h new file mode 100644 index 0000000000..9fa3455084 --- /dev/null +++ b/ctags/parsers/sh.h @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2022, Masatake YAMATO +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +*/ + +#ifndef CTAGS_PARSER_SH_H +#define CTAGS_PARSER_SH_H + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include "subparser.h" +#include "vstring.h" + +typedef struct sShSubparser shSubparser; + +struct sShSubparser { + subparser subparser; + + /* Scan the line pointed by CP and return the number of + * consumed bytes if something interesting is found. + * Return 0 if no interest. + */ + int (* lineNotify) (shSubparser *s, const unsigned char *cp); + + /* Extract the interesting item from CP and store it to NAME. + * Return the number of consumed bytes during extracting. + */ + int (* extractName) (shSubparser *s, const unsigned char *cp, + vString *name); + + /* Make a tag for NAME. + * Return the cork index for the tag. + */ + int (* makeTag) (shSubparser *s, vString *name); +}; + +#endif /* CTAGS_PARSER_SH_H */ diff --git a/ctags/parsers/sql.c b/ctags/parsers/sql.c index 2101d9eea2..395c012953 100644 --- a/ctags/parsers/sql.c +++ b/ctags/parsers/sql.c @@ -74,10 +74,12 @@ enum eKeywordId { KEYWORD_call, KEYWORD_case, KEYWORD_check, + KEYWORD_commit, KEYWORD_comment, KEYWORD_constraint, KEYWORD_create, KEYWORD_cursor, + KEYWORD_database, KEYWORD_datatype, KEYWORD_declare, KEYWORD_do, @@ -85,6 +87,7 @@ enum eKeywordId { KEYWORD_drop, KEYWORD_else, KEYWORD_elseif, + KEYWORD_elsif, KEYWORD_end, KEYWORD_endif, KEYWORD_event, @@ -130,6 +133,7 @@ enum eKeywordId { KEYWORD_result, KEYWORD_return, KEYWORD_returns, + KEYWORD_schema, KEYWORD_select, KEYWORD_service, KEYWORD_subtype, @@ -174,7 +178,12 @@ typedef enum eTokenType { TOKEN_CLOSE_SQUARE, TOKEN_TILDE, TOKEN_FORWARD_SLASH, - TOKEN_EQUAL + TOKEN_EQUAL, + TOKEN_PREPROC_IF, + TOKEN_PREPROC_ELSIF, + TOKEN_PREPROC_ELSE, + TOKEN_PREPROC_THEN, + TOKEN_PREPROC_END, } tokenType; typedef struct sTokenInfoSQL { @@ -211,58 +220,62 @@ typedef struct sTokenInfoSQL { static langType Lang_sql; typedef enum { + SQLTAG_PLSQL_CCFLAGS, + SQLTAG_DOMAIN, + SQLTAG_FIELD, + SQLTAG_BLOCK_LABEL, + SQLTAG_PACKAGE, + SQLTAG_SERVICE, + SQLTAG_SCHEMA, + SQLTAG_TRIGGER, + SQLTAG_PUBLICATION, + SQLTAG_VIEW, + SQLTAG_DATABASE, SQLTAG_CURSOR, SQLTAG_PROTOTYPE, + SQLTAG_EVENT, SQLTAG_FUNCTION, - SQLTAG_FIELD, + SQLTAG_INDEX, SQLTAG_LOCAL_VARIABLE, - SQLTAG_BLOCK_LABEL, - SQLTAG_PACKAGE, + SQLTAG_SYNONYM, SQLTAG_PROCEDURE, SQLTAG_RECORD, SQLTAG_SUBTYPE, SQLTAG_TABLE, - SQLTAG_TRIGGER, SQLTAG_VARIABLE, - SQLTAG_INDEX, - SQLTAG_EVENT, - SQLTAG_PUBLICATION, - SQLTAG_SERVICE, - SQLTAG_DOMAIN, - SQLTAG_VIEW, - SQLTAG_SYNONYM, SQLTAG_MLTABLE, SQLTAG_MLCONN, SQLTAG_MLPROP, - SQLTAG_PLSQL_CCFLAGS, SQLTAG_COUNT } sqlKind; static kindDefinition SqlKinds [] = { + { true, 'C', "ccflag", "PLSQL_CCFLAGS" }, + { true, 'D', "domain", "domains" }, + { true, 'E', "field", "record fields" }, + { true, 'L', "label", "block label" }, + { true, 'P', "package", "packages" }, + { true, 'R', "service", "services" }, + { true, 'S', "schema", "schemas" }, + { true, 'T', "trigger", "triggers" }, + { true, 'U', "publication", "publications" }, + { true, 'V', "view", "views" }, + { true, 'b', "database", "database" }, { true, 'c', "cursor", "cursors" }, { false, 'd', "prototype", "prototypes" }, + { true, 'e', "event", "events" }, { true, 'f', "function", "functions" }, - { true, 'E', "field", "record fields" }, + { true, 'i', "index", "indexes" }, { false, 'l', "local", "local variables" }, - { true, 'L', "label", "block label" }, - { true, 'P', "package", "packages" }, + { true, 'n', "synonym", "synonyms" }, { true, 'p', "procedure", "procedures" }, { false, 'r', "record", "records" }, { true, 's', "subtype", "subtypes" }, { true, 't', "table", "tables" }, - { true, 'T', "trigger", "triggers" }, { true, 'v', "variable", "variables" }, - { true, 'i', "index", "indexes" }, - { true, 'e', "event", "events" }, - { true, 'U', "publication", "publications" }, - { true, 'R', "service", "services" }, - { true, 'D', "domain", "domains" }, - { true, 'V', "view", "views" }, - { true, 'n', "synonym", "synonyms" }, { true, 'x', "mltable", "MobiLink Table Scripts" }, { true, 'y', "mlconn", "MobiLink Conn Scripts" }, { true, 'z', "mlprop", "MobiLink Properties" }, - { true, 'C', "ccflag", "PLSQL_CCFLAGS" }, }; static const keywordTable SqlKeywordTable [] = { @@ -274,10 +287,12 @@ static const keywordTable SqlKeywordTable [] = { { "call", KEYWORD_call }, { "case", KEYWORD_case }, { "check", KEYWORD_check }, + { "commit", KEYWORD_commit }, { "comment", KEYWORD_comment }, { "constraint", KEYWORD_constraint }, { "create", KEYWORD_create }, { "cursor", KEYWORD_cursor }, + { "database", KEYWORD_database }, { "datatype", KEYWORD_datatype }, { "declare", KEYWORD_declare }, { "do", KEYWORD_do }, @@ -285,6 +300,7 @@ static const keywordTable SqlKeywordTable [] = { { "drop", KEYWORD_drop }, { "else", KEYWORD_else }, { "elseif", KEYWORD_elseif }, + { "elsif", KEYWORD_elsif }, { "end", KEYWORD_end }, { "endif", KEYWORD_endif }, { "event", KEYWORD_event }, @@ -329,6 +345,7 @@ static const keywordTable SqlKeywordTable [] = { { "result", KEYWORD_result }, { "return", KEYWORD_return }, { "returns", KEYWORD_returns }, + { "schema", KEYWORD_schema }, { "select", KEYWORD_select }, { "service", KEYWORD_service }, { "subtype", KEYWORD_subtype }, @@ -348,7 +365,7 @@ static const keywordTable SqlKeywordTable [] = { { "without", KEYWORD_without }, }; -const static struct keywordGroup predefinedInquiryDirective = { +static const struct keywordGroup predefinedInquiryDirective = { .value = KEYWORD_inquiry_directive, .addingUnlessExisting = false, .keywords = { @@ -393,12 +410,14 @@ struct SqlReservedWord { * ORACLE11g, PLSQL * => https://docs.oracle.com/cd/B28359_01/appdev.111/b31231/appb.htm#CJHIIICD * SQLANYWERE - * => http://dcx.sap.com/1200/en/dbreference/alhakeywords.html + * => http://dcx.sap.com/1200/en/dbreference/alhakeywords.html */ static bool SqlReservedWordPredicatorForIsOrAs (tokenInfo *const token); static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { /* * RESERVED_BIT: MYSQL & POSTGRESQL&SQL2016&SQL2011&SQL92 & ORACLE11g&PLSQL & SQLANYWERE + * + * { 0 } means we have not inspect whether the keyword is reserved or not. */ [KEYWORD_at] = {0 & 0&1&1&1 & 0&1 & 0}, [KEYWORD_begin] = {0 & 0&1&1&1 & 0&1 & 1}, @@ -406,10 +425,12 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { [KEYWORD_call] = {1 & 0&1&1&0 & 0&0 & 1}, [KEYWORD_case] = {1 & 1&1&1&1 & 0&1 & 1}, [KEYWORD_check] = {1 & 1&1&1&1 & 1&1 & 1}, + [KEYWORD_commit] = {0 & 0&1&1&1 & 0&0 & 0}, /* SQLANYWERE:??? */ [KEYWORD_comment] = {0 & 0&0&0&0 & 1&1 & 1}, [KEYWORD_constraint] = {1 & 1&1&1&1 & 0&1 & 1}, [KEYWORD_create] = {1 & 1&1&1&1 & 1&1 & 1}, [KEYWORD_cursor] = {1 & 0&1&1&1 & 0&1 & 1}, + [KEYWORD_database] = { 0 }, [KEYWORD_datatype] = {0 & 0&0&0&0 & 0&0 & 0}, [KEYWORD_declare] = {1 & 0&1&1&1 & 0&1 & 1}, [KEYWORD_do] = {0 & 1&0&0&0 & 0&1 & 1}, @@ -417,6 +438,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { [KEYWORD_drop] = {1 & 0&1&1&1 & 1&1 & 1}, [KEYWORD_else] = {1 & 1&1&1&1 & 1&1 & 1}, [KEYWORD_elseif] = {1 & 0&0&0&0 & 0&0 & 1}, + [KEYWORD_elsif] = {0 & 0&0&0&0 & 0&1 & 0}, [KEYWORD_end] = {0 & 1&1&1&1 & 0&1 & 1}, [KEYWORD_endif] = {0 & 0&0&0&0 & 0&0 & 1}, [KEYWORD_event] = {0 & 0&0&0&0 & 0&0 & 0}, @@ -462,6 +484,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { [KEYWORD_result] = {0 & 0&1&1&0 & 0&0 & 0}, [KEYWORD_return] = {1 & 0&1&1&0 & 0&1 & 1}, [KEYWORD_returns] = {0 & 0&0&0&0 & 0&0 & 0}, + [KEYWORD_schema] = {0 & 0&0&0&0 & 0&0 & 0}, [KEYWORD_select] = {1 & 1&1&1&1 & 1&1 & 1}, [KEYWORD_service] = {0 & 0&0&0&0 & 0&0 & 0}, [KEYWORD_subtype] = {0 & 0&0&0&0 & 0&1 & 0}, @@ -489,7 +512,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { static void parseBlock (tokenInfo *const token, const bool local); static void parseBlockFull (tokenInfo *const token, const bool local, langType lang); static void parseDeclare (tokenInfo *const token, const bool local); -static void parseKeywords (tokenInfo *const token); +static void parseKeywords (tokenInfo *const token, enum eKeywordId precedingKeyword); static tokenType parseSqlFile (tokenInfo *const token); /* @@ -599,8 +622,7 @@ static void makeSqlTag (tokenInfo *const token, const sqlKind kind) tagEntryInfo e; initTagEntry (&e, name, kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); if (vStringLength (token->scope) > 0) { @@ -701,7 +723,7 @@ static void parseIdentifier (vString *const string, const int firstChar) static bool isCCFlag(const char *str) { - return (anyKindEntryInScope(CORK_NIL, str, SQLTAG_PLSQL_CCFLAGS) != 0); + return (anyKindEntryInScope(CORK_NIL, str, SQLTAG_PLSQL_CCFLAGS, false) != 0); } /* Parse a PostgreSQL: dollar-quoted string @@ -710,6 +732,10 @@ static bool isCCFlag(const char *str) * The syntax for dollar-quoted string ca collide with PL/SQL inquiry directive ($$name). * https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-E918087C-D5A8-4CEE-841B-5333DE6D4C15 * https://github.com/universal-ctags/ctags/issues/3006 + + * In addition, it can also collide with variable checks in PL/SQL selection directives such as: + * $IF $$my_var > 1 $THEN ... $END + * https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-language-fundamentals.html#GUID-78F2074C-C799-4CF9-9290-EB8473D0C8FB */ static tokenType parseDollarQuote (vString *const string, const int delimiter, int *promise) { @@ -737,8 +763,20 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i if (c != delimiter) { - /* damn that's not valid, what can we do? */ + /* not a dollar quote */ + keywordId kw = lookupCaseKeyword (tag+1, Lang_sql); ungetcToInputFile (c); + + if (kw == KEYWORD_if) + return TOKEN_PREPROC_IF; + else if (kw == KEYWORD_elsif) + return TOKEN_PREPROC_ELSIF; + else if (kw == KEYWORD_else) + return TOKEN_PREPROC_ELSE; + else if (kw == KEYWORD_then) + return TOKEN_PREPROC_THEN; + else if (kw == KEYWORD_end) + return TOKEN_PREPROC_END; return TOKEN_UNDEFINED; } @@ -813,7 +851,7 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i return TOKEN_STRING; } -static void readToken (tokenInfo *const token) +static void readTokenFull (tokenInfo *const token, bool skippingPreproc) { int c; @@ -937,10 +975,39 @@ static void readToken (tokenInfo *const token) } case '$': - token->type = parseDollarQuote (token->string, c, &token->promise); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - break; + { + tokenType t; + if (skippingPreproc) + { + int d = getcFromInputFile (); + if (d != '$') + ungetcToInputFile (d); + } + t = parseDollarQuote (token->string, c, &token->promise); + if (t == TOKEN_PREPROC_IF) + { + /* skip until $THEN and keep the content of this branch */ + readTokenFull (token, true); + while (!isType (token, TOKEN_PREPROC_THEN) && !isType (token, TOKEN_EOF)) + readTokenFull (token, true); + readTokenFull (token, false); + } + else if (!skippingPreproc && (t == TOKEN_PREPROC_ELSIF || t == TOKEN_PREPROC_ELSE)) + { + /* skip until $END and drop $ELSIF and $ELSE branches */ + readTokenFull (token, true); + while (!isType (token, TOKEN_PREPROC_END) && !isType (token, TOKEN_EOF)) + readTokenFull (token, true); + readTokenFull (token, false); + } + else + { + token->type = t; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + break; + } default: if (! isIdentChar1 (c)) @@ -966,6 +1033,11 @@ static void readToken (tokenInfo *const token) } } +static void readToken (tokenInfo *const token) +{ + readTokenFull (token, false); +} + /* * reads an identifier, possibly quoted: * identifier @@ -1003,11 +1075,7 @@ static void readIdentifier (tokenInfo *const token) static void addToScope (tokenInfo* const token, vString* const extra, sqlKind kind) { - if (vStringLength (token->scope) > 0) - { - vStringPut (token->scope, '.'); - } - vStringCat (token->scope, extra); + vStringJoin (token->scope, '.', extra); token->scopeKind = kind; } @@ -1530,14 +1598,7 @@ static void parseDeclare (tokenInfo *const token, const bool local) default: if (isType (token, TOKEN_IDENTIFIER)) { - if (local) - { - makeSqlTag (token, SQLTAG_LOCAL_VARIABLE); - } - else - { - makeSqlTag (token, SQLTAG_VARIABLE); - } + makeSqlTag (token, local? SQLTAG_LOCAL_VARIABLE: SQLTAG_VARIABLE); } break; } @@ -1590,10 +1651,7 @@ static void parseDeclareANSI (tokenInfo *const token, const bool local) else if (isType (token, TOKEN_IDENTIFIER) || isType (token, TOKEN_STRING)) { - if (local) - makeSqlTag (token, SQLTAG_LOCAL_VARIABLE); - else - makeSqlTag (token, SQLTAG_VARIABLE); + makeSqlTag (token, local? SQLTAG_LOCAL_VARIABLE: SQLTAG_VARIABLE); } findToken (token, TOKEN_SEMICOLON); readToken (token); @@ -1851,7 +1909,7 @@ static void parseStatements (tokenInfo *const token, const bool exit_on_endif ) case KEYWORD_create: readToken (token); - parseKeywords(token); + parseKeywords(token, KEYWORD_create); break; case KEYWORD_declare: @@ -2005,15 +2063,51 @@ static void parseBlockFull (tokenInfo *const token, const bool local, langType l } if (isKeyword (token, KEYWORD_begin)) { + bool is_transaction = false; + readToken (token); - /* - * Check for ANSI declarations which always follow - * a BEGIN statement. This routine will not advance - * the token if none are found. + + /* BEGIN of Postgresql initiates a transaction. + * + * BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ] + * + * BEGIN of MySQL does the same. + * + * BEGIN [WORK] + * + * BEGIN of SQLite does the same. + * + * BEGIN [[DEFERRED | IMMEDIATE | EXCLUSIVE] TRANSACTION] + * */ - parseDeclareANSI (token, local); + if (isCmdTerm(token)) + { + is_transaction = true; + readToken (token); + } + else if (isType (token, TOKEN_IDENTIFIER) + && (strcasecmp (vStringValue(token->string), "work") == 0 + || strcasecmp (vStringValue(token->string), "transaction") == 0 + || ( + strcasecmp (vStringValue(token->string), "deferred") == 0 + || strcasecmp (vStringValue(token->string), "immediate") == 0 + || strcasecmp (vStringValue(token->string), "exclusive") == 0 + ) + )) + is_transaction = true; + else + { + /* + * Check for ANSI declarations which always follow + * a BEGIN statement. This routine will not advance + * the token if none are found. + */ + parseDeclareANSI (token, local); + } + token->begin_end_nest_lvl++; while (! isKeyword (token, KEYWORD_end) && + ! (is_transaction && isKeyword(token, KEYWORD_commit)) && ! isType (token, TOKEN_EOF)) { parseStatements (token, false); @@ -2056,7 +2150,7 @@ static void parsePackage (tokenInfo *const token) * or by specifying a package body * CREATE OR REPLACE PACKAGE BODY pkg_name AS * CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS - */ + */ tokenInfo *const name = newToken (); readIdentifier (name); if (isKeyword (name, KEYWORD_body)) @@ -2179,6 +2273,44 @@ static void parseColumnsAndAliases (tokenInfo *const token) deleteToken (lastId); } +/* Skip "IF NOT EXISTS" + * https://dev.mysql.com/doc/refman/8.0/en/create-table.html + * https://www.postgresql.org/docs/current/sql-createtable.html + * https://sqlite.org/lang_createtable.html + */ +static bool parseIdAfterIfNotExists(tokenInfo *const name, + tokenInfo *const token, + bool authorization_following) +{ + if (isKeyword (name, KEYWORD_if) + && (isType (token, TOKEN_IDENTIFIER) + && vStringLength (token->string) == 3 + && strcasecmp ("not", vStringValue (token->string)) == 0)) + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER) + && vStringLength (token->string) == 6 + && strcasecmp ("exists", vStringValue (token->string)) == 0) + { + readIdentifier (name); + if (authorization_following + && isType (name, TOKEN_IDENTIFIER) + && vStringLength (name->string) == 13 + && strcasecmp("authorization", vStringValue(name->string)) == 0) + { + /* + * PostgreSQL: + * - CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_specification + */ + readIdentifier (name); + } + readToken (token); + return true; + } + } + return false; +} + static void parseTable (tokenInfo *const token) { tokenInfo *const name = newToken (); @@ -2217,25 +2349,7 @@ static void parseTable (tokenInfo *const token) readIdentifier (name); readToken (token); - /* Skip "IF NOT EXISTS" - * https://dev.mysql.com/doc/refman/8.0/en/create-table.html - * https://www.postgresql.org/docs/current/sql-createtable.html - * https://sqlite.org/lang_createtable.html - */ - if (isKeyword (name, KEYWORD_if) - && (isType (token, TOKEN_IDENTIFIER) - && vStringLength (token->string) == 3 - && strcasecmp ("not", vStringValue (token->string)) == 0)) - { - readToken (token); - if (isType (token, TOKEN_IDENTIFIER) - && vStringLength (token->string) == 6 - && strcasecmp ("exists", vStringValue (token->string)) == 0) - { - readIdentifier (name); - readToken (token); - } - } + parseIdAfterIfNotExists(name, token, false); if (isType (token, TOKEN_PERIOD)) { @@ -2633,10 +2747,15 @@ static void parseView (tokenInfo *const token) * create view VIEW; * create view VIEW as ...; * create view VIEW (...) as ...; + * create view if not exists VIEW as ...; + * -- [SQLITE] https://www.sqlite.org/lang_createview.html */ readIdentifier (name); readToken (token); + + parseIdAfterIfNotExists(name, token, false); + if (isType (token, TOKEN_PERIOD)) { readIdentifier (name); @@ -2891,7 +3010,7 @@ static void parseCCFLAGS (tokenInfo *const token) /* http://web.deu.edu.tr/doc/oracle/B19306_01/server.102/b14237/initparams158.htm#REFRN10261 */ while (*s) { - if (in_var && isIdentChar1((int)*s)) + if (in_var && isIdentChar1((unsigned char) *s)) vStringPut(ccflag, *s); else if (*s == ':' && !vStringIsEmpty(ccflag)) { @@ -2912,7 +3031,71 @@ static void parseCCFLAGS (tokenInfo *const token) } -static void parseKeywords (tokenInfo *const token) +static void parseDatabase (tokenInfo *const token, enum eKeywordId keyword) +{ + tokenInfo * name; + + /* + * In MySQL and HPL/SQL, "CREATE DATABASE" and "CREATE SCHEMA" + * are the same. However, In PostgreSQL, they are different. + * Too support PostgreSQL, we prepare different kinds for them. + * + * MySQL + * A. CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name ...; + * + * PostgreSQL + * + * B. CREATE DATABASE name ...; + * + * C. CREATE SCHEMA schema_name [ AUTHORIZATION role_specification ] [ schema_element [ ... ] ] + * D. CREATE SCHEMA AUTHORIZATION role_specification [ schema_element [ ... ] ] + * E. CREATE SCHEMA IF NOT EXISTS schema_name [ AUTHORIZATION role_specification ] + * F. CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_specification + * + * HPL/SQL + * G. CREATE DATABASE | SCHEMA [IF NOT EXISTS] dbname_expr...; + */ + readIdentifier (token); + if (keyword == KEYWORD_schema + && isType (token, TOKEN_IDENTIFIER) + && vStringLength (token->string) == 13 + && strcasecmp("authorization", vStringValue(token->string)) == 0) + { + /* D. */ + readIdentifier (token); + makeSqlTag (token, SQLTAG_SCHEMA); + findCmdTerm (token, false); + return; + } + + name = newToken (); + copyToken (name, token); + readIdentifier (token); + parseIdAfterIfNotExists (name, token, true); + + makeSqlTag (name, + keyword == KEYWORD_database + ? SQLTAG_DATABASE: SQLTAG_SCHEMA); + deleteToken (name); + + /* TODO: + * + * In PostgreSQL, CREATE FOO can follow to CREATE SCHEMA like: + * + * -- https://www.postgresql.org/docs/current/sql-createschema.html + * + * CREATE SCHEMA hollywood + * CREATE TABLE films (title text, release date, awards text[]) + * CREATE VIEW winners AS + * SELECT title, release FROM films WHERE awards IS NOT NULL; + * + * In above example, "hollywood.films" and "hollywood.winners" should be + * tagged. + */ + findCmdTerm (token, true); +} + +static void parseKeywords (tokenInfo *const token, enum eKeywordId precedingKeyword) { switch (token->keyword) { @@ -2923,6 +3106,10 @@ static void parseKeywords (tokenInfo *const token) break; case KEYWORD_comment: parseComment (token); break; case KEYWORD_cursor: parseSimple (token, SQLTAG_CURSOR); break; + case KEYWORD_database: + if (precedingKeyword == KEYWORD_create) + parseDatabase (token, KEYWORD_database); + break; case KEYWORD_datatype: parseDomain (token); break; case KEYWORD_declare: parseBlock (token, false); break; case KEYWORD_domain: parseDomain (token); break; @@ -2946,6 +3133,10 @@ static void parseKeywords (tokenInfo *const token) case KEYWORD_package: parsePackage (token); break; case KEYWORD_procedure: parseSubProgram (token); break; case KEYWORD_publication: parsePublication (token); break; + case KEYWORD_schema: + if (precedingKeyword == KEYWORD_create) + parseDatabase (token, KEYWORD_schema); + break; case KEYWORD_service: parseService (token); break; case KEYWORD_subtype: parseSimple (token, SQLTAG_SUBTYPE); break; case KEYWORD_synonym: parseSynonym (token); break; @@ -2964,12 +3155,13 @@ static tokenType parseSqlFile (tokenInfo *const token) { do { + enum eKeywordId k = token->keyword; readToken (token); if (isType (token, TOKEN_BLOCK_LABEL_BEGIN)) parseLabel (token); else - parseKeywords (token); + parseKeywords (token, k); } while (! isKeyword (token, KEYWORD_end) && ! isType (token, TOKEN_EOF)); diff --git a/ctags/parsers/tcl.c b/ctags/parsers/tcl.c index 944e054d51..9fb8cfe5ab 100644 --- a/ctags/parsers/tcl.c +++ b/ctags/parsers/tcl.c @@ -177,7 +177,7 @@ static void readIdentifier (vString *string) while (1) { int c = getcFromInputFile (); - if (isgraph (c) && (!strchr ("{}[]", c))) + if (isgraph (c) && (!strchr ("{}[]\"", c))) vStringPut (string, c); else { @@ -466,8 +466,7 @@ static void parseProc (tokenInfo *const token, tagEntryInfo e; initTagEntry (&e, last, K_PROCEDURE); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); int len = (last - tokenString (token)); vString *ns = vStringNew(); @@ -557,7 +556,7 @@ static void parseProc (tokenInfo *const token, tagEntryInfo *e = getEntryInCorkQueue (index); if (e) { - e->extensionFields.endLine = token->lineNumber; + setTagEndLine (e, token->lineNumber); if (signature) { @@ -569,7 +568,7 @@ static void parseProc (tokenInfo *const token, if (e_fq) { const char *sig = e->extensionFields.signature; - e_fq->extensionFields.endLine = token->lineNumber; + setTagEndLine (e_fq, token->lineNumber); if (sig) e_fq->extensionFields.signature = eStrdup (sig); } @@ -632,13 +631,14 @@ static void parseNamespace (tokenInfo *const token, parseProc (token, index); else if (tokenIsType (token, TCL_IDENTIFIER)) { - notifyCommand (token, index); - skipToEndOfCmdline(token); /* ??? */ + int r = notifyCommand (token, index); + if (r == CORK_NIL) + skipToEndOfCmdline(token); } else if (token->type == '}') { if (e) - e->extensionFields.endLine = token->lineNumber; + setTagEndLine (e, token->lineNumber); break; } else @@ -682,8 +682,9 @@ static void findTclTags (void) parsePackage (token); else if (tokenIsType (token, TCL_IDENTIFIER)) { - notifyCommand (token, CORK_NIL); - skipToEndOfCmdline(token); /* ??? */ + int r = notifyCommand (token, CORK_NIL); + if (r == CORK_NIL) + skipToEndOfCmdline(token); } else skipToEndOfCmdline(token); diff --git a/ctags/parsers/tcloo.c b/ctags/parsers/tcloo.c index f0dd7e09ae..ae3338b837 100644 --- a/ctags/parsers/tcloo.c +++ b/ctags/parsers/tcloo.c @@ -163,16 +163,17 @@ static void findTclOOTags(void) scheduleRunningBaseparser (RUN_DEFAULT_SUBPARSERS); } -static void tclooForceUseParamHandler (const langType language CTAGS_ATTR_UNUSED, +static bool tclooForceUseParamHandler (const langType language CTAGS_ATTR_UNUSED, const char *name, const char *arg) { tclooForceUse = paramParserBool (arg, tclooForceUse, name, "parameter"); + return true; } -static parameterHandlerTable TclOOParameterHandlerTable [] = { +static paramDefinition TclOOParams [] = { { .name = "forceUse", .desc = "enable the parser even when `oo' namespace is not specified in the input (true or [false])" , - .handleParameter = tclooForceUseParamHandler, + .handleParam = tclooForceUseParamHandler, }, }; @@ -194,8 +195,8 @@ extern parserDefinition* TclOOParser (void) def->useCork = CORK_QUEUE; def->requestAutomaticFQTag = true; - def->parameterHandlerTable = TclOOParameterHandlerTable; - def->parameterHandlerCount = ARRAY_SIZE(TclOOParameterHandlerTable); + def->paramTable = TclOOParams; + def->paramCount = ARRAY_SIZE(TclOOParams); return def; } diff --git a/ctags/parsers/tex.c b/ctags/parsers/tex.c index ac58cf9df8..bf9ecdb556 100644 --- a/ctags/parsers/tex.c +++ b/ctags/parsers/tex.c @@ -341,8 +341,7 @@ static int makeTexTag (tokenInfo *const token, int kind, tagEntryInfo e; initTagEntry (&e, name, kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); vString *parentName = NULL; @@ -603,11 +602,7 @@ static bool parseWithStrategy (tokenInfo *token, while (! isType (token, terminator)) { if (capture_name && isType (token, TOKEN_IDENTIFIER)) - { - if (vStringLength (name->string) > 0) - vStringPut (name->string, ' '); - vStringCat (name->string, token->string); - } + vStringJoin (name->string, ' ', token->string); if (!readTokenFull (token, s->flags & TEX_NAME_FLAG_INCLUDING_WHITESPACE)) diff --git a/ctags/parsers/typescript.c b/ctags/parsers/typescript.c index 7dcc338ef9..0e04b1b695 100644 --- a/ctags/parsers/typescript.c +++ b/ctags/parsers/typescript.c @@ -278,8 +278,7 @@ static int emitTag(const tokenInfo *const token, const tsKind kind) tagEntryInfo e; initTagEntry (&e, name, kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); e.extensionFields.scopeIndex = token->scope; switch (token->accessKeyword) @@ -1664,11 +1663,7 @@ static void parseClassBody (const int scope, tokenInfo *const token) if (token->type == TOKEN_KEYWORD && (token->keyword == KEYWORD_extends || token->keyword == KEYWORD_implements) && inheritance == NULL) inheritance = vStringNew (); - else if (inheritance && token->type == TOKEN_IDENTIFIER) - { - if (!vStringIsEmpty (inheritance)) vStringPut(inheritance, ','); - vStringCat(inheritance, token->string); - } + else if (inheritance && token->type == TOKEN_IDENTIFIER) vStringJoin (inheritance, ',', token->string); } while (parsed && token->type != TOKEN_OPEN_CURLY); if (! parsed) @@ -1820,8 +1815,11 @@ static void parseClassBody (const int scope, tokenInfo *const token) parsingValue = false; break; case TOKEN_OPEN_PAREN: - if (! member) break; uwiUngetC ('('); + if (! member) { + parsed = tryParser (parseParens, token, false); + break; + } const int nscope = emitTag (member, isGenerator ? TSTAG_GENERATOR : TSTAG_METHOD); diff --git a/ctags/parsers/verilog.c b/ctags/parsers/verilog.c index 25a9ba8d58..0b107687b7 100644 --- a/ctags/parsers/verilog.c +++ b/ctags/parsers/verilog.c @@ -1,27 +1,27 @@ /* - * Copyright (c) 2003, Darren Hiebert - * Copyright (c) 2017, Vitor Antunes - * Copyright (c) 2020, Hiroo Hayashi + * Copyright (c) 2003, Darren Hiebert + * Copyright (c) 2017, Vitor Antunes + * Copyright (c) 2020, Hiroo Hayashi * - * This source code is released for free distribution under the terms of the - * GNU General Public License version 2 or (at your option) any later version. + * This source code is released for free distribution under the terms of the + * GNU General Public License version 2 or (at your option) any later version. * - * This module contains functions for generating tags for the Verilog or - * SystemVerilog HDL (Hardware Description Language). + * This module contains functions for generating tags for the Verilog or + * SystemVerilog HDL (Hardware Description Language). * - * References: - * IEEE Std 1800-2017, SystemVerilog Language Reference Manual - * https://ieeexplore.ieee.org/document/8299595 - * SystemVerilog IEEE Std 1800-2012 Grammer - * https://insights.sigasi.com/tech/systemverilog.ebnf/ - * Verilog Formal Syntax Specification - * http://www.verilog.com/VerilogBNF.html + * References: + * IEEE Std 1800-2017, SystemVerilog Language Reference Manual + * https://ieeexplore.ieee.org/document/8299595 + * SystemVerilog IEEE Std 1800-2012 Grammer + * https://insights.sigasi.com/tech/systemverilog.ebnf/ + * Verilog Formal Syntax Specification + * http://www.verilog.com/VerilogBNF.html */ /* - * INCLUDE FILES + * INCLUDE FILES */ -#include "general.h" /* must always come first */ +#include "general.h" /* must always come first */ #include @@ -32,18 +32,31 @@ #include "parse.h" #include "read.h" #include "routines.h" +#include "selectors.h" #include "xtag.h" #include "ptrarray.h" /* - * MACROS + * MACROS */ -#define NUMBER_LANGUAGES 2 /* Indicates number of defined indexes */ -#define IDX_SYSTEMVERILOG 0 -#define IDX_VERILOG 1 +#define NUMBER_LANGUAGES 2 /* Indicates number of defined indexes */ +#define IDX_SYSTEMVERILOG 0 +#define IDX_VERILOG 1 + +#ifndef DEBUG +#define VERBOSE(...) do { \ + verbose("%s:%ld:%s:%d:Internal Error:", getInputFileName(), getInputLineNumber(), __FILE__, __LINE__); \ + verbose(__VA_ARGS__); \ + } while (0) +#else +#define VERBOSE(...) do { \ + fprintf(stderr, "%s:%ld:%s:%d:Internal Error:", getInputFileName(), getInputLineNumber(), __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) +#endif /* - * DATA DECLARATIONS + * DATA DECLARATIONS */ /* A callback function searching a symbol from the cork symbol table assumes @@ -53,7 +66,6 @@ typedef enum { /* parser private items */ K_IGNORE = -16, /* Verilog/SystemVerilog keywords to be ignored */ - K_DEFINE, K_DIRECTIVE, K_END, K_END_DE, /* End of Design Elements */ @@ -66,6 +78,7 @@ typedef enum { K_UNDEFINED = KEYWORD_NONE, /* the followings items are also used as indices for VerilogKinds[] and SystemVerilogKinds[] */ K_CONSTANT= 0, + K_DEFINE, K_EVENT, K_FUNCTION, K_MODULE, @@ -94,8 +107,13 @@ typedef enum { K_IFCLASS, /* interface class */ K_CONSTRAINT, K_NETTYPE, + K_VIRTUAL, } verilogKind; +typedef enum { + R_MODULE_DECL, +} verilogModuleRole; + typedef struct { const char *keyword; verilogKind kind; @@ -103,19 +121,20 @@ typedef struct { } keywordAssoc; typedef struct sTokenInfo { - verilogKind kind; - vString* name; /* the name of the token */ - unsigned long lineNumber; /* line number where token was found */ - MIOPos filePosition; /* file position where token was found */ - struct sTokenInfo* scope; /* context of keyword */ - int nestLevel; /* Current nest level */ - verilogKind lastKind; /* Kind of last found tag */ - vString* blockName; /* Current block name */ - vString* inheritance; /* Class inheritance */ - bool prototype; /* Is only a prototype */ - bool classScope; /* Context is local to the current sub-context */ - bool parameter; /* parameter which can be overridden */ - bool hasParamList; /* module definition has a parameter port list */ + verilogKind kind; + vString* name; /* the name of the token */ + unsigned long lineNumber; /* line number where token was found */ + MIOPos filePosition; /* file position where token was found */ + struct sTokenInfo* scope; /* context of keyword */ + int nestLevel; /* Current nest level */ + verilogKind lastKind; /* Kind of last found tag */ + vString* blockName; /* Current block name */ + vString* inheritance; /* Class inheritance */ + bool prototype; /* Is only a prototype */ + bool classScope; /* Context is local to the current sub-context */ + bool parameter; /* parameter which can be overridden */ + bool hasParamList; /* module definition has a parameter port list */ + bool virtual; /* has virtual */ } tokenInfo; typedef enum { @@ -123,148 +142,160 @@ typedef enum { } verilogField; /* - * DATA DEFINITIONS + * DATA DEFINITIONS */ static int Ungetc; static int Lang_verilog; static int Lang_systemverilog; +static roleDefinition VerilogModuleRoles [] = { + { true, "decl", "declaring instances" }, +}; + +static roleDefinition SystemVerilogModuleRoles [] = { + { true, "decl", "declaring instances" }, +}; + static kindDefinition VerilogKinds [] = { - { true, 'c', "constant", "constants (define, parameter, specparam)" }, - { true, 'e', "event", "events" }, - { true, 'f', "function", "functions" }, - { true, 'm', "module", "modules" }, - { true, 'n', "net", "net data types" }, - { true, 'p', "port", "ports" }, - { true, 'r', "register", "variable data types" }, - { true, 't', "task", "tasks" }, - { true, 'b', "block", "blocks (begin, fork)" }, - { true, 'i', "instance", "instances of module" }, + { true, 'c', "constant", "constants (parameter, specparam)" }, + { true, 'd', "define", "text macros" }, + { true, 'e', "event", "events" }, + { true, 'f', "function", "functions" }, + { true, 'm', "module", "modules", + .referenceOnly = false, ATTACH_ROLES(VerilogModuleRoles) }, + { true, 'n', "net", "net data types" }, + { true, 'p', "port", "ports" }, + { true, 'r', "register", "variable data types" }, + { true, 't', "task", "tasks" }, + { true, 'b', "block", "blocks (begin, fork)" }, + { true, 'i', "instance", "instances of module" }, }; static kindDefinition SystemVerilogKinds [] = { - { true, 'c', "constant", "constants (define, parameter, specparam, enum values)" }, - { true, 'e', "event", "events" }, - { true, 'f', "function", "functions" }, - { true, 'm', "module", "modules" }, - { true, 'n', "net", "net data types" }, - { true, 'p', "port", "ports" }, - { true, 'r', "register", "variable data types" }, - { true, 't', "task", "tasks" }, - { true, 'b', "block", "blocks (begin, fork)" }, - { true, 'i', "instance", "instances of module or interface" }, - { true, 'A', "assert", "assertions (assert, assume, cover, restrict)" }, - { true, 'C', "class", "classes" }, - { true, 'V', "covergroup","covergroups" }, - { true, 'E', "enum", "enumerators" }, - { true, 'I', "interface", "interfaces" }, - { true, 'M', "modport", "modports" }, - { true, 'K', "package", "packages" }, - { true, 'P', "program", "programs" }, - { false,'Q', "prototype", "prototypes (extern, pure)" }, - { true, 'R', "property", "properties" }, - { true, 'S', "struct", "structs and unions" }, - { true, 'T', "typedef", "type declarations" }, - { true, 'H', "checker", "checkers" }, - { true, 'L', "clocking", "clocking" }, - { true, 'q', "sequence", "sequences" }, - { true, 'w', "member", "struct and union members" }, - { true, 'l', "ifclass", "interface class" }, - { true, 'O', "constraint","constraints" }, - { true, 'N', "nettype", "nettype declarations" }, + { true, 'c', "constant", "constants (parameter, specparam, enum values)" }, + { true, 'd', "define", "text macros" }, + { true, 'e', "event", "events" }, + { true, 'f', "function", "functions" }, + { true, 'm', "module", "modules", + .referenceOnly = false, ATTACH_ROLES(SystemVerilogModuleRoles) }, + { true, 'n', "net", "net data types" }, + { true, 'p', "port", "ports" }, + { true, 'r', "register", "variable data types" }, + { true, 't', "task", "tasks" }, + { true, 'b', "block", "blocks (begin, fork)" }, + { true, 'i', "instance", "instances of module or interface" }, + { true, 'A', "assert", "assertions (assert, assume, cover, restrict)" }, + { true, 'C', "class", "classes" }, + { true, 'V', "covergroup", "covergroups" }, + { true, 'E', "enum", "enumerators" }, + { true, 'I', "interface", "interfaces" }, + { true, 'M', "modport", "modports" }, + { true, 'K', "package", "packages" }, + { true, 'P', "program", "programs" }, + { false,'Q', "prototype", "prototypes (extern, pure)" }, + { true, 'R', "property", "properties" }, + { true, 'S', "struct", "structs and unions" }, + { true, 'T', "typedef", "type declarations" }, + { true, 'H', "checker", "checkers" }, + { true, 'L', "clocking", "clocking" }, + { true, 'q', "sequence", "sequences" }, + { true, 'w', "member", "struct and union members" }, + { true, 'l', "ifclass", "interface class" }, + { true, 'O', "constraint", "constraints" }, + { true, 'N', "nettype", "nettype declarations" }, }; static const keywordAssoc KeywordTable [] = { - /* SystemVerilog */ - /* | Verilog */ - /* keyword keyword ID | | */ - { "`define", K_DEFINE, { 1, 1 } }, - { "begin", K_BLOCK, { 1, 1 } }, - { "end", K_END, { 1, 1 } }, - { "endfunction", K_END_DE, { 1, 1 } }, - { "endmodule", K_END_DE, { 1, 1 } }, - { "endtask", K_END_DE, { 1, 1 } }, - { "event", K_EVENT, { 1, 1 } }, - { "fork", K_BLOCK, { 1, 1 } }, - { "function", K_FUNCTION, { 1, 1 } }, - { "genvar", K_REGISTER, { 1, 1 } }, - { "inout", K_PORT, { 1, 1 } }, - { "input", K_PORT, { 1, 1 } }, - { "integer", K_REGISTER, { 1, 1 } }, - { "join", K_END, { 1, 1 } }, - { "localparam", K_LOCALPARAM, { 1, 1 } }, - { "module", K_MODULE, { 1, 1 } }, - { "output", K_PORT, { 1, 1 } }, - { "parameter", K_PARAMETER, { 1, 1 } }, - { "real", K_REGISTER, { 1, 1 } }, - { "realtime", K_REGISTER, { 1, 1 } }, - { "reg", K_REGISTER, { 1, 1 } }, - { "signed", K_IGNORE, { 1, 1 } }, - { "specparam", K_CONSTANT, { 1, 1 } }, - { "supply0", K_NET, { 1, 1 } }, - { "supply1", K_NET, { 1, 1 } }, - { "task", K_TASK, { 1, 1 } }, - { "time", K_REGISTER, { 1, 1 } }, - { "tri", K_NET, { 1, 1 } }, - { "triand", K_NET, { 1, 1 } }, - { "trior", K_NET, { 1, 1 } }, - { "trireg", K_NET, { 1, 1 } }, - { "tri0", K_NET, { 1, 1 } }, - { "tri1", K_NET, { 1, 1 } }, - { "uwire", K_NET, { 1, 1 } }, - { "wand", K_NET, { 1, 1 } }, - { "wire", K_NET, { 1, 1 } }, - { "wor", K_NET, { 1, 1 } }, - { "assert", K_ASSERTION, { 1, 0 } }, - { "assume", K_ASSERTION, { 1, 0 } }, - { "bit", K_REGISTER, { 1, 0 } }, - { "byte", K_REGISTER, { 1, 0 } }, - { "chandle", K_REGISTER, { 1, 0 } }, - { "checker", K_CHECKER, { 1, 0 } }, - { "class", K_CLASS, { 1, 0 } }, - { "constraint", K_CONSTRAINT, { 1, 0 } }, - { "cover", K_ASSERTION, { 1, 0 } }, - { "clocking", K_CLOCKING, { 1, 0 } }, - { "covergroup", K_COVERGROUP, { 1, 0 } }, - { "endchecker", K_END_DE, { 1, 0 } }, - { "endclass", K_END_DE, { 1, 0 } }, - { "endclocking", K_END_DE, { 1, 0 } }, - { "endgroup", K_END_DE, { 1, 0 } }, - { "endinterface", K_END_DE, { 1, 0 } }, - { "endpackage", K_END_DE, { 1, 0 } }, - { "endprogram", K_END_DE, { 1, 0 } }, - { "endproperty", K_END_DE, { 1, 0 } }, - { "endsequence", K_END_DE, { 1, 0 } }, - { "enum", K_ENUM, { 1, 0 } }, - { "extern", K_PROTOTYPE, { 1, 0 } }, - { "import", K_IMPORT, { 1, 0 } }, - { "int", K_REGISTER, { 1, 0 } }, - { "interconnect", K_NET, { 1, 0 } }, - { "interface", K_INTERFACE, { 1, 0 } }, - { "join_any", K_END, { 1, 0 } }, - { "join_none", K_END, { 1, 0 } }, - { "logic", K_REGISTER, { 1, 0 } }, - { "longint", K_REGISTER, { 1, 0 } }, - { "modport", K_MODPORT, { 1, 0 } }, - { "package", K_PACKAGE, { 1, 0 } }, - { "program", K_PROGRAM, { 1, 0 } }, - { "property", K_PROPERTY, { 1, 0 } }, - { "pure", K_PROTOTYPE, { 1, 0 } }, - { "ref", K_PORT, { 1, 0 } }, - { "restrict", K_ASSERTION, { 1, 0 } }, - { "sequence", K_SEQUENCE, { 1, 0 } }, - { "shortint", K_REGISTER, { 1, 0 } }, - { "shortreal", K_REGISTER, { 1, 0 } }, - { "string", K_REGISTER, { 1, 0 } }, - { "struct", K_STRUCT, { 1, 0 } }, - { "type", K_REGISTER, { 1, 0 } }, - { "typedef", K_TYPEDEF, { 1, 0 } }, - { "union", K_STRUCT, { 1, 0 } }, - { "var", K_REGISTER, { 1, 0 } }, - { "void", K_REGISTER, { 1, 0 } }, - { "with", K_WITH, { 1, 0 } }, - { "nettype", K_NETTYPE, { 1, 0 } }, -// { "virtual", K_PROTOTYPE, { 1, 0 } }, // do not add for now + /* SystemVerilog */ + /* | Verilog */ + /* keyword keyword ID | | */ + { "`define", K_DEFINE, { 1, 1 } }, + { "begin", K_BLOCK, { 1, 1 } }, + { "end", K_END, { 1, 1 } }, + { "endfunction", K_END_DE, { 1, 1 } }, + { "endmodule", K_END_DE, { 1, 1 } }, + { "endtask", K_END_DE, { 1, 1 } }, + { "event", K_EVENT, { 1, 1 } }, + { "fork", K_BLOCK, { 1, 1 } }, + { "function", K_FUNCTION, { 1, 1 } }, + { "genvar", K_REGISTER, { 1, 1 } }, + { "inout", K_PORT, { 1, 1 } }, + { "input", K_PORT, { 1, 1 } }, + { "integer", K_REGISTER, { 1, 1 } }, + { "join", K_END, { 1, 1 } }, + { "localparam", K_LOCALPARAM, { 1, 1 } }, + { "module", K_MODULE, { 1, 1 } }, + { "output", K_PORT, { 1, 1 } }, + { "parameter", K_PARAMETER, { 1, 1 } }, + { "real", K_REGISTER, { 1, 1 } }, + { "realtime", K_REGISTER, { 1, 1 } }, + { "reg", K_REGISTER, { 1, 1 } }, + { "signed", K_IGNORE, { 1, 1 } }, + { "specparam", K_CONSTANT, { 1, 1 } }, + { "supply0", K_NET, { 1, 1 } }, + { "supply1", K_NET, { 1, 1 } }, + { "task", K_TASK, { 1, 1 } }, + { "time", K_REGISTER, { 1, 1 } }, + { "tri", K_NET, { 1, 1 } }, + { "triand", K_NET, { 1, 1 } }, + { "trior", K_NET, { 1, 1 } }, + { "trireg", K_NET, { 1, 1 } }, + { "tri0", K_NET, { 1, 1 } }, + { "tri1", K_NET, { 1, 1 } }, + { "uwire", K_NET, { 1, 1 } }, + { "wand", K_NET, { 1, 1 } }, + { "wire", K_NET, { 1, 1 } }, + { "wor", K_NET, { 1, 1 } }, + { "assert", K_ASSERTION, { 1, 0 } }, + { "assume", K_ASSERTION, { 1, 0 } }, + { "bit", K_REGISTER, { 1, 0 } }, + { "byte", K_REGISTER, { 1, 0 } }, + { "chandle", K_REGISTER, { 1, 0 } }, + { "checker", K_CHECKER, { 1, 0 } }, + { "class", K_CLASS, { 1, 0 } }, + { "constraint", K_CONSTRAINT, { 1, 0 } }, + { "cover", K_ASSERTION, { 1, 0 } }, + { "clocking", K_CLOCKING, { 1, 0 } }, + { "covergroup", K_COVERGROUP, { 1, 0 } }, + { "endchecker", K_END_DE, { 1, 0 } }, + { "endclass", K_END_DE, { 1, 0 } }, + { "endclocking", K_END_DE, { 1, 0 } }, + { "endgroup", K_END_DE, { 1, 0 } }, + { "endinterface", K_END_DE, { 1, 0 } }, + { "endpackage", K_END_DE, { 1, 0 } }, + { "endprogram", K_END_DE, { 1, 0 } }, + { "endproperty", K_END_DE, { 1, 0 } }, + { "endsequence", K_END_DE, { 1, 0 } }, + { "enum", K_ENUM, { 1, 0 } }, + { "extern", K_PROTOTYPE, { 1, 0 } }, + { "import", K_IMPORT, { 1, 0 } }, + { "int", K_REGISTER, { 1, 0 } }, + { "interconnect", K_NET, { 1, 0 } }, + { "interface", K_INTERFACE, { 1, 0 } }, + { "join_any", K_END, { 1, 0 } }, + { "join_none", K_END, { 1, 0 } }, + { "logic", K_REGISTER, { 1, 0 } }, + { "longint", K_REGISTER, { 1, 0 } }, + { "modport", K_MODPORT, { 1, 0 } }, + { "package", K_PACKAGE, { 1, 0 } }, + { "program", K_PROGRAM, { 1, 0 } }, + { "property", K_PROPERTY, { 1, 0 } }, + { "pure", K_PROTOTYPE, { 1, 0 } }, + { "ref", K_PORT, { 1, 0 } }, + { "restrict", K_ASSERTION, { 1, 0 } }, + { "sequence", K_SEQUENCE, { 1, 0 } }, + { "shortint", K_REGISTER, { 1, 0 } }, + { "shortreal", K_REGISTER, { 1, 0 } }, + { "string", K_REGISTER, { 1, 0 } }, + { "struct", K_STRUCT, { 1, 0 } }, + { "type", K_REGISTER, { 1, 0 } }, + { "typedef", K_TYPEDEF, { 1, 0 } }, + { "union", K_STRUCT, { 1, 0 } }, + { "var", K_REGISTER, { 1, 0 } }, + { "void", K_REGISTER, { 1, 0 } }, + { "with", K_WITH, { 1, 0 } }, + { "nettype", K_NETTYPE, { 1, 0 } }, + { "virtual", K_VIRTUAL, { 1, 0 } }, }; static tokenInfo *currentContext = NULL; @@ -272,7 +303,7 @@ static ptrArray *tagContents; static fieldDefinition *fieldTable = NULL; // IEEE Std 1364-2005 LRM, Appendix B "List of Keywords" -const static struct keywordGroup verilogKeywords = { +static const struct keywordGroup verilogKeywords = { .value = K_IGNORE, .addingUnlessExisting = true, .keywords = { @@ -299,7 +330,7 @@ const static struct keywordGroup verilogKeywords = { }, }; // IEEE Std 1800-2017 LRM, Annex B "Keywords" -const static struct keywordGroup systemVerilogKeywords = { +static const struct keywordGroup systemVerilogKeywords = { .value = K_IGNORE, .addingUnlessExisting = true, .keywords = { @@ -349,7 +380,7 @@ const static struct keywordGroup systemVerilogKeywords = { }; // IEEE Std 1364-2005 LRM, "19. Compiler directives" -const static struct keywordGroup verilogDirectives = { +static const struct keywordGroup verilogDirectives = { .value = K_DIRECTIVE, .addingUnlessExisting = true, .keywords = { @@ -362,7 +393,7 @@ const static struct keywordGroup verilogDirectives = { }; // IEEE Std 1800-2017 LRM, "22. Compiler directives" -const static struct keywordGroup systemVerilogDirectives = { +static const struct keywordGroup systemVerilogDirectives = { .value = K_DIRECTIVE, .addingUnlessExisting = true, .keywords = { @@ -377,21 +408,25 @@ const static struct keywordGroup systemVerilogDirectives = { // .enabled field cannot be shared by two languages static fieldDefinition VerilogFields[] = { - { .name = "parameter", - .description = "parameter whose value can be overridden.", - .enabled = false, - .dataType = FIELDTYPE_BOOL }, + { + .name = "parameter", + .description = "parameter whose value can be overridden.", + .enabled = false, + .dataType = FIELDTYPE_BOOL, + }, }; static fieldDefinition SystemVerilogFields[] = { - { .name = "parameter", - .description = "parameter whose value can be overridden.", - .enabled = false, - .dataType = FIELDTYPE_BOOL }, + { + .name = "parameter", + .description = "parameter whose value can be overridden.", + .enabled = false, + .dataType = FIELDTYPE_BOOL, + }, }; /* - * PROTOTYPE DEFINITIONS + * PROTOTYPE DEFINITIONS */ static bool isIdentifier (tokenInfo* token); @@ -404,11 +439,11 @@ static int readWordTokenNoSkip (tokenInfo *const token, int c); static int skipBlockName (tokenInfo *const token, int c); static int skipClockEvent (tokenInfo* token, int c); static int skipDelay (tokenInfo* token, int c); -static int tagIdentifierList (tokenInfo *const token, int c, verilogKind kind, bool mayPortDecl); -static int tagNameList (tokenInfo* token, int c, verilogKind kind); +static int tagIdsInPort (tokenInfo *const token, int c, verilogKind kind, bool mayPortDecl); +static int tagIdsInDataDecl (tokenInfo* token, int c, verilogKind kind); /* - * FUNCTION DEFINITIONS + * FUNCTION DEFINITIONS */ static short isContainer (verilogKind kind) @@ -468,6 +503,7 @@ static void clearToken (tokenInfo *token) token->classScope = false; token->parameter = false; token->hasParamList = false; + token->virtual = false; } static tokenInfo *newToken (void) @@ -558,7 +594,7 @@ static void buildKeywordHash (const langType language, unsigned int idx) { size_t i; const size_t count = ARRAY_SIZE (KeywordTable); - for (i = 0 ; i < count ; ++i) + for (i = 0; i < count; ++i) { const keywordAssoc *p = &KeywordTable [i]; if (p->isValid [idx]) @@ -596,13 +632,13 @@ static void vUngetc (int c) /* Mostly copied from cppSkipOverCComment() in cpreprocessor.c. * * cppSkipOverCComment() uses the internal ungetc buffer of - * CPreProcessor. On the other hand, the Verilog parser uses + * CPreProcessor. On the other hand, the Verilog parser uses * getcFromInputFile() directly. getcFromInputFile() uses just * another internal ungetc buffer. Using them mixed way will * cause a trouble. */ static int verilogSkipOverCComment (void) { - int c = getcFromInputFile (); + int c = getcFromInputFile (); while (c != EOF) { @@ -616,7 +652,7 @@ static int verilogSkipOverCComment (void) c = next; else { - c = SPACE; /* replace comment with space */ + c = ' '; /* replace comment with space */ break; } } @@ -639,13 +675,13 @@ static int _vGetc (bool inSkipPastMatch) int c2 = getcFromInputFile (); if (c2 == EOF) return EOF; - else if (c2 == '/') /* strip comment until end-of-line */ + else if (c2 == '/') /* strip comment until end-of-line */ { do c = getcFromInputFile (); - while (c != '\n' && c != EOF); + while (c != '\n' && c != EOF); } - else if (c2 == '*') /* strip block comment */ + else if (c2 == '*') /* strip block comment */ c = verilogSkipOverCComment (); else ungetcToInputFile (c2); @@ -657,7 +693,7 @@ static int _vGetc (bool inSkipPastMatch) int c2; do c2 = getcFromInputFile (); - while (c2 != '"' && c2 != EOF); + while (c2 != '"' && c2 != EOF); c = '@'; } return c; @@ -680,6 +716,20 @@ static bool isIdentifierCharacter (const int c) return (isalnum (c) || c == '_' || c == '`' || c == '$'); } +// check if double colon. +static bool isDoubleColon (int c) +{ + if (c != ':') + return false; + c = vGetc (); + if (c == ':') { + return true; + } else { + vUngetc (c); + return false; + } +} + static int skipWhite (int c) { while (isspace (c)) @@ -732,7 +782,7 @@ static int skipString (int c) static int skipExpression (int c) { - while (c != ',' && c != ';' && c != ')' && c != '}' && c != ']' && c != EOF) + while (c != ',' && c != ';' && c != ')' && c != '}' && c != ']' && c != EOF) { if (c == '(') c = skipPastMatch ("()"); @@ -752,11 +802,14 @@ static int skipExpression (int c) // Should be used after readWordTokenNoSkip() for compiler directives static int skipToNewLine (int c) { - bool escape = false; - for ( ; (c != '\n' || escape) && c != EOF; c = vGetc ()) - escape = (c == '\\'); - - return c; // '\n' or EOF + int prev = EOF; // The previous char of 'c' never be a EOF. + while (!((prev != '\\') && (c == '\n')) && (c != EOF)) { + prev = c; + // Getc() does not work for a comment in multi-line macro + c = getcFromInputFile (); + } + Assert(c == '\n' || c == EOF); + return c; } static int skipMacro (int c, tokenInfo *token) @@ -778,7 +831,7 @@ static int skipMacro (int c, tokenInfo *token) c = processDefine (localToken, c); } /* return macro expansion */ - else + else if (localToken->kind == K_IDENTIFIER) { swapToken (token, localToken); c = skipWhite (c); @@ -786,6 +839,11 @@ static int skipMacro (int c, tokenInfo *token) c = skipPastMatch ("()"); break; } + else + { + VERBOSE ("Unexpected input: localToken->kind %d\n", localToken->kind); + break; + } } deleteToken (localToken); return c; @@ -825,24 +883,25 @@ static int readWordToken (tokenInfo *const token, int c) // read a word token starting with "c". // returns the next charactor of the token read. -// for compiler directives. Since they are line-based, skipWhite() cannot be used. +// For compiler directives which are line-based, skipWhite() cannot be used. static int readWordTokenNoSkip (tokenInfo *const token, int c) { return _readWordToken (token, c, false); } /* check if an identifier: - * simple_identifier ::= [ a-zA-Z_ ] { [ a-zA-Z0-9_$ ] } */ + * simple_identifier ::= [ a-zA-Z_ ] { [ a-zA-Z0-9_$ ] } */ static bool isIdentifier (tokenInfo* token) { if (token->kind == K_UNDEFINED) { for (int i = 0; i < vStringLength (token->name); i++) { - int c = vStringChar (token->name, i); + int c = (unsigned char) vStringChar (token->name, i); if (i == 0) { - if (c == '`' || !isWordToken (c)) + // treat a text-macro as an identifier (#3712) + if (!isWordToken (c)) return false; } else @@ -882,7 +941,7 @@ static void createContext (verilogKind kind, vString* const name) } } -static void dropContext () +static void dropContext (void) { verbose ("Dropping context %s\n", vStringValue (currentContext->name)); currentContext = popToken (currentContext); @@ -893,7 +952,7 @@ static int dropEndContext (tokenInfo *const token, int c) { verbose ("current context %s; context kind %0d; nest level %0d\n", vStringValue (currentContext->name), currentContext->kind, currentContext->nestLevel); if ((currentContext->kind == K_COVERGROUP && strcmp (vStringValue (token->name), "endgroup") == 0) - || (currentContext->kind == K_IFCLASS && strcmp (vStringValue (token->name), "endclass") == 0)) + || (currentContext->kind == K_IFCLASS && strcmp (vStringValue (token->name), "endclass") == 0)) { dropContext (); c = skipBlockName (token ,c); @@ -915,12 +974,12 @@ static int dropEndContext (tokenInfo *const token, int c) vStringDelete (endTokenName); } else - verbose ("Unexpected current context %s\n", vStringValue (currentContext->name)); + VERBOSE ("Unexpected current context %s\n", vStringValue (currentContext->name)); return c; } -static void createTag (tokenInfo *const token, verilogKind kind) +static void createTagFull (tokenInfo *const token, verilogKind kind, int role, tokenInfo *const typeref) { tagEntryInfo tag; @@ -952,9 +1011,11 @@ static void createTag (tokenInfo *const token, verilogKind kind) } /* Create tag */ - initTagEntry (&tag, vStringValue (token->name), kind); - tag.lineNumber = token->lineNumber; - tag.filePosition = token->filePosition; + if (role == ROLE_DEFINITION_INDEX) + initTagEntry (&tag, vStringValue (token->name), kind); + else + initRefTagEntry (&tag, vStringValue (token->name), kind, role); + updateTagLine (&tag, token->lineNumber, token->filePosition); verbose ("Adding tag %s (kind %d)", vStringValue (token->name), kind); if (currentContext->kind != K_UNDEFINED) @@ -971,12 +1032,19 @@ static void createTag (tokenInfo *const token, verilogKind kind) verbose ("Class %s extends %s\n", vStringValue (token->name), tag.extensionFields.inheritance); } + if (typeref) + { + tag.extensionFields.typeRef [0] = getNameForKind (typeref->kind); + tag.extensionFields.typeRef [1] = vStringValue (typeref->name); + } + if (token->parameter) - attachParserField (&tag, false, fieldTable [F_PARAMETER].ftype, ""); + attachParserField (&tag, fieldTable [F_PARAMETER].ftype, ""); makeTagEntry (&tag); - if (isXtagEnabled (XTAG_QUALIFIED_TAGS) && currentContext->kind != K_UNDEFINED) + if (isXtagEnabled (XTAG_QUALIFIED_TAGS) && currentContext->kind != K_UNDEFINED + && role == ROLE_DEFINITION_INDEX) { vString *const scopedName = vStringNew (); @@ -992,7 +1060,7 @@ static void createTag (tokenInfo *const token, verilogKind kind) } /* Push token as context if it is a container */ - if (container) + if (container && role == ROLE_DEFINITION_INDEX) { createContext (kind, token->name); @@ -1002,7 +1070,7 @@ static void createTag (tokenInfo *const token, verilogKind kind) for (unsigned int i = 0; i < ptrArrayCount (tagContents); i++) { tokenInfo *content = ptrArrayItem (tagContents, i); - createTag (content, content->kind); + createTagFull (content, content->kind, ROLE_DEFINITION_INDEX, NULL); } /* Drop temporary contexts */ @@ -1014,6 +1082,21 @@ static void createTag (tokenInfo *const token, verilogKind kind) vStringClear (token->inheritance); } +static void createTagWithTypeRef (tokenInfo *const token, verilogKind kind, tokenInfo *const typeref) +{ + createTagFull (token, kind, ROLE_DEFINITION_INDEX, typeref); +} + +static void createTag (tokenInfo *const token, verilogKind kind) +{ + createTagWithTypeRef (token, kind, NULL); +} + +static void createRefTag (tokenInfo *const token, verilogKind kind, int role) +{ + createTagFull (token, kind, role, NULL); +} + static int skipBlockName (tokenInfo *const token, int c) { if (c == ':') @@ -1060,11 +1143,11 @@ static int processPortList (tokenInfo *token, int c, bool mayPortDecl) if (c == '(') { c = skipWhite (vGetc ()); // skip '(' - c = tagIdentifierList (token, c, K_PORT, mayPortDecl); + c = tagIdsInPort (token, c, K_PORT, mayPortDecl); if (c == ')') // sanity check c = skipWhite (vGetc ()); else - verbose ("Unexpected input: %c\n", c); + VERBOSE ("Unexpected input: %c\n", c); } return c; } @@ -1098,21 +1181,15 @@ static int processFunction (tokenInfo *const token, int c) else c = skipWhite (vGetc ()); /* skip parameter assignment of a class type - * ex. function uvm_port_base #(IF) get_if(int index=0); */ + * ex. function uvm_port_base #(IF) get_if(int index=0); */ c = skipParameterAssignment (c); /* Identify class type prefixes and create respective context*/ - if (isInputLanguage (Lang_systemverilog) && c == ':') + if (isInputLanguage (Lang_systemverilog) && isDoubleColon(c)) { - c = vGetc (); - if (c == ':') - { - verbose ("Found function declaration with class type %s\n", vStringValue (token->name)); - createContext (K_CLASS, token->name); - currentContext->classScope = true; - } - else - vUngetc (c); + verbose ("Found function declaration with class type %s\n", vStringValue (token->name)); + createContext (K_CLASS, token->name); + currentContext->classScope = true; } } verbose ("Found function: %s\n", vStringValue (token->name)); @@ -1142,11 +1219,11 @@ static int processEnum (tokenInfo *const token, int c) /* Following identifiers are tag names */ verbose ("Find enum tags. Token %s kind %d\n", vStringValue (enumToken->name), enumToken->kind); - c = tagNameList (enumToken, c, enumToken->kind); + c = tagIdsInDataDecl (enumToken, c, enumToken->kind); deleteToken (enumToken); // Clean up the tag content list at the end of the declaration to support multiple variables - // enum { ... } foo, bar; + // enum { ... } foo, bar; ptrArrayClear (tagContents); return c; } @@ -1168,19 +1245,19 @@ static int processStruct (tokenInfo *const token, int c) /* Following identifiers are tag names */ verbose ("Find struct|union tags. Token %s kind %d\n", vStringValue (token->name), token->kind); - c = tagNameList (token, c, kind); + c = tagIdsInDataDecl (token, c, kind); ptrArrayClear (tagContents); return c; } // data_declaration ::= -// [ const ] [ var ] [ static | automatic ] data_type_or_implicit list_of_variable_decl_assignments ; -// | typedef data_type type_identifier { [ ... ] } ; -// | typedef interface_instance_identifier [ ... ] . type_identifier type_identifier ; // interface based typedef -// | typedef [ enum | struct | union | class | interface class ] type_identifier ; -// | import < package_import_item > ; -// | nettype data_type net_type_identifier [ with [ class_type :: | package_identifier :: | $unit :: ] tf_identifier ] ; -// | nettype [ class_type :: | package_identifier :: | $unit :: ] net_type_identifier net_type_identifier ; +// [ const ] [ var ] [ static | automatic ] data_type_or_implicit list_of_variable_decl_assignments ; +// | typedef data_type type_identifier { [ ... ] } ; +// | typedef interface_instance_identifier [ ... ] . type_identifier type_identifier ; // interface based typedef +// | typedef [ enum | struct | union | class | interface class ] type_identifier ; +// | import < package_import_item > ; +// | nettype data_type net_type_identifier [ with [ class_type :: | package_identifier :: | $unit :: ] tf_identifier ] ; +// | nettype [ class_type :: | package_identifier :: | $unit :: ] net_type_identifier net_type_identifier ; static int processTypedef (tokenInfo *const token, int c) { verilogKind kindSave = token->kind; // K_TYPEDEF or K_NETTYPE @@ -1192,7 +1269,7 @@ static int processTypedef (tokenInfo *const token, int c) kind = token->kind; } // forward typedef (LRM 6.18) is tagged as prototype - // (I don't know why...) + // (I don't know why...) switch (kind) { case K_CLASS: @@ -1279,7 +1356,7 @@ static int processParameterList (tokenInfo *token, int c) } // [ virtual ] class [ static | automatic ] class_identifier [ parameter_port_list ] -// [ extends class_type [ ( list_of_arguments ) ] ] [ implements < interface_class_type > ] ; +// [ extends class_type [ ( list_of_arguments ) ] ] [ implements < interface_class_type > ] ; // interface class class_identifier [ parameter_port_list ] [ extends < interface_class_type > ] ; static int processClass (tokenInfo *const token, int c, verilogKind kind) { @@ -1296,7 +1373,7 @@ static int processClass (tokenInfo *const token, int c, verilogKind kind) if (token->kind != K_IDENTIFIER) { - verbose ("Unexpected input: class name is expected.\n"); + VERBOSE ("Unexpected input: class name is expected.\n"); return c; } @@ -1350,7 +1427,7 @@ static int processDefine (tokenInfo *const token, int c) if (isWordToken (c)) { c = readWordTokenNoSkip (token, c); - createTag (token, K_CONSTANT); + createTag (token, K_DEFINE); } c = skipToNewLine (c); c = skipWhite (c); @@ -1358,13 +1435,13 @@ static int processDefine (tokenInfo *const token, int c) } // immediate_assertion_statement ::= -// ( assert | asume | cover ) [ #0 | final ] '(' expression ')' block +// ( assert | asume | cover ) [ #0 | final ] '(' expression ')' block // concurrent_assertion_statement ::= -// ( assert | assume ) property ( property_spec ) action_block -// | expect ( property_spec ) action_block # ignore : processed as same as "if" -// | cover property ( property_spec ) statement_or_null -// | cover sequence ( [clocking_event ] [ disable iff ( expression_or_dist ) ] sequence_expr ) statement_or_null -// | restrict property ( property_spec ) ; +// ( assert | assume ) property ( property_spec ) action_block +// | expect ( property_spec ) action_block # ignore : processed as same as "if" +// | cover property ( property_spec ) statement_or_null +// | cover sequence ( [clocking_event ] [ disable iff ( expression_or_dist ) ] sequence_expr ) statement_or_null +// | restrict property ( property_spec ) ; static int processAssertion (tokenInfo *const token, int c) { if (vStringLength (currentContext->blockName) > 0) @@ -1384,6 +1461,25 @@ static int processAssertion (tokenInfo *const token, int c) return c; } +// data_declaration ::= +// ... +// import < package_identifier :: identifier | package_identifier :: * > ; +// dpi_import_export ::= +// import ( "DPI-C" | "DPI" ) [ context | pure ] [ c_identifier = ] function data_type_or_void function_identifier [ ( [ tf_port_list ] ) ] ; +// | import ( "DPI-C" | "DPI" ) [ context ] [ c_identifier = ] task task_identifier [ ( [ tf_port_list ] ) ] ; +// | export ( "DPI-C" | "DPI" ) [ c_identifier = ] function function_identifier ; +// | export ( "DPI-C" | "DPI" ) [ c_identifier = ] task task_identifier ; +static int processImport (tokenInfo *const token, int c) +{ + if (c == '"') { // dpi_import: we don't care about export. + currentContext->prototype = true; + } else { + c = skipToSemiColon (c); + c = skipWhite (vGetc ()); // skip semicolon + } + return c; +} + // non-ANSI type // ( module | interface | program ) [ static | automatic ] identifier { package_import_declaration } [ parameter_port_list ] ( port { , port } ) ; // ANSI type @@ -1418,7 +1514,7 @@ static int processDesignElementL (tokenInfo *const token, int c) } else { - verbose ("Unexpected input\n"); + VERBOSE ("Unexpected input\n"); return c; } } @@ -1445,7 +1541,7 @@ static int processDesignElementL (tokenInfo *const token, int c) // ( checker | property | sequence ) identifier [ ( [ port_list ] ) ] ; // covergroup identifier [ ( [ port_list ] ) ] [ coverage_event ] ; -// coverage_event ::= clocking_event | with function sample ( ... ) | @@( ... ) +// coverage_event ::= clocking_event | with function sample ( ... ) | @@( ... ) // package identifier ; // modport < identifier ( < ports_declaration > ) > ; // [ default | global ] clocking [ identifier ] ( @ identifier | @ ( event_expression ) ) @@ -1520,7 +1616,7 @@ static int pushEnumNames (tokenInfo* token, int c) { if (!isWordToken (c)) { - verbose ("Unexpected input: %c\n", c); + VERBOSE ("Unexpected input: %c\n", c); return c; } c = readWordToken (token, c); @@ -1558,7 +1654,7 @@ static int pushMembers (tokenInfo* token, int c) bool not_used; if (!isWordToken (c)) { - verbose ("Unexpected input: %c\n", c); + VERBOSE ("Unexpected input: %c\n", c); return c; } c = readWordToken (token, c); @@ -1585,7 +1681,7 @@ static int pushMembers (tokenInfo* token, int c) c = readWordToken (token, c); else { - verbose ("Unexpected input.\n"); + VERBOSE ("Unexpected input.\n"); break; } } @@ -1600,10 +1696,10 @@ static int pushMembers (tokenInfo* token, int c) } // input -// kind: kind of context +// kind: kind of context // output -// kind: kind of type -// token: identifier token (unless K_IDENTIFIER nor K_UNDEFINED) +// kind: kind of type +// token: identifier token (unless K_IDENTIFIER nor K_UNDEFINED) static int processType (tokenInfo* token, int c, verilogKind* kind, bool* with) { verilogKind actualKind = K_UNDEFINED; @@ -1657,7 +1753,7 @@ static int processType (tokenInfo* token, int c, verilogKind* kind, bool* with) } else { - verbose ("Unexpected input\n"); // FIXME: x dist {}, with + VERBOSE ("Unexpected input\n"); // FIXME: x dist {}, with break; } } @@ -1672,7 +1768,7 @@ static int processType (tokenInfo* token, int c, verilogKind* kind, bool* with) } // class_type ::= -// ps_class_identifier [ # ( ... ) ] { :: class_identifier [ # ( ... ) ] } +// ps_class_identifier [ # ( ... ) ] { :: class_identifier [ # ( ... ) ] } // "interface_identifier ." is also handled static int skipClassType (tokenInfo* token, int c) { @@ -1688,12 +1784,10 @@ static int skipClassType (tokenInfo* token, int c) } else if (c == ':') { - c = skipWhite (vGetc ()); - if (c != ':') + if (!isDoubleColon(c)) { - verbose ("Unexpected input.\n"); - vUngetc (c); - return ':'; + VERBOSE ("Unexpected input.\n"); + return c; } c = skipWhite (vGetc ()); if (isWordToken (c)) @@ -1709,16 +1803,16 @@ static int skipClassType (tokenInfo* token, int c) return c; } -// Tag a list of identifiers +// Tag a list of identifiers in a port list // data_type :: = -// ... -// | virtual [ interface ] identifier [ # ( [ ... ] ) ] [ . identifier ] -// | [ class_type :: | identifier :: | $unit :: ] identifier { [ ... ] } -// | [ identifier :: | $unit :: ] identifier [ # ( ... ) ] { :: identifier [ # ( ... ) ] } -// | ... +// ... +// | virtual [ interface ] identifier [ # ( [ ... ] ) ] [ . identifier ] +// | [ class_type :: | identifier :: | $unit :: ] identifier { [ ... ] } +// | [ identifier :: | $unit :: ] identifier [ # ( ... ) ] { :: identifier [ # ( ... ) ] } +// | ... // -// mayPortDecl: may be a ANSI port declaration. true for module, interface, or program. -static int tagIdentifierList (tokenInfo *const token, int c, verilogKind kind, bool mayPortDecl) +// mayPortDecl: may be a ANSI port declaration. true for module, interface, or program. +static int tagIdsInPort (tokenInfo *const token, int c, verilogKind kind, bool mayPortDecl) { bool first_port = true; bool enableTag = true; @@ -1781,10 +1875,12 @@ static int tagIdentifierList (tokenInfo *const token, int c, verilogKind kind, b return c; } -static int tagNameList (tokenInfo* token, int c, verilogKind kind) +// Tag a list of identifiers in a data declaration +static int tagIdsInDataDecl (tokenInfo* token, int c, verilogKind kind) { - c = skipClassType (token, c); - if (c == ':' || c == ';') // ## (cycle delay) or unexpected input + if (token->kind != K_NET) + c = skipClassType (token, c); + if (c == ';') return c; // skip drive|charge strength or type_reference, dimensions, and delay for net @@ -1793,8 +1889,9 @@ static int tagNameList (tokenInfo* token, int c, verilogKind kind) c = skipDimension (c); if (c == '.') return c; // foo[...].bar = ..; - c = skipDelay (token, c); + c = skipDelay (token, c); // ## (cycle delay) + tokenInfo *tokenSaved = dupToken(token); // maybe a module_identifier while (c != EOF) { bool with = false; @@ -1808,7 +1905,7 @@ static int tagNameList (tokenInfo* token, int c, verilogKind kind) if (c == '=') c = skipExpression (c); } - else if (c == '(' || c == '[') // should be instance + else if (c == '(' || c == '[') // should be an instance { c = skipDimension (c); // name_of_instance {unpacked_dimension} c = skipPastMatch ("()"); // list_of_port_connections @@ -1817,8 +1914,10 @@ static int tagNameList (tokenInfo* token, int c, verilogKind kind) // var `add_t(foo) = '0; if (c == ';' || c == ',') { + tokenSaved->kind = K_MODULE; // for typeRef field verbose ("find instance: %s with kind %s\n", vStringValue (token->name), getNameForKind (K_INSTANCE)); - createTag (token, K_INSTANCE); + createTagWithTypeRef (token, K_INSTANCE, tokenSaved); + createRefTag (tokenSaved, K_MODULE, R_MODULE_DECL); } } c = skipMacro (c, token); // `ifdef, `else, `endif, etc. (before comma) @@ -1827,6 +1926,7 @@ static int tagNameList (tokenInfo* token, int c, verilogKind kind) c = skipWhite (vGetc ()); // skip ',' c = skipMacro (c, token); // `ifdef, `else, `endif, etc. (after comma) } + deleteToken (tokenSaved); return c; } @@ -1846,7 +1946,7 @@ static int findTag (tokenInfo *const token, int c) if (token->kind == K_PORT && currentContext->kind == K_CLOCKING) c = skipToSemiColon (c); // clocking items are not port definitions else - c = tagNameList (token, c, token->kind); + c = tagIdsInDataDecl (token, c, token->kind); break; case K_IDENTIFIER: { @@ -1855,14 +1955,14 @@ static int findTag (tokenInfo *const token, int c) if (c == ':') ; /* label */ - else if (c == ',' || c == '{') // "foo, ..." or "coverpoint foo { ... }" + else if (c == ',' || c == '.' || c == '{') // "foo, ...", "foo.bar,...", or "coverpoint foo { ... }" c = skipWhite (vGetc ()); else if (c == '(') // task, function, or method call c = skipPastMatch ("()"); else if (c == '=') // assignment c = skipExpression (skipWhite (vGetc ())); else - c = tagNameList (token, c, token->kind); /* user defined type */ + c = tagIdsInDataDecl (token, c, token->kind); /* user defined type */ } break; case K_CLASS: @@ -1878,13 +1978,19 @@ static int findTag (tokenInfo *const token, int c) case K_STRUCT: c = processStruct (token, c); break; - case K_PROTOTYPE: case K_IMPORT: + c = processImport (token, c); + break; + case K_PROTOTYPE: case K_WITH: currentContext->prototype = true; break; case K_INTERFACE: + // a virtual interface variable + if (currentContext->virtual) + break; + // fallthrough case K_MODULE: case K_PROGRAM: c = processDesignElementL (token, c); @@ -1922,10 +2028,14 @@ static int findTag (tokenInfo *const token, int c) c = processDefine (token, c); break; + case K_VIRTUAL: + currentContext->virtual = true; + break; + case K_IGNORE: break; default: - verbose ("Unexpected kind->token %d\n", token->kind); + VERBOSE ("Unexpected kind->token %d\n", token->kind); } return c; } @@ -1945,7 +2055,8 @@ static void findVerilogTags (void) case ':': /* Store current block name whenever a : is found * This is used later by any tag type that requires this information */ - vStringCopy (currentContext->blockName, token->name); + if (!isDoubleColon(c)) + vStringCopy (currentContext->blockName, token->name); c = skipWhite (vGetc ()); break; case ';': @@ -1959,10 +2070,10 @@ static void findVerilogTags (void) c = skipWhite (vGetc ()); break; case '(': // ignore locally declared variables in a for-loop (LRM 12.7.1) - c = skipPastMatch ("()");; + c = skipPastMatch ("()"); break; case '{': - c = skipPastMatch ("{}");; + c = skipPastMatch ("{}"); break; case '#': c = skipDelay (token, c); @@ -1976,6 +2087,7 @@ static void findVerilogTags (void) default : if (isWordToken (c)) { + // NoSkip for compiler directives c = readWordTokenNoSkip (token, c); if (token->kind == K_DIRECTIVE) { @@ -1983,7 +2095,12 @@ static void findVerilogTags (void) c = skipToNewLine (c); c = skipWhite (c); } - else if (token->kind != K_UNDEFINED) + else if ((token->kind == K_UNDEFINED) + || (token->kind == K_IDENTIFIER && vStringChar(token->name, 0) == '`')) + // ignore an undefined token and text-macro identifier, i.e.`foo(bar) (#3712) + break; + else + // call findTag() after skipping whitespaces c = findTag (token, skipWhite (c)); } else @@ -1999,6 +2116,9 @@ extern parserDefinition* VerilogParser (void) { static const char *const extensions [] = { "v", NULL }; parserDefinition* def = parserNew ("Verilog"); + static selectLanguage selectors[] = { selectVOrVerilogByKeywords, NULL }; + def->versionCurrent = 1; + def->versionAge = 1; def->kindTable = VerilogKinds; def->kindCount = ARRAY_SIZE (VerilogKinds); def->fieldTable = VerilogFields; @@ -2006,6 +2126,7 @@ extern parserDefinition* VerilogParser (void) def->extensions = extensions; def->parser = findVerilogTags; def->initialize = initializeVerilog; + def->selectLanguage = selectors; return def; } @@ -2013,6 +2134,8 @@ extern parserDefinition* SystemVerilogParser (void) { static const char *const extensions [] = { "sv", "svh", "svi", NULL }; parserDefinition* def = parserNew ("SystemVerilog"); + def->versionCurrent = 1; + def->versionAge = 1; def->kindTable = SystemVerilogKinds; def->kindCount = ARRAY_SIZE (SystemVerilogKinds); def->fieldTable = SystemVerilogFields; diff --git a/ctags/parsers/vhdl.c b/ctags/parsers/vhdl.c index 15f3d5b864..30e3d9d5e7 100644 --- a/ctags/parsers/vhdl.c +++ b/ctags/parsers/vhdl.c @@ -570,8 +570,7 @@ static int makeVhdlTagWithScope (tokenInfo * const token, const vhdlKind kind, i const char *const name = vStringValue (token->string); tagEntryInfo e; initTagEntry (&e, name, kind); - e.lineNumber = token->lineNumber; - e.filePosition = token->filePosition; + updateTagLine (&e, token->lineNumber, token->filePosition); e.extensionFields.scopeIndex = parent; return makeTagEntry (&e); } @@ -605,7 +604,7 @@ static void parseTillEnd (tokenInfo * const token, int parent, const int end_key if (!isType (token, TOKEN_SEMICOLON)) skipToCharacterInInputFile (';'); if (ended) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine (e, getInputLineNumber ()); } else { @@ -748,7 +747,7 @@ static void parseModule (tokenInfo * const token, int parent) { tagEntryInfo *e = getEntryInCorkQueue (index); if (e) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine (e, getInputLineNumber ()); } if (kind == VHDLTAG_ENTITY) @@ -776,7 +775,7 @@ static void parseRecord (tokenInfo * const token, int parent) { tagEntryInfo *e = getEntryInCorkQueue (parent); if (e) - e->extensionFields.endLine = getInputLineNumber (); + setTagEndLine (e, getInputLineNumber ()); } deleteToken (name); @@ -905,7 +904,8 @@ static void parseArchitecture (tokenInfo * const token) VHDLTAG_ENTITY, VHDL_ENTITY_DESIGNED); int entity_index = anyKindEntryInScope (CORK_NIL, vStringValue (token->string), - VHDLTAG_ENTITY); + VHDLTAG_ENTITY, + false); tagEntryInfo *e = getEntryInCorkQueue (index); if (e) { diff --git a/meson.build b/meson.build index 919d18a109..74a7e0f8ed 100644 --- a/meson.build +++ b/meson.build @@ -500,6 +500,7 @@ ctags = static_library('ctags', 'ctags/main/htable.h', 'ctags/main/inline.h', 'ctags/main/interactive_p.h', + 'ctags/main/interval_tree_generic.h', 'ctags/main/keyword.c', 'ctags/main/keyword.h', 'ctags/main/keyword_p.h', @@ -547,6 +548,7 @@ ctags = static_library('ctags', 'ctags/main/ptrarray.h', 'ctags/main/rbtree.c', 'ctags/main/rbtree.h', + 'ctags/main/rbtree_augmented.h', 'ctags/main/read.c', 'ctags/main/read.h', 'ctags/main/read_p.h', @@ -578,6 +580,8 @@ ctags = static_library('ctags', 'ctags/main/types.h', 'ctags/main/unwindi.c', 'ctags/main/unwindi.h', + 'ctags/main/utf8_str.c', + 'ctags/main/utf8_str.h', 'ctags/main/vstring.c', 'ctags/main/vstring.h', 'ctags/main/writer.c', @@ -597,6 +601,7 @@ ctags = static_library('ctags', 'ctags/parsers/autoit.c', 'ctags/parsers/basic.c', 'ctags/parsers/bibtex.c', + 'ctags/parsers/bibtex.h', 'ctags/parsers/clojure.c', 'ctags/parsers/cobol.c', 'ctags/parsers/cpreprocessor.c', @@ -614,6 +619,7 @@ ctags = static_library('ctags', 'ctags/parsers/cxx/cxx_parser.h', 'ctags/parsers/cxx/cxx_parser_internal.h', 'ctags/parsers/cxx/cxx_parser_lambda.c', + 'ctags/parsers/cxx/cxx_parser_module.c', 'ctags/parsers/cxx/cxx_parser_namespace.c', 'ctags/parsers/cxx/cxx_parser_template.c', 'ctags/parsers/cxx/cxx_parser_tokenizer.c', @@ -623,6 +629,8 @@ ctags = static_library('ctags', 'ctags/parsers/cxx/cxx_qtmoc.c', 'ctags/parsers/cxx/cxx_scope.c', 'ctags/parsers/cxx/cxx_scope.h', + 'ctags/parsers/cxx/cxx_side_chain.c', + 'ctags/parsers/cxx/cxx_side_chain.h', 'ctags/parsers/cxx/cxx_subparser.c', 'ctags/parsers/cxx/cxx_subparser.h', 'ctags/parsers/cxx/cxx_subparser_internal.h', @@ -650,6 +658,7 @@ ctags = static_library('ctags', 'ctags/parsers/iniconf.c', 'ctags/parsers/iniconf.h', 'ctags/parsers/jscript.c', + 'ctags/parsers/jscript.h', 'ctags/parsers/json.c', 'ctags/parsers/julia.c', 'ctags/parsers/lisp.c', @@ -672,8 +681,10 @@ ctags = static_library('ctags', 'ctags/parsers/raku.c', 'ctags/parsers/rst.c', 'ctags/parsers/ruby.c', + 'ctags/parsers/ruby.h', 'ctags/parsers/rust.c', 'ctags/parsers/sh.c', + 'ctags/parsers/sh.h', 'ctags/parsers/sql.c', 'ctags/parsers/tcl.c', 'ctags/parsers/tcl.h', From 7943b7d9ec47805caac6b071ca13da4a73c14326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 10:52:19 +0200 Subject: [PATCH 03/18] Add ldscript parser The parser isn't used by Geany itself but is started from within the cxx parser and the asm parser which don't run correctly without it. --- ctags/Makefile.am | 1 + ctags/parsers/ldscript.c | 925 ++++++++++++++++++++++++++++++++++++ meson.build | 1 + src/tagmanager/tm_parser.c | 12 + src/tagmanager/tm_parser.h | 1 + src/tagmanager/tm_parsers.h | 3 +- 6 files changed, 942 insertions(+), 1 deletion(-) create mode 100644 ctags/parsers/ldscript.c diff --git a/ctags/Makefile.am b/ctags/Makefile.am index ed233537f3..f3226341f3 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -78,6 +78,7 @@ parsers = \ parsers/julia.c \ parsers/geany_lcpp.c \ parsers/geany_lcpp.h \ + parsers/ldscript.c \ parsers/lisp.c \ parsers/lua.c \ parsers/make.c \ diff --git a/ctags/parsers/ldscript.c b/ctags/parsers/ldscript.c new file mode 100644 index 0000000000..fe92ee8186 --- /dev/null +++ b/ctags/parsers/ldscript.c @@ -0,0 +1,925 @@ +/* + * Copyright (c) 2016, Masatake YAMATO + * Copyright (c) 2016, Red Hat, Inc. + * + * This source code is released for free distribution under the terms of the + * GNU General Public License version 2 or (at your option) any later version. + * + * This module contains functions for generating tags for GNU linker script + * files. + */ + +#include "general.h" +#include "tokeninfo.h" + +#include "entry.h" +#include "cpreprocessor.h" +#include "keyword.h" +#include "parse.h" +#include "ptrarray.h" +#include "read.h" +#include "trace.h" +#include "xtag.h" + +#include + +/* + * DATA DEFINITIONS + */ + +typedef enum { + LD_SCRIPT_SYMBOL_ENTRYPOINT, + LD_SCRIPT_SYMBOL_ALIASED, +} ldScriptSymbolRole; + +static roleDefinition LdScriptSymbolRoles [] = { + { true, "entrypoint", "entry points" }, + { true, "aliased", "aliased with __attribute__((alias(...))) in C/C++ code" }, +}; + +typedef enum { + LD_SCRIPT_INPUT_SECTION_MAPPED, + LD_SCRIPT_INPUT_SECTION_DISCARDED, + LD_SCRIPT_INPUT_SECTION_DESTINATION, +} ldScriptInputSectionRole; + +static roleDefinition LdScriptInputSectionRoles [] = { + { true, "mapped", "mapped to output section" }, + { true, "discarded", "discarded when linking" }, + { true, "destination", "specified as the destination of code and data" }, +}; + +typedef enum { + K_SECTION, + K_SYMBOL, + K_VERSION, + K_INPUT_SECTION, +} ldScriptKind; + +static kindDefinition LdScriptKinds [] = { + { true, 'S', "section", "sections" }, + { true, 's', "symbol", "symbols", + .referenceOnly = false, ATTACH_ROLES(LdScriptSymbolRoles)}, + { true, 'v', "version", "versions" }, + { true, 'i', "inputSection", "input sections", + .referenceOnly = false, ATTACH_ROLES(LdScriptInputSectionRoles)}, +}; + +enum { + KEYWORD_ENTRY, + KEYWORD_SECTIONS, + KEYWORD_LOC, + KEYWORD_AT, + KEYWORD_VERSION, + KEYWORD_PROVIDE, + KEYWORD_PROVIDE_HIDDEN, + KEYWORD_HIDDEN, + KEYWORD_EXCLUDE_FILE, + KEYWORD_INPUT_SECTION_FLAGS, + KEYWORD_COMMON, + KEYWORD_KEEP, + KEYWORD_DATA, +}; +typedef int keywordId; /* to allow KEYWORD_NONE */ + + +static const keywordTable LdScriptKeywordTable[] = { + /* keyword keyword ID */ + { "ENTRY", KEYWORD_ENTRY }, + { "SECTIONS", KEYWORD_SECTIONS }, + { ".", KEYWORD_LOC }, + { "AT", KEYWORD_AT }, + { "VERSION", KEYWORD_VERSION }, + { "PROVIDE", KEYWORD_PROVIDE }, + { "PROVIDE_HIDDEN", KEYWORD_PROVIDE_HIDDEN }, + { "HIDDEN", KEYWORD_HIDDEN }, + { "EXCLUDE_FILE", KEYWORD_EXCLUDE_FILE }, + { "INPUT_SECTION_FLAGS", KEYWORD_INPUT_SECTION_FLAGS }, + { "COMMON", KEYWORD_COMMON }, + { "KEEP", KEYWORD_KEEP }, + { "SORT", KEYWORD_KEEP }, + { "BYTE", KEYWORD_DATA }, + { "SHORT", KEYWORD_DATA }, + { "LONG", KEYWORD_DATA }, + { "QUAD", KEYWORD_DATA }, + { "SQUAD", KEYWORD_DATA }, + { "FILL", KEYWORD_DATA }, +}; + +enum eTokenType { + /* 0..255 are the byte's value */ + TOKEN_EOF = 256, + TOKEN_UNDEFINED, + TOKEN_KEYWORD, + TOKEN_IDENTIFIER, + TOKEN_NUMBER, + TOKEN_ASSIGNMENT_OP, + TOKEN_OP, + TOKEN_PHDIR, + TOKEN_REGION, + TOKEN_FILLEXP, + TOKEN_DISCARD, +}; + +static void readToken (tokenInfo *const token, void *data CTAGS_ATTR_UNUSED); +static void clearToken (tokenInfo *token); +static void copyToken (tokenInfo *dest, tokenInfo *src, void *data CTAGS_ATTR_UNUSED); + +typedef struct sLdScriptToken { + tokenInfo base; + int scopeIndex; + tokenKeyword assignment; + bool whitespacePrefixed; +} ldScriptToken; + +#define LDSCRIPT(TOKEN) ((ldScriptToken *)TOKEN) + +static struct tokenTypePair ldScriptTypePairs [] = { + { '{', '}' }, +}; + +static struct tokenInfoClass ldScriptTokenInfoClass = { + .nPreAlloc = 4, + .typeForUndefined = TOKEN_UNDEFINED, + .keywordNone = KEYWORD_NONE, + .typeForKeyword = TOKEN_KEYWORD, + .typeForEOF = TOKEN_EOF, + .extraSpace = sizeof (ldScriptToken) - sizeof (tokenInfo), + .pairs = ldScriptTypePairs, + .pairCount = ARRAY_SIZE (ldScriptTypePairs), + .read = readToken, + .clear = clearToken, + .copy = copyToken, +}; + +typedef enum { + F_ASSIGNMENT, + COUNT_FIELD +} ldScriptField; + +static fieldDefinition LdScriptFields[COUNT_FIELD] = { + { .name = "assignment", + .description = "how a value is assigned to the symbol", + .enabled = true }, +}; + +static langType Lang_ldscript; + +/* + * FUNCTION DEFINITIONS + */ + +static tokenInfo *newLdScriptToken (void) +{ + return newToken (&ldScriptTokenInfoClass); +} + +static void clearToken (tokenInfo *token) +{ + LDSCRIPT(token)->scopeIndex = CORK_NIL; + LDSCRIPT(token)->assignment = KEYWORD_NONE; +} + +static void copyToken (tokenInfo *dest, tokenInfo *src, void *data CTAGS_ATTR_UNUSED) +{ + LDSCRIPT (dest)->scopeIndex = + LDSCRIPT (src)->scopeIndex; + LDSCRIPT (dest)->assignment = + LDSCRIPT (src)->assignment; + LDSCRIPT (dest)->whitespacePrefixed = + LDSCRIPT (src)->whitespacePrefixed; +} + +static int makeLdScriptTagMaybe (tagEntryInfo *const e, tokenInfo *const token, + int kind, int role) +{ + if (role == ROLE_DEFINITION_INDEX) + { + if (! LdScriptKinds[kind].enabled) + return CORK_NIL; + } + else if (! (isXtagEnabled (XTAG_REFERENCE_TAGS) + && LdScriptKinds[kind].roles[role].enabled)) + return CORK_NIL; + + initRefTagEntry (e, tokenString (token), + kind, + role); + updateTagLine (e, token->lineNumber, token->filePosition); + e->extensionFields.scopeIndex = LDSCRIPT (token)->scopeIndex; + + /* TODO: implement file: field. */ + if ((kind == K_SYMBOL) + && LdScriptFields[F_ASSIGNMENT].enabled) + { + const char *assignment = NULL; + + switch (LDSCRIPT (token)->assignment) + { + case KEYWORD_PROVIDE: + assignment = "provide"; + break; + case KEYWORD_PROVIDE_HIDDEN: + assignment = "provide_hidden"; + break; + case KEYWORD_HIDDEN: + assignment = "hidden"; + break; + } + + if (assignment) + attachParserField (e, LdScriptFields[F_ASSIGNMENT].ftype, + assignment); + } + + return makeTagEntry (e); +} + +#define isIdentifierChar(c) \ + (cppIsalnum (c) || (c) == '_' || (c) == '.' || (c) == '-' \ + || (((c) >= 0x80) && ((c) <= 0xff))) + +static int readPrefixedToken (tokenInfo *const token, int type) +{ + int n = 0; + int c; + + while ((c = cppGetc()) != EOF) + { + if (isIdentifierChar (c)) + { + n++; + tokenPutc (token, c); + } + else + { + cppUngetc (c); + break; + } + } + + if (n) + token->type = type; + return n; +} + +// We stop applying macro replacements if a macro is used so many +// times in a recursive macro expansion. +#define LD_SCRIPT_PARSER_MAXIMUM_MACRO_USE_COUNT 8 + +static bool collectMacroArguments (ptrArray *args) +{ + vString *s = vStringNew (); + tokenInfo *const t = newLdScriptToken (); + int depth = 1; + + do + { + tokenRead (t); + + if (tokenIsType (t, EOF)) + break; + else if (tokenIsTypeVal (t, ')')) + { + depth--; + if (depth == 0) + { + char *cstr = vStringDeleteUnwrap (s); + ptrArrayAdd (args, cstr); + s = NULL; + } + else + vStringCat (s, t->string); + } + else if (tokenIsTypeVal (t, '(')) + { + depth++; + vStringCat (s, t->string); + } + else if (tokenIsTypeVal (t, ',')) + { + char *cstr = vStringDeleteUnwrap (s); + ptrArrayAdd (args, cstr); + s = vStringNew (); + } + else + { + if (LDSCRIPT(t)->whitespacePrefixed) + vStringPut(s, ' '); + vStringCat (s, t->string); + } + } + while (depth > 0); + + vStringDelete (s); /* NULL is acceptable. */ + + tokenDelete (t); + + if (depth > 0) + TRACE_PRINT("unbalanced argument list"); + + return (depth > 0)? false: true; +} + +static bool expandCppMacro (cppMacroInfo *macroInfo) +{ + ptrArray *args = NULL; + + if (macroInfo->hasParameterList) + { + tokenInfo *const t = newLdScriptToken (); + + /* Though macro arguments are expected, they are not found. */ + tokenRead (t); + if (!tokenIsTypeVal (t, '(')) + { + tokenUnread (t); + tokenDelete (t); + TRACE_PRINT("no argument for the %s<%p>", macroInfo->name, macroInfo); + return false; + } + + args = ptrArrayNew (eFree); + if (!collectMacroArguments (args)) + { + ptrArrayDelete (args); + tokenUnread (t); + tokenDelete (t); + return false; + } + tokenDelete (t); + } + +#ifdef DO_TRACING + if (args) + { + for (int i = 0; i < ptrArrayCount(args); i++) + TRACE_PRINT("[%d] %s", i, (const char *)ptrArrayItem (args, i)); + } +#endif + + cppBuildMacroReplacementWithPtrArrayAndUngetResult (macroInfo, args); + + ptrArrayDelete (args); /* NULL is acceptable. */ + return true; +} + +static void readToken (tokenInfo *const token, void *data CTAGS_ATTR_UNUSED) +{ + int c, c0, c1; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear (token->string); + + int prefix_count = -1; + LDSCRIPT (token)->whitespacePrefixed = false; + do { + c = cppGetc(); + prefix_count++; + if (prefix_count > 0) + LDSCRIPT (token)->whitespacePrefixed = true; + } while (c == ' ' || c== '\t' || c == '\f' || c == '\r' || c == '\n' + || c == CPP_STRING_SYMBOL || c == CPP_CHAR_SYMBOL); + + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + + switch (c) + { + case EOF: + token->type = TOKEN_EOF; + break; + case ';': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + tokenPutc(token, c); + token->type = c; + break; + case '~': + case '%': + case '?': + tokenPutc(token, c); + token->type = TOKEN_OP; + break; + case '-': + case '+': + case '*': + case '/': /* -,+,*,/,-=,+=,*=,/= */ + tokenPutc(token, c); + c0 = cppGetc (); + token->type = TOKEN_OP; + if (c0 == '=') + { + tokenPutc(token, c0); + token->type = TOKEN_ASSIGNMENT_OP; + } + else if (c == '/' && c0 == 'D') + { + tokenInfo *const discard = newLdScriptToken (); + + cppUngetc (c0); + tokenRead (discard); + if (tokenIsType(discard, IDENTIFIER) && + (strcmp(tokenString(discard), "DISCARD")) == 0) + { + c1 = cppGetc (); + if (c1 == '/') + token->type = TOKEN_DISCARD; + else + { + cppUngetc (c1); + tokenUnread (discard); + } + } + else + tokenUnread (discard); + tokenDelete (discard); + } + else + cppUngetc (c0); + break; + case '!': /* !, != */ + tokenPutc(token, c); + token->type = TOKEN_OP; + c0 = cppGetc (); + if (c0 == '=') + tokenPutc(token, c0); + else + cppUngetc (c0); + case '<': /* <,<=,<<,<<= */ + tokenPutc(token, c); + token->type = TOKEN_OP; + c0 = cppGetc (); + if (c0 == c || c0 == '=') + { + tokenPutc(token, c0); + if (c0 == c) + { + c1 = cppGetc (); + if (c1 == '=') + { + tokenPutc(token, c1); + token->type = TOKEN_ASSIGNMENT_OP; + } + else + cppUngetc (c1); + } + } + else + cppUngetc (c0); + case '|': /* |,||,|= */ + case '&': /* &,&&,&= */ + tokenPutc(token, c); + token->type = TOKEN_OP; + c0 = cppGetc (); + if (c0 == c) + tokenPutc(token, c0); + else if (c0 == '=') + { + tokenPutc(token, c0); + token->type = TOKEN_ASSIGNMENT_OP; + } + else + cppUngetc (c0); + break; + case '=': /* =,== */ + tokenPutc(token, c); + if (!readPrefixedToken (token, TOKEN_FILLEXP)) + { + c0 = cppGetc (); + if (c0 == '=') + { + tokenPutc(token, c0); + token->type = TOKEN_OP; + } + else + { + cppUngetc (c0); + token->type = TOKEN_ASSIGNMENT_OP; + } + } + break; + case '>': /* >,>>,>>= */ + tokenPutc(token, c); + if (!readPrefixedToken (token, TOKEN_REGION)) + { + token->type = TOKEN_OP; + c0 = cppGetc (); + if (c0 == c || c0 == '=') + { + tokenPutc(token, c0); + c1 = cppGetc(); + if (c1 == '=') + { + tokenPutc(token, c1); + token->type = TOKEN_ASSIGNMENT_OP; + } + else + cppUngetc (c1); + } + else + cppUngetc (c0); + } + break; + case ':': + tokenPutc(token, c); + if (!readPrefixedToken (token, TOKEN_PHDIR)) + token->type = c; + break; + default: + if (cppIsdigit (c)) + { + /* Using cppIsdigit here is redundant. + * + * `c' never takes STRING_SYMBOL or CHAR_SYMBOL as + * its value here. + * However, the practice using cppIs... macros for the value + * returned from cppGetc() may avoid unexpected programming + * mistakes I took repeatedly. + */ + token->type = TOKEN_NUMBER; + tokenPutc(token, c); + while ((c = cppGetc())) + { + if (isIdentifierChar (c)) + tokenPutc(token, c); + else + { + cppUngetc (c); + break; + } + } + } + else if (isIdentifierChar(c)) + { + tokenPutc(token, c); + while ((c = cppGetc())) + { + if (isIdentifierChar(c)) + tokenPutc(token, c); + else + { + cppUngetc (c); + break; + } + } + token->keyword = lookupKeyword (vStringValue (token->string), Lang_ldscript); + if (token->keyword == KEYWORD_NONE) + { + token->type = TOKEN_IDENTIFIER; + + cppMacroInfo *macroInfo = cppFindMacro (vStringValue (token->string)); + if (macroInfo) + { + TRACE_PRINT("Macro expansion: %s<%p>%s", vStringValue (token->string), + macroInfo, macroInfo->hasParameterList? "(...)": ""); + if (!(macroInfo->useCount < LD_SCRIPT_PARSER_MAXIMUM_MACRO_USE_COUNT)) + TRACE_PRINT ("Overly uesd macro %s<%p> useCount: %d (> %d)", + vStringValue (token->string), macroInfo, macroInfo->useCount, + LD_SCRIPT_PARSER_MAXIMUM_MACRO_USE_COUNT); + else if (expandCppMacro (macroInfo)) + readToken (token, NULL); + } + } + else + token->type = TOKEN_KEYWORD; + } + else + { + tokenPutc(token, c); + token->type = c; + } + break; + } +} + +static void parseEntry (tokenInfo *const token) +{ + tokenRead (token); + if (token->type == '(') + { + tokenInfo *const name = newLdScriptToken (); + + tokenRead (name); + if (tokenIsType(name, IDENTIFIER)) + { + tagEntryInfo e; + + makeLdScriptTagMaybe (&e, name, K_SYMBOL, LD_SCRIPT_SYMBOL_ENTRYPOINT); + tokenRead (token); + tokenSkipToType (token, ')'); + } + tokenDelete (name); + } +} + +static void parseProvide (tokenInfo * token) +{ + tokenKeyword p = token->keyword; + + if (tokenSkipToType (token, '(')) + { + tagEntryInfo e; + tokenRead (token); + if (tokenIsType(token, IDENTIFIER)) + { + LDSCRIPT (token)->assignment = p; + + makeLdScriptTagMaybe (&e, token, + K_SYMBOL, ROLE_DEFINITION_INDEX); + LDSCRIPT (token)->assignment = KEYWORD_NONE; + } + tokenSkipToType (token, ')'); + } +} + +/* An example of input sections: + + (.text .rdata) + + .text and .rtada are input sections. */ +static void parseInputSections (tokenInfo *const token) +{ + tagEntryInfo e; + do { + tokenRead (token); + if (token->type == ')') + break; + else if (tokenIsType (token, IDENTIFIER)) + makeLdScriptTagMaybe (&e, token, + K_INPUT_SECTION, + LDSCRIPT(token)->scopeIndex == CORK_NIL + ? LD_SCRIPT_INPUT_SECTION_DISCARDED + : LD_SCRIPT_INPUT_SECTION_MAPPED); + else if (tokenIsKeyword (token, EXCLUDE_FILE)) + tokenSkipToType (token, ')'); + } while (!tokenIsEOF (token)); +} + +/* Symbols and input sections are captured here. + An example of output sections: + + __brk_base = .; + . += 64 * 1024; + *(.brk_reservation) + __brk_limit = .; + + __brk_base and __brk_limit should be recorded as a symbol. + .brk_reservationshould be recorded as an input section. */ + +static void parseOutputSectionCommands (tokenInfo *const token, int terminator) +{ + tokenInfo *const tmp = newLdScriptToken (); + int scope_index = LDSCRIPT (token)->scopeIndex; + + do { + tokenRead (token); + LDSCRIPT (token)->scopeIndex = scope_index; + + if (tokenIsKeyword (token, INPUT_SECTION_FLAGS)) + { + tokenSkipToType (token, '('); + tokenSkipToType (token, ')'); + } + else if (tokenIsKeyword (token, KEEP)) + { + tokenSkipToType (token, '('); + parseOutputSectionCommands (token, ')'); + } + else if (tokenIsType (token,IDENTIFIER) + || tokenIsKeyword (token, LOC)) + { + tagEntryInfo e; + + tokenRead (tmp); + if (tokenIsType (tmp, ASSIGNMENT_OP)) + { + if (! tokenIsKeyword (token, LOC)) + makeLdScriptTagMaybe (&e, token, + K_SYMBOL, ROLE_DEFINITION_INDEX); + tokenSkipToType (token, ';'); + } + else if (tmp->type == '(') + parseInputSections (token); + else + tokenUnread (tmp); + } + /* `*',`?', `[', `]' can be part of a pattern of input object file names + as a meta character. + `[' can enumerated here because it cannot be at the end of pattern. */ + else if ((token->type == TOKEN_OP + && ((tokenString(token)[0] == '*') + || (tokenString(token)[0] == '?')) + && (tokenString(token)[1] == '\0')) + || token->type == ']') + { + tokenRead (tmp); + if (tmp->type == '(') + parseInputSections (token); + else + tokenUnread (tmp); + } + else if (tokenIsKeyword (token, PROVIDE) + || tokenIsKeyword (token, PROVIDE_HIDDEN) + || tokenIsKeyword (token, HIDDEN)) + parseProvide (token); + } while (! (tokenIsEOF (token) || token->type == terminator)); + + tokenDelete (tmp); +} + +static void parseSection (tokenInfo * name) +{ + tokenInfo *const token = newLdScriptToken (); + tagEntryInfo e; + + tokenRead (token); + + if (tokenIsType (token, ASSIGNMENT_OP)) + { + if (!tokenIsKeyword (name, LOC)) + makeLdScriptTagMaybe (&e, name, + K_SYMBOL, ROLE_DEFINITION_INDEX); + tokenSkipToType (token, ';'); + } + else + { + retry: + if (tokenIsType (token, NUMBER)) + tokenSkipToType (token, ':'); + else if (tokenIsType (token, IDENTIFIER)) + { + tokenCopy (name, token); + tokenRead (token); + goto retry; + } + else if (token->type == '(') + tokenSkipToType (token, ')'); + + if (token->type == ':') + { + int scope_index; + + if (tokenIsType (name, DISCARD)) + scope_index = CORK_NIL; + else + scope_index = makeLdScriptTagMaybe (&e, name, + K_SECTION, ROLE_DEFINITION_INDEX); + + if (tokenSkipToType (token, '{')) + { + LDSCRIPT (token)->scopeIndex = scope_index; + parseOutputSectionCommands (token, '}'); + } + } + } + tokenDelete (token); +} + +static void parseSections (tokenInfo *const token) +{ + tokenRead (token); + if (token->type == '{') + { + do { + tokenRead (token); + if (tokenIsKeyword (token, ENTRY)) + parseEntry (token); + else if (tokenIsType(token, IDENTIFIER) + || tokenIsKeyword (token, LOC) + || tokenIsType (token, DISCARD)) + parseSection (token); + else if (tokenIsKeyword (token, PROVIDE) + || tokenIsKeyword (token, PROVIDE_HIDDEN) + || tokenIsKeyword (token, HIDDEN)) + parseProvide (token); + } while (! (tokenIsEOF (token) || token->type == '}')); + } +} + +static void parseVersion (tokenInfo *const token) +{ + tagEntryInfo e; + makeLdScriptTagMaybe (&e, token, + K_VERSION, ROLE_DEFINITION_INDEX); + + if (tokenSkipToType (token, '{')) + tokenSkipOverPair (token); +} + +static void parseVersions (tokenInfo *const token) +{ + tokenRead (token); + if (token->type == '{') + { + tokenInfo *curly = newTokenByCopying(token); + tokenRead (token); + if (token->type == '{') + { + vString *anonver = anonGenerateNew ("ver", K_VERSION); + makeSimpleTag (anonver, K_VERSION); + vStringDelete(anonver); + tokenUnread (token); + tokenSkipOverPair (curly); + tokenCopy (token, curly); + tokenDelete (curly); + return; + } + tokenDelete (curly); + tokenUnread (token); + + do { + tokenRead (token); + if (tokenIsType(token, IDENTIFIER)) + { + parseVersion (token); + tokenSkipToType (token, ';'); + } + } while (! (tokenIsEOF (token) || token->type == '}')); + } +} + +static void findLdScriptTags (void) +{ + tokenInfo *const token = newLdScriptToken (); + tokenInfo *const tmp = newLdScriptToken (); + + cppInit (false, false, false, false, + KIND_GHOST_INDEX, 0, 0, + KIND_GHOST_INDEX, + KIND_GHOST_INDEX, 0, 0, + FIELD_UNKNOWN); + + do { + tokenRead (token); + if (tokenIsKeyword (token, ENTRY)) + parseEntry (token); + else if (tokenIsKeyword (token, SECTIONS)) + parseSections (token); + else if (tokenIsType(token, IDENTIFIER)) + { + tagEntryInfo e; + tokenRead (tmp); + if (tokenIsType(tmp, ASSIGNMENT_OP)) + { + makeLdScriptTagMaybe (&e, token, + K_SYMBOL, ROLE_DEFINITION_INDEX); + tokenSkipToType (tmp, ';'); + } + } + else if (tokenIsKeyword (token, VERSION)) + parseVersions(token); + } while (!tokenIsEOF (token)); + + cppTerminate (); + + tokenDelete (tmp); + tokenDelete (token); + + flashTokenBacklog (&ldScriptTokenInfoClass); +} + +static void initialize (const langType language) +{ + Lang_ldscript = language; +} + +extern parserDefinition* LdScriptParser (void) +{ + parserDefinition* def = parserNew ("LdScript"); + + /* File name patters are picked from Linux kernel and ecos. */ + static const char *const extensions [] = { "lds", "scr", "ld", "ldi", NULL }; + + /* lds.S must be here because Asm parser registers .S as an extension. + * ld.script is used in linux/arch/mips/boot/compressed/ld.script. */ + static const char *const patterns [] = { "*.lds.S", "ld.script", NULL }; + + /* Emacs's mode */ + static const char *const aliases [] = { "ld-script", NULL }; + + def->initialize = initialize; + def->parser = findLdScriptTags; + + def->kindTable = LdScriptKinds; + def->kindCount = ARRAY_SIZE (LdScriptKinds); + def->extensions = extensions; + def->patterns = patterns; + def->aliases = aliases; + def->keywordTable = LdScriptKeywordTable; + def->keywordCount = ARRAY_SIZE (LdScriptKeywordTable); + def->fieldTable = LdScriptFields; + def->fieldCount = ARRAY_SIZE (LdScriptFields); + + def->useCork = CORK_QUEUE|CORK_SYMTAB; + + def->versionCurrent = 1; + def->versionAge = 1; + + return def; +} diff --git a/meson.build b/meson.build index 74a7e0f8ed..ba0edce902 100644 --- a/meson.build +++ b/meson.build @@ -661,6 +661,7 @@ ctags = static_library('ctags', 'ctags/parsers/jscript.h', 'ctags/parsers/json.c', 'ctags/parsers/julia.c', + 'ctags/parsers/ldscript.c', 'ctags/parsers/lisp.c', 'ctags/parsers/lua.c', 'ctags/parsers/make.c', diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 7ab7411b38..817db984f7 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -1135,6 +1135,17 @@ static TMParserMapGroup group_OCAML[] = { {N_("Variables"), TM_ICON_VAR, tm_tag_variable_t}, }; +/* unused by Geany but used by the cxx parser as an external parser so it has to + * be defined */ +static TMParserMapEntry map_LDSCRIPT[] = { + {'S', tm_tag_undef_t}, // section + {'s', tm_tag_undef_t}, // symbol + {'v', tm_tag_undef_t}, // version + {'i', tm_tag_undef_t}, // inputSection +}; +static TMParserMapGroup group_LDSCRIPT[] = { +}; + typedef struct { TMParserMapEntry *entries; @@ -1209,6 +1220,7 @@ static TMParserMap parser_map[] = { MAP_ENTRY(AUTOIT), MAP_ENTRY(RAKU), MAP_ENTRY(OCAML), + MAP_ENTRY(LDSCRIPT), }; /* make sure the parser map is consistent and complete */ G_STATIC_ASSERT(G_N_ELEMENTS(parser_map) == TM_PARSER_COUNT); diff --git a/src/tagmanager/tm_parser.h b/src/tagmanager/tm_parser.h index 043edc7713..edee9b1ea2 100644 --- a/src/tagmanager/tm_parser.h +++ b/src/tagmanager/tm_parser.h @@ -121,6 +121,7 @@ enum TM_PARSER_AUTOIT, TM_PARSER_RAKU, TM_PARSER_OCAML, + TM_PARSER_LDSCRIPT, TM_PARSER_COUNT }; diff --git a/src/tagmanager/tm_parsers.h b/src/tagmanager/tm_parsers.h index 808cd419a3..417747b04c 100644 --- a/src/tagmanager/tm_parsers.h +++ b/src/tagmanager/tm_parsers.h @@ -75,6 +75,7 @@ DosBatchParser, \ AutoItParser, \ Perl6Parser, \ - OcamlParser + OcamlParser, \ + LdScriptParser #endif From f21e1de08482a73fdc41694e7dbc81c9735a29dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 18:36:38 +0200 Subject: [PATCH 04/18] Update parser kind mappings All extra kinds are mapped to undef_t. Geany starts after this step. --- src/tagmanager/tm_parser.c | 51 ++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 817db984f7..b61bb269f8 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -105,6 +105,8 @@ static TMParserMapEntry map_CPP[] = { {'N', tm_tag_undef_t}, // name {'U', tm_tag_undef_t}, // using {'Z', tm_tag_undef_t}, // tparam + {'M', tm_tag_undef_t}, // module + {'P', tm_tag_undef_t}, // partition }; #define group_CPP group_C @@ -155,6 +157,7 @@ static TMParserMapEntry map_PERL[] = { {'s', tm_tag_function_t}, // subroutine {'d', tm_tag_prototype_t}, // subroutineDeclaration {'M', tm_tag_undef_t}, // module + {'h', tm_tag_undef_t}, // heredoc }; static TMParserMapGroup group_PERL[] = { {N_("Package"), TM_ICON_NAMESPACE, tm_tag_package_t}, @@ -194,7 +197,7 @@ static TMParserMapEntry map_PYTHON[] = { {'i', tm_tag_externvar_t}, // module /* defined as externvar to get those excluded as forward type in symbols.c:goto_tag() * so we can jump to the real implementation (if known) instead of to the import statement */ - {'x', tm_tag_externvar_t}, // unknown + {'Y', tm_tag_externvar_t}, // unknown {'z', tm_tag_local_var_t}, // parameter {'l', tm_tag_local_var_t}, // local }; @@ -269,7 +272,7 @@ static TMParserMapEntry map_ASM[] = { {'l', tm_tag_namespace_t}, // label {'m', tm_tag_function_t}, // macro {'t', tm_tag_struct_t}, // type - {'s', tm_tag_undef_t}, // section + {'z', tm_tag_undef_t}, // parameter }; static TMParserMapGroup group_ASM[] = { {N_("Labels"), TM_ICON_NAMESPACE, tm_tag_namespace_t}, @@ -288,30 +291,32 @@ static TMParserMapGroup group_CONF[] = { }; static TMParserMapEntry map_SQL[] = { + {'C', tm_tag_undef_t}, // ccflag + {'D', tm_tag_undef_t}, // domain + {'E', tm_tag_field_t}, // field + {'L', tm_tag_undef_t}, // label + {'P', tm_tag_package_t}, // package + {'R', tm_tag_undef_t}, // service + {'S', tm_tag_undef_t}, // schema + {'T', tm_tag_macro_t}, // trigger + {'U', tm_tag_undef_t}, // publication + {'V', tm_tag_member_t}, // view + {'b', tm_tag_undef_t}, // database {'c', tm_tag_undef_t}, // cursor {'d', tm_tag_prototype_t}, // prototype + {'e', tm_tag_undef_t}, // event {'f', tm_tag_function_t}, // function - {'E', tm_tag_field_t}, // field + {'i', tm_tag_struct_t}, // index {'l', tm_tag_undef_t}, // local - {'L', tm_tag_undef_t}, // label - {'P', tm_tag_package_t}, // package + {'n', tm_tag_undef_t}, // synonym {'p', tm_tag_namespace_t}, // procedure {'r', tm_tag_undef_t}, // record {'s', tm_tag_undef_t}, // subtype {'t', tm_tag_class_t}, // table - {'T', tm_tag_macro_t}, // trigger {'v', tm_tag_variable_t}, // variable - {'i', tm_tag_struct_t}, // index - {'e', tm_tag_undef_t}, // event - {'U', tm_tag_undef_t}, // publication - {'R', tm_tag_undef_t}, // service - {'D', tm_tag_undef_t}, // domain - {'V', tm_tag_member_t}, // view - {'n', tm_tag_undef_t}, // synonym {'x', tm_tag_undef_t}, // mltable {'y', tm_tag_undef_t}, // mlconn {'z', tm_tag_undef_t}, // mlprop - {'C', tm_tag_undef_t}, // ccflag }; static TMParserMapGroup group_SQL[] = { {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t | tm_tag_prototype_t}, @@ -489,7 +494,7 @@ static TMParserMapGroup group_VHDL[] = { static TMParserMapEntry map_LUA[] = { {'f', tm_tag_function_t}, // function - {'X', tm_tag_undef_t}, // unknown + {'Y', tm_tag_undef_t}, // unknown }; static TMParserMapGroup group_LUA[] = { {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, @@ -557,6 +562,7 @@ static TMParserMapEntry map_FREEBASIC[] = { {'t', tm_tag_struct_t}, // type {'v', tm_tag_variable_t}, // variable {'g', tm_tag_externvar_t}, // enum + {'n', tm_tag_undef_t}, // namespace }; static TMParserMapGroup group_FREEBASIC[] = { {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, @@ -584,6 +590,8 @@ static TMParserMapGroup group_HAXE[] = { }; static TMParserMapEntry map_REST[] = { + {'H', tm_tag_undef_t}, // title + {'h', tm_tag_undef_t}, // subtitle {'c', tm_tag_namespace_t}, // chapter {'s', tm_tag_member_t}, // section {'S', tm_tag_macro_t}, // subsection @@ -603,6 +611,7 @@ static TMParserMapGroup group_REST[] = { static TMParserMapEntry map_HTML[] = { {'a', tm_tag_member_t}, // anchor {'c', tm_tag_undef_t}, // class + {'t', tm_tag_undef_t}, // title {'h', tm_tag_namespace_t}, // heading1 {'i', tm_tag_class_t}, // heading2 {'j', tm_tag_variable_t}, // heading3 @@ -733,6 +742,7 @@ static TMParserMapEntry map_MARKDOWN[] = { {'T', tm_tag_struct_t}, //l4subsection {'u', tm_tag_union_t}, //l5subsection {'n', tm_tag_undef_t}, //footnote + {'h', tm_tag_undef_t}, //hashtag }; static TMParserMapGroup group_MARKDOWN[] = { {N_("Chapters"), TM_ICON_NONE, tm_tag_namespace_t}, @@ -756,6 +766,7 @@ static TMParserMapEntry map_ABC[] = { static TMParserMapEntry map_VERILOG[] = { {'c', tm_tag_variable_t}, // constant + {'d', tm_tag_undef_t}, // define {'e', tm_tag_typedef_t}, // event {'f', tm_tag_function_t}, // function {'m', tm_tag_class_t}, // module @@ -872,6 +883,7 @@ static TMParserMapEntry map_RUST[] = { {'m', tm_tag_field_t}, // field {'e', tm_tag_enumerator_t}, // enumerator {'P', tm_tag_method_t}, // method + {'C', tm_tag_undef_t}, // constant }; static TMParserMapGroup group_RUST[] = { {N_("Modules"), TM_ICON_NAMESPACE, tm_tag_namespace_t}, @@ -896,7 +908,7 @@ static TMParserMapEntry map_GO[] = { {'m', tm_tag_member_t}, // member {'M', tm_tag_undef_t}, // anonMember {'n', tm_tag_undef_t}, // methodSpec - {'u', tm_tag_undef_t}, // unknown + {'Y', tm_tag_undef_t}, // unknown {'P', tm_tag_undef_t}, // packageName {'a', tm_tag_undef_t}, // talias {'R', tm_tag_undef_t}, // receiver @@ -931,6 +943,9 @@ static TMParserMapGroup group_JSON[] = { static TMParserMapEntry map_POWERSHELL[] = { {'f', tm_tag_function_t}, // function {'v', tm_tag_variable_t}, // variable + {'c', tm_tag_undef_t}, // class + {'i', tm_tag_undef_t}, // filter + {'g', tm_tag_undef_t}, // enum }; static TMParserMapGroup group_POWERSHELL[] = { {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, @@ -947,7 +962,7 @@ static TMParserMapEntry map_JULIA[] = { {'t', tm_tag_typedef_t}, // type /* defined as externvar to get those excluded as forward type in symbols.c:goto_tag() * so we can jump to the real implementation (if known) instead of to the import statement */ - {'x', tm_tag_externvar_t}, // unknown + {'Y', tm_tag_externvar_t}, // unknown }; static TMParserMapGroup group_JULIA[] = { {N_("Constants"), TM_ICON_VAR, tm_tag_variable_t}, @@ -995,7 +1010,7 @@ static TMParserMapGroup group_CLOJURE[] = { }; static TMParserMapEntry map_LISP[] = { - {'u', tm_tag_undef_t}, // unknown + {'Y', tm_tag_undef_t}, // unknown {'f', tm_tag_function_t}, // function {'v', tm_tag_variable_t}, // variable {'m', tm_tag_macro_t}, // macro From e00a2684d34446ae7426ee8e00349f8d3595e2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 18:49:46 +0200 Subject: [PATCH 05/18] Map title/subtitle of the rst parser Without it we might not get proper hierarchy in the sidebar because of missing parent tags. --- src/tagmanager/tm_parser.c | 6 ++++-- tests/ctags/simple.rst | 8 ++++++++ tests/ctags/simple.rst.tags | 12 ++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index b61bb269f8..1231727826 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -590,8 +590,8 @@ static TMParserMapGroup group_HAXE[] = { }; static TMParserMapEntry map_REST[] = { - {'H', tm_tag_undef_t}, // title - {'h', tm_tag_undef_t}, // subtitle + {'H', tm_tag_enumerator_t}, // title + {'h', tm_tag_field_t}, // subtitle {'c', tm_tag_namespace_t}, // chapter {'s', tm_tag_member_t}, // section {'S', tm_tag_macro_t}, // subsection @@ -601,6 +601,8 @@ static TMParserMapEntry map_REST[] = { {'d', tm_tag_undef_t}, // substdef }; static TMParserMapGroup group_REST[] = { + {N_("Title"), TM_ICON_NONE, tm_tag_enumerator_t}, + {N_("Subtitle"), TM_ICON_NONE, tm_tag_field_t}, {N_("Chapter"), TM_ICON_NONE, tm_tag_namespace_t}, {N_("Section"), TM_ICON_NONE, tm_tag_member_t}, {N_("Subsection"), TM_ICON_NONE, tm_tag_macro_t}, diff --git a/tests/ctags/simple.rst b/tests/ctags/simple.rst index b5e4f5275e..a6837204b1 100644 --- a/tests/ctags/simple.rst +++ b/tests/ctags/simple.rst @@ -1,3 +1,11 @@ +======================================= +Title +======================================= + +--------------------------------------- +Subtitle +--------------------------------------- + Chapter 1 ========= diff --git a/tests/ctags/simple.rst.tags b/tests/ctags/simple.rst.tags index de1fea1bc0..ca147cc24a 100644 --- a/tests/ctags/simple.rst.tags +++ b/tests/ctags/simple.rst.tags @@ -1,7 +1,7 @@ -Chapter 1Ì256Ö0 -namespace: Chapter 1 -Chapter 2Ì256Ö0 -namespace: Chapter 2 +Chapter 1Ì256ÎSubtitleÖ0 +namespace: Subtitle :: Chapter 1 +Chapter 2Ì256ÎSubtitleÖ0 +namespace: Subtitle :: Chapter 2 Section 1.1Ì64ÎChapter 1Ö0 member: Chapter 1 :: Section 1.1 Section 1.2Ì64ÎChapter 1Ö0 @@ -14,3 +14,7 @@ Subsection 1.1.1 macro: Section 1.1 :: Subsection 1.1.1 Subsubsection 1.1.1.1Ì16384ÎSubsection 1.1.1Ö0 variable: Subsection 1.1.1 :: Subsubsection 1.1.1.1 +SubtitleÌ8ÎTitleÖ0 +field: Title :: Subtitle +TitleÌ4Ö0 +enumerator: Title From cdf1aa1fbc7ba8d93dc3373a0c8600a80a4c03be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 18:55:31 +0200 Subject: [PATCH 06/18] Map defines to tm_tag_variable_t of verilog so we get the same output as before --- src/tagmanager/tm_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 1231727826..fe0c554a15 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -768,7 +768,7 @@ static TMParserMapEntry map_ABC[] = { static TMParserMapEntry map_VERILOG[] = { {'c', tm_tag_variable_t}, // constant - {'d', tm_tag_undef_t}, // define + {'d', tm_tag_variable_t}, // define {'e', tm_tag_typedef_t}, // event {'f', tm_tag_function_t}, // function {'m', tm_tag_class_t}, // module From 786819ba79574afcfd0cd92d0ac561a925441c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 19:00:31 +0200 Subject: [PATCH 07/18] Map filter to tm_tag_function_t for powershell to get the same output as before --- src/tagmanager/tm_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index fe0c554a15..6506672371 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -946,7 +946,7 @@ static TMParserMapEntry map_POWERSHELL[] = { {'f', tm_tag_function_t}, // function {'v', tm_tag_variable_t}, // variable {'c', tm_tag_undef_t}, // class - {'i', tm_tag_undef_t}, // filter + {'i', tm_tag_function_t}, // filter {'g', tm_tag_undef_t}, // enum }; static TMParserMapGroup group_POWERSHELL[] = { From 65bad205df3ce7a5bc089d3a71beac27e03b54de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 19:07:12 +0200 Subject: [PATCH 08/18] Update asm test Note: in C and other languages function arguments contain braces but it doesn't seem to be the case here. So instead of mymacro(args) we get mymacroargs in the pretty-printer script. Not sure if it's our problem or if the asm parser should be updated upstream. --- tests/ctags/masm.asm.tags | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ctags/masm.asm.tags b/tests/ctags/masm.asm.tags index 57d656e673..9a680afdc9 100644 --- a/tests/ctags/masm.asm.tags +++ b/tests/ctags/masm.asm.tags @@ -16,7 +16,7 @@ myequ macro: myequ myequalÌ65536Ö0 macro: myequal -mymacroÌ16Ö0 -function: mymacro +mymacroÌ16ÍargsÖ0 +function: mymacroargs mystructÌ2048Ö0 struct: mystruct From 27659bdef5000de4cdf79ca0c154f7c9ad1dcd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 19:15:59 +0200 Subject: [PATCH 09/18] Disable roles for macro kinds in C/C++ When role kinds are enabled, they report tags also for checks like #ifdef MY_MACRO in which we are not interested. --- src/tagmanager/tm_parser.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 6506672371..3138b8e707 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -1557,13 +1557,16 @@ gboolean tm_parser_enable_role(TMParserType lang, gchar kind) { switch (lang) { + case TM_PARSER_C: + case TM_PARSER_CPP: + return kind != 'd'; case TM_PARSER_GDSCRIPT: - return kind == 'c' ? FALSE : TRUE; + return kind != 'c'; case TM_PARSER_GO: /* 'p' is used both for package definition tags and imported package * tags and we can't tell which is which just by kind. By disabling * roles for this kind, we only get package definition tags. */ - return kind == 'p' ? FALSE : TRUE; + return kind != 'p'; } return TRUE; } From 978eb03e3bb33612013495054238fbc2a66683cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 19:38:51 +0200 Subject: [PATCH 10/18] Update unit tests for javascript There are lots of differences because of https://github.com/universal-ctags/ctags/commit/6d85089456ed215ce6b6a673744ae42ccc5e0e99 Also https://github.com/universal-ctags/ctags/commit/b1870b826a384c35671937743720464947af3b7e seems to confuse the parser in simple.js so it doesn't generate my_global_var2. Finally, Geany reports (geany:820768): Tagmanager-WARNING **: 20:38:28.755: ignoring null tag in /home/parallels/projects/geany/doc/reference/jquery.js(line: 2, language: JavaScript) --- tests/ctags/1795612.js.tags | 4 +- tests/ctags/1850914.js.tags | 4 +- tests/ctags/1878155.js.tags | 4 +- tests/ctags/1880687.js.tags | 4 +- tests/ctags/3470609.js.tags | 8 +- tests/ctags/arraylist.js.tags | 34 ++++++- tests/ctags/bracematch.js.tags | 8 +- tests/ctags/bug1950327.js.tags | 4 +- tests/ctags/bug2888482.js.tags | 2 + tests/ctags/bug3571233.js.tags | 6 +- tests/ctags/complex-return.js.tags | 16 +-- .../js-class-related-unterminated.js.tags | 4 +- tests/ctags/js-const.js.tags | 4 +- tests/ctags/js-let.js.tags | 4 +- tests/ctags/js-string-continuation.js.tags | 4 +- .../js-unknown-construct-nesting.js.tags | 4 +- tests/ctags/jsFunc_tutorial.js.tags | 98 +++++++++++-------- tests/ctags/parenthesis-rvalue.js.tags | 8 +- tests/ctags/secondary_fcn_name.js.tags | 12 +-- tests/ctags/simple.js.tags | 10 +- tests/ctags/ui5.controller.js.tags | 2 + 21 files changed, 146 insertions(+), 98 deletions(-) diff --git a/tests/ctags/1795612.js.tags b/tests/ctags/1795612.js.tags index eb99a3d6e0..ff0aef4adb 100644 --- a/tests/ctags/1795612.js.tags +++ b/tests/ctags/1795612.js.tags @@ -1,5 +1,5 @@ -RPCÌ1ÎTestÖ0 -class: Test :: RPC +Test.RPCÌ64Ö0 +member: Test.RPC asyncMethodÌ128Í( uri, method, params, callback)ÎTest.RPCÖ0 method: Test.RPC :: asyncMethod( uri, method, params, callback) asyncRequestÌ128Í( uri, data, callback)ÎTest.RPCÖ0 diff --git a/tests/ctags/1850914.js.tags b/tests/ctags/1850914.js.tags index 662dc392ba..e4ad3ee5f6 100644 --- a/tests/ctags/1850914.js.tags +++ b/tests/ctags/1850914.js.tags @@ -2,5 +2,5 @@ objLiteralMethod method: objectLiteral :: objLiteralMethod() objLiteralPropertyÌ64ÎobjectLiteralÖ0 member: objectLiteral :: objLiteralProperty -objectLiteralÌ1Ö0 -class: objectLiteral +objectLiteralÌ16384Ö0 +variable: objectLiteral diff --git a/tests/ctags/1878155.js.tags b/tests/ctags/1878155.js.tags index dc0236f603..f641ca6265 100644 --- a/tests/ctags/1878155.js.tags +++ b/tests/ctags/1878155.js.tags @@ -1,5 +1,5 @@ -REÌ1Ö0 -class: RE +REÌ16384Ö0 +variable: RE barÌ64ÎREÖ0 member: RE :: bar fooÌ16384Ö0 diff --git a/tests/ctags/1880687.js.tags b/tests/ctags/1880687.js.tags index 140c66f50b..3f25514410 100644 --- a/tests/ctags/1880687.js.tags +++ b/tests/ctags/1880687.js.tags @@ -1,5 +1,5 @@ -MyClassÌ1Ö0 -class: MyClass +MyClassÌ16384Ö0 +variable: MyClass MyClass_sub1Ì128Í(x)ÎMyClassÖ0 method: MyClass :: MyClass_sub1(x) MyClass_sub2Ì128Í(x)ÎMyClassÖ0 diff --git a/tests/ctags/3470609.js.tags b/tests/ctags/3470609.js.tags index 37cfda7a9f..160fa1bcfe 100644 --- a/tests/ctags/3470609.js.tags +++ b/tests/ctags/3470609.js.tags @@ -12,13 +12,13 @@ neg member: root :: neg parenthesesÌ64ÎrootÖ0 member: root :: parentheses -rootÌ1Ö0 -class: root +rootÌ16384Ö0 +variable: root stringÌ64ÎrootÖ0 member: root :: string subFunctionÌ128Í()Îroot.subObjectÖ0 method: root.subObject :: subFunction() -subObjectÌ1ÎrootÖ0 -class: root :: subObject +subObjectÌ64ÎrootÖ0 +member: root :: subObject subPropertyÌ64Îroot.subObjectÖ0 member: root.subObject :: subProperty diff --git a/tests/ctags/arraylist.js.tags b/tests/ctags/arraylist.js.tags index 634931b816..4778d9cbea 100644 --- a/tests/ctags/arraylist.js.tags +++ b/tests/ctags/arraylist.js.tags @@ -1,16 +1,42 @@ +aÌ64Îanon_variable_1Ö0 +member: anon_variable_1 :: a +aÌ64Îanon_variable_2Ö0 +member: anon_variable_2 :: a +aÌ64Îclass.anon_variable_3Ö0 +member: class.anon_variable_3 :: a +aÌ64Îclass.anon_variable_4Ö0 +member: class.anon_variable_4 :: a aÌ16384Ö0 variable: a +anon_variable_1Ì16384Ö1 +variable: anon_variable_1 flags: 1 +anon_variable_2Ì16384Ö1 +variable: anon_variable_2 flags: 1 +anon_variable_3Ì16384ÎclassÖ1 +variable: class :: anon_variable_3 flags: 1 +anon_variable_4Ì16384ÎclassÖ1 +variable: class :: anon_variable_4 flags: 1 +bÌ64Îanon_variable_1Ö0 +member: anon_variable_1 :: b +bÌ64Îanon_variable_2Ö0 +member: anon_variable_2 :: b +bÌ64Îclass.anon_variable_3Ö0 +member: class.anon_variable_3 :: b +bÌ64Îclass.anon_variable_4Ö0 +member: class.anon_variable_4 :: b bÌ16384Ö0 variable: b barÌ64Îclass.test1Ö0 member: class.test1 :: bar cÌ16384Ö0 variable: c -classÌ1Í()Ö0 -class: class() +classÌ16Í()Ö0 +function: class() fooÌ64Îclass.test1Ö0 member: class.test1 :: foo -test1Ì1ÎclassÖ0 -class: class :: test1 +test1Ì64ÎclassÖ0 +member: class :: test1 +test2Ì64ÎclassÖ0 +member: class :: test2 test3Ì128Í()ÎclassÖ0 method: class :: test3() diff --git a/tests/ctags/bracematch.js.tags b/tests/ctags/bracematch.js.tags index f8d29ec69d..3cb16151c0 100644 --- a/tests/ctags/bracematch.js.tags +++ b/tests/ctags/bracematch.js.tags @@ -1,7 +1,7 @@ -ContainerÌ16Í()Ö0 -function: Container() -MyClassÌ1Ö0 -class: MyClass +ContainerÌ1Í()Ö0 +class: Container() +MyClassÌ16384Ö0 +variable: MyClass insertÌ128Í(element, insertions)ÎMyClassÖ0 method: MyClass :: insert(element, insertions) wrapÌ128Í(element, wrapper, attributes)ÎMyClassÖ0 diff --git a/tests/ctags/bug1950327.js.tags b/tests/ctags/bug1950327.js.tags index 4c7b93cb60..a99a94368d 100644 --- a/tests/ctags/bug1950327.js.tags +++ b/tests/ctags/bug1950327.js.tags @@ -4,6 +4,8 @@ Different class: Different TabChromeÌ1Ö0 class: TabChrome +container.dirtyTabÌ64Ö0 +member: container.dirtyTab createTabTileÌ128Í(browser)ÎDifferentÖ0 method: Different :: createTabTile(browser) createTabTileÌ128Í(browser)ÎTabChromeÖ0 @@ -12,8 +14,6 @@ destroyTabTile method: Different :: destroyTabTile(tile) destroyTabTileÌ128Í(tile)ÎTabChromeÖ0 method: TabChrome :: destroyTabTile(tile) -dirtyTabÌ1ÎcontainerÖ0 -class: container :: dirtyTab initÌ128Í()ÎDifferentÖ0 method: Different :: init() initÌ128Í()ÎTabChromeÖ0 diff --git a/tests/ctags/bug2888482.js.tags b/tests/ctags/bug2888482.js.tags index 32334ac122..67ce0af406 100644 --- a/tests/ctags/bug2888482.js.tags +++ b/tests/ctags/bug2888482.js.tags @@ -1,3 +1,5 @@ +editFormElÌ16384Ö0 +variable: editFormEl onsubmitÌ16Í()ÎeditFormElÖ0 function: editFormEl :: onsubmit() scrollEditBoxÌ16Í()Ö0 diff --git a/tests/ctags/bug3571233.js.tags b/tests/ctags/bug3571233.js.tags index c4bf558bfb..ca1abb8710 100644 --- a/tests/ctags/bug3571233.js.tags +++ b/tests/ctags/bug3571233.js.tags @@ -1,7 +1,5 @@ -MyClassÌ1Ö0 -class: MyClass -MyClassÌ16Í()Ö0 -function: MyClass() +MyClassÌ1Í()Ö0 +class: MyClass() function1Ì16Í()Ö0 function: function1() function2Ì16Í()Ö0 diff --git a/tests/ctags/complex-return.js.tags b/tests/ctags/complex-return.js.tags index 588df1dcf5..f1bff570c0 100644 --- a/tests/ctags/complex-return.js.tags +++ b/tests/ctags/complex-return.js.tags @@ -12,14 +12,14 @@ c3m1 method: class3 :: c3m1() c3m2Ì128Í()Îclass3Ö0 method: class3 :: c3m2() -class1Ì1Í()Ö0 -class: class1() -class2Ì1Í()Ö0 -class: class2() -class3Ì1Í()Ö0 -class: class3() -class4Ì1Í()Ö0 -class: class4() +class1Ì16Í()Ö0 +function: class1() +class2Ì16Í()Ö0 +function: class2() +class3Ì16Í()Ö0 +function: class3() +class4Ì16Í()Ö0 +function: class4() func1Ì16Í()Ö0 function: func1() func2Ì16Í()Ö0 diff --git a/tests/ctags/js-class-related-unterminated.js.tags b/tests/ctags/js-class-related-unterminated.js.tags index f86abfa572..db8112827f 100644 --- a/tests/ctags/js-class-related-unterminated.js.tags +++ b/tests/ctags/js-class-related-unterminated.js.tags @@ -4,8 +4,8 @@ B class: Cls :: B(a, b) CÌ1Í()ÎClsÖ0 class: Cls :: C() -ClsÌ1Ö0 -class: Cls +ClsÌ16384Ö0 +variable: Cls SubÌ1Ö0 class: Sub SubÌ1Í()ÎCls.BÖ0 diff --git a/tests/ctags/js-const.js.tags b/tests/ctags/js-const.js.tags index 142e3eca9e..ceac335659 100644 --- a/tests/ctags/js-const.js.tags +++ b/tests/ctags/js-const.js.tags @@ -2,8 +2,8 @@ A macro: A BÌ65536Ö0 macro: B -GroupÌ1Ö0 -class: Group +GroupÌ16384Ö0 +variable: Group XÌ64ÎGroupÖ0 member: Group :: X YÌ64ÎGroupÖ0 diff --git a/tests/ctags/js-let.js.tags b/tests/ctags/js-let.js.tags index 7b3151ea25..9ee6b7bbf1 100644 --- a/tests/ctags/js-let.js.tags +++ b/tests/ctags/js-let.js.tags @@ -4,8 +4,8 @@ b variable: b funcÌ16Í()Ö0 function: func() -groupÌ1Ö0 -class: group +groupÌ16384Ö0 +variable: group xÌ64ÎgroupÖ0 member: group :: x yÌ64ÎgroupÖ0 diff --git a/tests/ctags/js-string-continuation.js.tags b/tests/ctags/js-string-continuation.js.tags index a0a827d8b2..3f71d23eb1 100644 --- a/tests/ctags/js-string-continuation.js.tags +++ b/tests/ctags/js-string-continuation.js.tags @@ -2,8 +2,8 @@ first method: o :: first() fourthÌ128Í()ÎoÖ0 method: o :: fourth() -oÌ1Ö0 -class: o +oÌ16384Ö0 +variable: o secondÌ128Í()ÎoÖ0 method: o :: second() thirdÌ128Í()ÎoÖ0 diff --git a/tests/ctags/js-unknown-construct-nesting.js.tags b/tests/ctags/js-unknown-construct-nesting.js.tags index 6cec0bfe52..4d41cf8948 100644 --- a/tests/ctags/js-unknown-construct-nesting.js.tags +++ b/tests/ctags/js-unknown-construct-nesting.js.tags @@ -4,5 +4,5 @@ bb method: o :: bb(a) ccÌ128Í()ÎoÖ0 method: o :: cc() -oÌ1Ö0 -class: o +oÌ16384Ö0 +variable: o diff --git a/tests/ctags/jsFunc_tutorial.js.tags b/tests/ctags/jsFunc_tutorial.js.tags index 251b7463bb..96060082c2 100644 --- a/tests/ctags/jsFunc_tutorial.js.tags +++ b/tests/ctags/jsFunc_tutorial.js.tags @@ -1,27 +1,27 @@ -Ball1Ì16Í()Ö0 -function: Ball1() -Ball3Ì16Í()Ö0 -function: Ball3() -D1Ì16Í(a, b)Ö0 -function: D1(a, b) -D2Ì16Í(a, b)Ö0 -function: D2(a, b) -D2AÌ16Í(a, b)Ö0 -function: D2A(a, b) -D3Ì16Ö0 -function: D3 -D4Ì16Ö0 -function: D4 -D5Ì16Í(myOperator)Ö0 -function: D5(myOperator) -DT1Ì16Í()Ö0 -function: DT1() -DT2Ì16Í(message)Ö0 -function: DT2(message) -DT2AÌ16Í(message)Ö0 -function: DT2A(message) -DT3Ì16Í()Ö0 -function: DT3() +Ball1Ì1Í()Ö0 +class: Ball1() +Ball3Ì1Í()Ö0 +class: Ball3() +D1Ì1Í(a, b)Ö0 +class: D1(a, b) +D2Ì1Í(a, b)Ö0 +class: D2(a, b) +D2AÌ1Í(a, b)Ö0 +class: D2A(a, b) +D3Ì1Ö0 +class: D3 +D4Ì1Ö0 +class: D4 +D5Ì1Í(myOperator)Ö0 +class: D5(myOperator) +DT1Ì1Í()Ö0 +class: DT1() +DT2Ì1Í(message)Ö0 +class: DT2(message) +DT2AÌ1Í(message)Ö0 +class: DT2A(message) +DT3Ì1Í()Ö0 +class: DT3() DT4Ì1Í(message, specifiedName)Ö0 class: DT4(message, specifiedName) DT5Ì1Í(color, specifiedName, owner, weight)Ö0 @@ -36,26 +36,28 @@ DT8 class: DT8(name, salary) DT9Ì1Í(name, salary)Ö0 class: DT9(name, salary) -PT1Ì16Í()Ö0 -function: PT1() +PT1Ì1Í()Ö0 +class: PT1() PT2Ì1Í(name, color)Ö0 class: PT2(name, color) PT3Ì1Í(name, salary)Ö0 class: PT3(name, salary) addÌ16Í(a,b)ÎmyObjectÖ0 function: myObject :: add(a,b) +addÌ16384Ö0 +variable: add addSalaryÌ128Í(addition)ÎPT3Ö0 method: PT3 :: addSalary(addition) -addSalaryFunctionÌ1Í(addition)Ö0 -class: addSalaryFunction(addition) -addSalaryFunctionDT9Ì1Í(addition)Ö0 -class: addSalaryFunctionDT9(addition) +addSalaryFunctionÌ16Í(addition)Ö0 +function: addSalaryFunction(addition) +addSalaryFunctionDT9Ì16Í(addition)Ö0 +function: addSalaryFunctionDT9(addition) ball0Ì16384Ö0 variable: ball0 ball1Ì16384Ö0 variable: ball1 -ball2Ì1Ö0 -class: ball2 +ball2Ì16384Ö0 +variable: ball2 ball3Ì16384Ö0 variable: ball3 ball4Ì16384Ö0 @@ -104,6 +106,8 @@ livesIn method: PT2 :: livesIn managerÌ16384Ö0 variable: manager +multiplyÌ16384Ö0 +variable: multiply myFunction4Ì16Í(message)Ö0 function: myFunction4(message) myFunction5Ì16Í()Ö0 @@ -118,22 +122,36 @@ myFunction6B function: myFunction6B() myFunction6EÌ16Í()Ö0 function: myFunction6E() -myObjectÌ1Ö0 -class: myObject +myObjectÌ16384Ö0 +variable: myObject my_global_var1Ì16384Ö0 variable: my_global_var1 -object1Ì1Ö0 -class: object1 -object2Ì1Ö0 -class: object2 -object3Ì1Ö0 -class: object3 +object1Ì16384Ö0 +variable: object1 +object2Ì16384Ö0 +variable: object2 +object3Ì16384Ö0 +variable: object3 priceÌ128ÎPT2Ö0 method: PT2 :: price +ptrÌ16384Ö0 +variable: ptr +ptr1Ì16384Ö0 +variable: ptr1 +ptr2Ì16384Ö0 +variable: ptr2 +resultStringÌ16384Ö0 +variable: resultString +savedFuncÌ16384Ö0 +variable: savedFunc savedFunc6BÌ16Í()Ö0 function: savedFunc6B() +savedFunctionÌ16384Ö0 +variable: savedFunction sayName4AÌ16Í(name)Ö0 function: sayName4A(name) +subtractÌ16384Ö0 +variable: subtract teamLeaderÌ16384Ö0 variable: teamLeader theAddÌ16Í(a, b)Ö0 diff --git a/tests/ctags/parenthesis-rvalue.js.tags b/tests/ctags/parenthesis-rvalue.js.tags index f589df55b7..fa6fb985b3 100644 --- a/tests/ctags/parenthesis-rvalue.js.tags +++ b/tests/ctags/parenthesis-rvalue.js.tags @@ -26,10 +26,10 @@ c1 variable: c1 c2Ì16384Ö0 variable: c2 -d1Ì1Ö0 -class: d1 -d2Ì1Ö0 -class: d2 +d1Ì16384Ö0 +variable: d1 +d2Ì16384Ö0 +variable: d2 e1Ì16Í()Ö0 function: e1() e1subÌ16Í()Îe1Ö0 diff --git a/tests/ctags/secondary_fcn_name.js.tags b/tests/ctags/secondary_fcn_name.js.tags index 6a69998943..79fe01aef8 100644 --- a/tests/ctags/secondary_fcn_name.js.tags +++ b/tests/ctags/secondary_fcn_name.js.tags @@ -1,9 +1,9 @@ -D1Ì16Í(a, b)Ö0 -function: D1(a, b) -D2Ì16Í(a, b)Ö0 -function: D2(a, b) -D2AÌ16Í(a, b)Ö0 -function: D2A(a, b) +D1Ì1Í(a, b)Ö0 +class: D1(a, b) +D2Ì1Í(a, b)Ö0 +class: D2(a, b) +D2AÌ1Í(a, b)Ö0 +class: D2A(a, b) my_global_var1Ì16384Ö0 variable: my_global_var1 theAddÌ16Í(a, b)Ö0 diff --git a/tests/ctags/simple.js.tags b/tests/ctags/simple.js.tags index 0cf52dd9a0..704fb1b52e 100644 --- a/tests/ctags/simple.js.tags +++ b/tests/ctags/simple.js.tags @@ -6,8 +6,12 @@ ValidClassTwo class: ValidClassTwo() calculateÌ16Í(number)ÎgetHalfOfÖ0 function: getHalfOf :: calculate(number) +coreÌ16384ÎtestlibÖ0 +variable: testlib :: core executeQueryStringÌ128ÎDatabaseÖ0 method: Database :: executeQueryString +extrasÌ1ÎtestlibÖ0 +class: testlib :: extras getHalfOfÌ16Í(num1, num2, num3)Ö0 function: getHalfOf(num1, num2, num3) getTodaysDateÌ128ÎDatabaseÖ0 @@ -18,8 +22,6 @@ invalidInnerFunction function: invalidInnerFunction(a,b) my_global_var1Ì16384Ö0 variable: my_global_var1 -my_global_var2Ì16384Ö0 -variable: my_global_var2 my_global_var3Ì16384Ö0 variable: my_global_var3 my_global_var4Ì16384Ö0 @@ -28,10 +30,10 @@ onchange function: my_global_var4 :: onchange() originalvalueÌ16384Ö0 variable: originalvalue +testlibÌ16384Ö0 +variable: testlib validFunctionFiveÌ16Í(a,b)ÎtestlibÖ0 function: testlib :: validFunctionFive(a,b) -validFunctionFourÌ16Í(a,b)ÎextraÖ0 -function: extra :: validFunctionFour(a,b) validFunctionOneÌ16Í(a,b)Ö0 function: validFunctionOne(a,b) validFunctionSixÌ16Í(a,b)Îtestlib.coreÖ0 diff --git a/tests/ctags/ui5.controller.js.tags b/tests/ctags/ui5.controller.js.tags index 4b2c5e8da9..08d1ba82df 100644 --- a/tests/ctags/ui5.controller.js.tags +++ b/tests/ctags/ui5.controller.js.tags @@ -1,3 +1,5 @@ +app.my_formÌ1Ö0 +class: app.my_form onInitÌ128Í()Îapp.my_formÖ0 method: app.my_form :: onInit() refreshFormÌ128Í(AUFNR)Îapp.my_formÖ0 From aa0c796b5c1e6a1b6a4050086309a5864819eca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 22:09:38 +0200 Subject: [PATCH 11/18] Switch to uctags regex-based matlab parser Even though the output isn't perfect, I'd prefer not to maintain our own version of the parser (with different kinds which makes ctags files incompatible with our version). --- ctags/Makefile.am | 2 +- ctags/parsers/geany_matlab.c | 150 ------------------------- ctags/parsers/matlab.c | 53 +++++++++ meson.build | 2 +- src/tagmanager/tm_parser.c | 6 +- tests/ctags/matlab_backtracking.m.tags | 10 ++ tests/ctags/matlab_test.m.tags | 6 +- 7 files changed, 73 insertions(+), 156 deletions(-) delete mode 100644 ctags/parsers/geany_matlab.c create mode 100644 ctags/parsers/matlab.c diff --git a/ctags/Makefile.am b/ctags/Makefile.am index f3226341f3..dc44508693 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -85,7 +85,7 @@ parsers = \ parsers/make.h \ parsers/markdown.c \ parsers/markdown.h \ - parsers/geany_matlab.c \ + parsers/matlab.c \ parsers/nsis.c \ parsers/objc.c \ parsers/ocaml.c \ diff --git a/ctags/parsers/geany_matlab.c b/ctags/parsers/geany_matlab.c deleted file mode 100644 index 09a946ee3e..0000000000 --- a/ctags/parsers/geany_matlab.c +++ /dev/null @@ -1,150 +0,0 @@ -/* -* -* Copyright (c) 2000-2001, Darren Hiebert -* -* This source code is released for free distribution under the terms of the -* GNU General Public License. -* -* This module contains functions for generating tags for Matlab scripts. -* The tags 'function' and 'struct' are parsed. -* Author Roland Baudin -*/ - -/* -* INCLUDE FILES -*/ -#include "general.h" /* must always come first */ - -#include - -#include "parse.h" -#include "routines.h" -#include "read.h" -#include "vstring.h" - -/* -* DATA DEFINITIONS -*/ -typedef enum { - K_FUNCTION, - K_STRUCT -} MatlabKind; - -static kindDefinition MatlabKinds [] = { - { true, 'f', "function", "Functions" }, - { true, 's', "struct", "Structures" }, -}; - -/* -* FUNCTION DEFINITIONS -*/ - -static void findMatlabTags (void) -{ - vString *name = vStringNew (); - const unsigned char *line; - const unsigned char *p; - - while ((line = readLineFromInputFile ()) != NULL) - { - int i, ic; - - if (line [0] == '\0' || line [0] == '%') - continue; - - /* search if the line has a comment */ - for (ic = 0 ; line [ic] != '\0' && line [ic]!='%' ; ++ic) - ; - - /* function tag */ - - /* read first word */ - for (i = 0 ; line [i] != '\0' && ! isspace (line [i]) ; ++i) - ; - - if (strncmp ((const char *) line, "function", (size_t) 8) == 0 - && isspace (line [8])) - { - const unsigned char *cp = line + i; - const unsigned char *ptr = cp; - bool eq=false; - - while (isspace ((int) *cp)) - ++cp; - - /* search for '=' character in the line (ignoring comments) */ - while (*ptr != '\0') - { - if (*ptr == '%') - break; - - if (*ptr == '=') - { - eq=true; - break; - } - ptr++; - } - - /* '=' was found => get the first word of the line after '=' */ - if (eq) - { - ptr++; - while (isspace ((int) *ptr)) - ++ptr; - - while (isalnum ((int) *ptr) || *ptr == '_') - { - vStringPut (name, (int) *ptr); - ++ptr; - } - } - - /* '=' was not found => get the first word of the line after "function" */ - else - { - while (isalnum ((int) *cp) || *cp == '_') - { - vStringPut (name, (int) *cp); - ++cp; - } - } - - makeSimpleTag (name, K_FUNCTION); - vStringClear (name); - } - - /* struct tag */ - - /* search if the line contains the keyword 'struct' */ - p=(const unsigned char*) strstr ((const char*) line, "struct"); - - /* and avoid the part after the '%' if any */ - if ( p != NULL && ic>0 && p < line+ic) - { - const unsigned char *cp = line; - - /* get the left most part of the line before '=' */ - while (*cp != '\0' && !isspace(*cp) && *cp != '=' ) - { - vStringPut (name, (int) *cp); - ++cp; - } - - makeSimpleTag (name, K_STRUCT); - vStringClear (name); - } - } - vStringDelete (name); -} - -extern parserDefinition* MatLabParser (void) -{ - static const char *const extensions [] = { "m", NULL }; - parserDefinition* def = parserNew ("Matlab"); - def->kindTable = MatlabKinds; - def->kindCount = ARRAY_SIZE (MatlabKinds); - def->extensions = extensions; - def->parser = findMatlabTags; - return def; -} diff --git a/ctags/parsers/matlab.c b/ctags/parsers/matlab.c new file mode 100644 index 0000000000..7da97e0387 --- /dev/null +++ b/ctags/parsers/matlab.c @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2008, David Fishburn +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for generating tags for MATLAB language files. +*/ + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include +#include "parse.h" +#include "routines.h" +#include "selectors.h" + +static tagRegexTable matlabTagRegexTable [] = { + /* function [x,y,z] = asdf */ + { "^[ \t]*function[ \t]*\\[.*\\][ \t]*=[ \t]*([.a-zA-Z0-9_]+)", + "\\1", "f,function", NULL}, + /* function x = asdf */ + {"^[ \t]*function[ \t]*[a-zA-Z0-9_]+[ \t]*=[ \t]*([.a-zA-Z0-9_]+)", + "\\1", "f,function", NULL}, + /* function asdf */ + {"^[ \t]*function[ \t]*([.a-zA-Z0-9_]+)[^=]*$", "\\1", + "f,function", NULL}, + /* variables */ + {"^[ \t]*([a-zA-Z0-9_]+)[ \t]*=[ \t]", "\\1", + "v,variable", NULL}, + /* class definitions */ + {"^[ \t]*classdef[ \t]*([a-zA-Z0-9_]+)", "\\1", + "c,class", NULL}, +}; + +/* +* FUNCTION DEFINITIONS +*/ +extern parserDefinition* MatLabParser (void) +{ + static const char *const extensions [] = { "m", NULL }; + static selectLanguage selectors [] = { selectByObjectiveCAndMatLabKeywords, + NULL }; + parserDefinition* const def = parserNew ("MatLab"); + def->extensions = extensions; + def->tagRegexTable = matlabTagRegexTable; + def->tagRegexCount = ARRAY_SIZE (matlabTagRegexTable); + def->method = METHOD_NOT_CRAFTED|METHOD_REGEX; + def->selectLanguage = selectors; + return def; +} diff --git a/meson.build b/meson.build index ba0edce902..6573463f6c 100644 --- a/meson.build +++ b/meson.build @@ -650,7 +650,7 @@ ctags = static_library('ctags', 'ctags/parsers/geany_docbook.c', 'ctags/parsers/geany_lcpp.c', 'ctags/parsers/geany_lcpp.h', - 'ctags/parsers/geany_matlab.c', + 'ctags/parsers/matlab.c', 'ctags/parsers/go.c', 'ctags/parsers/haskell.c', 'ctags/parsers/haxe.c', diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 3138b8e707..d2c661bf6b 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -668,11 +668,13 @@ static TMParserMapGroup group_FORTRAN[] = { static TMParserMapEntry map_MATLAB[] = { {'f', tm_tag_function_t}, // function - {'s', tm_tag_struct_t}, // struct + {'v', tm_tag_variable_t}, // variable + {'c', tm_tag_class_t}, // class }; static TMParserMapGroup group_MATLAB[] = { + {N_("Classes"), TM_ICON_CLASS, tm_tag_class_t}, {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, - {N_("Structures"), TM_ICON_STRUCT, tm_tag_struct_t}, + {N_("Variables"), TM_ICON_VAR, tm_tag_variable_t}, }; #define map_CUDA map_C diff --git a/tests/ctags/matlab_backtracking.m.tags b/tests/ctags/matlab_backtracking.m.tags index 2441d47e66..604eb758cc 100644 --- a/tests/ctags/matlab_backtracking.m.tags +++ b/tests/ctags/matlab_backtracking.m.tags @@ -1,2 +1,12 @@ backtrackÌ16Ö0 function: backtrack +cDDfncÌ16384Ö0 +variable: cDDfnc +dÌ16384Ö0 +variable: d +fcallÌ16384Ö0 +variable: fcall +fnÌ16384Ö0 +variable: fn +xnÌ16384Ö0 +variable: xn diff --git a/tests/ctags/matlab_test.m.tags b/tests/ctags/matlab_test.m.tags index bff0674717..76cb917f27 100644 --- a/tests/ctags/matlab_test.m.tags +++ b/tests/ctags/matlab_test.m.tags @@ -1,3 +1,5 @@ +FAIL6Ì16Ö0 +function: FAIL6 func1Ì16Ö0 function: func1 func2Ì16Ö0 @@ -6,5 +8,5 @@ func3 function: func3 func4Ì16Ö0 function: func4 -func5Ì16Ö0 -function: func5 +functionalityÌ16384Ö0 +variable: functionality From d515bce2167fd8623c83dc51e38562a4b822d019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Apr 2024 22:11:33 +0200 Subject: [PATCH 12/18] Use repoinfo.h with the used tag version --- ctags/main/repoinfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctags/main/repoinfo.h b/ctags/main/repoinfo.h index 25f0d94a27..661227107f 100644 --- a/ctags/main/repoinfo.h +++ b/ctags/main/repoinfo.h @@ -1 +1 @@ -#define CTAGS_REPOINFO "p6.0.20231001.0" +#define CTAGS_REPOINFO "p6.1.20240421.0" From ef2255bced523a3ad75773bac3e77b02725f10fd Mon Sep 17 00:00:00 2001 From: Frank Lanitz Date: Wed, 1 May 2024 13:49:00 +0200 Subject: [PATCH 13/18] Small update of German translation --- po/de.po | 1850 +++++++++++++++++++++++++++--------------------------- 1 file changed, 941 insertions(+), 909 deletions(-) diff --git a/po/de.po b/po/de.po index 29c1c674b5..5309b9cee9 100644 --- a/po/de.po +++ b/po/de.po @@ -2,7 +2,7 @@ # Copyright (C) 2006 The Geany contributors. # This file is distributed under the same license as the geany package. # Enrico Tröger 2006 - 2009 -# Frank Lanitz 2006 - 2023 +# Frank Lanitz 2006 - 2024 # Dominic Hopf 2008 - 2009 # # Basic guidelines for this translation: http://wiki.xfce.org/de/translations @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Geany 2.0\n" "Report-Msgid-Bugs-To: https://github.com/geany/geany/issues\n" -"POT-Creation-Date: 2023-10-16 20:23+0200\n" +"POT-Creation-Date: 2024-05-01 13:44+0200\n" "PO-Revision-Date: 2023-08-08 23:24+0200\n" "Last-Translator: Frank Lanitz \n" "Language-Team: German \n" @@ -24,7 +24,7 @@ msgstr "" "X-Poedit-SourceCharset: utf-8\n" "X-Poedit-Language: German\n" -#: geany.desktop.in:5 data/geany.glade:6456 +#: geany.desktop.in:5 data/geany.glade:6425 msgid "Geany" msgstr "Geany" @@ -48,11 +48,11 @@ msgstr "_Werkzeugleisteneinstellungen" msgid "_Hide Toolbar" msgstr "Werkzeugleiste _verbergen" -#: data/geany.glade:305 data/geany.glade:6714 +#: data/geany.glade:305 data/geany.glade:6683 msgid "_Edit" msgstr "_Bearbeiten" -#: data/geany.glade:313 data/geany.glade:6929 +#: data/geany.glade:313 data/geany.glade:6898 msgid "_Format" msgstr "_Format" @@ -60,69 +60,69 @@ msgstr "_Format" msgid "I_nsert" msgstr "_Einfügen" -#: data/geany.glade:332 data/geany.glade:7082 +#: data/geany.glade:332 data/geany.glade:7051 msgid "Insert _ChangeLog Entry" msgstr "_ChangeLog-Eintrag hinzufügen" -#: data/geany.glade:341 data/geany.glade:7091 +#: data/geany.glade:341 data/geany.glade:7060 msgid "Insert _Function Description" msgstr "_Funktionsbeschreibung einfügen" -#: data/geany.glade:350 data/geany.glade:7100 +#: data/geany.glade:350 data/geany.glade:7069 msgid "Insert Mu_ltiline Comment" msgstr "_Mehrzeiligen Kommentar einfügen" -#: data/geany.glade:357 data/geany.glade:7367 +#: data/geany.glade:357 data/geany.glade:7336 msgid "_More" msgstr "_Weitere" -#: data/geany.glade:370 data/geany.glade:7115 +#: data/geany.glade:370 data/geany.glade:7084 msgid "Insert File _Header" msgstr "_Dateikopf einfügen" -#: data/geany.glade:379 data/geany.glade:7124 +#: data/geany.glade:379 data/geany.glade:7093 msgid "Insert _GPL Notice" msgstr "_GPL-Hinweis einfügen" -#: data/geany.glade:388 data/geany.glade:7133 +#: data/geany.glade:388 data/geany.glade:7102 msgid "Insert _BSD License Notice" msgstr "_BSD-Lizenz-Hinweis einfügen" -#: data/geany.glade:405 data/geany.glade:7144 +#: data/geany.glade:405 data/geany.glade:7113 msgid "Insert Dat_e" msgstr "_Datum einfügen" -#: data/geany.glade:417 data/geany.glade:439 data/geany.glade:6602 -#: data/geany.glade:7049 data/geany.glade:7156 data/geany.glade:7178 -#: data/geany.glade:7885 data/geany.glade:7905 +#: data/geany.glade:417 data/geany.glade:439 data/geany.glade:6571 +#: data/geany.glade:7018 data/geany.glade:7125 data/geany.glade:7147 +#: data/geany.glade:7854 data/geany.glade:7874 msgid "invisible" msgstr "unsichtbar" -#: data/geany.glade:427 data/geany.glade:7166 +#: data/geany.glade:427 data/geany.glade:7135 msgid "_Insert \"include <...>\"" msgstr "\"include <...>\" ei_nfügen" -#: data/geany.glade:457 data/geany.glade:7190 src/keybindings.c:516 +#: data/geany.glade:457 data/geany.glade:7159 src/keybindings.c:516 msgid "Insert Alternative _White Space" msgstr "Alternatives _Leerzeichen einfügen" -#: data/geany.glade:470 data/geany.glade:7231 +#: data/geany.glade:470 data/geany.glade:7200 msgid "_Search" msgstr "_Suchen" -#: data/geany.glade:482 data/geany.glade:6526 +#: data/geany.glade:482 data/geany.glade:6495 msgid "Open Selected F_ile" msgstr "_Markierte Datei öffnen" -#: data/geany.glade:493 data/geany.glade:7400 src/symbols.c:2161 +#: data/geany.glade:493 data/geany.glade:7369 src/symbols.c:2161 msgid "Find _Usage" msgstr "A_uftreten finden" -#: data/geany.glade:504 data/geany.glade:7409 src/symbols.c:2166 +#: data/geany.glade:504 data/geany.glade:7378 src/symbols.c:2166 msgid "Find _Document Usage" msgstr "_Auftreten im Dokument finden" -#: data/geany.glade:515 data/geany.glade:7439 +#: data/geany.glade:515 data/geany.glade:7408 msgid "Go to Symbol Defini_tion" msgstr "Gehe zur Symbol-Defini_tion" @@ -130,7 +130,7 @@ msgstr "Gehe zur Symbol-Defini_tion" msgid "Conte_xt Action" msgstr "_Kontextaktion" -#: data/geany.glade:727 src/filetypes.c:127 src/filetypes.c:1531 +#: data/geany.glade:727 src/filetypes.c:127 src/filetypes.c:1533 msgid "None" msgstr "Keiner" @@ -146,12 +146,12 @@ msgstr "Aktuelle Zeichenkette" msgid "Match braces" msgstr "Ãœbereinstimmende Klammerung" -#: data/geany.glade:747 data/geany.glade:1700 data/geany.glade:2108 +#: data/geany.glade:747 data/geany.glade:1684 data/geany.glade:2092 msgid "Left" msgstr "Links" -#: data/geany.glade:750 data/geany.glade:1716 data/geany.glade:1801 -#: data/geany.glade:2125 +#: data/geany.glade:750 data/geany.glade:1700 data/geany.glade:1785 +#: data/geany.glade:2109 msgid "Right" msgstr "Rechts" @@ -159,7 +159,7 @@ msgstr "Rechts" msgid "Top" msgstr "Oben" -#: data/geany.glade:756 data/geany.glade:1786 +#: data/geany.glade:756 data/geany.glade:1770 msgid "Bottom" msgstr "Unten" @@ -319,63 +319,52 @@ msgstr "" "das Hauptfenster, das Notizbuch, den Such- und Springe-zu-Feldern in der " "Werkzeugliste sowie für das integrierte Terminal." -#: data/geany.glade:1316 -msgid "Use Windows native dialogs" -msgstr "Windows-typische Dialoge nutzen" - -#: data/geany.glade:1320 -msgid "" -"Defines whether to use the Windows native dialogs or whether to use the GTK " -"default dialogs" -msgstr "" -"Bestimmt, ob Dialoge im Stil von Windows oder von GTK geöffnet werden sollen" - -#: data/geany.glade:1338 data/geany.glade:2005 data/geany.glade:4854 +#: data/geany.glade:1322 data/geany.glade:1989 data/geany.glade:4823 msgid "Miscellaneous" msgstr "Sonstiges" -#: data/geany.glade:1366 +#: data/geany.glade:1350 msgid "Always wrap search" msgstr "Suche immer umbrechen" -#: data/geany.glade:1370 +#: data/geany.glade:1354 msgid "Always wrap search around the document" msgstr "" "Bei der Suche immer automatisch wieder am Beginn des Dokumentes beginnen." -#: data/geany.glade:1382 +#: data/geany.glade:1366 msgid "Hide the Find dialog" msgstr "Suche ausblenden" -#: data/geany.glade:1386 +#: data/geany.glade:1370 msgid "Hide the Find dialog after clicking Find Next/Previous" msgstr "Suche nach Klicken von »nächstes« oder »vorheriges« ausblenden." -#: data/geany.glade:1398 +#: data/geany.glade:1382 msgid "Use the current word under the cursor for Find dialogs" msgstr "Aktuelle Cursorposition zur Suche heranziehen" -#: data/geany.glade:1402 +#: data/geany.glade:1386 msgid "" "Use current word under the cursor when opening the Find, Find in Files or " "Replace dialog and there is no selection" msgstr "" "Bestimmt das aktuelle Wort zum Suchen & Ersetzen anhand der Cursorposition" -#: data/geany.glade:1414 +#: data/geany.glade:1398 msgid "Use the current file's directory for Find in Files" msgstr "Verzeichnis der aktuellen Datei für »In Dateien suchen« benutzen" -#: data/geany.glade:1435 +#: data/geany.glade:1419 msgid "Search" msgstr "Suche" -#: data/geany.glade:1463 +#: data/geany.glade:1447 msgid "Store project file inside the project base directory" msgstr "Projektdatei im Projektbasisverzeichnises erstellen" # : ../data/geany.glade.h:75 -#: data/geany.glade:1467 +#: data/geany.glade:1451 msgid "" "When enabled, a project file is stored by default inside the project base " "directory when creating new projects instead of one directory above the base " @@ -387,117 +376,117 @@ msgstr "" "Verzeichnis über dem Basisverzeichnis. Den Pfad kann im »Neues Projekt«-" "Dialog ändern." -#: data/geany.glade:1485 +#: data/geany.glade:1469 msgid "Projects" msgstr "Projekte" -#: data/geany.glade:1505 src/dialogs.c:230 +#: data/geany.glade:1489 src/dialogs.c:229 msgid "Miscellaneous" msgstr "Sonstiges" -#: data/geany.glade:1518 src/prefs.c:1600 +#: data/geany.glade:1502 src/prefs.c:1579 msgid "General" msgstr "Allgemein" -#: data/geany.glade:1566 +#: data/geany.glade:1550 msgid "Show symbol list" msgstr "Symbolliste anzeigen" -#: data/geany.glade:1570 +#: data/geany.glade:1554 msgid "Toggle the symbol list on and off" msgstr "Blendet die Symbolliste ein und aus" -#: data/geany.glade:1590 +#: data/geany.glade:1574 msgid "Default symbol sorting mode" msgstr "Standardmodus zur Sortierung der Symbole" -#: data/geany.glade:1596 +#: data/geany.glade:1580 msgid "Default sorting mode:" msgstr "Standard Sortierungsmodus:" -#: data/geany.glade:1606 src/stash.c:1219 +#: data/geany.glade:1590 src/stash.c:1223 msgid "Name" msgstr "Name" -#: data/geany.glade:1621 +#: data/geany.glade:1605 msgid "Appearance" msgstr "Aussehen" -#: data/geany.glade:1646 +#: data/geany.glade:1630 msgid "Show documents list" msgstr "Dokumentenliste anzeigen" -#: data/geany.glade:1650 +#: data/geany.glade:1634 msgid "Toggle the documents list on and off" msgstr "Blendet die Dokumentenliste ein und aus" -#: data/geany.glade:1666 +#: data/geany.glade:1650 msgid "Show sidebar" msgstr "Seitenleiste anzeigen" -#: data/geany.glade:1690 data/geany.glade:1776 +#: data/geany.glade:1674 data/geany.glade:1760 msgid "Position:" msgstr "Position:" -#: data/geany.glade:1745 +#: data/geany.glade:1729 msgid "Sidebar" msgstr "Seitenleiste" -#: data/geany.glade:1823 +#: data/geany.glade:1807 msgid "Message window" msgstr "Meldungsfenster" -#: data/geany.glade:1858 +#: data/geany.glade:1842 msgid "Symbol list:" msgstr "Symbolliste:" -#: data/geany.glade:1873 data/geany.glade:2288 +#: data/geany.glade:1857 data/geany.glade:2272 msgid "Message window:" msgstr "Meldungsfenster:" -#: data/geany.glade:1888 data/geany.glade:2360 +#: data/geany.glade:1872 data/geany.glade:2344 msgid "Editor:" msgstr "Editor:" -#: data/geany.glade:1902 +#: data/geany.glade:1886 msgid "Sets the font for the message window" msgstr "Ändert die Schriftart für das Meldungsfenster im Infobereich" -#: data/geany.glade:1920 +#: data/geany.glade:1904 msgid "Sets the font for the symbol list" msgstr "Legt die Schriftart für die Symbolliste fest" -#: data/geany.glade:1937 +#: data/geany.glade:1921 msgid "Sets the editor font" msgstr "Legt die Schriftart für das Editorfenster fest" -#: data/geany.glade:1955 +#: data/geany.glade:1939 msgid "Fonts" msgstr "Schriftarten" -#: data/geany.glade:1983 +#: data/geany.glade:1967 msgid "Show status bar" msgstr "Statusleiste anzeigen" -#: data/geany.glade:1987 +#: data/geany.glade:1971 msgid "Whether to show the status bar at the bottom of the main window" msgstr "" "Legt fest, ob die Statuszeile an der unteren Seite des Fensters angezeigt " "werden soll oder nicht" -#: data/geany.glade:2022 data/geany.glade:2818 src/prefs.c:1602 +#: data/geany.glade:2006 data/geany.glade:2802 src/prefs.c:1581 msgid "Interface" msgstr "Schnittstelle" -#: data/geany.glade:2051 +#: data/geany.glade:2035 msgid "Show editor tabs" msgstr "Zeige Dateireiter für geöffnete Dateien" -#: data/geany.glade:2066 +#: data/geany.glade:2050 msgid "Show close buttons" msgstr "»Schließen«-Schaltflächen anzeigen" -#: data/geany.glade:2070 +#: data/geany.glade:2054 msgid "" "Shows a small cross button in the file tabs to easily close files when " "clicking on it (requires restart of Geany)" @@ -505,23 +494,23 @@ msgstr "" "Zeigt ein kleines Kreuz auf den Dateireitern zum einfachen Schließen einer " "Datei an. (Diese Option benötigt einen Neustart von Geany zum Aktivieren.)" -#: data/geany.glade:2093 +#: data/geany.glade:2077 msgid "Placement of new file tabs:" msgstr "Platzierung neuer Dateireiter:" -#: data/geany.glade:2112 +#: data/geany.glade:2096 msgid "File tabs will be placed on the left of the notebook" msgstr "Neue Dateireiter werden links von der Dateiliste platziert" -#: data/geany.glade:2129 +#: data/geany.glade:2113 msgid "File tabs will be placed on the right of the notebook" msgstr "Neue Dateireiter werden rechts von der Dateiliste platziert" -#: data/geany.glade:2149 +#: data/geany.glade:2133 msgid "Next to current" msgstr "Reiter neben aktuellem öffnen" -#: data/geany.glade:2153 +#: data/geany.glade:2137 msgid "" "Whether to place file tabs next to the current tab rather than at the edges " "of the notebook" @@ -529,121 +518,121 @@ msgstr "" "Legt fest, ob neue Reiter direkt neben dem aktuellen oder an den Enden der " "Liste eingefügt werden." -#: data/geany.glade:2175 +#: data/geany.glade:2159 msgid "Double-clicking hides all additional widgets" msgstr "" "Doppelklick versteckt die zusätzlichen Unterfenster und zeigt nur den Editor " "an" -#: data/geany.glade:2179 +#: data/geany.glade:2163 msgid "Calls the View->Toggle All Additional Widgets command" msgstr "Zusätzliche Infofenster ein-/ausblenden" -#: data/geany.glade:2191 +#: data/geany.glade:2175 msgid "Switch to last used document after closing a tab" msgstr "Zum letzten Dokument wechseln, wenn der Karteireiter geschlossen wird" -#: data/geany.glade:2213 +#: data/geany.glade:2197 msgid "Set limit on visible characters in tab label." msgstr "Setze ein Limit an sichtbaren Zeichen in der Tab-Beschriftung" -#: data/geany.glade:2214 +#: data/geany.glade:2198 msgid "Tab label length:" msgstr "Länge der Tab-Beschriftung" -#: data/geany.glade:2226 +#: data/geany.glade:2210 msgid "Characters to be visible on the tab label of the file." msgstr "" "Anzahl der Zeichen die in der Tab-Beschriftung einer Datei sichtbar sein " "sollen." -#: data/geany.glade:2254 +#: data/geany.glade:2238 msgid "Editor tabs" msgstr "Dateireiter" -#: data/geany.glade:2324 +#: data/geany.glade:2308 msgid "Sidebar:" msgstr "Seitenleiste:" -#: data/geany.glade:2396 +#: data/geany.glade:2380 msgid "Tab positions" msgstr "Reiterposition" -#: data/geany.glade:2416 +#: data/geany.glade:2400 msgid "Notebook tabs" msgstr "Notizbuchreiter" -#: data/geany.glade:2456 +#: data/geany.glade:2440 msgid "Show t_oolbar" msgstr "Werkzeugleiste _anzeigen" -#: data/geany.glade:2471 +#: data/geany.glade:2455 msgid "_Append toolbar to the menu" msgstr "_Werkzeugliste direkt an das Menü anfügen" -#: data/geany.glade:2475 +#: data/geany.glade:2459 msgid "Pack the toolbar to the main menu to save vertical space" msgstr "" "Die Werkzeugliste direkt hinter dem Hauptmenü platzieren um etwas vertikalen " "Platz zu sparen." -#: data/geany.glade:2529 src/toolbar.c:941 +#: data/geany.glade:2513 src/toolbar.c:941 msgid "Customize Toolbar" msgstr "Werkzeugleiste anpassen" -#: data/geany.glade:2584 +#: data/geany.glade:2568 msgid "System _default" msgstr "S_ystemvorgabe" -#: data/geany.glade:2598 +#: data/geany.glade:2582 msgid "Images _and text" msgstr "Symbole _und Text" -#: data/geany.glade:2615 +#: data/geany.glade:2599 msgid "_Images only" msgstr "Nur _Symbole" -#: data/geany.glade:2632 +#: data/geany.glade:2616 msgid "_Text only" msgstr "Nur _Text" -#: data/geany.glade:2657 +#: data/geany.glade:2641 msgid "Icon style" msgstr "Symbolstil" -#: data/geany.glade:2690 +#: data/geany.glade:2674 msgid "S_ystem default" msgstr "S_ystemvorgabe" -#: data/geany.glade:2704 +#: data/geany.glade:2688 msgid "_Small icons" msgstr "_Kleine Symbole" -#: data/geany.glade:2721 +#: data/geany.glade:2705 msgid "_Very small icons" msgstr "_Sehr kleine Symbole" -#: data/geany.glade:2738 +#: data/geany.glade:2722 msgid "_Large icons" msgstr "_Große Symbole" -#: data/geany.glade:2763 +#: data/geany.glade:2747 msgid "Icon size" msgstr "Symbolgröße" -#: data/geany.glade:2782 +#: data/geany.glade:2766 msgid "Toolbar" msgstr "Werkzeugleiste" -#: data/geany.glade:2802 src/prefs.c:1604 +#: data/geany.glade:2786 src/prefs.c:1583 msgid "Toolbar" msgstr "Werkzeugleiste" -#: data/geany.glade:2852 data/geany.glade:9171 +#: data/geany.glade:2836 data/geany.glade:9140 msgid "Line wrapping" msgstr "Visueller Zeilenumbruch" -#: data/geany.glade:2856 data/geany.glade:9175 +#: data/geany.glade:2840 data/geany.glade:9144 msgid "" "Wrap the line at the window border and continue it on the next line. Note: " "line wrapping has a high performance cost for large documents so should be " @@ -653,11 +642,11 @@ msgstr "" "fort. Achtung: Bei großen Dokumenten erfordert der Zeilenumbruch viel " "Rechenleistung und sollte daher auf langsameren Rechnern deaktiviert werden." -#: data/geany.glade:2868 +#: data/geany.glade:2852 msgid "\"Smart\" home key" msgstr "»Intelligente« Pos1-Taste (Home)" -#: data/geany.glade:2872 +#: data/geany.glade:2856 msgid "" "When \"smart\" home is enabled, the HOME key will move the caret to the " "first non-blank character of the line, unless it is already there, it moves " @@ -671,11 +660,11 @@ msgstr "" "ist, springt der Cursor immer zum Beginn der Zeile ohne auf die aktuelle " "Position Rücksicht zu nehmen." -#: data/geany.glade:2884 +#: data/geany.glade:2868 msgid "Disable Drag and Drop" msgstr "Drag and Drop deaktivieren" -#: data/geany.glade:2888 +#: data/geany.glade:2872 msgid "" "Disable drag and drop completely in the editor window so you can't drag and " "drop any selections within or outside of the editor window" @@ -683,15 +672,15 @@ msgstr "" "Deaktiviert Drag and Drop für das Editorfenster. Dies verhindert, dass " "markierter Text mit der Maus verschoben werden kann." -#: data/geany.glade:2900 +#: data/geany.glade:2884 msgid "Code folding" msgstr "Quelltext-Ausblendung" -#: data/geany.glade:2915 +#: data/geany.glade:2899 msgid "Fold/unfold all children of a fold point" msgstr "Alle untergeordneten Quelltextblöcke ein/ausklappen" -#: data/geany.glade:2919 +#: data/geany.glade:2903 msgid "" "Fold or unfold all children of a fold point. By pressing the Shift key while " "clicking on a fold symbol the contrary behavior is used." @@ -700,11 +689,11 @@ msgstr "" "gedrückt halten der Umschalttaste wird das Gegenteil gemacht, wenn auf das " "Symbol geklickt wird." -#: data/geany.glade:2931 +#: data/geany.glade:2915 msgid "Use indicators to show compile errors" msgstr "Benutzt Markierungen, um Probleme beim Kompilieren anzuzeigen" -#: data/geany.glade:2935 +#: data/geany.glade:2919 msgid "" "Whether to use indicators (a squiggly underline) to highlight the lines " "where the compiler found a warning or an error" @@ -712,26 +701,26 @@ msgstr "" "Legt fest, ob Markierungen (gewellte Unterstreichungen) benutzt werden " "sollen, um Zeilen mit Fehlern beim Kompiliervorgang zu markieren" -#: data/geany.glade:2947 +#: data/geany.glade:2931 msgid "Newline strips trailing spaces" msgstr "Neue Zeile entfernt Leerzeichen am Zeilenende" -#: data/geany.glade:2951 +#: data/geany.glade:2935 msgid "Enable newline to strip the trailing spaces on the previous line" msgstr "" "Legt fest ob beim Wechseln in eine neue Zeile unnötige Leerzeichen am Ende " "einer Zeile automatisch entfernt werden sollen" -#: data/geany.glade:2970 data/geany.glade:9194 +#: data/geany.glade:2954 data/geany.glade:9163 msgid "Line breaking column:" msgstr "Spalte für automatischen Zeilenumbruch:" # TODO woah, hier muss noch was anderes her :D -#: data/geany.glade:3011 +#: data/geany.glade:2995 msgid "Comment toggle marker:" msgstr "Kommentarumschaltzeichen:" -#: data/geany.glade:3024 +#: data/geany.glade:3008 msgid "" "A string which is added when toggling a line comment in a source file, it is " "used to mark the comment as toggled." @@ -740,15 +729,47 @@ msgstr "" "Kommentar zu markieren, der per Tastenkombination ein- oder ausgeschaltet " "werden kann." -#: data/geany.glade:3049 data/geany.glade:9237 +#: data/geany.glade:3033 data/geany.glade:9206 msgid "Features" msgstr "Funktionen" -#: data/geany.glade:3066 +#: data/geany.glade:3061 data/geany.glade:9310 +msgid "Disabled" +msgstr "Deaktiviert" + +#: data/geany.glade:3065 +msgid "Do not show virtual spaces" +msgstr "Keine virtuellen Leerzeichen anzeigen" + +#: data/geany.glade:3078 +msgid "Only for rectangular selections" +msgstr "Nur für rechteckige Auswahl" + +#: data/geany.glade:3082 +msgid "" +"Only show virtual spaces beyond the end of lines when drawing a rectangular " +"selection" +msgstr "" +"Virtuelle Leerzeichen nur in Zeilen anzeigen, in denen gerade eine " +"rechteckige Auswahl statt findet." + +#: data/geany.glade:3095 +msgid "Always" +msgstr "Immer" + +#: data/geany.glade:3099 +msgid "Always show virtual spaces beyond the end of lines" +msgstr "Immer virtuelle Leerzeichen am Ende der Zeilen anzeigen" + +#: data/geany.glade:3118 +msgid "Virtual spaces" +msgstr "Virtuelle Leerzeichen" + +#: data/geany.glade:3135 msgid "Features" msgstr "Funktionen" -#: data/geany.glade:3096 data/geany.glade:8935 +#: data/geany.glade:3165 data/geany.glade:8904 msgid "" "Note: To apply these settings to all currently open documents, use " "Project->Apply Default Indentation." @@ -756,23 +777,23 @@ msgstr "" "Anmerkung: Um diese Einstellungen auf alle geöffneten Dokumente anzuwenden, " "bitte nutzen Sie Projekt->Standardeinrückung anwenden." -#: data/geany.glade:3146 data/geany.glade:8951 +#: data/geany.glade:3215 data/geany.glade:8920 msgid "_Width:" msgstr "_Breite:" -#: data/geany.glade:3160 data/geany.glade:8967 +#: data/geany.glade:3229 data/geany.glade:8936 msgid "The width in chars of a single indent" msgstr "Die Breite einer Einrückung in Zeichen" -#: data/geany.glade:3179 data/geany.glade:8988 +#: data/geany.glade:3248 data/geany.glade:8957 msgid "Auto-indent _mode:" msgstr "_Modus für automatische Einrückung:" -#: data/geany.glade:3214 data/geany.glade:9023 +#: data/geany.glade:3283 data/geany.glade:8992 msgid "Detect type from file" msgstr "Einrücktyp aus Datei lesen" -#: data/geany.glade:3218 data/geany.glade:9027 +#: data/geany.glade:3287 data/geany.glade:8996 msgid "" "Whether to detect the indentation type from file contents when a file is " "opened" @@ -781,38 +802,38 @@ msgstr "" "(Tabulatoren oder Leerzeichen) automatisch aus der geöffneten Datei zu " "bestimmen" -#: data/geany.glade:3233 data/geany.glade:9042 +#: data/geany.glade:3302 data/geany.glade:9011 msgid "T_abs and spaces" msgstr "Tabulatoren _und Leerzeichen" -#: data/geany.glade:3237 data/geany.glade:9046 +#: data/geany.glade:3306 data/geany.glade:9015 msgid "" "Use spaces if the total indent is less than the tab width, otherwise use both" msgstr "" "Benutzt Leerzeichen, falls die Einrückung kleiner ist als die " "Tabulatorenbreite, anderenfalls beides (Leerzeichen und Tabulatoren)" -#: data/geany.glade:3252 data/geany.glade:7715 data/geany.glade:9061 +#: data/geany.glade:3321 data/geany.glade:7684 data/geany.glade:9030 msgid "_Spaces" msgstr "_Leerzeichen" -#: data/geany.glade:3256 data/geany.glade:9065 +#: data/geany.glade:3325 data/geany.glade:9034 msgid "Use spaces when inserting indentation" msgstr "Benutze Leerzeichen zum Einrücken" -#: data/geany.glade:3272 data/geany.glade:7705 data/geany.glade:9081 +#: data/geany.glade:3341 data/geany.glade:7674 data/geany.glade:9050 msgid "_Tabs" msgstr "_Tabulatoren" -#: data/geany.glade:3276 data/geany.glade:9085 +#: data/geany.glade:3345 data/geany.glade:9054 msgid "Use one tab per indent" msgstr "Ein Tabulator pro Einzug" -#: data/geany.glade:3292 data/geany.glade:9115 +#: data/geany.glade:3361 data/geany.glade:9084 msgid "Detect width from file" msgstr "Einrücktiefe aus Datei lesen" -#: data/geany.glade:3296 data/geany.glade:9119 +#: data/geany.glade:3365 data/geany.glade:9088 msgid "" "Whether to detect the indentation width from file contents when a file is " "opened" @@ -820,27 +841,27 @@ msgstr "" "Wenn diese Option aktiviert ist, versucht Geany den Breite der Einrückung " "automatisch aus der geöffneten Datei zu bestimmen" -#: data/geany.glade:3313 data/geany.glade:4082 data/geany.glade:9103 -#: data/geany.glade:9678 +#: data/geany.glade:3382 data/geany.glade:4151 data/geany.glade:9072 +#: data/geany.glade:9647 msgid "Type:" msgstr "Typ:" -#: data/geany.glade:3332 +#: data/geany.glade:3401 msgid "Tab _key indents" msgstr "Einrüc_ken mit der Tabulatortaste" -#: data/geany.glade:3336 +#: data/geany.glade:3405 msgid "" "Pressing tab/shift-tab indents/unindents instead of inserting a tab character" msgstr "" "Tabulator und Shift+Tabulator rückt den Text ein oder aus anstatt nur ein " "Tabulatorzeichen einzufügen" -#: data/geany.glade:3348 +#: data/geany.glade:3417 msgid "_Backspace key unindents" msgstr "_Rücktaste verringer Einrückung" -#: data/geany.glade:3352 +#: data/geany.glade:3421 msgid "" "With the cursor in the indentation, pressing backspace unindents instead of " "deleting one character" @@ -848,19 +869,19 @@ msgstr "" "Wenn der Cursor in einer Einrückung steht, verringert die Rücktaste die " "Einrückung anstatt das vorherige Zeichen zu löschen" -#: data/geany.glade:3370 +#: data/geany.glade:3439 msgid "Indentation" msgstr "Einrückung" -#: data/geany.glade:3390 data/geany.glade:9141 +#: data/geany.glade:3459 data/geany.glade:9110 msgid "Indentation" msgstr "Einrückung" -#: data/geany.glade:3420 +#: data/geany.glade:3489 msgid "Snippet completion" msgstr "Vervollständigung von (Code-)Schnipseln" -#: data/geany.glade:3424 +#: data/geany.glade:3493 msgid "" "Type a defined short character sequence and complete it to a more complex " "string using a single keypress" @@ -868,19 +889,19 @@ msgstr "" "Mittels eines Tastenkürzels kann ein kurzer (Code-)Schnipsel zu einem " "komplexeren Text erweitert werden" -#: data/geany.glade:3436 +#: data/geany.glade:3505 msgid "XML/HTML tag auto-closing" msgstr "Automatisches Schließen von XML/HTML-Tag " -#: data/geany.glade:3440 +#: data/geany.glade:3509 msgid "Insert matching closing tag for XML/HTML" msgstr "Passendes schließendes Tag für XML/HTML einfügen" -#: data/geany.glade:3452 data/geany.glade:9261 +#: data/geany.glade:3521 data/geany.glade:9230 msgid "Automatic continuation of multi-line comments" msgstr "Automatisches Weiterführen von mehrzeiligen Kommentaren" -#: data/geany.glade:3456 data/geany.glade:9265 +#: data/geany.glade:3525 data/geany.glade:9234 msgid "" "Continue automatically multi-line comments in languages like C, C++ and Java " "when a new line is entered inside such a comment" @@ -888,11 +909,11 @@ msgstr "" "Verlängert die Kommentarzeilen in Sprachen wie C, C++ und Java, wenn eine " "neue Zeile innerhalb eines Kommentars hinzugefügt wird." -#: data/geany.glade:3468 +#: data/geany.glade:3537 msgid "Autocomplete symbols" msgstr "Autovervollständigung von Symbolen" -#: data/geany.glade:3472 +#: data/geany.glade:3541 msgid "" "Automatic completion of known symbols in open files (function names, global " "variables, ...)" @@ -900,47 +921,47 @@ msgstr "" "Automatische Vervollständigung von bekannten Variablen und Funktionsnamen " "aus den geöffneten Dateien" -#: data/geany.glade:3484 +#: data/geany.glade:3553 msgid "Autocomplete all words in document" msgstr "Autovervollständigung aller Wörter im Dokument" -#: data/geany.glade:3499 +#: data/geany.glade:3568 msgid "Drop rest of word on completion" msgstr "Bei Vervollständigung den Rest des Wortes ersetzen." -#: data/geany.glade:3524 +#: data/geany.glade:3593 msgid "Max. symbol name suggestions:" msgstr "Max. Vorschläge der Symbolvervollständigung:" -#: data/geany.glade:3539 +#: data/geany.glade:3608 msgid "Completion list height:" msgstr "Höhe der Vervollständigungsliste:" -#: data/geany.glade:3554 +#: data/geany.glade:3623 msgid "Characters to type for autocompletion:" msgstr "Zu tippende Zeichen für die Vervollständigung:" -#: data/geany.glade:3567 +#: data/geany.glade:3636 msgid "" "The amount of characters which are necessary to show the symbol " "autocompletion list" msgstr "Die Anzahl der Zeichen, die nötig sind um die Vorschläge anzuzeigen" -#: data/geany.glade:3584 +#: data/geany.glade:3653 msgid "Display height in rows for the autocompletion list" msgstr "Höhe der Liste in Zeilen" -#: data/geany.glade:3603 +#: data/geany.glade:3672 msgid "Maximum number of entries to display in the autocompletion list" msgstr "" "Anzahl der Elemente, die maximal angezeigt werden sollen, wenn die Liste " "angezeigt wird" -#: data/geany.glade:3622 +#: data/geany.glade:3691 msgid "Symbol list update frequency:" msgstr "Aktualisierungsfrequenz der Symbolliste:" -#: data/geany.glade:3637 +#: data/geany.glade:3706 msgid "" "Minimal delay (in milliseconds) between two automatic updates of the symbol " "list. Note that a too short delay may have performance impact, especially " @@ -951,104 +972,104 @@ msgstr "" "Geschwindigkeitseinbußen nach sich ziehen. Ein Wert von 0 deaktiviert die " "dynamischen Aktualisierungen." -#: data/geany.glade:3667 data/geany.glade:9276 +#: data/geany.glade:3736 data/geany.glade:9245 msgid "Completions" msgstr "Vervollständigungen" -#: data/geany.glade:3695 +#: data/geany.glade:3764 msgid "Parenthesis ( )" msgstr "Klammern ( )" -#: data/geany.glade:3699 +#: data/geany.glade:3768 msgid "Auto-close parenthesis when typing an opening one" msgstr "Automatisches Schließen der Klammern beim Tippen der öffnenden Klammer" -#: data/geany.glade:3711 +#: data/geany.glade:3780 msgid "Curly brackets { }" msgstr "Geschweifte Klammern {}" -#: data/geany.glade:3715 +#: data/geany.glade:3784 msgid "Auto-close curly bracket when typing an opening one" msgstr "Automatisches Schließen der Klammern beim Tippen der öffnenden Klammer" -#: data/geany.glade:3727 +#: data/geany.glade:3796 msgid "Square brackets [ ]" msgstr "Eckige Klammern [ ]" -#: data/geany.glade:3731 +#: data/geany.glade:3800 msgid "Auto-close square-bracket when typing an opening one" msgstr "" "Automatisches Schließen von eckigen Klammern beim Tippen der öffnenden " "Klammer" -#: data/geany.glade:3743 +#: data/geany.glade:3812 msgid "Single quotes ' '" msgstr "Einfache Anführungs-/Schlusszeichen" -#: data/geany.glade:3747 +#: data/geany.glade:3816 msgid "Auto-close single quote when typing an opening one" msgstr "" "Automatisches Setzen der schließenden Anführungsstriche beim Tippen der " "öffnenden" -#: data/geany.glade:3759 +#: data/geany.glade:3828 msgid "Double quotes \" \"" msgstr "Doppelte Anführungs-/Schlusszeichen" -#: data/geany.glade:3763 +#: data/geany.glade:3832 msgid "Auto-close double quote when typing an opening one" msgstr "" "Automatisches Setzen der schließenden Anführungsstriche beim Tippen der " "öffnenden" -#: data/geany.glade:3781 +#: data/geany.glade:3850 msgid "Auto-close quotes and brackets" msgstr "Automatisches Schließen von Klammern und Anführungszeichen" -#: data/geany.glade:3801 +#: data/geany.glade:3870 msgid "Completions" msgstr "Vervollständigungen" -#: data/geany.glade:3833 +#: data/geany.glade:3902 msgid "Invert syntax highlighting colors" msgstr "Invertiere Syntaxhervorhebungen" -#: data/geany.glade:3837 +#: data/geany.glade:3906 msgid "Invert all colors, by default using white text on a black background" msgstr "" "Vertauscht die Farben für die Anzeige. Im Standard wird weißer Text auf " "einem schwarzen Hintergrund genutzt." -#: data/geany.glade:3849 +#: data/geany.glade:3918 msgid "Show indentation guides" msgstr "Zeige Einrückungshinweise" -#: data/geany.glade:3853 +#: data/geany.glade:3922 msgid "Shows small dotted lines to help you to use the right indentation" msgstr "" "Blendet gepunktete Linien ein, um die richtige Einrückung zu erleichtern" -#: data/geany.glade:3865 +#: data/geany.glade:3934 msgid "Show white space" msgstr "Zeige Leerzeichen" -#: data/geany.glade:3869 +#: data/geany.glade:3938 msgid "Marks spaces with dots and tabs with arrows" msgstr "Markiert Leerzeichen mit Punkten und Tabulatoren mit kleinen Pfeilen" -#: data/geany.glade:3887 +#: data/geany.glade:3956 msgid "Show line endings" msgstr "Zeige Zeilenenden" -#: data/geany.glade:3891 +#: data/geany.glade:3960 msgid "Shows the line ending character" msgstr "Macht Zeilenenden mit einem Sonderzeichen sichtbar" -#: data/geany.glade:3903 +#: data/geany.glade:3972 msgid "Show only non-default line endings" msgstr "Nur vom Standard abweichende Zeilenenden anzeigen" -#: data/geany.glade:3907 +#: data/geany.glade:3976 msgid "" "Shows line ending characters only when they differ from the file default " "line ending character" @@ -1056,19 +1077,19 @@ msgstr "" "Zeigt Zeilenendenzeichen nur in den Zeilen an an, in denen sie von den " "Standardeinstellungen für die gerade geöffnete Datei abweichen." -#: data/geany.glade:3923 +#: data/geany.glade:3992 msgid "Show line numbers" msgstr "Zeilennummern anzeigen" -#: data/geany.glade:3927 +#: data/geany.glade:3996 msgid "Shows or hides the Line Number margin" msgstr "Zeigt oder versteckt den Rand mit den Zeilennummern" -#: data/geany.glade:3939 +#: data/geany.glade:4008 msgid "Show markers margin" msgstr "Markierungsrand anzeigen" -#: data/geany.glade:3943 +#: data/geany.glade:4012 msgid "" "Shows or hides the small margin right of the line numbers, which is used to " "mark lines" @@ -1076,42 +1097,42 @@ msgstr "" "Zeigt oder versteckt den kleinen Rand rechts von den Zeilennummern, welcher " "zum Anzeigen von Markierungen genutzt wird" -#: data/geany.glade:3955 +#: data/geany.glade:4024 msgid "Stop scrolling at last line" msgstr "Am Ende des Dokuments nicht mehr weiter rollen" -#: data/geany.glade:3959 +#: data/geany.glade:4028 msgid "Whether to stop scrolling one page past the last line of a document" msgstr "" "Legt fest, ob am Ende des Dokuments noch eine Seite weiter nach unten " "gerollt werden kann oder nicht" -#: data/geany.glade:3979 +#: data/geany.glade:4048 msgid "Lines visible _around the cursor:" msgstr "Zielen _rund um den Cursor:" -#: data/geany.glade:4018 +#: data/geany.glade:4087 msgid "Display" msgstr "Anzeige" -#: data/geany.glade:4052 data/geany.glade:9328 +#: data/geany.glade:4121 data/geany.glade:9297 msgid "Column:" msgstr "Spalte:" -#: data/geany.glade:4067 +#: data/geany.glade:4136 msgid "Color:" msgstr "Farbe:" -#: data/geany.glade:4098 +#: data/geany.glade:4167 msgid "Sets the color of the long line marker" msgstr "Stellt die Farbe der »Umbruchhilfe für lange Zeilen« ein" -#: data/geany.glade:4099 data/geany.glade:6053 data/geany.glade:6070 +#: data/geany.glade:4168 data/geany.glade:6022 data/geany.glade:6039 #: src/toolbar.c:73 src/tools.c:835 msgid "Color Chooser" msgstr "Farbwähler" -#: data/geany.glade:4114 +#: data/geany.glade:4183 msgid "" "The long line marker is a thin vertical line in the editor, it helps to mark " "long lines, or as a hint to break the line. Set this value to a value " @@ -1122,11 +1143,11 @@ msgstr "" "notwendigen Zeilenumbruch hin. Werte größer als 0 geben die Spalte an, in " "der die Linie angezeigt werden soll." -#: data/geany.glade:4138 +#: data/geany.glade:4207 msgid "Line" msgstr "Linie" -#: data/geany.glade:4142 +#: data/geany.glade:4211 msgid "" "Prints a vertical line in the editor window at the given cursor position " "(see below)" @@ -1134,11 +1155,11 @@ msgstr "" "Zeichnet eine vertikale Linie im Editor an der angegebenen Cursor-Position " "(nur sinnvoll mit dicktengleichen Schriften)" -#: data/geany.glade:4154 +#: data/geany.glade:4223 msgid "Background" msgstr "Hintergrund" -#: data/geany.glade:4158 +#: data/geany.glade:4227 msgid "" "The background color of characters after the given cursor position (see " "below) changed to the color set below, (this is recommended if you use " @@ -1148,136 +1169,88 @@ msgstr "" "(siehe unten) stehen, wird auf die unten angegebene Farbe geändert (nützlich " "für proportionale Schriftarten)" -#: data/geany.glade:4181 +#: data/geany.glade:4250 msgid "Enabled" msgstr "Aktiviert" -#: data/geany.glade:4202 data/geany.glade:9421 +#: data/geany.glade:4271 data/geany.glade:9390 msgid "Long line marker" msgstr "Umbruchhilfe für lange Zeilen" -#: data/geany.glade:4230 data/geany.glade:9341 -msgid "Disabled" -msgstr "Deaktiviert" - -#: data/geany.glade:4234 -msgid "Do not show virtual spaces" -msgstr "Keine virtuellen Leerzeichen anzeigen" - -#: data/geany.glade:4246 -msgid "Only for rectangular selections" -msgstr "Nur für rechteckige Auswahl" - -#: data/geany.glade:4250 -msgid "" -"Only show virtual spaces beyond the end of lines when drawing a rectangular " -"selection" -msgstr "" -"Virtuelle Leerzeichen nur in Zeilen anzeigen, in denen gerade eine " -"rechteckige Auswahl statt findet." - -#: data/geany.glade:4263 -msgid "Always" -msgstr "Immer" - -#: data/geany.glade:4267 -msgid "Always show virtual spaces beyond the end of lines" -msgstr "Immer virtuelle Leerzeichen am Ende der Zeilen anzeigen" - -#: data/geany.glade:4286 -msgid "Virtual spaces" -msgstr "Virtuelle Leerzeichen" - -#: data/geany.glade:4315 +#: data/geany.glade:4300 msgid "Show in markers margin" msgstr "Im Markierungsrand anzeigen" -#: data/geany.glade:4329 +#: data/geany.glade:4314 msgid "Show as underline indicators" msgstr "Als unterstrichenen Indikator anzeigen" -#: data/geany.glade:4349 +#: data/geany.glade:4334 msgid "Change History" msgstr "Verlauf" -#: data/geany.glade:4369 +#: data/geany.glade:4354 msgid "Display" msgstr "Ansicht" -#: data/geany.glade:4385 data/geany.glade:9441 src/keybindings.c:310 -#: src/prefs.c:1606 +#: data/geany.glade:4370 data/geany.glade:9410 src/keybindings.c:310 +#: src/prefs.c:1585 msgid "Editor" msgstr "Editor" -#: data/geany.glade:4420 +#: data/geany.glade:4405 msgid "Open new documents from the command-line" msgstr "Öffne neue Dokumente von der Kommandozeile" -#: data/geany.glade:4424 +#: data/geany.glade:4409 msgid "Create a new file for each command-line filename that doesn't exist" msgstr "" "Erstellt eine neue Datei für jeden Dateinamen, der auf der Kommandozeile " "angegeben wurde, aber nicht geöffnet werden konnte" -#: data/geany.glade:4464 +#: data/geany.glade:4449 msgid "Default end of line characters:" msgstr "Zeichen für das Standardzeilenende:" -#: data/geany.glade:4495 +#: data/geany.glade:4480 msgid "New files" msgstr "Neue Dateien" -#: data/geany.glade:4530 +#: data/geany.glade:4515 msgid "Default encoding (new files):" msgstr "Standardzeichenkodierung (neue Dateien):" -#: data/geany.glade:4544 +#: data/geany.glade:4529 msgid "Sets the default encoding for newly created files" msgstr "Setzt die Zeichenkodierung für neu erstellte Dateien" -#: data/geany.glade:4570 -msgid "Use fixed encoding when opening non-Unicode files" -msgstr "" -"Benutze feststehende Zeichenkodierung beim Öffnen neuer, nicht Unicode-" -"Dateien" - -#: data/geany.glade:4574 -msgid "" -"This option disables the automatic detection of the file encoding when " -"opening non-Unicode files and opens the file with the specified encoding " -"(usually not needed)" -msgstr "" -"Diese Option deaktiviert die automatische Erkennung der Zeichenkodierung und " -"öffnet die ausgewählten nicht Unicode-Dateien mit der angegebenen Kodierung. " -"(Wird nur in Ausnahmen benötigt)" - -#: data/geany.glade:4592 +#: data/geany.glade:4561 msgid "Default encoding (existing non-Unicode files):" msgstr "Standardzeichenkodierung (nicht Unicode-Dateien):" -#: data/geany.glade:4606 +#: data/geany.glade:4575 msgid "Sets the default encoding for opening existing non-Unicode files" msgstr "" "Setzt die Standardzeichenkodierung für zu öffnende Dateien, wenn kein " "Unicode (z.B. UTF-8) zur Anwendung kommt" -#: data/geany.glade:4638 +#: data/geany.glade:4607 msgid "Encodings" msgstr "Zeichenkodierungen:" -#: data/geany.glade:4666 data/geany.glade:9471 +#: data/geany.glade:4635 data/geany.glade:9440 msgid "Ensure new line at file end" msgstr "Neue Zeile am Dateiende" -#: data/geany.glade:4670 +#: data/geany.glade:4639 msgid "Ensures that at the end of the file is a new line" msgstr "Fügt am Dateiende eine neue Zeile an, falls keine vorhanden ist" -#: data/geany.glade:4682 data/geany.glade:9486 +#: data/geany.glade:4651 data/geany.glade:9455 msgid "Ensure consistent line endings" msgstr "Konsistente Zeilenenden sicherstellen" -#: data/geany.glade:4686 +#: data/geany.glade:4655 msgid "" "Ensures that newline characters always get converted before saving, avoiding " "mixed line endings in the same file" @@ -1285,39 +1258,39 @@ msgstr "" "Stellt sicher, dass die Zeilenumbrüche vor dem Speichern immer umgewandelt " "werden um gemischte Zeilenenden zu vermeiden." -#: data/geany.glade:4698 data/geany.glade:9501 +#: data/geany.glade:4667 data/geany.glade:9470 msgid "Strip trailing spaces and tabs" msgstr "Leerzeichen und Tabulatoren am Zeilenende entfernen" -#: data/geany.glade:4702 +#: data/geany.glade:4671 msgid "Removes trailing spaces and tabs at the end of lines" msgstr "Entfernt Leerzeichen und Tabulatoren am Ende einer Zeile" -#: data/geany.glade:4714 data/geany.glade:9516 src/keybindings.c:673 +#: data/geany.glade:4683 data/geany.glade:9485 src/keybindings.c:673 msgid "Replace tabs with space" msgstr "Tabulatoren durch Leerzeichen ersetzen" -#: data/geany.glade:4718 +#: data/geany.glade:4687 msgid "Replaces all tabs in document with spaces" msgstr "Ersetzt alle Tabulatoren im Dokument durch Leerzeichen" -#: data/geany.glade:4736 data/geany.glade:9537 +#: data/geany.glade:4705 data/geany.glade:9506 msgid "Saving files" msgstr "Speichern" -#: data/geany.glade:4775 +#: data/geany.glade:4744 msgid "Recent files list length:" msgstr "Anzahl der »Zuletzt geöffneten Dateien«" -#: data/geany.glade:4788 +#: data/geany.glade:4757 msgid "Specifies the number of files which are stored in the Recent files list" msgstr "Gibt die Länge der Liste der zuletzt geöffneten Dateien an" -#: data/geany.glade:4807 +#: data/geany.glade:4776 msgid "Disk check timeout:" msgstr "Zeitintervall zum Prüfen auf Dateiänderungen:" -#: data/geany.glade:4822 +#: data/geany.glade:4791 msgid "" "How often to check for changes to document files on disk, in seconds. Zero " "disables checking." @@ -1325,46 +1298,46 @@ msgstr "" "Wie oft soll auf Veränderungen geprüft werden? Angabe in Sekunden. 0 " "deaktiviert die Funktion." -#: data/geany.glade:4874 data/geany.glade:9557 src/prefs.c:1608 -#: src/tagmanager/tm_parser.c:456 plugins/filebrowser.c:1167 +#: data/geany.glade:4843 data/geany.glade:9526 src/prefs.c:1587 +#: src/tagmanager/tm_parser.c:457 plugins/filebrowser.c:1163 msgid "Files" msgstr "Dateien" -#: data/geany.glade:4929 +#: data/geany.glade:4898 msgid "Terminal:" msgstr "Terminal:" -#: data/geany.glade:4942 +#: data/geany.glade:4911 msgid "Browser:" msgstr "Browser:" -#: data/geany.glade:4957 +#: data/geany.glade:4926 msgid "" "A terminal emulator command (%c is substituted with the Geany run script " "filename)" msgstr "Ein Befehl für ein Terminal (%c wird durch Geanys run-Skript ersetzt)." -#: data/geany.glade:4971 +#: data/geany.glade:4940 msgid "Path (and possibly additional arguments) to your favorite browser" msgstr "Pfad und evtl. Argumente zum Starten eines Browsers" -#: data/geany.glade:4974 +#: data/geany.glade:4943 msgid "System default" msgstr "Systemvorgabe" -#: data/geany.glade:5030 +#: data/geany.glade:4999 msgid "Grep:" msgstr "Grep:" -#: data/geany.glade:5093 +#: data/geany.glade:5062 msgid "Tool paths" msgstr "Pfade zu den Werkzeugen" -#: data/geany.glade:5126 +#: data/geany.glade:5095 msgid "Context action:" msgstr "Kontextaktion:" -#: data/geany.glade:5138 +#: data/geany.glade:5107 msgid "" "Context action command. The currently selected word can be used with %s. It " "can appear anywhere in the given command and will be replaced before " @@ -1373,67 +1346,67 @@ msgstr "" "Der aktuell markierte Text kann mit %s angegeben werden. Es darf überall in " "der Kommandozeile vorkommen und wird vor dem Ausführen ersetzt." -#: data/geany.glade:5177 +#: data/geany.glade:5146 msgid "Commands" msgstr "Befehle" -#: data/geany.glade:5197 src/keybindings.c:322 src/prefs.c:1610 +#: data/geany.glade:5166 src/keybindings.c:322 src/prefs.c:1589 msgid "Tools" msgstr "Werkzeuge" -#: data/geany.glade:5252 +#: data/geany.glade:5221 msgid "Email address of the developer" msgstr "E-Mailadresse des Entwicklers" -#: data/geany.glade:5268 +#: data/geany.glade:5237 msgid "Initials of the developer name" msgstr "Initialen des Entwicklernamens" -#: data/geany.glade:5284 +#: data/geany.glade:5253 msgid "Initial version:" msgstr "Anfangsversion:" -#: data/geany.glade:5299 +#: data/geany.glade:5268 msgid "Version number, which a new file initially has" msgstr "Versionsnummer, welche eine neue Datei zu Beginn hat" -#: data/geany.glade:5315 +#: data/geany.glade:5284 msgid "Company name" msgstr "Firmenname" -#: data/geany.glade:5331 +#: data/geany.glade:5300 msgid "Developer:" msgstr "Entwickler:" -#: data/geany.glade:5344 +#: data/geany.glade:5313 msgid "Company:" msgstr "Firma:" -#: data/geany.glade:5359 +#: data/geany.glade:5328 msgid "Mail address:" msgstr "E-Mail-Adresse:" -#: data/geany.glade:5374 +#: data/geany.glade:5343 msgid "Initials:" msgstr "Initialen:" -#: data/geany.glade:5389 +#: data/geany.glade:5358 msgid "The name of the developer" msgstr "Der Name des Entwicklers" -#: data/geany.glade:5403 +#: data/geany.glade:5372 msgid "Year:" msgstr "Jahr:" -#: data/geany.glade:5418 +#: data/geany.glade:5387 msgid "Date:" msgstr "Datum:" -#: data/geany.glade:5433 +#: data/geany.glade:5402 msgid "Date & time:" msgstr "Datum & Zeit:" -#: data/geany.glade:5448 +#: data/geany.glade:5417 msgid "" "Specify a format for the {datetime} wildcard. For a list of available " "conversion specifiers see https://docs.gtk.org/glib/method.DateTime.format." @@ -1443,7 +1416,7 @@ msgstr "" "verfügbaren Kombinationen kann unter https://docs.gtk.org/glib/method." "DateTime.format abgerufen werden." -#: data/geany.glade:5464 +#: data/geany.glade:5433 msgid "" "Specify a format for the {year} wildcard. For a list of available conversion " "specifiers see https://docs.gtk.org/glib/method.DateTime.format.html." @@ -1452,7 +1425,7 @@ msgstr "" "möglichen Kombinationen kann unter https://docs.gtk.org/glib/method.DateTime." "format.html abgerufen werden." -#: data/geany.glade:5480 +#: data/geany.glade:5449 msgid "" "Specify a format for the {date} wildcard. For a list of available conversion " "specifiers see https://docs.gtk.org/glib/method.DateTime.format.html." @@ -1461,62 +1434,62 @@ msgstr "" "möglichen Kombinationen kann unter https://docs.gtk.org/glib/method.DateTime." "format.html abgerufen werden." -#: data/geany.glade:5507 +#: data/geany.glade:5476 msgid "Template data" msgstr "Daten für Vorlagen:" -#: data/geany.glade:5527 src/prefs.c:1612 +#: data/geany.glade:5496 src/prefs.c:1591 msgid "Templates" msgstr "Vorlagen" -#: data/geany.glade:5595 +#: data/geany.glade:5564 msgid "C_hange" msgstr "Ä_ndern" -#: data/geany.glade:5617 +#: data/geany.glade:5586 msgid "Keyboard shortcuts" msgstr "Tastaturkürzel" -#: data/geany.glade:5630 src/plugins.c:1899 src/plugins.c:1936 src/prefs.c:1614 +#: data/geany.glade:5599 src/plugins.c:1899 src/plugins.c:1936 src/prefs.c:1593 msgid "Keybindings" msgstr "Tastenkürzel" -#: data/geany.glade:5674 +#: data/geany.glade:5643 msgid "Command:" msgstr "Befehl:" -#: data/geany.glade:5687 +#: data/geany.glade:5656 msgid "Path to the command for printing files (use %f for the filename)" msgstr "Pfad zum Druckbefehl (benutzen Sie %f für den Dateinamen)" -#: data/geany.glade:5722 +#: data/geany.glade:5691 msgid "Use an external command for printing" msgstr "Ein externes Programm zum Drucken benutzen" -#: data/geany.glade:5754 src/printing.c:237 +#: data/geany.glade:5723 src/printing.c:237 msgid "Print line numbers" msgstr "Drucke Zeilennummern" -#: data/geany.glade:5758 src/printing.c:239 +#: data/geany.glade:5727 src/printing.c:239 msgid "Add line numbers to the printed page" msgstr "Zeige Zeilennummern auf der gedruckten Seite" -#: data/geany.glade:5770 src/printing.c:242 +#: data/geany.glade:5739 src/printing.c:242 msgid "Print page numbers" msgstr "Drucke Seitenzahlen" -#: data/geany.glade:5774 src/printing.c:244 +#: data/geany.glade:5743 src/printing.c:244 msgid "" "Add page numbers at the bottom of each page. It takes 2 lines of the page." msgstr "" "Füge jeder Seite die Seitenzahlen hinzu. Dazu werden zwei Zeilen der Seite " "benutzt." -#: data/geany.glade:5786 src/printing.c:247 +#: data/geany.glade:5755 src/printing.c:247 msgid "Print page header" msgstr "Drucke Seitenkopf" -#: data/geany.glade:5790 src/printing.c:249 +#: data/geany.glade:5759 src/printing.c:249 msgid "" "Add a little header to every page containing the page number, the filename " "and the current date (see below). It takes 3 lines of the page." @@ -1524,21 +1497,21 @@ msgstr "" "Fügt drei Kopfzeilen am Anfang jeder Seite ein (beinhaltet die Seitenzahl, " "den Dateinamen sowie das Datum)." -#: data/geany.glade:5820 src/printing.c:265 +#: data/geany.glade:5789 src/printing.c:265 msgid "Use the basename of the printed file" msgstr "Basisnamen der aktuellen Datei benutzen" -#: data/geany.glade:5824 +#: data/geany.glade:5793 src/printing.c:267 msgid "Print only the basename (without the path) of the printed file" msgstr "" "Nur den Basisdateinamen (ohne die Pfadangabe) der zu druckenden Datei " "verwenden" -#: data/geany.glade:5843 src/printing.c:273 +#: data/geany.glade:5812 src/printing.c:273 msgid "Date format:" msgstr "Datumsformat:" -#: data/geany.glade:5856 src/printing.c:279 +#: data/geany.glade:5825 src/printing.c:279 msgid "" "Specify a format for the date and time stamp which is added to the page " "header on each page. For a list of available conversion specifiers see " @@ -1548,108 +1521,108 @@ msgstr "" "Liste mit möglichen Werten kann unter https://docs.gtk.org/glib/method." "DateTime.format.html abgerufen werden." -#: data/geany.glade:5891 +#: data/geany.glade:5860 msgid "Use native GTK printing" msgstr "GTK-Druckunterstützung benutzen" -#: data/geany.glade:5915 +#: data/geany.glade:5884 msgid "Printing" msgstr "Drucken" -#: data/geany.glade:5928 src/prefs.c:1616 +#: data/geany.glade:5897 src/prefs.c:1595 msgid "Printing" msgstr "Drucken" -#: data/geany.glade:5963 +#: data/geany.glade:5932 msgid "Font:" msgstr "Schriftart:" -#: data/geany.glade:5977 +#: data/geany.glade:5946 msgid "Sets the font for the terminal widget" msgstr "Ändert die Schriftart des Terminals" -#: data/geany.glade:5979 +#: data/geany.glade:5948 msgid "Choose Terminal Font" msgstr "Terminalschriftart auswählen" -#: data/geany.glade:5991 +#: data/geany.glade:5960 msgid "Foreground color:" msgstr "Vordergrundfarbe:" -#: data/geany.glade:6006 +#: data/geany.glade:5975 msgid "Background color:" msgstr "Hintergrundfarbe:" -#: data/geany.glade:6021 +#: data/geany.glade:5990 msgid "Scrollback lines:" msgstr "Zeilen zum Zurückrollen:" -#: data/geany.glade:6036 +#: data/geany.glade:6005 msgid "Shell:" msgstr "Shell:" -#: data/geany.glade:6052 +#: data/geany.glade:6021 msgid "Sets the foreground color of the text in the terminal widget" msgstr "Setzt die Vordergrundfarbe für das Terminal" -#: data/geany.glade:6069 +#: data/geany.glade:6038 msgid "Sets the background color of the text in the terminal widget" msgstr "Setzt die Hintergrundfarbe für Text innerhalb des Terminals" -#: data/geany.glade:6085 +#: data/geany.glade:6054 msgid "" "Specifies the history in lines, which you can scroll back in the terminal " "widget" msgstr "Gibt die Anzahl der Zeilen an, die man im Terminal zurückrollen kann" -#: data/geany.glade:6105 +#: data/geany.glade:6074 msgid "" "Sets the path to the shell which should be started inside the terminal " "emulation" msgstr "Setzt den Pfad zu der Shell, die in der VTE benutzt werden soll" -#: data/geany.glade:6154 +#: data/geany.glade:6123 msgid "Scroll on keystroke" msgstr "Bei Tastendruck rollen" -#: data/geany.glade:6158 +#: data/geany.glade:6127 msgid "Whether to scroll to the bottom if a key was pressed" msgstr "" "Legt fest, ob das Fenster bei einem Tastendruck bis zum Ende gerollt werden " "soll" -#: data/geany.glade:6169 +#: data/geany.glade:6138 msgid "Scroll on output" msgstr "Bei Ausgabe rollen" -#: data/geany.glade:6173 +#: data/geany.glade:6142 msgid "Whether to scroll to the bottom when output is generated" msgstr "Legt fest, ob das Fenster bei einer Ausgabe gerollt werden soll" -#: data/geany.glade:6184 +#: data/geany.glade:6153 msgid "Cursor blinks" msgstr "Blinkender Cursor" -#: data/geany.glade:6188 +#: data/geany.glade:6157 msgid "Whether to blink the cursor" msgstr "Legt fest, ob der Cursor blinken soll" -#: data/geany.glade:6199 +#: data/geany.glade:6168 msgid "Override Geany keybindings" msgstr "Geanys Tastenkombination überschreiben" -#: data/geany.glade:6203 +#: data/geany.glade:6172 msgid "" "Allows the VTE to receive keyboard shortcuts (apart from focus commands)" msgstr "" "Erlaubt das integrierte Terminal über Tastenkürzel anzusprechen (mit " "Ausnahme des Fokus-Kommandos)" -#: data/geany.glade:6214 +#: data/geany.glade:6183 msgid "Disable menu shortcut key (F10 by default)" msgstr "Menütastenkombination deaktivieren (Vorgabe ist F10)" -#: data/geany.glade:6218 +#: data/geany.glade:6187 msgid "" "This option disables the keybinding to popup the menu bar (default is F10). " "Disabling it can be useful if you use, for example, Midnight Commander " @@ -1659,21 +1632,21 @@ msgstr "" "Deaktivierung kann sinnvoll sein, wenn Sie zum Beispiel den Midnight " "Commander in der VTE benutzen möchten." -#: data/geany.glade:6229 +#: data/geany.glade:6198 msgid "Follow path of the current file" msgstr "Pfad der aktuellen Datei setzen" -#: data/geany.glade:6233 +#: data/geany.glade:6202 msgid "Whether to execute \"cd $path\" when you switch between opened files" msgstr "" "Legt fest, ob »cd $path« in der VTE ausgeführt werden soll, wenn Sie " "zwischen geöffneten Dateien wechseln" -#: data/geany.glade:6244 +#: data/geany.glade:6213 msgid "Execute programs in the VTE" msgstr "Führe Programme in der VTE aus" -#: data/geany.glade:6248 +#: data/geany.glade:6217 msgid "" "Run programs in VTE instead of opening a terminal emulation window. Please " "note, programs executed in VTE cannot be stopped" @@ -1682,11 +1655,11 @@ msgstr "" "Achtung: Programme die in der VTE ausgeführt werden, können nicht gestoppt " "werden." -#: data/geany.glade:6259 +#: data/geany.glade:6228 msgid "Don't use run script" msgstr "Das Run-Skript nicht benutzen" -#: data/geany.glade:6264 +#: data/geany.glade:6233 msgid "" "Don't use the simple run script which is usually used to display the exit " "status of the executed program" @@ -1694,490 +1667,490 @@ msgstr "" "Das Run-Skript, welches gewöhnlich zur Ausgabe des Rückgabe-Wertes eines " "ausgeführten Programms genutzt wird, nicht benutzen" -#: data/geany.glade:6288 +#: data/geany.glade:6257 msgid "Terminal" msgstr "Terminal" -#: data/geany.glade:6301 src/prefs.c:1620 src/vte.c:381 +#: data/geany.glade:6270 src/prefs.c:1599 src/vte.c:377 msgid "Terminal" msgstr "Terminal" -#: data/geany.glade:6390 +#: data/geany.glade:6359 msgid "Warning: read the manual before changing these preferences." msgstr "" "Bitte konsultieren Sie zuallererst die Dokumentation für diese " "Einstellungen" -#: data/geany.glade:6416 +#: data/geany.glade:6385 msgid "Various preferences" msgstr "Verschiedene Einstellungen" -#: data/geany.glade:6429 src/prefs.c:1618 +#: data/geany.glade:6398 src/prefs.c:1597 msgid "Various" msgstr "Verschiedenes" -#: data/geany.glade:6479 +#: data/geany.glade:6448 msgid "_File" msgstr "_Datei" -#: data/geany.glade:6497 +#: data/geany.glade:6466 msgid "New (with _Template)" msgstr "Neu (aus _Vorlage)" -#: data/geany.glade:6513 data/geany.glade:8084 +#: data/geany.glade:6482 data/geany.glade:8053 msgid "_Open..." msgstr "Ö_ffnen…" -#: data/geany.glade:6535 +#: data/geany.glade:6504 msgid "Recent _Files" msgstr "_Zuletzt geöffnet" -#: data/geany.glade:6557 +#: data/geany.glade:6526 msgid "Save _As..." msgstr "Speichern _unter…" -#: data/geany.glade:6568 +#: data/geany.glade:6537 msgid "Sa_ve All" msgstr "A_lle speichern" -#: data/geany.glade:6579 src/callbacks.c:352 src/document.c:1671 -#: src/document.c:3622 src/sidebar.c:1215 +#: data/geany.glade:6548 src/callbacks.c:352 src/document.c:1670 +#: src/document.c:3621 src/sidebar.c:1215 msgid "_Reload" msgstr "Neu _laden" -#: data/geany.glade:6590 +#: data/geany.glade:6559 msgid "R_eload As" msgstr "N_eu laden als" -#: data/geany.glade:6636 +#: data/geany.glade:6605 msgid "Page Set_up" msgstr "Seiteneigensc_haften" -#: data/geany.glade:6643 +#: data/geany.glade:6612 msgid "_Print..." msgstr "_Drucken…" -#: data/geany.glade:6670 src/notebook.c:498 +#: data/geany.glade:6639 src/notebook.c:498 msgid "Close Ot_her Documents" msgstr "_Inaktive Dateien schließen" -#: data/geany.glade:6681 src/notebook.c:510 +#: data/geany.glade:6650 src/notebook.c:510 msgid "C_lose All" msgstr "_Alle schließen" -#: data/geany.glade:6813 +#: data/geany.glade:6782 msgid "Co_mmands" msgstr "_Kommandos" -#: data/geany.glade:6820 src/keybindings.c:439 +#: data/geany.glade:6789 src/keybindings.c:439 msgid "Cu_t Current Line(s)" msgstr "Aktuelle Zeile(n) _ausschneiden" -#: data/geany.glade:6831 src/keybindings.c:436 +#: data/geany.glade:6800 src/keybindings.c:436 msgid "_Copy Current Line(s)" msgstr "Aktuelle Zeile(n) _kopieren" -#: data/geany.glade:6844 src/keybindings.c:389 +#: data/geany.glade:6813 src/keybindings.c:389 msgid "_Delete Current Line(s)" msgstr "Aktuelle Zeile(n) _löschen" -#: data/geany.glade:6853 src/keybindings.c:386 +#: data/geany.glade:6822 src/keybindings.c:386 msgid "D_uplicate Line or Selection" msgstr "_Zeile oder Auswahl duplizieren" -#: data/geany.glade:6868 src/keybindings.c:449 +#: data/geany.glade:6837 src/keybindings.c:449 msgid "S_elect Current Line(s)" msgstr "Aktuelle Zeile(n) a_uswählen" -#: data/geany.glade:6877 src/keybindings.c:452 +#: data/geany.glade:6846 src/keybindings.c:452 msgid "Se_lect Current Paragraph" msgstr "Aktuellen Absatz au_swählen" -#: data/geany.glade:6892 +#: data/geany.glade:6861 msgid "_Move Line(s) Up" msgstr "Zeile(n) nach _oben bewegen" -#: data/geany.glade:6901 +#: data/geany.glade:6870 msgid "M_ove Line(s) Down" msgstr "Zeile(n) nach _unten bewegen" -#: data/geany.glade:6916 src/keybindings.c:503 +#: data/geany.glade:6885 src/keybindings.c:503 msgid "_Send Selection to Terminal" msgstr "_Auswahl an Terminal senden" -#: data/geany.glade:6938 src/keybindings.c:508 +#: data/geany.glade:6907 src/keybindings.c:508 msgid "_Join Lines" msgstr "Zeilen _zusammenführen" -#: data/geany.glade:6947 src/keybindings.c:505 +#: data/geany.glade:6916 src/keybindings.c:505 msgid "_Reflow Lines/Block" msgstr "_Neuformatieren der Zeile/des Abschnitts" -#: data/geany.glade:6956 src/keybindings.c:463 +#: data/geany.glade:6925 src/keybindings.c:463 msgid "T_oggle Case of Selection" msgstr "_Groß- und Kleinschreibung für die Auswahl tauschen" -#: data/geany.glade:6971 +#: data/geany.glade:6940 msgid "_Comment Line(s)" msgstr "Zeile(n) _auskommentieren" -#: data/geany.glade:6980 +#: data/geany.glade:6949 msgid "U_ncomment Line(s)" msgstr "Zeile(n) _einkommentieren" -#: data/geany.glade:6989 +#: data/geany.glade:6958 msgid "_Toggle Line Commentation" msgstr "Kommentierung _umschalten" -#: data/geany.glade:7002 +#: data/geany.glade:6971 msgid "_Increase Indent" msgstr "Einzug _erhöhen" -#: data/geany.glade:7013 +#: data/geany.glade:6982 msgid "_Decrease Indent" msgstr "Einzug _verringern" -#: data/geany.glade:7026 src/keybindings.c:482 +#: data/geany.glade:6995 src/keybindings.c:482 msgid "S_mart Line Indent" msgstr "Intelligentes _Einrücken" -#: data/geany.glade:7041 +#: data/geany.glade:7010 msgid "_Send Selection to" msgstr "_Auswahl senden an" -#: data/geany.glade:7069 +#: data/geany.glade:7038 msgid "I_nsert Comments" msgstr "K_ommentare einfügen" -#: data/geany.glade:7203 +#: data/geany.glade:7172 msgid "Preference_s" msgstr "E_instellungen" -#: data/geany.glade:7214 src/keybindings.c:529 +#: data/geany.glade:7183 src/keybindings.c:529 msgid "P_lugin Preferences" msgstr "Plugin-_Einstellungen" -#: data/geany.glade:7239 +#: data/geany.glade:7208 msgid "_Find..." msgstr "_Suchen…" -#: data/geany.glade:7252 +#: data/geany.glade:7221 msgid "Find _Next" msgstr "_Nächstes" -#: data/geany.glade:7261 +#: data/geany.glade:7230 msgid "Find _Previous" msgstr "_Vorheriges" -#: data/geany.glade:7274 src/symbols.c:2171 +#: data/geany.glade:7243 src/symbols.c:2171 msgid "Find in F_iles..." msgstr "In _Dateien suchen…" -#: data/geany.glade:7285 +#: data/geany.glade:7254 msgid "_Replace..." msgstr "_Ersetzen…" -#: data/geany.glade:7302 +#: data/geany.glade:7271 msgid "Next Me_ssage" msgstr "Nä_chste Nachricht" -#: data/geany.glade:7313 +#: data/geany.glade:7282 msgid "Pr_evious Message" msgstr "V_orherige Nachricht" -#: data/geany.glade:7332 src/keybindings.c:578 +#: data/geany.glade:7301 src/keybindings.c:578 msgid "Go to Ne_xt Marker" msgstr "Zur _nächsten Markierung springen" -#: data/geany.glade:7341 src/keybindings.c:581 +#: data/geany.glade:7310 src/keybindings.c:581 msgid "Go to Pre_vious Marker" msgstr "Zur _vorherigen Markierung springen" -#: data/geany.glade:7354 +#: data/geany.glade:7323 msgid "_Go to Line..." msgstr "_Gehe zu Zeile…" -#: data/geany.glade:7376 src/keybindings.c:541 +#: data/geany.glade:7345 src/keybindings.c:541 msgid "Find Next _Selection" msgstr "Auswahl _vorwärts im Dokument finden " -#: data/geany.glade:7385 src/keybindings.c:543 +#: data/geany.glade:7354 src/keybindings.c:543 msgid "Find Pre_vious Selection" msgstr "Auswahl _rückwärts im Dokument finden" -#: data/geany.glade:7424 src/keybindings.c:560 +#: data/geany.glade:7393 src/keybindings.c:560 msgid "_Mark All" msgstr "_Alles markieren" -#: data/geany.glade:7448 +#: data/geany.glade:7417 msgid "Go to Symbol Decl_aration" msgstr "Gehe zur Symbol-Dekl_aration" -#: data/geany.glade:7465 +#: data/geany.glade:7434 msgid "_View" msgstr "_Ansicht" -#: data/geany.glade:7472 +#: data/geany.glade:7441 msgid "Change _Font..." msgstr "_Schriftart ändern…" -#: data/geany.glade:7483 +#: data/geany.glade:7452 msgid "Change _Color Scheme..." msgstr "_Farbschemata…" -#: data/geany.glade:7502 +#: data/geany.glade:7471 msgid "Show _Markers Margin" msgstr "M_arkierungsrand anzeigen" -#: data/geany.glade:7512 +#: data/geany.glade:7481 msgid "Show _Line Numbers" msgstr "_Zeilennummern anzeigen" -#: data/geany.glade:7522 +#: data/geany.glade:7491 msgid "Show White S_pace" msgstr "_Leerzeichen anzeigen" -#: data/geany.glade:7531 +#: data/geany.glade:7500 msgid "Show Line _Endings" msgstr "Zeile_nenden anzeigen" -#: data/geany.glade:7540 +#: data/geany.glade:7509 msgid "Show Indentation _Guides" msgstr "Zeige _Einrückungshinweise" -#: data/geany.glade:7555 +#: data/geany.glade:7524 msgid "Full_screen" msgstr "_Vollbild" -#: data/geany.glade:7564 +#: data/geany.glade:7533 msgid "Toggle All _Additional Widgets" msgstr "Zusätzliche _Infofenster ein-/ausblenden" -#: data/geany.glade:7573 +#: data/geany.glade:7542 msgid "Show Message _Window" msgstr "_Meldungsfenster anzeigen" -#: data/geany.glade:7583 +#: data/geany.glade:7552 msgid "Show _Toolbar" msgstr "W_erkzeugleiste anzeigen" -#: data/geany.glade:7593 +#: data/geany.glade:7562 msgid "Show Side_bar" msgstr "Seiten_leiste anzeigen" -#: data/geany.glade:7643 +#: data/geany.glade:7612 msgid "_Document" msgstr "D_okument" -#: data/geany.glade:7652 +#: data/geany.glade:7621 msgid "_Line Wrapping" msgstr "_Visueller Zeilenumbruch" -#: data/geany.glade:7662 +#: data/geany.glade:7631 msgid "Line _Breaking" msgstr "Automatischer Zeilen_umbruch" -#: data/geany.glade:7671 +#: data/geany.glade:7640 msgid "_Auto-indentation" msgstr "_Automatische Einrückung benutzen" -#: data/geany.glade:7681 +#: data/geany.glade:7650 msgid "In_dent Type" msgstr "Art der _Einrückung" -#: data/geany.glade:7690 data/geany.glade:7748 +#: data/geany.glade:7659 data/geany.glade:7717 msgid "_Detect from Content" msgstr "Aus _Inhalt lesen" -#: data/geany.glade:7725 +#: data/geany.glade:7694 msgid "T_abs and Spaces" msgstr "Tabulatoren _und Leerzeichen" -#: data/geany.glade:7739 +#: data/geany.glade:7708 msgid "Indent Widt_h" msgstr "Einzugs_breite" -#: data/geany.glade:7763 +#: data/geany.glade:7732 msgid "_1" msgstr "_1" -#: data/geany.glade:7772 +#: data/geany.glade:7741 msgid "_2" msgstr "_2" -#: data/geany.glade:7782 +#: data/geany.glade:7751 msgid "_3" msgstr "_3" -#: data/geany.glade:7792 +#: data/geany.glade:7761 msgid "_4" msgstr "_4" -#: data/geany.glade:7803 +#: data/geany.glade:7772 msgid "_5" msgstr "_5" -#: data/geany.glade:7813 +#: data/geany.glade:7782 msgid "_6" msgstr "_6" -#: data/geany.glade:7823 +#: data/geany.glade:7792 msgid "_7" msgstr "_7" -#: data/geany.glade:7833 +#: data/geany.glade:7802 msgid "_8" msgstr "_8" -#: data/geany.glade:7853 +#: data/geany.glade:7822 msgid "Read _Only" msgstr "_Nur Lesen" -#: data/geany.glade:7862 +#: data/geany.glade:7831 msgid "_Write Unicode BOM" msgstr "_Unicode BOM schreiben" -#: data/geany.glade:7877 +#: data/geany.glade:7846 msgid "Set File_type" msgstr "Datei_typ festlegen" -#: data/geany.glade:7897 +#: data/geany.glade:7866 msgid "Set _Encoding" msgstr "Ze_ichenkodierung festlegen" -#: data/geany.glade:7917 +#: data/geany.glade:7886 msgid "Set Line E_ndings" msgstr "Zeilenen_den festlegen" -#: data/geany.glade:7926 +#: data/geany.glade:7895 msgid "Convert and Set to _CR/LF (Windows)" msgstr "Auf _CR/LF setzen und umwandeln (Windows)" -#: data/geany.glade:7935 +#: data/geany.glade:7904 msgid "Convert and Set to _LF (Unix)" msgstr "Auf _LF setzen und umwandeln (Unix)" -#: data/geany.glade:7946 +#: data/geany.glade:7915 msgid "Convert and Set to CR (Classic _Mac)" msgstr "Auf CR setzen und umwandeln (Klassischer _Apple)" -#: data/geany.glade:7966 src/keybindings.c:669 +#: data/geany.glade:7935 src/keybindings.c:669 msgid "_Clone" msgstr "_Klonen" -#: data/geany.glade:7975 src/keybindings.c:671 +#: data/geany.glade:7944 src/keybindings.c:671 msgid "_Strip Trailing Spaces" msgstr "Lee_rzeichen am Zeilenende entfernen" -#: data/geany.glade:7984 +#: data/geany.glade:7953 msgid "Replace Tabs with S_paces" msgstr "Tabulat_oren durch Leerzeichen ersetzen" -#: data/geany.glade:7993 +#: data/geany.glade:7962 msgid "_Replace Spaces with Tabs..." msgstr "_Leerzeichen durch Tabulatoren ersetzen…" -#: data/geany.glade:8008 +#: data/geany.glade:7977 msgid "_Fold All" msgstr "A_lle einklappen" -#: data/geany.glade:8017 +#: data/geany.glade:7986 msgid "_Unfold All" msgstr "_Alle ausklappen" -#: data/geany.glade:8032 +#: data/geany.glade:8001 msgid "Remove _Markers" msgstr "Alle _Markierungen entfernen" -#: data/geany.glade:8041 +#: data/geany.glade:8010 msgid "Remove Error _Indicators" msgstr "Alle _Fehlermarkierungen entfernen" -#: data/geany.glade:8054 +#: data/geany.glade:8023 msgid "_Project" msgstr "_Projekt" -#: data/geany.glade:8062 +#: data/geany.glade:8031 msgid "_New..." msgstr "_Neu…" -#: data/geany.glade:8073 +#: data/geany.glade:8042 msgid "New from _Folder..." msgstr "Neu aus _Ordner..." -#: data/geany.glade:8097 +#: data/geany.glade:8066 msgid "_Recent Projects" msgstr "_Zuletzt geöffnete Projekte" -#: data/geany.glade:8103 +#: data/geany.glade:8072 msgid "_Close" msgstr "S_chließen" -#: data/geany.glade:8122 +#: data/geany.glade:8091 msgid "Apply the default indentation settings to all documents" msgstr "Wende die Standardeinrückung auf alle Dokumente an" -#: data/geany.glade:8123 +#: data/geany.glade:8092 msgid "_Apply Default Indentation" msgstr "_Standardeinrückung anwenden" -#: data/geany.glade:8152 src/build.c:2446 src/build.c:2717 +#: data/geany.glade:8121 src/build.c:2448 src/build.c:2719 msgid "_Build" msgstr "_Erstellen" -#: data/geany.glade:8160 +#: data/geany.glade:8129 msgid "_Tools" msgstr "_Werkzeuge" -#: data/geany.glade:8167 +#: data/geany.glade:8136 msgid "_Reload Configuration" msgstr "_Einstellungen erneut laden" -#: data/geany.glade:8178 +#: data/geany.glade:8147 msgid "C_onfiguration Files" msgstr "_Konfigurationsdateien" -#: data/geany.glade:8194 +#: data/geany.glade:8163 msgid "_Color Chooser" msgstr "_Farb-Wähler" -#: data/geany.glade:8207 +#: data/geany.glade:8176 msgid "_Word Count" msgstr "_Wörter zählen" -#: data/geany.glade:8216 +#: data/geany.glade:8185 msgid "Load Ta_gs File..." msgstr "_Symbole laden…" -#: data/geany.glade:8229 data/geany.glade:8236 +#: data/geany.glade:8198 data/geany.glade:8205 msgid "_Help" msgstr "_Hilfe" -#: data/geany.glade:8249 +#: data/geany.glade:8218 msgid "Keyboard _Shortcuts" msgstr "_Tastenkürzel" -#: data/geany.glade:8258 +#: data/geany.glade:8227 msgid "Debug _Messages" msgstr "Debug-_Meldungen" -#: data/geany.glade:8273 +#: data/geany.glade:8242 msgid "_Website" msgstr "_Webseite" -#: data/geany.glade:8282 +#: data/geany.glade:8251 msgid "Wi_ki" msgstr "Wi_ki" -#: data/geany.glade:8291 +#: data/geany.glade:8260 msgid "Report a _Bug..." msgstr "Einen Fehler _berichten…" -#: data/geany.glade:8300 +#: data/geany.glade:8269 msgid "_Donate..." msgstr "_Spenden…" -#: data/geany.glade:8369 +#: data/geany.glade:8338 msgid "" "Filter the symbol list using the entered text. Separate multiple filters " "with a space." @@ -2185,63 +2158,63 @@ msgstr "" "Filtern der Symbol-Liste mit dem eingegebenen Text. Verschiedene Filter " "können mit einem Leerzeichen getrennt werden." -#: data/geany.glade:8411 src/sidebar.c:114 src/tagmanager/tm_parser.c:1234 +#: data/geany.glade:8380 src/sidebar.c:114 src/tagmanager/tm_parser.c:1278 msgid "Symbols" msgstr "Symbole" -#: data/geany.glade:8439 +#: data/geany.glade:8408 msgid "Documents" msgstr "Dokumente" -#: data/geany.glade:8498 +#: data/geany.glade:8467 msgid "Status" msgstr "Status" -#: data/geany.glade:8527 +#: data/geany.glade:8496 msgid "Compiler" msgstr "Compiler" -#: data/geany.glade:8558 +#: data/geany.glade:8527 msgid "Messages" msgstr "Meldungen" -#: data/geany.glade:8586 +#: data/geany.glade:8555 msgid "Scribble" msgstr "Notizen" -#: data/geany.glade:8633 +#: data/geany.glade:8602 msgid "Project Properties" msgstr "Projekteigenschaften" -#: data/geany.glade:8703 src/project.c:203 +#: data/geany.glade:8672 src/project.c:203 msgid "Filename:" msgstr "Dateiname:" -#: data/geany.glade:8716 +#: data/geany.glade:8685 msgid "_Name:" msgstr "_Name:" -#: data/geany.glade:8732 +#: data/geany.glade:8701 msgid "_Description:" msgstr "_Beschreibung:" -#: data/geany.glade:8749 +#: data/geany.glade:8718 msgid "_Base path:" msgstr "_Basisverzeichnis:" -#: data/geany.glade:8765 src/search.c:905 +#: data/geany.glade:8734 src/search.c:905 msgid "File _patterns:" msgstr "_Dateinamenmuster:" -#: data/geany.glade:8826 +#: data/geany.glade:8795 msgid "" -"Space separated list of file patterns used for the find in files dialog (e." +"Space separated list of file patterns used for the Find in Files dialog (e." "g. *.c *.h)" msgstr "" -"Mit Leerzeichen getrennte Liste mit Mustern für »in Dateien finden« (z.B. *." +"Mit Leerzeichen getrennte Liste mit Mustern für »in Dateien finden« (z. B. *." "c *.h)" -#: data/geany.glade:8860 src/project.c:232 +#: data/geany.glade:8829 src/project.c:232 msgid "" "Base directory of all files that make up the project. This can be a new " "path, or an existing directory tree. You can use paths relative to the " @@ -2252,79 +2225,79 @@ msgstr "" "Verzeichnis angeben. Weiterhin kann es sowohl in relativer als auch " "absoluter Form eingegeben werden." -#: data/geany.glade:8904 src/keybindings.c:320 +#: data/geany.glade:8873 src/keybindings.c:320 msgid "Project" msgstr "Projekt" -#: data/geany.glade:9316 +#: data/geany.glade:9285 msgid "Display:" msgstr "Anzeige:" -#: data/geany.glade:9357 +#: data/geany.glade:9326 msgid "Custom" msgstr "Benutzerdefiniert" -#: data/geany.glade:9376 +#: data/geany.glade:9345 msgid "Use global settings" msgstr "Benutze globale Einstellungen" -#: data/geany.glade:9703 +#: data/geany.glade:9672 msgid "Size:" msgstr "Größe" -#: data/geany.glade:9719 +#: data/geany.glade:9688 msgid "Location:" msgstr "Ort:" -#: data/geany.glade:9735 +#: data/geany.glade:9704 msgid "Read-only:" msgstr "Nur lesend:" -#: data/geany.glade:9751 +#: data/geany.glade:9720 msgid "Encoding:" msgstr "Kodierung" -#: data/geany.glade:9767 +#: data/geany.glade:9736 msgid "Modified:" msgstr "Modifiziert:" -#: data/geany.glade:9783 +#: data/geany.glade:9752 msgid "Changed:" msgstr "Geändert" -#: data/geany.glade:9799 +#: data/geany.glade:9768 msgid "Accessed:" msgstr "Zugegriffen:" -#: data/geany.glade:9843 +#: data/geany.glade:9812 msgid "(only inside Geany)" msgstr "(nur innerhalb von Geany)" -#: data/geany.glade:9942 +#: data/geany.glade:9911 msgid "Permissions:" msgstr "Zugriffsrechte:" -#: data/geany.glade:9956 +#: data/geany.glade:9925 msgid "Read:" msgstr "Lesen:" -#: data/geany.glade:9970 +#: data/geany.glade:9939 msgid "Write:" msgstr "Schreiben:" -#: data/geany.glade:9984 +#: data/geany.glade:9953 msgid "Execute:" msgstr "Ausführen:" -#: data/geany.glade:9998 +#: data/geany.glade:9967 msgid "Owner:" msgstr "Eigentümer:" -#: data/geany.glade:10010 +#: data/geany.glade:9979 msgid "Group:" msgstr "Gruppe:" -#: data/geany.glade:10022 +#: data/geany.glade:9991 msgid "Other:" msgstr "Andere:" @@ -2419,28 +2392,28 @@ msgstr "konnte %%p nicht ersetzen. Kein Projekt aktiv." msgid "Process failed, no working directory" msgstr "Ausführung fehlgeschlagen. Kein Arbeitsverzeichnis gefunden." -#: src/build.c:796 +#: src/build.c:798 #, c-format msgid "%s (in directory: %s)" msgstr "%s (im Verzeichnis: %s)" -#: src/build.c:821 +#: src/build.c:823 #, c-format msgid "Process failed (%s)" msgstr "Prozess fehlgeschlagen (%s)" -#: src/build.c:855 +#: src/build.c:857 #, c-format msgid "Invalid working directory \"%s\"" msgstr "Ungültiges Arbeitsverzeichnis »%s«!" -#: src/build.c:891 +#: src/build.c:893 #, c-format msgid "Failed to execute \"%s\" (start-script could not be created: %s)" msgstr "" "Konnte »%s« nicht ausführen (Start-Script konnte nicht erzeugt werden: %s)" -#: src/build.c:933 +#: src/build.c:935 msgid "" "File not executed because the terminal may contain some input (press Ctrl+C " "or Enter to clear it)." @@ -2448,7 +2421,7 @@ msgstr "" "Datei konnte nicht ausgeführt werden, da das Terminal nicht leer zu sein " "scheint. Es könnte helfen Strg+C oder Enter im Terminal zu drücken." -#: src/build.c:981 +#: src/build.c:983 #, c-format msgid "" "Cannot execute build command \"%s\": %s. Check the Terminal setting in " @@ -2457,113 +2430,113 @@ msgstr "" "Konnte das Kommando zum Erstellen »%s« nicht ausführen. Sind die Terminal-" "Einstellungen korrekt? Die Fehlermeldung lautete: %s" -#: src/build.c:1098 +#: src/build.c:1100 msgid "Compilation failed." msgstr "Kompilierung fehlgeschlagen." -#: src/build.c:1112 +#: src/build.c:1114 msgid "Compilation finished successfully." msgstr "Kompilierung erfolgreich beendet." -#: src/build.c:1272 +#: src/build.c:1274 msgid "Custom Text" msgstr "Freitext" -#: src/build.c:1273 +#: src/build.c:1275 msgid "Enter custom text here, all entered text is appended to the command." msgstr "" "Hier kann freier Text eingefügt werden, welcher an das Kommando angefügt " "wird." -#: src/build.c:1352 +#: src/build.c:1354 msgid "_Next Error" msgstr "Nächster _Fehler" -#: src/build.c:1354 +#: src/build.c:1356 msgid "_Previous Error" msgstr "_Vorheriger Fehler" -#: src/build.c:1364 src/build.c:2757 +#: src/build.c:1366 src/build.c:2759 msgid "_Set Build Commands" msgstr "_Kommandos zum Erstellen konfigurieren" -#: src/build.c:1641 src/toolbar.c:375 +#: src/build.c:1643 src/toolbar.c:375 msgid "Build the current file" msgstr "Erstellt die aktuelle Datei" -#: src/build.c:1652 +#: src/build.c:1654 msgid "Build the current file with Make and the default target" msgstr "Erstellt die aktuelle Datei mit »make« und dem Standard-Target" -#: src/build.c:1654 +#: src/build.c:1656 msgid "Build the current file with Make and the specified target" msgstr "Erstellt die aktuelle Datei mit »make« und dem angegebenem Target" -#: src/build.c:1656 +#: src/build.c:1658 msgid "Compile the current file with Make" msgstr "Kompiliert die aktuelle Datei mit make" -#: src/build.c:1675 +#: src/build.c:1677 #, c-format msgid "Process could not be stopped (%s)." msgstr "Der Prozess konnte nicht angehalten werden (%s)." -#: src/build.c:1689 src/build.c:1701 +#: src/build.c:1691 src/build.c:1703 msgid "No more build errors." msgstr "Keine weiteren Fehlermeldungen." -#: src/build.c:1806 src/build.c:1808 +#: src/build.c:1808 src/build.c:1810 msgid "Set menu item label" msgstr "Bezeichnung für den Menüeintrag definieren" -#: src/build.c:1833 src/tools.c:394 src/tagmanager/tm_parser.c:235 +#: src/build.c:1835 src/tools.c:394 src/tagmanager/tm_parser.c:235 msgid "Label" msgstr "Label" -#: src/build.c:1834 src/tools.c:379 src/tagmanager/tm_parser.c:227 +#: src/build.c:1836 src/tools.c:379 src/tagmanager/tm_parser.c:227 msgid "Command" msgstr "Kommando" -#: src/build.c:1835 +#: src/build.c:1837 msgid "Working directory" msgstr "Arbeitsverzeichnis" -#: src/build.c:1836 +#: src/build.c:1838 msgid "Reset" msgstr "Zurücksetzen" -#: src/build.c:1889 +#: src/build.c:1891 msgid "Click to set menu item label" msgstr "Klicken, um die Bezeichnung für den Menüeintrag zu definieren" -#: src/build.c:1973 src/build.c:1975 +#: src/build.c:1975 src/build.c:1977 #, c-format msgid "%s commands" msgstr "Kommandos für %s" -#: src/build.c:1975 +#: src/build.c:1977 msgid "No filetype" msgstr "Kein Dateityp" -#: src/build.c:1984 src/build.c:2019 +#: src/build.c:1986 src/build.c:2021 msgid "Error regular expression:" msgstr "Regulärer Ausdruck für Fehlermeldungen:" -#: src/build.c:2012 +#: src/build.c:2014 msgid "Independent commands" msgstr "Dateitypunabhängige Befehle" -#: src/build.c:2044 +#: src/build.c:2046 msgid "Note: Item 2 opens a dialog and appends the response to the command." msgstr "" "Notiz: Element 2 öffnet ein Dialog und fügt das Ergebnis am Ende des " "Kommandos an" -#: src/build.c:2053 +#: src/build.c:2055 msgid "Execute commands" msgstr "Befehle zum Ausführen" -#: src/build.c:2065 +#: src/build.c:2067 msgid "" "%d, %e, %f, %p, %l are substituted in command and directory fields, see " "manual for details." @@ -2571,31 +2544,31 @@ msgstr "" "%d, %e, %f, %p, %l werden innerhalb der Kommando- und Verzeichnisfelder " "ersetzt - Details gibt es in der Dokumentation." -#: src/build.c:2223 +#: src/build.c:2225 msgid "Set Build Commands" msgstr "Kommandos zum Erstellen konfigurieren" -#: src/build.c:2439 +#: src/build.c:2441 msgid "_Compile" msgstr "_Kompilieren" -#: src/build.c:2453 src/build.c:2483 src/build.c:2685 +#: src/build.c:2455 src/build.c:2485 src/build.c:2687 msgid "_Execute" msgstr "_Ausführen" -#: src/build.c:2498 src/build.c:2683 src/build.c:2737 +#: src/build.c:2500 src/build.c:2685 src/build.c:2739 msgid "Make Custom _Target..." msgstr "Make (eigenes _Target)…" -#: src/build.c:2500 src/build.c:2684 src/build.c:2745 +#: src/build.c:2502 src/build.c:2686 src/build.c:2747 msgid "Make _Object" msgstr "Make _Objekt-Datei" -#: src/build.c:2502 src/build.c:2682 +#: src/build.c:2504 src/build.c:2684 msgid "_Make" msgstr "_Make" -#: src/build.c:2729 +#: src/build.c:2731 msgid "_Make All" msgstr "_Make all" @@ -2655,41 +2628,41 @@ msgstr "Kann angegebenen externen Befehl »%s« nicht ausführen: %s. %s" msgid "No context action set." msgstr "Keine Kontextaktion ausgewählt." -#: src/dialogs.c:159 src/document.c:2316 src/document.c:2381 -#: src/document.c:2389 +#: src/dialogs.c:159 src/document.c:2315 src/document.c:2380 +#: src/document.c:2388 #, c-format msgid "\"%s\" was not found." msgstr "»%s« wurde nicht gefunden." -#: src/dialogs.c:221 src/encodings.c:530 +#: src/dialogs.c:220 src/encodings.c:557 msgid "Detect from file" msgstr "Aus Datei lesen" -#: src/dialogs.c:224 +#: src/dialogs.c:223 msgid "Programming Languages" msgstr "_Kompilersprachen" -#: src/dialogs.c:226 +#: src/dialogs.c:225 msgid "Scripting Languages" msgstr "_Skriptsprachen" -#: src/dialogs.c:228 +#: src/dialogs.c:227 msgid "Markup Languages" msgstr "_Markup-Sprachen" -#: src/dialogs.c:306 +#: src/dialogs.c:305 msgid "_More Options" msgstr "_Weitere Optionen" -#: src/dialogs.c:313 +#: src/dialogs.c:312 msgid "Show _hidden files" msgstr "_Versteckte Dateien anzeigen" -#: src/dialogs.c:324 +#: src/dialogs.c:323 msgid "Set encoding:" msgstr "Zeichenkodierung festlegen:" -#: src/dialogs.c:333 +#: src/dialogs.c:332 msgid "" "Explicitly defines an encoding for the file, if it would not be detected. " "This is useful when you know that the encoding of a file cannot be detected " @@ -2702,11 +2675,11 @@ msgstr "" "Beachten Sie: Wenn Sie mehrere Dateien auswählen, werden alle mit der " "gewählten Zeichenkodierung geöffnet." -#: src/dialogs.c:340 +#: src/dialogs.c:339 msgid "Set filetype:" msgstr "Dateityp festlegen:" -#: src/dialogs.c:349 +#: src/dialogs.c:348 msgid "" "Explicitly defines a filetype for the file, if it would not be detected by " "filename extension.\n" @@ -2718,16 +2691,16 @@ msgstr "" "Beachten Sie: Wenn Sie mehrere Dateien auswählen, werden alle mit dem " "gewählten Dateityp geöffnet." -#: src/dialogs.c:375 +#: src/dialogs.c:374 msgid "Open File" msgstr "Datei öffnen" -#: src/dialogs.c:379 +#: src/dialogs.c:378 msgctxt "Open dialog action" msgid "_View" msgstr "_Anzeigen" -#: src/dialogs.c:381 +#: src/dialogs.c:380 msgid "" "Opens the file in read-only mode. If you choose more than one file to open, " "all files will be opened read-only." @@ -2735,60 +2708,60 @@ msgstr "" "Öffnet die Datei schreibgeschützt. Bei Auswahl mehrerer Dateien, werden alle " "schreibgeschützt geöffnet." -#: src/dialogs.c:528 src/document.c:2067 +#: src/dialogs.c:527 src/document.c:2066 msgid "Overwrite?" msgstr "Ãœberschreiben?" -#: src/dialogs.c:529 +#: src/dialogs.c:528 msgid "Filename already exists!" msgstr "Der Dateiname existiert bereits!" -#: src/dialogs.c:558 +#: src/dialogs.c:557 msgid "Save File" msgstr "Datei speichern" -#: src/dialogs.c:567 +#: src/dialogs.c:566 msgid "R_ename" msgstr "_Umbenennen" -#: src/dialogs.c:568 +#: src/dialogs.c:567 msgid "Save the file and rename it" msgstr "Speichert und benennt die Datei um" -#: src/dialogs.c:680 src/win32.c:291 +#: src/dialogs.c:679 src/win32.c:291 msgid "Error" msgstr "Fehler" -#: src/dialogs.c:683 src/dialogs.c:762 src/dialogs.c:1298 src/win32.c:297 +#: src/dialogs.c:682 src/dialogs.c:761 src/dialogs.c:1297 src/win32.c:297 msgid "Question" msgstr "Frage" -#: src/dialogs.c:686 src/win32.c:303 +#: src/dialogs.c:685 src/win32.c:303 msgid "Warning" msgstr "Warnung" -#: src/dialogs.c:689 src/win32.c:309 +#: src/dialogs.c:688 src/win32.c:309 msgid "Information" msgstr "Information" -#: src/dialogs.c:766 +#: src/dialogs.c:765 msgid "_Don't save" msgstr "_Nicht speichern" -#: src/dialogs.c:795 +#: src/dialogs.c:794 #, c-format msgid "The file '%s' is not saved." msgstr "»%s« wurde nicht gespeichert." -#: src/dialogs.c:796 +#: src/dialogs.c:795 msgid "Do you want to save it before closing?" msgstr "Möchten Sie vor dem Schließen speichern?" -#: src/dialogs.c:852 +#: src/dialogs.c:851 msgid "Choose font" msgstr "Schriftart auswählen" -#: src/dialogs.c:1146 +#: src/dialogs.c:1145 msgid "" "An error occurred or file information could not be retrieved (e.g. from a " "new file)." @@ -2796,22 +2769,22 @@ msgstr "" "Es ist ein Fehler aufgetreten oder Datei-Informationen konnten nicht gelesen " "werden (z.B. bei einer neuen, noch nicht gespeicherten Datei)." -#: src/dialogs.c:1165 src/dialogs.c:1166 src/dialogs.c:1167 src/dialogs.c:1173 -#: src/dialogs.c:1174 src/dialogs.c:1175 src/symbols.c:1947 src/symbols.c:1963 +#: src/dialogs.c:1164 src/dialogs.c:1165 src/dialogs.c:1166 src/dialogs.c:1172 +#: src/dialogs.c:1173 src/dialogs.c:1174 src/symbols.c:1947 src/symbols.c:1963 #: src/ui_utils.c:286 msgid "unknown" msgstr "unbekannt" -#: src/dialogs.c:1180 +#: src/dialogs.c:1179 #, c-format msgid "%s Properties" msgstr "%s Eigenschaften" -#: src/dialogs.c:1212 src/ui_utils.c:290 +#: src/dialogs.c:1211 src/ui_utils.c:290 msgid "(with BOM)" msgstr "(mit BOM)" -#: src/dialogs.c:1212 +#: src/dialogs.c:1211 msgid "(without BOM)" msgstr "(ohne BOM)" @@ -2830,21 +2803,15 @@ msgstr "Neue Datei »%s« geöffnet." msgid "Could not open file %s (%s)" msgstr "Konnte Datei »%s« nicht öffnen (%s)." -#: src/document.c:1005 -#, c-format -msgid "The file \"%s\" is not valid %s." -msgstr "Die Datei »%s« ist kein gültiges %s." +#: src/document.c:1004 +msgid "Failed to load file \"%s\" as %s: %s." +msgstr "Konnte die Datei »%s« nicht als %s öffnen: %s" -#: src/document.c:1011 -#, c-format -msgid "" -"The file \"%s\" does not look like a text file or the file encoding is not " -"supported." -msgstr "" -"Die Datei »%s« scheint keine Textdatei zu sein, oder die Zeichenkodierung " -"wird nicht unterstützt." +#: src/document.c:1007 +msgid "Failed to load file \"%s\": %s." +msgstr "Konnte Datei »%s« nicht öffnen: %s" -#: src/document.c:1021 +#: src/document.c:1017 #, c-format msgid "" "The file \"%s\" could not be opened properly and has been truncated. This " @@ -2857,47 +2824,47 @@ msgstr "" "enthält und kann zu Datenverlust beim Speichern führen!\n" "Die Datei wird schreibgeschützt geöffnet." -#: src/document.c:1233 +#: src/document.c:1232 msgid "Spaces" msgstr "Leerzeichen" -#: src/document.c:1236 +#: src/document.c:1235 msgid "Tabs" msgstr "Tabulatoren" -#: src/document.c:1239 +#: src/document.c:1238 msgid "Tabs and Spaces" msgstr "Tabulatoren und Leerzeichen" -#: src/document.c:1244 +#: src/document.c:1243 #, c-format msgid "Setting %s indentation mode for %s." msgstr "Setze Einrückungsmodus %s für »%s«." -#: src/document.c:1255 +#: src/document.c:1254 #, c-format msgid "Setting indentation width to %d for %s." msgstr "Setze Einrückungsbreite auf %d für »%s«." -#: src/document.c:1509 +#: src/document.c:1508 #, c-format msgid "File %s reloaded." msgstr "Datei »%s« neu geladen." -#: src/document.c:1517 +#: src/document.c:1516 #, c-format msgid "File %s opened (%d%s)." msgstr "Datei »%s« geöffnet (%d%s)." -#: src/document.c:1519 +#: src/document.c:1518 msgid ", read-only" msgstr ", schreibgeschützt" -#: src/document.c:1637 +#: src/document.c:1636 msgid "Discard history" msgstr "Verlauf verwerfen" -#: src/document.c:1638 +#: src/document.c:1637 msgid "" "The buffer's previous state is stored in the history and undoing restores " "it. You can disable this by discarding the history upon reload. This message " @@ -2909,28 +2876,28 @@ msgstr "" "werden kann. Diese Funktion kann deaktiviert werden. Diese Nachricht wird " "nicht noch einmal angezeigt." -#: src/document.c:1642 +#: src/document.c:1641 msgid "The file has been reloaded." msgstr "Das Dokument wurde neu geladen." -#: src/document.c:1672 +#: src/document.c:1671 msgid "Any unsaved changes will be lost." msgstr "Alle ungesicherten Änderungen werden verloren gehen." -#: src/document.c:1673 +#: src/document.c:1672 msgid "Undo history will be lost." msgstr "Verlauf wird verloren gehen." -#: src/document.c:1674 +#: src/document.c:1673 #, c-format msgid "Are you sure you want to reload '%s'?" msgstr "Möchten Sie »%s« wirklich neu laden?" -#: src/document.c:1780 +#: src/document.c:1779 msgid "Error renaming file." msgstr "Fehler beim Umbenennen der Datei." -#: src/document.c:1898 +#: src/document.c:1897 #, c-format msgid "" "An error occurred while converting the file from UTF-8 in \"%s\". The file " @@ -2939,7 +2906,7 @@ msgstr "" "Beim Konvertieren der Datei von UTF-8 nach »%s« ist ein Fehler aufgetreten. " "Die Datei wird nicht gespeichert." -#: src/document.c:1919 +#: src/document.c:1918 #, c-format msgid "" "Error message: %s\n" @@ -2948,58 +2915,58 @@ msgstr "" "Fehlermeldung: %s\n" "Der Fehler trat bei »%s« (Zeile: %d, Spalte: %d) auf." -#: src/document.c:1923 +#: src/document.c:1922 #, c-format msgid "Error message: %s." msgstr "Fehlermeldung: %s." -#: src/document.c:1983 +#: src/document.c:1982 #, c-format msgid "Failed to open file '%s' for writing: fopen() failed: %s" msgstr "" "Konnte Datei »%s« nicht zum Schreiben öffnen: fopen() fehlgeschlagen: %s" -#: src/document.c:2001 +#: src/document.c:2000 #, c-format msgid "Failed to write file '%s': fwrite() failed: %s" msgstr "Konnte Datei »%s« nicht schreiben: fwrite() fehlgeschlagen: %s" -#: src/document.c:2015 +#: src/document.c:2014 #, c-format msgid "Failed to close file '%s': fclose() failed: %s" msgstr "Konnte Datei »%s« nicht schließen: fclose() fehlgeschlagen: %s" -#: src/document.c:2066 src/document.c:3623 +#: src/document.c:2065 src/document.c:3622 msgid "_Overwrite" msgstr "Ãœ_berschreiben?" -#: src/document.c:2068 src/document.c:3626 +#: src/document.c:2067 src/document.c:3625 #, c-format msgid "The file '%s' on the disk is more recent than the current buffer." msgstr "" "Die Datei »%s« auf dem Datenträger ist aktueller als die momentan geöffnete " "Version." -#: src/document.c:2076 src/document.c:3675 +#: src/document.c:2075 src/document.c:3674 msgid "Try to resave the file?" msgstr "Versuchen die Datei erneut zu speichern?" -#: src/document.c:2077 src/document.c:3676 +#: src/document.c:2076 src/document.c:3675 #, c-format msgid "File \"%s\" was not found on disk!" msgstr "»%s« wurde nicht auf dem Datenträger gefunden!" -#: src/document.c:2140 +#: src/document.c:2139 #, c-format msgid "Cannot save read-only document '%s'!" msgstr "Kann das nur zum Lesen geöffnete Dokument »%s« nicht speichern!" -#: src/document.c:2208 +#: src/document.c:2207 #, c-format msgid "Error saving file (%s)." msgstr "Fehler beim Speichern der Datei (%s)." -#: src/document.c:2213 +#: src/document.c:2212 #, c-format msgid "" "%s\n" @@ -3011,201 +2978,213 @@ msgstr "" "Die Datei ist möglicherweise nicht vollständig auf der Festplatte " "gespeichert!" -#: src/document.c:2215 +#: src/document.c:2214 msgid "Error saving file." msgstr "Fehler beim Speichern der Datei." -#: src/document.c:2239 +#: src/document.c:2238 #, c-format msgid "File %s saved." msgstr "Datei »%s« wurde gespeichert." -#: src/document.c:2389 +#: src/document.c:2388 msgid "Wrap search and find again?" msgstr "Suche vom Dokumentanfang bzw. -ende neu beginnen?" -#: src/document.c:2478 src/search.c:1374 src/search.c:1427 src/search.c:2237 -#: src/search.c:2238 +#: src/document.c:2477 src/search.c:1376 src/search.c:1429 src/search.c:2254 +#: src/search.c:2255 #, c-format msgid "No matches found for \"%s\"." msgstr "Keine Treffer für »%s« gefunden." -#: src/document.c:2484 +#: src/document.c:2483 #, c-format msgid "%s: replaced %d occurrence of \"%s\" with \"%s\"." msgid_plural "%s: replaced %d occurrences of \"%s\" with \"%s\"." msgstr[0] "%s: %d mal wurde »%s« mit »%s« ersetzt." msgstr[1] "%s: %d mal wurde »%s« mit »%s« ersetzt." -#: src/document.c:3625 +#: src/document.c:3624 msgid "Do you want to reload it?" msgstr "Möchten Sie die Datei neu laden?" -#: src/editor.c:4463 +#: src/editor.c:4464 msgid "Enter Tab Width" msgstr "Tabulatorbreite:" -#: src/editor.c:4464 +#: src/editor.c:4465 msgid "Enter the amount of spaces which should be replaced by a tab character." msgstr "" "Geben Sie die Anzahl der Leerzeichen an, welche durch den Tabulator ersetzt " "werden sollen." -#: src/editor.c:4680 +#: src/editor.c:4681 #, c-format msgid "Warning: non-standard hard tab width: %d != 8!" msgstr "Achtung: Keine gewöhnliche harte Tabulatorbreite:: %d != 8!" -#: src/encodings.c:71 +#: src/encodings.c:83 msgid "Celtic" msgstr "Keltisch" -#: src/encodings.c:72 src/encodings.c:73 +#: src/encodings.c:84 src/encodings.c:85 msgid "Greek" msgstr "Griechisch" -#: src/encodings.c:74 +#: src/encodings.c:86 msgid "Nordic" msgstr "Nordisch" -#: src/encodings.c:75 +#: src/encodings.c:87 msgid "South European" msgstr "Südeuropäisch" -#: src/encodings.c:76 src/encodings.c:77 src/encodings.c:78 src/encodings.c:79 +#: src/encodings.c:88 src/encodings.c:89 src/encodings.c:90 src/encodings.c:91 msgid "Western" msgstr "Westlich" -#: src/encodings.c:81 src/encodings.c:82 src/encodings.c:83 +#: src/encodings.c:93 src/encodings.c:94 src/encodings.c:95 msgid "Baltic" msgstr "Baltisch" -#: src/encodings.c:84 src/encodings.c:85 src/encodings.c:86 +#: src/encodings.c:96 src/encodings.c:97 src/encodings.c:98 msgid "Central European" msgstr "Mitteleuropäisch" -#: src/encodings.c:87 src/encodings.c:88 src/encodings.c:90 src/encodings.c:91 -#: src/encodings.c:92 +#: src/encodings.c:99 src/encodings.c:100 src/encodings.c:102 +#: src/encodings.c:103 src/encodings.c:104 msgid "Cyrillic" msgstr "Kyrillisch" -#: src/encodings.c:93 +#: src/encodings.c:105 msgid "Cyrillic/Russian" msgstr "Kyrillisch/Russisch" -#: src/encodings.c:94 +#: src/encodings.c:106 msgid "Cyrillic/Ukrainian" msgstr "Kyrillisch/Ukrainisch" -#: src/encodings.c:95 +#: src/encodings.c:107 msgid "Romanian" msgstr "Rumänisch" -#: src/encodings.c:97 src/encodings.c:98 src/encodings.c:99 +#: src/encodings.c:109 src/encodings.c:110 src/encodings.c:111 msgid "Arabic" msgstr "Arabisch" -#: src/encodings.c:100 src/encodings.c:102 src/encodings.c:103 +#: src/encodings.c:112 src/encodings.c:114 src/encodings.c:115 msgid "Hebrew" msgstr "Hebräisch" -#: src/encodings.c:104 +#: src/encodings.c:116 msgid "Hebrew Visual" msgstr "Hebräisch visuell" -#: src/encodings.c:106 +#: src/encodings.c:118 msgid "Armenian" msgstr "Armenisch" -#: src/encodings.c:107 +#: src/encodings.c:119 msgid "Georgian" msgstr "Georgisch" -#: src/encodings.c:108 +#: src/encodings.c:120 msgid "Thai" msgstr "Thai" -#: src/encodings.c:109 src/encodings.c:110 src/encodings.c:111 +#: src/encodings.c:121 src/encodings.c:122 src/encodings.c:123 msgid "Turkish" msgstr "Türkisch" -#: src/encodings.c:112 src/encodings.c:113 src/encodings.c:114 +#: src/encodings.c:124 src/encodings.c:125 src/encodings.c:126 msgid "Vietnamese" msgstr "Vietnamesisch" -#: src/encodings.c:116 src/encodings.c:117 src/encodings.c:118 -#: src/encodings.c:119 src/encodings.c:120 src/encodings.c:121 -#: src/encodings.c:122 src/encodings.c:123 src/encodings.c:544 +#: src/encodings.c:128 src/encodings.c:129 src/encodings.c:130 +#: src/encodings.c:131 src/encodings.c:132 src/encodings.c:133 +#: src/encodings.c:134 src/encodings.c:135 src/encodings.c:571 msgid "Unicode" msgstr "Unicode" -#: src/encodings.c:125 src/encodings.c:126 src/encodings.c:127 -#: src/encodings.c:129 +#: src/encodings.c:137 src/encodings.c:138 src/encodings.c:139 +#: src/encodings.c:141 msgid "Chinese Simplified" msgstr "Chinesisch, vereinfacht" -#: src/encodings.c:130 src/encodings.c:131 src/encodings.c:132 +#: src/encodings.c:142 src/encodings.c:143 src/encodings.c:144 msgid "Chinese Traditional" msgstr "Chinesisch, traditionell" -#: src/encodings.c:133 src/encodings.c:134 src/encodings.c:135 -#: src/encodings.c:136 +#: src/encodings.c:145 src/encodings.c:146 src/encodings.c:147 +#: src/encodings.c:148 msgid "Japanese" msgstr "Japanisch" -#: src/encodings.c:137 src/encodings.c:138 src/encodings.c:139 -#: src/encodings.c:140 +#: src/encodings.c:149 src/encodings.c:150 src/encodings.c:151 +#: src/encodings.c:152 msgid "Korean" msgstr "Koreanisch" -#: src/encodings.c:142 +#: src/encodings.c:154 msgid "Without encoding" msgstr "Ohne Zeichenkodierung" -#: src/encodings.c:413 +#: src/encodings.c:449 msgid "_West European" msgstr "_Westeuropäisch" -#: src/encodings.c:414 +#: src/encodings.c:450 msgid "_East European" msgstr "_Osteuropäisch" -#: src/encodings.c:415 +#: src/encodings.c:451 msgid "East _Asian" msgstr "Ost_asiatisch" -#: src/encodings.c:416 +#: src/encodings.c:452 msgid "_SE & SW Asian" msgstr "_SO- & SW-Asiatisch" -#: src/encodings.c:417 +#: src/encodings.c:453 msgid "_Middle Eastern" msgstr "_Nahöstlich" -#: src/encodings.c:418 +#: src/encodings.c:454 msgid "_Unicode" msgstr "_Unicode" -#: src/encodings.c:534 +#: src/encodings.c:561 msgid "West European" msgstr "Westeuropäisch" -#: src/encodings.c:536 +#: src/encodings.c:563 msgid "East European" msgstr "Osteuropäisch" -#: src/encodings.c:538 +#: src/encodings.c:565 msgid "East Asian" msgstr "Ostasiatisch" -#: src/encodings.c:540 +#: src/encodings.c:567 msgid "SE & SW Asian" msgstr "SO- & SW-Asiatisch" -#: src/encodings.c:542 +#: src/encodings.c:569 msgid "Middle Eastern" msgstr "Nahöstlich" +#: src/encodings.c:674 +msgid "Data contains NULs" +msgstr "Daten enthalten NUL." + +#: src/encodings.c:816 +msgid "Data contains NULs or the encoding is not supported" +msgstr "Daten enthalten NUL oder die Zeichencodierung wird nicht unterstützt." + +#: src/encodings.c:937 +msgid "Data contains NULs or is not valid UTF-8" +msgstr "Daten enthalten NUL oder es handelt sich nicht um valides UTF-8." + #: src/filetypes.c:86 #, c-format msgid "%s source file" @@ -3246,31 +3225,31 @@ msgstr "Konfigurationsdatei" msgid "Gettext translation" msgstr "Gettext-Ãœbersetzungsdatei" -#: src/filetypes.c:434 +#: src/filetypes.c:435 msgid "_Programming Languages" msgstr "_Kompilersprachen" -#: src/filetypes.c:435 +#: src/filetypes.c:436 msgid "_Scripting Languages" msgstr "_Interpretersprachen" -#: src/filetypes.c:436 +#: src/filetypes.c:437 msgid "_Markup Languages" msgstr "_Markup-Sprachen" -#: src/filetypes.c:437 +#: src/filetypes.c:438 msgid "M_iscellaneous" msgstr "_Sonstiges" -#: src/filetypes.c:1190 +#: src/filetypes.c:1192 msgid "All Source" msgstr "Alle Quellen" -#: src/filetypes.c:1215 src/project.c:363 +#: src/filetypes.c:1217 src/project.c:363 msgid "All files" msgstr "Alle Dateien" -#: src/filetypes.c:1264 +#: src/filetypes.c:1266 #, c-format msgid "Bad regex for filetype %s: %s" msgstr "Schlechter RegEx für Dateityp %s: %s" @@ -3279,33 +3258,33 @@ msgstr "Schlechter RegEx für Dateityp %s: %s" msgid "untitled" msgstr "unbenannt" -#: src/highlighting.c:1237 src/libmain.c:885 src/socket.c:169 -#: src/templates.c:230 +#: src/highlighting.c:1239 src/libmain.c:885 src/socket.c:169 +#: src/templates.c:233 #, c-format msgid "Could not find file '%s'." msgstr "Konnte die Datei »%s« nicht finden." -#: src/highlighting.c:1307 +#: src/highlighting.c:1309 msgid "Default" msgstr "Standard" -#: src/highlighting.c:1348 +#: src/highlighting.c:1350 msgid "The current filetype overrides the default style." msgstr "" "Der aktuelle Dateityp überschreibt die Standardeinstellungen für die " "Darstellung." -#: src/highlighting.c:1349 +#: src/highlighting.c:1351 msgid "This may cause color schemes to display incorrectly." msgstr "" "Dies kann dazu führen, dass eventuell Farbprofile nicht korrekt angezeigt " "werden." -#: src/highlighting.c:1374 +#: src/highlighting.c:1376 msgid "Color Schemes" msgstr "Farbschemata" -#: src/keybindings.c:309 src/tagmanager/tm_parser.c:804 +#: src/keybindings.c:309 src/tagmanager/tm_parser.c:805 msgid "File" msgstr "Datei" @@ -3341,12 +3320,12 @@ msgstr "Gehe zu" msgid "View" msgstr "Ansicht" -#: src/keybindings.c:319 src/tagmanager/tm_parser.c:841 +#: src/keybindings.c:319 src/tagmanager/tm_parser.c:842 msgid "Document" msgstr "Dokument" #: src/keybindings.c:321 src/keybindings.c:696 src/project.c:520 -#: src/ui_utils.c:2263 +#: src/ui_utils.c:2268 msgid "Build" msgstr "Erstellen" @@ -3386,7 +3365,7 @@ msgstr "Speichern unter" msgid "Save all" msgstr "Alle speichern" -#: src/keybindings.c:348 src/tagmanager/tm_parser.c:704 +#: src/keybindings.c:348 src/tagmanager/tm_parser.c:705 msgid "Properties" msgstr "Eigenschaften" @@ -3863,7 +3842,7 @@ msgid "Type here what you want, use it as a notice/scratch board" msgstr "" "Schreiben Sie hier rein, was sie möchten. Sie können es als Notizbuch nutzen." -#: src/keyfile.c:1395 +#: src/keyfile.c:1381 msgid "Failed to load one or more session files." msgstr "" "Eine oder mehrere Datei(en) aus der letzten Sitzung konnte(n) nicht geladen " @@ -4032,27 +4011,27 @@ msgstr "" "Es könnte zu Problemen bei der Verwendung von Geany kommen.\n" "Geany trotzdem starten?" -#: src/libmain.c:1174 +#: src/libmain.c:1176 #, c-format msgid "This is Geany %s." msgstr "Willkommen bei Geany %s." -#: src/libmain.c:1177 +#: src/libmain.c:1179 #, c-format msgid "Configuration directory could not be created (%s)." msgstr "Konfigurationsverzeichnis konnte nicht erstellt werden (%s)." -#: src/libmain.c:1185 +#: src/libmain.c:1189 msgid "IPC socket could not be created, see Help->Debug Messages for details." msgstr "" "Der IPC-Socket konnt konnte nicht erstellt werden. Mehr Details gibt es " "unter Hilfe->Debug-Meldungen" -#: src/libmain.c:1411 +#: src/libmain.c:1415 msgid "Do you really want to quit?" msgstr "Soll Geany wirklich beendet werden?" -#: src/libmain.c:1449 +#: src/libmain.c:1453 msgid "Configuration files reloaded." msgstr "Einstellungen erneut geladen." @@ -4111,7 +4090,7 @@ msgstr "" "Das Plugin »%s« ist nicht kompatibel mit dieser Version von Geany. Bitte neu " "kompilieren." -#: src/plugins.c:1220 +#: src/plugins.c:1219 msgid "_Plugin Manager" msgstr "_Plugin-Verwaltung" @@ -4169,55 +4148,55 @@ msgstr "" msgid "Configure Plugins" msgstr "Plugins konfigurieren" -#: src/prefs.c:180 +#: src/prefs.c:179 msgid "Grab Key" msgstr "Tasten festlegen" -#: src/prefs.c:186 +#: src/prefs.c:185 #, c-format msgid "Press the combination of the keys you want to use for \"%s\"." msgstr "Welche Tastenkombination soll für »%s« genutzt werden?" -#: src/prefs.c:224 src/symbols.c:2120 src/sidebar.c:1244 +#: src/prefs.c:223 src/symbols.c:2120 src/sidebar.c:1244 msgid "_Expand All" msgstr "Alle a_usklappen" -#: src/prefs.c:229 src/symbols.c:2125 src/sidebar.c:1250 +#: src/prefs.c:228 src/symbols.c:2125 src/sidebar.c:1250 msgid "_Collapse All" msgstr "Alle _einklappen" -#: src/prefs.c:278 +#: src/prefs.c:277 msgid "Action" msgstr "Aktion" -#: src/prefs.c:283 +#: src/prefs.c:282 msgid "Shortcut" msgstr "Tastenkürzel" -#: src/prefs.c:1474 +#: src/prefs.c:1462 msgid "_Allow" msgstr "_Zulassen" -#: src/prefs.c:1476 +#: src/prefs.c:1464 msgid "_Override" msgstr "_Ersetzen" -#: src/prefs.c:1477 +#: src/prefs.c:1465 msgid "Override that keybinding?" msgstr "Diese Tastenkombination ersetzen?" -#: src/prefs.c:1478 +#: src/prefs.c:1466 #, c-format msgid "The combination '%s' is already used for \"%s\"." msgstr "Die Tastenkombination '%s' wird bereits für »%s« verwendet." -#: src/prefs.c:1695 +#: src/prefs.c:1675 msgid "Enter tool paths below. Tools you do not need can be left blank." msgstr "" "Geben Sie hier die Pfade zu den einzelnen Werkzeugen an. Programme die nicht " "benötigt werden, können freigelassen werden." -#: src/prefs.c:1700 +#: src/prefs.c:1680 msgid "" "Set the information to be used in templates. See the documentation for " "details." @@ -4227,7 +4206,7 @@ msgstr "" "Konsultieren Sie die Dokumentation, um mehr über die Funktionsweise von " "Vorlagen zu erfahren." -#: src/prefs.c:1705 +#: src/prefs.c:1685 msgid "" "Here you can change keyboard shortcuts for various actions. Select one and " "press the Change button to enter a new shortcut, or double click on an " @@ -4238,7 +4217,7 @@ msgstr "" "einfach Doppelklicken oder markieren und auf Ändern klicken. Zudem können " "Sie die Tastenkombinationen auch direkt eingeben." -#: src/prefs.c:1710 +#: src/prefs.c:1690 msgid "" "Warning: these settings are overridden by the current project. See " "Project->Properties." @@ -4255,12 +4234,6 @@ msgstr "Seite %d von %d" msgid "Document Setup" msgstr "Dokument einrichten" -#: src/printing.c:267 -msgid "Print only the basename(without the path) of the printed file" -msgstr "" -"Nur den Basisdateinamen (ohne die Pfadangabe) der zu druckenden Datei " -"verwenden" - #: src/printing.c:419 msgid "Paginating" msgstr "Seitennummerierung" @@ -4625,47 +4598,47 @@ msgstr "_Zus. Optionen:" msgid "Other options to pass to Grep" msgstr "Andere Argumente, die an grep übergeben werden sollen" -#: src/search.c:1377 src/search.c:2243 src/search.c:2246 +#: src/search.c:1379 src/search.c:2260 src/search.c:2263 #, c-format msgid "Found %d match for \"%s\"." msgid_plural "Found %d matches for \"%s\"." msgstr[0] "%d Treffer für »%s« gefunden." msgstr[1] "%d Treffer für »%s« gefunden." -#: src/search.c:1433 +#: src/search.c:1435 #, c-format msgid "Replaced %u matches in %u documents." msgstr "%u Treffer in %u Dokumenten ersetzt" -#: src/search.c:1464 +#: src/search.c:1466 msgid "" "This operation will modify all open files which contain the text to replace." msgstr "" "Diese Operation wird alle geöffneten Dateien ändern, die den zu ersetzenden " "Text beinhalten." -#: src/search.c:1465 +#: src/search.c:1467 msgid "Are you sure to replace in the whole session?" msgstr "Wirklich alle Vorkommen in der aktuellen Sitzung ersetzen?" -#: src/search.c:1630 -msgid "Invalid directory for find in files." -msgstr "Ungültiges Verzeichnis." +#: src/search.c:1643 +msgid "Invalid directory for Find in Files." +msgstr "Ungültiges Verzeichnis für in Dateien finden." -#: src/search.c:1647 +#: src/search.c:1648 msgid "No text to find." msgstr "Kein Text zum Suchen angegeben." -#: src/search.c:1723 +#: src/search.c:1740 msgid "Searching..." msgstr "Suchen…" -#: src/search.c:1725 +#: src/search.c:1742 #, c-format msgid "%s %s -- %s (in directory: %s)" msgstr "%s %s -- %s (im Verzeichnis: %s)" -#: src/search.c:1733 +#: src/search.c:1750 #, c-format msgid "" "Cannot execute grep tool \"%s\": %s. Check the path setting in Preferences." @@ -4673,27 +4646,27 @@ msgstr "" "Konnte das grep Kommando »%s« nicht finden. Fehlermeldung: %s (Pfad zum Grep " "Kommando in den Einstellungen überprüfen)." -#: src/search.c:1773 +#: src/search.c:1790 #, c-format msgid "Could not open directory (%s)" msgstr "Konnte Verzeichnis nicht öffnen (%s)." -#: src/search.c:1863 +#: src/search.c:1880 msgid "Search failed." msgstr "Suche fehlgeschlagen." -#: src/search.c:1887 +#: src/search.c:1904 #, c-format msgid "Search completed with %d match." msgid_plural "Search completed with %d matches." msgstr[0] "Suche mit %d Treffer abgeschlossen." msgstr[1] "Suche mit %d Treffern abgeschlossen." -#: src/search.c:1895 +#: src/search.c:1914 msgid "No matches found." msgstr "Keine Treffer gefunden." -#: src/search.c:1925 +#: src/search.c:1944 #, c-format msgid "Bad regex: %s" msgstr "Fehler in Ausdruck: %s" @@ -4735,7 +4708,7 @@ msgstr "Konnte das Arbeitsverzeichnis nicht wechseln." msgid "Unknown error executing child process" msgstr "Unbekannter Fehler beim Ausführen des Kind-Prozess aufgetreten." -#: src/stash.c:1226 +#: src/stash.c:1230 msgid "Value" msgstr "Wert" @@ -4810,12 +4783,11 @@ msgstr "Nach _Auftreten sortieren" msgid "_Group by Type" msgstr "_Gruppieren nach Typ" -#: src/templates.c:81 -#, c-format -msgid "Failed to convert template file \"%s\" to UTF-8" -msgstr "Konnte die Vorlage »%s« nicht nach UTF-8 umwandeln." +#: src/templates.c:82 +msgid "Failed to convert template file \"%s\" to UTF-8: %s" +msgstr "Konnte die Vorlage »%s« nicht nach UTF-8 umwandeln: %s" -#: src/templates.c:651 +#: src/templates.c:654 #, c-format msgid "" "Cannot execute template command \"%s\". Hint: incorrect paths in the command " @@ -5066,7 +5038,7 @@ msgstr "S_ymbolliste anzeigen" msgid "Show _Document List" msgstr "_Dokumentenliste anzeigen" -#: src/sidebar.c:1084 plugins/filebrowser.c:707 +#: src/sidebar.c:1084 plugins/filebrowser.c:703 msgid "H_ide Sidebar" msgstr "Seitenleiste _verstecken" @@ -5082,7 +5054,7 @@ msgstr "_Pfade anzeigen" msgid "Show _Tree" msgstr "_Baum anzeigen" -#: src/sidebar.c:1228 plugins/filebrowser.c:678 +#: src/sidebar.c:1228 plugins/filebrowser.c:674 msgid "_Find in Files..." msgstr "In _Dateien suchen…" @@ -5206,31 +5178,31 @@ msgstr "" msgid "_Set Custom Date Format" msgstr "_Benutzerdefiniertes Datumsformat einstellen" -#: src/ui_utils.c:2004 +#: src/ui_utils.c:2009 msgid "Select Project Base Path" msgstr "Projektbasisverzeichnis auswählen" -#: src/ui_utils.c:2029 +#: src/ui_utils.c:2034 msgid "Select Folder" msgstr "Ordner wählen" -#: src/ui_utils.c:2029 +#: src/ui_utils.c:2034 msgid "Select File" msgstr "Datei wählen" -#: src/ui_utils.c:2224 +#: src/ui_utils.c:2229 msgid "_Filetype Configuration" msgstr "_Dateityp-Einstellungen" -#: src/ui_utils.c:2261 +#: src/ui_utils.c:2266 msgid "Save All" msgstr "Alle speichern" -#: src/ui_utils.c:2262 +#: src/ui_utils.c:2267 msgid "Close All" msgstr "Alle schließen" -#: src/ui_utils.c:2498 +#: src/ui_utils.c:2503 msgid "Geany cannot start!" msgstr "Geany kann nicht starten!" @@ -5244,7 +5216,8 @@ msgid "" "or leave it empty in order to spawn the system default browser." msgstr "" "Konnte den konfigurierten Browser nicht öffnen. Bitte prüfen Sie die " -"Konfiguration und/oder wählen einen anderen Browser. Ein leerer Eintrag wird den System-Standard-Browser öffnen." +"Konfiguration und/oder wählen einen anderen Browser. Ein leerer Eintrag wird " +"den System-Standard-Browser öffnen." #: src/utils.c:388 msgid "Windows (CRLF)" @@ -5270,20 +5243,20 @@ msgstr "CR" msgid "LF" msgstr "LF" -#: src/vte.c:574 +#: src/vte.c:570 #, c-format msgid "invalid VTE library \"%s\": missing symbol \"%s\"" msgstr "Ungültige VTE-Bibliothek \"%s\": Es fehlt das Symbol \"%s\"" -#: src/vte.c:754 +#: src/vte.c:749 msgid "_Set Path From Document" msgstr "Pfad des Dokumentes _übernehmen" -#: src/vte.c:759 +#: src/vte.c:754 msgid "_Restart Terminal" msgstr "Terminal _neustarten" -#: src/vte.c:864 +#: src/vte.c:857 msgid "" "Directory not changed because the terminal may contain some input (press " "Ctrl+C or Enter to clear it)." @@ -5297,109 +5270,109 @@ msgid "Failed to open URI \"%s\": %s" msgstr "Konnte URI \"%s\" nicht öffnen: %s" #: src/tagmanager/tm_parser.c:87 src/tagmanager/tm_parser.c:179 -#: src/tagmanager/tm_parser.c:393 src/tagmanager/tm_parser.c:992 -#: src/tagmanager/tm_parser.c:1027 +#: src/tagmanager/tm_parser.c:394 src/tagmanager/tm_parser.c:993 +#: src/tagmanager/tm_parser.c:1028 msgid "Namespaces" msgstr "Namensräume" #: src/tagmanager/tm_parser.c:88 src/tagmanager/tm_parser.c:123 #: src/tagmanager/tm_parser.c:181 src/tagmanager/tm_parser.c:202 -#: src/tagmanager/tm_parser.c:365 src/tagmanager/tm_parser.c:382 -#: src/tagmanager/tm_parser.c:394 src/tagmanager/tm_parser.c:439 -#: src/tagmanager/tm_parser.c:510 src/tagmanager/tm_parser.c:579 -#: src/tagmanager/tm_parser.c:702 src/tagmanager/tm_parser.c:981 -#: src/tagmanager/tm_parser.c:1028 +#: src/tagmanager/tm_parser.c:366 src/tagmanager/tm_parser.c:383 +#: src/tagmanager/tm_parser.c:395 src/tagmanager/tm_parser.c:440 +#: src/tagmanager/tm_parser.c:511 src/tagmanager/tm_parser.c:580 +#: src/tagmanager/tm_parser.c:703 src/tagmanager/tm_parser.c:982 +#: src/tagmanager/tm_parser.c:1029 src/tagmanager/tm_parser.c:1132 msgid "Classes" msgstr "Klassen" #: src/tagmanager/tm_parser.c:89 src/tagmanager/tm_parser.c:122 -#: src/tagmanager/tm_parser.c:180 src/tagmanager/tm_parser.c:440 -#: src/tagmanager/tm_parser.c:578 src/tagmanager/tm_parser.c:648 -#: src/tagmanager/tm_parser.c:701 src/tagmanager/tm_parser.c:906 -#: src/tagmanager/tm_parser.c:1029 +#: src/tagmanager/tm_parser.c:180 src/tagmanager/tm_parser.c:441 +#: src/tagmanager/tm_parser.c:579 src/tagmanager/tm_parser.c:649 +#: src/tagmanager/tm_parser.c:702 src/tagmanager/tm_parser.c:907 +#: src/tagmanager/tm_parser.c:1030 msgid "Interfaces" msgstr "Interfaces" #: src/tagmanager/tm_parser.c:90 src/tagmanager/tm_parser.c:146 #: src/tagmanager/tm_parser.c:161 src/tagmanager/tm_parser.c:182 #: src/tagmanager/tm_parser.c:204 src/tagmanager/tm_parser.c:317 -#: src/tagmanager/tm_parser.c:352 src/tagmanager/tm_parser.c:419 -#: src/tagmanager/tm_parser.c:441 src/tagmanager/tm_parser.c:494 -#: src/tagmanager/tm_parser.c:511 src/tagmanager/tm_parser.c:528 -#: src/tagmanager/tm_parser.c:561 src/tagmanager/tm_parser.c:613 -#: src/tagmanager/tm_parser.c:662 src/tagmanager/tm_parser.c:703 -#: src/tagmanager/tm_parser.c:723 src/tagmanager/tm_parser.c:788 -#: src/tagmanager/tm_parser.c:880 src/tagmanager/tm_parser.c:905 -#: src/tagmanager/tm_parser.c:935 src/tagmanager/tm_parser.c:954 -#: src/tagmanager/tm_parser.c:993 src/tagmanager/tm_parser.c:1004 -#: src/tagmanager/tm_parser.c:1030 src/tagmanager/tm_parser.c:1067 -#: src/tagmanager/tm_parser.c:1091 +#: src/tagmanager/tm_parser.c:352 src/tagmanager/tm_parser.c:420 +#: src/tagmanager/tm_parser.c:442 src/tagmanager/tm_parser.c:495 +#: src/tagmanager/tm_parser.c:512 src/tagmanager/tm_parser.c:529 +#: src/tagmanager/tm_parser.c:562 src/tagmanager/tm_parser.c:614 +#: src/tagmanager/tm_parser.c:663 src/tagmanager/tm_parser.c:704 +#: src/tagmanager/tm_parser.c:724 src/tagmanager/tm_parser.c:789 +#: src/tagmanager/tm_parser.c:881 src/tagmanager/tm_parser.c:906 +#: src/tagmanager/tm_parser.c:936 src/tagmanager/tm_parser.c:955 +#: src/tagmanager/tm_parser.c:994 src/tagmanager/tm_parser.c:1005 +#: src/tagmanager/tm_parser.c:1031 src/tagmanager/tm_parser.c:1068 +#: src/tagmanager/tm_parser.c:1092 src/tagmanager/tm_parser.c:1134 msgid "Functions" msgstr "Funktionen" #: src/tagmanager/tm_parser.c:91 src/tagmanager/tm_parser.c:125 -#: src/tagmanager/tm_parser.c:442 src/tagmanager/tm_parser.c:512 -#: src/tagmanager/tm_parser.c:911 src/tagmanager/tm_parser.c:923 +#: src/tagmanager/tm_parser.c:443 src/tagmanager/tm_parser.c:513 +#: src/tagmanager/tm_parser.c:912 src/tagmanager/tm_parser.c:924 msgid "Members" msgstr "Instanzvariablen" #: src/tagmanager/tm_parser.c:92 src/tagmanager/tm_parser.c:353 -#: src/tagmanager/tm_parser.c:443 src/tagmanager/tm_parser.c:907 +#: src/tagmanager/tm_parser.c:444 src/tagmanager/tm_parser.c:908 msgid "Structs" msgstr "Strukturen" #: src/tagmanager/tm_parser.c:93 src/tagmanager/tm_parser.c:354 -#: src/tagmanager/tm_parser.c:444 src/tagmanager/tm_parser.c:881 +#: src/tagmanager/tm_parser.c:445 src/tagmanager/tm_parser.c:882 msgid "Typedefs / Enums" msgstr "Typendefinition und Enumerates" #: src/tagmanager/tm_parser.c:94 src/tagmanager/tm_parser.c:138 #: src/tagmanager/tm_parser.c:276 src/tagmanager/tm_parser.c:355 -#: src/tagmanager/tm_parser.c:513 src/tagmanager/tm_parser.c:883 -#: src/tagmanager/tm_parser.c:956 src/tagmanager/tm_parser.c:1005 +#: src/tagmanager/tm_parser.c:514 src/tagmanager/tm_parser.c:884 +#: src/tagmanager/tm_parser.c:957 src/tagmanager/tm_parser.c:1006 msgid "Macros" msgstr "Makros" #: src/tagmanager/tm_parser.c:95 src/tagmanager/tm_parser.c:184 #: src/tagmanager/tm_parser.c:205 src/tagmanager/tm_parser.c:323 -#: src/tagmanager/tm_parser.c:445 src/tagmanager/tm_parser.c:514 -#: src/tagmanager/tm_parser.c:562 src/tagmanager/tm_parser.c:582 -#: src/tagmanager/tm_parser.c:650 src/tagmanager/tm_parser.c:705 -#: src/tagmanager/tm_parser.c:724 src/tagmanager/tm_parser.c:772 -#: src/tagmanager/tm_parser.c:882 src/tagmanager/tm_parser.c:910 -#: src/tagmanager/tm_parser.c:936 src/tagmanager/tm_parser.c:983 -#: src/tagmanager/tm_parser.c:1006 src/tagmanager/tm_parser.c:1032 -#: src/tagmanager/tm_parser.c:1069 src/tagmanager/tm_parser.c:1080 -#: src/tagmanager/tm_parser.c:1093 +#: src/tagmanager/tm_parser.c:446 src/tagmanager/tm_parser.c:515 +#: src/tagmanager/tm_parser.c:563 src/tagmanager/tm_parser.c:583 +#: src/tagmanager/tm_parser.c:651 src/tagmanager/tm_parser.c:706 +#: src/tagmanager/tm_parser.c:725 src/tagmanager/tm_parser.c:773 +#: src/tagmanager/tm_parser.c:883 src/tagmanager/tm_parser.c:911 +#: src/tagmanager/tm_parser.c:937 src/tagmanager/tm_parser.c:984 +#: src/tagmanager/tm_parser.c:1007 src/tagmanager/tm_parser.c:1033 +#: src/tagmanager/tm_parser.c:1070 src/tagmanager/tm_parser.c:1081 +#: src/tagmanager/tm_parser.c:1094 src/tagmanager/tm_parser.c:1135 msgid "Variables" msgstr "Variablen" -#: src/tagmanager/tm_parser.c:96 src/tagmanager/tm_parser.c:446 +#: src/tagmanager/tm_parser.c:96 src/tagmanager/tm_parser.c:447 msgid "Extern Variables" msgstr "Externe Variablen" #: src/tagmanager/tm_parser.c:97 src/tagmanager/tm_parser.c:127 #: src/tagmanager/tm_parser.c:164 src/tagmanager/tm_parser.c:264 -#: src/tagmanager/tm_parser.c:707 src/tagmanager/tm_parser.c:789 -#: src/tagmanager/tm_parser.c:1034 src/tagmanager/tm_parser.c:1071 +#: src/tagmanager/tm_parser.c:708 src/tagmanager/tm_parser.c:790 +#: src/tagmanager/tm_parser.c:1035 src/tagmanager/tm_parser.c:1072 msgid "Other" msgstr "Sonstiges" #: src/tagmanager/tm_parser.c:121 src/tagmanager/tm_parser.c:160 -#: src/tagmanager/tm_parser.c:480 src/tagmanager/tm_parser.c:700 -#: src/tagmanager/tm_parser.c:904 +#: src/tagmanager/tm_parser.c:481 src/tagmanager/tm_parser.c:701 +#: src/tagmanager/tm_parser.c:905 msgid "Package" msgstr "Package" #: src/tagmanager/tm_parser.c:124 src/tagmanager/tm_parser.c:203 -#: src/tagmanager/tm_parser.c:384 src/tagmanager/tm_parser.c:395 -#: src/tagmanager/tm_parser.c:580 src/tagmanager/tm_parser.c:884 -#: src/tagmanager/tm_parser.c:982 +#: src/tagmanager/tm_parser.c:385 src/tagmanager/tm_parser.c:396 +#: src/tagmanager/tm_parser.c:581 src/tagmanager/tm_parser.c:885 +#: src/tagmanager/tm_parser.c:983 src/tagmanager/tm_parser.c:1113 msgid "Methods" msgstr "Methoden" -#: src/tagmanager/tm_parser.c:126 src/tagmanager/tm_parser.c:654 -#: src/tagmanager/tm_parser.c:984 src/tagmanager/tm_parser.c:1031 +#: src/tagmanager/tm_parser.c:126 src/tagmanager/tm_parser.c:655 +#: src/tagmanager/tm_parser.c:985 src/tagmanager/tm_parser.c:1032 msgid "Enums" msgstr "Enums" @@ -5408,23 +5381,23 @@ msgid "Targets" msgstr "Targets" #: src/tagmanager/tm_parser.c:162 src/tagmanager/tm_parser.c:275 -#: src/tagmanager/tm_parser.c:565 src/tagmanager/tm_parser.c:1079 +#: src/tagmanager/tm_parser.c:566 src/tagmanager/tm_parser.c:1080 msgid "Labels" msgstr "Label" #: src/tagmanager/tm_parser.c:163 src/tagmanager/tm_parser.c:183 -#: src/tagmanager/tm_parser.c:563 src/tagmanager/tm_parser.c:706 -#: src/tagmanager/tm_parser.c:909 src/tagmanager/tm_parser.c:952 -#: src/tagmanager/tm_parser.c:1007 src/tagmanager/tm_parser.c:1033 -#: src/tagmanager/tm_parser.c:1070 +#: src/tagmanager/tm_parser.c:564 src/tagmanager/tm_parser.c:707 +#: src/tagmanager/tm_parser.c:910 src/tagmanager/tm_parser.c:953 +#: src/tagmanager/tm_parser.c:1008 src/tagmanager/tm_parser.c:1034 +#: src/tagmanager/tm_parser.c:1071 msgid "Constants" msgstr "Konstanten" -#: src/tagmanager/tm_parser.c:185 src/tagmanager/tm_parser.c:878 +#: src/tagmanager/tm_parser.c:185 src/tagmanager/tm_parser.c:879 msgid "Traits" msgstr "Charakteristika" -#: src/tagmanager/tm_parser.c:206 src/tagmanager/tm_parser.c:699 +#: src/tagmanager/tm_parser.c:206 src/tagmanager/tm_parser.c:700 msgid "Imports" msgstr "Importe" @@ -5437,20 +5410,20 @@ msgid "Part" msgstr "Teil" #: src/tagmanager/tm_parser.c:230 src/tagmanager/tm_parser.c:335 -#: src/tagmanager/tm_parser.c:595 +#: src/tagmanager/tm_parser.c:596 msgid "Chapter" msgstr "Kapitel" #: src/tagmanager/tm_parser.c:231 src/tagmanager/tm_parser.c:336 -#: src/tagmanager/tm_parser.c:596 +#: src/tagmanager/tm_parser.c:597 msgid "Section" msgstr "Abschnitt" -#: src/tagmanager/tm_parser.c:232 src/tagmanager/tm_parser.c:597 +#: src/tagmanager/tm_parser.c:232 src/tagmanager/tm_parser.c:598 msgid "Subsection" msgstr "Unterabschnitt" -#: src/tagmanager/tm_parser.c:233 src/tagmanager/tm_parser.c:598 +#: src/tagmanager/tm_parser.c:233 src/tagmanager/tm_parser.c:599 msgid "Subsubsection" msgstr "Unterunterabschnitt" @@ -5490,16 +5463,16 @@ msgstr "Unveröffentlicht" msgid "Defines" msgstr "Definitionen" -#: src/tagmanager/tm_parser.c:278 src/tagmanager/tm_parser.c:483 -#: src/tagmanager/tm_parser.c:526 src/tagmanager/tm_parser.c:564 -#: src/tagmanager/tm_parser.c:581 src/tagmanager/tm_parser.c:651 -#: src/tagmanager/tm_parser.c:908 src/tagmanager/tm_parser.c:958 -#: src/tagmanager/tm_parser.c:1066 +#: src/tagmanager/tm_parser.c:278 src/tagmanager/tm_parser.c:484 +#: src/tagmanager/tm_parser.c:527 src/tagmanager/tm_parser.c:565 +#: src/tagmanager/tm_parser.c:582 src/tagmanager/tm_parser.c:652 +#: src/tagmanager/tm_parser.c:909 src/tagmanager/tm_parser.c:959 +#: src/tagmanager/tm_parser.c:1067 src/tagmanager/tm_parser.c:1133 msgid "Types" msgstr "Typen" -#: src/tagmanager/tm_parser.c:286 src/tagmanager/tm_parser.c:722 -#: src/tagmanager/tm_parser.c:738 src/tagmanager/tm_parser.c:806 +#: src/tagmanager/tm_parser.c:286 src/tagmanager/tm_parser.c:723 +#: src/tagmanager/tm_parser.c:739 src/tagmanager/tm_parser.c:807 msgid "Sections" msgstr "Abschnitte" @@ -5507,7 +5480,7 @@ msgstr "Abschnitte" msgid "Keys" msgstr "Indizes" -#: src/tagmanager/tm_parser.c:318 src/tagmanager/tm_parser.c:396 +#: src/tagmanager/tm_parser.c:318 src/tagmanager/tm_parser.c:397 msgid "Procedures" msgstr "Prozeduren" @@ -5543,197 +5516,218 @@ msgstr "Sect3" msgid "Appendix" msgstr "Anhang" -#: src/tagmanager/tm_parser.c:366 +#: src/tagmanager/tm_parser.c:356 src/tagmanager/tm_parser.c:439 +#: src/tagmanager/tm_parser.c:526 src/tagmanager/tm_parser.c:647 +msgid "Module" +msgstr "Modul" + +#: src/tagmanager/tm_parser.c:367 msgid "ID Selectors" msgstr "ID" -#: src/tagmanager/tm_parser.c:367 +#: src/tagmanager/tm_parser.c:368 msgid "Type Selectors" msgstr "Typ" -#: src/tagmanager/tm_parser.c:381 src/tagmanager/tm_parser.c:770 -#: src/tagmanager/tm_parser.c:876 src/tagmanager/tm_parser.c:953 +#: src/tagmanager/tm_parser.c:382 src/tagmanager/tm_parser.c:771 +#: src/tagmanager/tm_parser.c:877 src/tagmanager/tm_parser.c:954 +#: src/tagmanager/tm_parser.c:1131 msgid "Modules" msgstr "Module" -#: src/tagmanager/tm_parser.c:383 +#: src/tagmanager/tm_parser.c:384 msgid "Singletons" msgstr "Singletons" -#: src/tagmanager/tm_parser.c:438 src/tagmanager/tm_parser.c:525 -#: src/tagmanager/tm_parser.c:646 -msgid "Module" -msgstr "Modul" - -#: src/tagmanager/tm_parser.c:481 +#: src/tagmanager/tm_parser.c:482 msgid "Entities" msgstr "Entitäten" -#: src/tagmanager/tm_parser.c:482 +#: src/tagmanager/tm_parser.c:483 msgid "Architectures" msgstr "Architekturen" -#: src/tagmanager/tm_parser.c:484 +#: src/tagmanager/tm_parser.c:485 msgid "Functions / Procedures" msgstr "Funktionen/Prozeduren" -#: src/tagmanager/tm_parser.c:485 +#: src/tagmanager/tm_parser.c:486 msgid "Variables / Signals / Ports" msgstr "Variablen / Signale / Ports" -#: src/tagmanager/tm_parser.c:486 +#: src/tagmanager/tm_parser.c:487 msgid "Processes / Blocks / Components" msgstr "Prozesse / Blöcke / Komponenten" -#: src/tagmanager/tm_parser.c:527 +#: src/tagmanager/tm_parser.c:528 msgid "Type constructors" msgstr "Typkonstruktoren" -#: src/tagmanager/tm_parser.c:614 +#: src/tagmanager/tm_parser.c:615 msgid "Anchors" msgstr "Anker" -#: src/tagmanager/tm_parser.c:615 +#: src/tagmanager/tm_parser.c:616 msgid "H1 Headings" msgstr "Ãœberschrift (H1)" -#: src/tagmanager/tm_parser.c:616 +#: src/tagmanager/tm_parser.c:617 msgid "H2 Headings" msgstr "Ãœberschrift (H2)" -#: src/tagmanager/tm_parser.c:617 +#: src/tagmanager/tm_parser.c:618 msgid "H3 Headings" msgstr "Ãœberschrift (H3)" -#: src/tagmanager/tm_parser.c:647 +#: src/tagmanager/tm_parser.c:648 msgid "Programs" msgstr "Programme" -#: src/tagmanager/tm_parser.c:649 +#: src/tagmanager/tm_parser.c:650 msgid "Functions / Subroutines" msgstr "Funktionen/Prozeduren" -#: src/tagmanager/tm_parser.c:652 +#: src/tagmanager/tm_parser.c:653 msgid "Components" msgstr "Komponenten" -#: src/tagmanager/tm_parser.c:653 +#: src/tagmanager/tm_parser.c:654 msgid "Blocks" msgstr "Blöcke" -#: src/tagmanager/tm_parser.c:663 src/tagmanager/tm_parser.c:877 -#: src/tagmanager/tm_parser.c:957 +#: src/tagmanager/tm_parser.c:664 src/tagmanager/tm_parser.c:878 +#: src/tagmanager/tm_parser.c:958 msgid "Structures" msgstr "Strukturen" -#: src/tagmanager/tm_parser.c:737 +#: src/tagmanager/tm_parser.c:738 msgid "Chapters" msgstr "Kapitel" -#: src/tagmanager/tm_parser.c:739 +#: src/tagmanager/tm_parser.c:740 msgid "Subsections" msgstr "Unterabschnitt" -#: src/tagmanager/tm_parser.c:740 +#: src/tagmanager/tm_parser.c:741 msgid "Subsubsections" msgstr "Unter-Unterabschnitt" -#: src/tagmanager/tm_parser.c:741 +#: src/tagmanager/tm_parser.c:742 msgid "Level 4 sections" msgstr "Stufe 4-Abschnitt" -#: src/tagmanager/tm_parser.c:742 +#: src/tagmanager/tm_parser.c:743 msgid "Level 5 sections" msgstr "Stufe 5-Abschnitt" -#: src/tagmanager/tm_parser.c:769 +#: src/tagmanager/tm_parser.c:770 msgid "Events" msgstr "Ereignisse" -#: src/tagmanager/tm_parser.c:771 +#: src/tagmanager/tm_parser.c:772 msgid "Functions / Tasks" msgstr "Funktionen" -#: src/tagmanager/tm_parser.c:803 +#: src/tagmanager/tm_parser.c:804 msgid "Program" msgstr "Programm" -#: src/tagmanager/tm_parser.c:805 +#: src/tagmanager/tm_parser.c:806 msgid "Divisions" msgstr "Bereiche (Divisions)" -#: src/tagmanager/tm_parser.c:807 +#: src/tagmanager/tm_parser.c:808 msgid "Paragraph" msgstr "Absatz" -#: src/tagmanager/tm_parser.c:808 +#: src/tagmanager/tm_parser.c:809 msgid "Group" msgstr "Gruppe" -#: src/tagmanager/tm_parser.c:809 +#: src/tagmanager/tm_parser.c:810 msgid "Data" msgstr "Daten" -#: src/tagmanager/tm_parser.c:810 +#: src/tagmanager/tm_parser.c:811 msgid "Copies" msgstr "Kopien (Copies)" -#: src/tagmanager/tm_parser.c:842 +#: src/tagmanager/tm_parser.c:843 msgid "Section Level 1" msgstr "Ãœberschrift Ebene 1" -#: src/tagmanager/tm_parser.c:843 +#: src/tagmanager/tm_parser.c:844 msgid "Section Level 2" msgstr "Ãœberschrift Ebene 2" -#: src/tagmanager/tm_parser.c:844 +#: src/tagmanager/tm_parser.c:845 msgid "Section Level 3" msgstr "Ãœberschrift Ebene 3" -#: src/tagmanager/tm_parser.c:845 +#: src/tagmanager/tm_parser.c:846 msgid "Section Level 4" msgstr "Ãœberschrift Ebene 4" -#: src/tagmanager/tm_parser.c:846 +#: src/tagmanager/tm_parser.c:847 msgid "Section Level 5" msgstr "Ãœberschrift Ebene 5" -#: src/tagmanager/tm_parser.c:856 +#: src/tagmanager/tm_parser.c:857 msgid "Parts" msgstr "Parts" -#: src/tagmanager/tm_parser.c:857 +#: src/tagmanager/tm_parser.c:858 msgid "Assembly" msgstr "Assembly" -#: src/tagmanager/tm_parser.c:858 +#: src/tagmanager/tm_parser.c:859 msgid "Steps" msgstr "Steps" -#: src/tagmanager/tm_parser.c:879 +#: src/tagmanager/tm_parser.c:880 msgid "Implementations" msgstr "Implementierungen" -#: src/tagmanager/tm_parser.c:955 +#: src/tagmanager/tm_parser.c:956 msgid "Fields" msgstr "Felder" -#: src/tagmanager/tm_parser.c:959 +#: src/tagmanager/tm_parser.c:960 msgid "Unknowns" msgstr "Unbekannte" -#: src/tagmanager/tm_parser.c:1065 +#: src/tagmanager/tm_parser.c:1066 msgid "Packages" msgstr "Pakete" -#: src/tagmanager/tm_parser.c:1068 +#: src/tagmanager/tm_parser.c:1069 msgid "Tasks" msgstr "Aufgaben" -#: src/tagmanager/tm_parser.c:1092 +#: src/tagmanager/tm_parser.c:1093 msgid "Regions" msgstr "Regionen" +#: src/tagmanager/tm_parser.c:1110 +msgid "Packages / Modules" +msgstr "Pakete/Module" + +#: src/tagmanager/tm_parser.c:1111 +msgid "Classes / Roles" +msgstr "Klassen/Rollen" + +#: src/tagmanager/tm_parser.c:1112 +msgid "Grammars" +msgstr "Grammatiken" + +#: src/tagmanager/tm_parser.c:1114 +msgid "Subroutines" +msgstr "Subroutinen" + +#: src/tagmanager/tm_parser.c:1115 +msgid "Rules / Tokens" +msgstr "Rollen/Token" + #: plugins/classbuilder.c:34 msgid "Class Builder" msgstr "Klassengenerator" @@ -5887,7 +5881,7 @@ msgstr "Zeichen für Interpunktion" msgid "Miscellaneous characters" msgstr "Sonstige Zeichen" -#: plugins/htmlchars.c:365 plugins/filebrowser.c:1202 plugins/saveactions.c:569 +#: plugins/htmlchars.c:365 plugins/filebrowser.c:1198 plugins/saveactions.c:569 msgid "Plugin configuration directory could not be created." msgstr "Plugin-Konfigurationsverzeichnis konnte nicht erstellt werden." @@ -6012,39 +6006,39 @@ msgstr "Zu viele Elemente ausgewählt!" msgid "Could not execute configured external command '%s' (%s)." msgstr "Kann angegebenen externen Befehl »%s« nicht ausführen (%s)." -#: plugins/filebrowser.c:657 +#: plugins/filebrowser.c:653 msgid "Open in _Geany" msgstr "Datei in _Geany öffnen" -#: plugins/filebrowser.c:663 +#: plugins/filebrowser.c:659 msgid "Open _Externally" msgstr "Mit _externer Anwendung öffnen" -#: plugins/filebrowser.c:688 +#: plugins/filebrowser.c:684 msgid "Show _Hidden Files" msgstr "V_ersteckte Dateien anzeigen" -#: plugins/filebrowser.c:918 +#: plugins/filebrowser.c:914 msgid "Up" msgstr "Aufwärts" -#: plugins/filebrowser.c:923 +#: plugins/filebrowser.c:919 msgid "Refresh" msgstr "Neu laden" -#: plugins/filebrowser.c:928 +#: plugins/filebrowser.c:924 msgid "Home" msgstr "Persönliches Verzeichnis" -#: plugins/filebrowser.c:933 +#: plugins/filebrowser.c:929 msgid "Set path from document" msgstr "Pfad des Dokumentes übernehmen" -#: plugins/filebrowser.c:947 +#: plugins/filebrowser.c:943 msgid "Filter:" msgstr "Filter:" -#: plugins/filebrowser.c:956 +#: plugins/filebrowser.c:952 msgid "" "Filter your files with the usual wildcards. Separate multiple patterns with " "a space." @@ -6052,19 +6046,19 @@ msgstr "" "Filtern der Dateien mit den gewohnten Platzhaltern. Verschiedene Filter " "mittels Leerzeichen trennen." -#: plugins/filebrowser.c:1172 +#: plugins/filebrowser.c:1168 msgid "Focus File List" msgstr "Dateiliste in den Vordergrund" -#: plugins/filebrowser.c:1174 +#: plugins/filebrowser.c:1170 msgid "Focus Path Entry" msgstr "Pfad in den Vordergrund" -#: plugins/filebrowser.c:1267 +#: plugins/filebrowser.c:1263 msgid "External open command:" msgstr "Externes öffnen Kommando:" -#: plugins/filebrowser.c:1275 +#: plugins/filebrowser.c:1271 #, c-format msgid "" "The command to execute when using \"Open with\". You can use %f and %d " @@ -6079,23 +6073,23 @@ msgstr "" "%d wird durch den Pfadnamen der ausgewählten Datei ersetzt (ohne den " "Dateinamen)." -#: plugins/filebrowser.c:1283 +#: plugins/filebrowser.c:1279 msgid "Show hidden files" msgstr "Versteckte Dateien anzeigen" -#: plugins/filebrowser.c:1291 +#: plugins/filebrowser.c:1287 msgid "Hide file extensions:" msgstr "Dateiendung für versteckte Dateien:" -#: plugins/filebrowser.c:1310 +#: plugins/filebrowser.c:1306 msgid "Follow the path of the current file" msgstr "Pfad der aktuellen Datei setzen" -#: plugins/filebrowser.c:1316 +#: plugins/filebrowser.c:1312 msgid "Use the project's base directory" msgstr "Das Projekt-Basisverzeichnis nutzen" -#: plugins/filebrowser.c:1320 +#: plugins/filebrowser.c:1316 msgid "" "Change the directory to the base directory of the currently opened project" msgstr "" @@ -6221,9 +6215,9 @@ msgid "" "Date/_Time format for backup files (for a list of available conversion " "specifiers see https://docs.gtk.org/glib/method.DateTime.format.html):" msgstr "" -"Datums- und Zeitformat für die Backup-Dateien. Eine vollständige " -"Liste gültiger Platzhalter ist unter https://docs.gtk.org/glib/method." -"DateTime.format.html verfügbar):" +"Datums- und Zeitformat für die Backup-Dateien. Eine vollständige Liste " +"gültiger Platzhalter ist unter https://docs.gtk.org/glib/method.DateTime." +"format.html verfügbar):" #: plugins/saveactions.c:830 msgid "Directory _levels to include in the backup destination:" @@ -6266,6 +6260,47 @@ msgstr "Seite an Seite" msgid "Top and Bottom" msgstr "Oben und Unten" +#~ msgid "Use Windows native dialogs" +#~ msgstr "Windows-typische Dialoge nutzen" + +#~ msgid "" +#~ "Defines whether to use the Windows native dialogs or whether to use the " +#~ "GTK default dialogs" +#~ msgstr "" +#~ "Bestimmt, ob Dialoge im Stil von Windows oder von GTK geöffnet werden " +#~ "sollen" + +#~ msgid "Use fixed encoding when opening non-Unicode files" +#~ msgstr "" +#~ "Benutze feststehende Zeichenkodierung beim Öffnen neuer, nicht Unicode-" +#~ "Dateien" + +#~ msgid "" +#~ "This option disables the automatic detection of the file encoding when " +#~ "opening non-Unicode files and opens the file with the specified encoding " +#~ "(usually not needed)" +#~ msgstr "" +#~ "Diese Option deaktiviert die automatische Erkennung der Zeichenkodierung " +#~ "und öffnet die ausgewählten nicht Unicode-Dateien mit der angegebenen " +#~ "Kodierung. (Wird nur in Ausnahmen benötigt)" + +#, c-format +#~ msgid "The file \"%s\" is not valid %s." +#~ msgstr "Die Datei »%s« ist kein gültiges %s." + +#, c-format +#~ msgid "" +#~ "The file \"%s\" does not look like a text file or the file encoding is " +#~ "not supported." +#~ msgstr "" +#~ "Die Datei »%s« scheint keine Textdatei zu sein, oder die Zeichenkodierung " +#~ "wird nicht unterstützt." + +#~ msgid "Print only the basename(without the path) of the printed file" +#~ msgstr "" +#~ "Nur den Basisdateinamen (ohne die Pfadangabe) der zu druckenden Datei " +#~ "verwenden" + #, c-format #~ msgid "%s:%lu: %s" #~ msgstr "%s:%lu: %s" @@ -6422,9 +6457,6 @@ msgstr "Oben und Unten" #~ msgid "Shell script" #~ msgstr "Shellskript" -#~ msgid "Subroutines" -#~ msgstr "Subroutinen" - #~ msgid "file name" #~ msgstr "Dateiname:" From 1ae53900e616eee1415fbbb90ed1733a3a54422e Mon Sep 17 00:00:00 2001 From: andy5995 Date: Fri, 9 Feb 2024 07:36:33 -0600 Subject: [PATCH 14/18] Fix deprecation warning about str.format() during meson setup Each object in the list is a file object, not a string: `ctags_tests = files([...` hence the message: "tests/meson.build:359: DEPRECATION: Project uses feature that was always broken, and is now deprecated since '1.3.0': str.format: Value other than strings, integers, bools, options, dictionaries and lists thereof..." --- tests/meson.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index bd975327ea..8757cf19af 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -2,7 +2,7 @@ test_deps = declare_dependency(compile_args: geany_cflags + [ '-DG_LOG_DOMAIN="G dependencies: [deps, dep_libgeany], include_directories: '..') -ctags_tests = files([ +ctags_tests = [ 'ctags/1795612.js.tags', 'ctags/1850914.js.tags', 'ctags/1878155.js.tags', @@ -351,12 +351,12 @@ ctags_tests = files([ 'ctags/vhdl-process.vhd.tags', 'ctags/vhdl-type.vhd.tags', 'ctags/whitespaces.php.tags' -]) +] runner = find_program('ctags/runner.sh') foreach t : ctags_tests - test('@0@'.format(t), runner, - args: [join_paths(meson.build_root(), 'geany'), t], + test(t, runner, + args: [join_paths(meson.build_root(), 'geany'), files(t)], env: ['top_srcdir='+meson.source_root(), 'top_builddir=' + meson.build_root()]) endforeach From 0b52e79c93942d115d94a2df6015a1c704fb0ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 10 May 2024 23:08:50 +0200 Subject: [PATCH 15/18] Add dummy TMParserMapGroup for ldscript to avoid warnings --- src/tagmanager/tm_parser.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index d2c661bf6b..4287a77538 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -1163,6 +1163,7 @@ static TMParserMapEntry map_LDSCRIPT[] = { {'i', tm_tag_undef_t}, // inputSection }; static TMParserMapGroup group_LDSCRIPT[] = { + {"unused", TM_ICON_NONE, tm_tag_undef_t}, }; typedef struct From acb4678486110d90dd46eb6dea336f5907cafb14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 10 May 2024 23:27:17 +0200 Subject: [PATCH 16/18] Map freepascal namespaces and add a unit test for them --- src/tagmanager/tm_parser.c | 5 +++-- tests/ctags/Makefile.am | 1 + tests/ctags/namespace.bas | 15 +++++++++++++++ tests/ctags/namespace.bas.tags | 12 ++++++++++++ tests/meson.build | 1 + 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/ctags/namespace.bas create mode 100644 tests/ctags/namespace.bas.tags diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 4287a77538..ec8dc32f41 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -562,13 +562,14 @@ static TMParserMapEntry map_FREEBASIC[] = { {'t', tm_tag_struct_t}, // type {'v', tm_tag_variable_t}, // variable {'g', tm_tag_externvar_t}, // enum - {'n', tm_tag_undef_t}, // namespace + {'n', tm_tag_package_t}, // namespace }; static TMParserMapGroup group_FREEBASIC[] = { + {N_("Namespaces"), TM_ICON_NAMESPACE, tm_tag_package_t}, {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, {N_("Variables"), TM_ICON_VAR, tm_tag_variable_t | tm_tag_externvar_t}, {N_("Constants"), TM_ICON_MACRO, tm_tag_macro_t}, - {N_("Types"), TM_ICON_NAMESPACE, tm_tag_struct_t}, + {N_("Types"), TM_ICON_STRUCT, tm_tag_struct_t}, {N_("Labels"), TM_ICON_MEMBER, tm_tag_namespace_t}, }; diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index 8390acd2d8..b1d3cdafdc 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -249,6 +249,7 @@ test_sources = \ mode.php \ moniker.x68.asm \ namelist.f \ + namespace.bas \ namespace.cpp \ namespaces2.php \ namespaces.php \ diff --git a/tests/ctags/namespace.bas b/tests/ctags/namespace.bas new file mode 100644 index 0000000000..76887ff56a --- /dev/null +++ b/tests/ctags/namespace.bas @@ -0,0 +1,15 @@ +namespace first 'first defines something' + sub first_func + end sub + + namespace second 'second defines something' + sub first_func 'oh a second first_func + end sub + + sub second_func + end sub + end namespace 'ignored' +end namespace + +sub first_func 'oh another first_func +end sub diff --git a/tests/ctags/namespace.bas.tags b/tests/ctags/namespace.bas.tags new file mode 100644 index 0000000000..b34653f250 --- /dev/null +++ b/tests/ctags/namespace.bas.tags @@ -0,0 +1,12 @@ +firstÌ512Ö0 +package: first +first_funcÌ16Ö0 +function: first_func +first_funcÌ16ÎfirstÖ0 +function: first :: first_func +first_funcÌ16Îfirst.secondÖ0 +function: first.second :: first_func +secondÌ512ÎfirstÖ0 +package: first :: second +second_funcÌ16Îfirst.secondÖ0 +function: first.second :: second_func diff --git a/tests/meson.build b/tests/meson.build index f6c6dac567..75769bb9ac 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -246,6 +246,7 @@ ctags_tests = files([ 'ctags/mode.php.tags', 'ctags/moniker.x68.asm.tags', 'ctags/namelist.f.tags', + 'ctags/namespace.bas.tags', 'ctags/namespace.cpp.tags', 'ctags/namespaces2.php.tags', 'ctags/namespaces.php.tags', From 77614bd228aaba187faaef3631d127f231fce1f4 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Mon, 13 May 2024 22:04:02 +0200 Subject: [PATCH 17/18] Map PowerShell classes and enums --- src/tagmanager/tm_parser.c | 6 ++++-- tests/ctags/Makefile.am | 2 ++ tests/ctags/class.ps1 | 29 +++++++++++++++++++++++++++++ tests/ctags/class.ps1.tags | 12 ++++++++++++ tests/ctags/enum.ps1 | 23 +++++++++++++++++++++++ tests/ctags/enum.ps1.tags | 8 ++++++++ tests/meson.build | 2 ++ 7 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/ctags/class.ps1 create mode 100644 tests/ctags/class.ps1.tags create mode 100644 tests/ctags/enum.ps1 create mode 100644 tests/ctags/enum.ps1.tags diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index ec8dc32f41..c422d491ca 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -948,12 +948,14 @@ static TMParserMapGroup group_JSON[] = { static TMParserMapEntry map_POWERSHELL[] = { {'f', tm_tag_function_t}, // function {'v', tm_tag_variable_t}, // variable - {'c', tm_tag_undef_t}, // class + {'c', tm_tag_class_t}, // class {'i', tm_tag_function_t}, // filter - {'g', tm_tag_undef_t}, // enum + {'g', tm_tag_enum_t}, // enum }; static TMParserMapGroup group_POWERSHELL[] = { + {N_("Classes"), TM_ICON_CLASS, tm_tag_class_t}, {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, + {N_("Enums"), TM_ICON_STRUCT, tm_tag_enum_t}, {N_("Variables"), TM_ICON_VAR, tm_tag_variable_t}, }; diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index b1d3cdafdc..f6761a9af6 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -128,6 +128,7 @@ test_sources = \ case_sensitivity.php \ char-selector.f90 \ classes.php \ + class.ps1 \ cobol/helloworld.cbl \ cobol/helloworld2.cbl \ cobol/levels.cbl \ @@ -165,6 +166,7 @@ test_sources = \ enum.c \ enum.f90 \ enum.java \ + enum.ps1 \ enumerators.f90 \ events.cs \ extern_variable.h \ diff --git a/tests/ctags/class.ps1 b/tests/ctags/class.ps1 new file mode 100644 index 0000000000..c1e035a7f8 --- /dev/null +++ b/tests/ctags/class.ps1 @@ -0,0 +1,29 @@ +class MyException : Exception { + MyException([String]$Message) : base([String]$Message) { + Write-Host "dummy" + } +} + +class Derived : Base { +} + +class Derived2: Base { +} + +class Foo { + $Property1 + $Property2 = 20 + Method($Arg1) { + $LocalVar1 = 100 + Write-Host "dummy" + } +} + +function GetBar { + $LocalVar2 = 200 + Write-Host "dummy" +} + +function GetBaz() { + Write-Host "dummy" +} diff --git a/tests/ctags/class.ps1.tags b/tests/ctags/class.ps1.tags new file mode 100644 index 0000000000..fe9c6f8dba --- /dev/null +++ b/tests/ctags/class.ps1.tags @@ -0,0 +1,12 @@ +DerivedÌ1Ö0 +class: Derived +Derived2Ì1Ö0 +class: Derived2 +FooÌ1Ö0 +class: Foo +GetBarÌ16Ö0 +function: GetBar +GetBazÌ16Í()Ö0 +function: GetBaz() +MyExceptionÌ1Ö0 +class: MyException diff --git a/tests/ctags/enum.ps1 b/tests/ctags/enum.ps1 new file mode 100644 index 0000000000..f7e9cf2f01 --- /dev/null +++ b/tests/ctags/enum.ps1 @@ -0,0 +1,23 @@ +# EnumName1 +enum EnumName1 { + Label11 + Label12 = 10 +} + +# EnumName2 +enum EnumName2 { + Label21 + Label22 = 20 +} + +# EnumName3 +Enum EnumName3 { + Label31 + Label32 = 30 +} + +# EnumName4 +[Flags()] enum EnumName4 { + Label41 + Label42 = 40 +} diff --git a/tests/ctags/enum.ps1.tags b/tests/ctags/enum.ps1.tags new file mode 100644 index 0000000000..126058b5d6 --- /dev/null +++ b/tests/ctags/enum.ps1.tags @@ -0,0 +1,8 @@ +EnumName1Ì2Ö0 +enum: EnumName1 +EnumName2Ì2Ö0 +enum: EnumName2 +EnumName3Ì2Ö0 +enum: EnumName3 +EnumName4Ì2Ö0 +enum: EnumName4 diff --git a/tests/meson.build b/tests/meson.build index 75769bb9ac..146573fb69 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -128,6 +128,7 @@ ctags_tests = files([ 'ctags/case_sensitivity.php.tags', 'ctags/char-selector.f90.tags', 'ctags/classes.php.tags', + 'ctags/class.ps1.tags', 'ctags/cobol/helloworld.cbl.tags', 'ctags/cobol/helloworld2.cbl.tags', 'ctags/cobol/levels.cbl.tags', @@ -165,6 +166,7 @@ ctags_tests = files([ 'ctags/enum.c.tags', 'ctags/enum.f90.tags', 'ctags/enum.java.tags', + 'ctags/enum.ps1.tags', 'ctags/enumerators.f90.tags', 'ctags/events.cs.tags', 'ctags/extern_variable.h.tags', From ae75c73b48db70a5d59e2dc02a566f1ebfa2ef46 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Mon, 13 May 2024 22:32:25 +0200 Subject: [PATCH 18/18] Fix a JavaScript test Remove invalid code (despite what the code says) that now confuses the parser, leading to missing tags after it. --- tests/ctags/simple.js | 3 ++- tests/ctags/simple.js.tags | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ctags/simple.js b/tests/ctags/simple.js index 6f48c632c7..a7689751ec 100644 --- a/tests/ctags/simple.js +++ b/tests/ctags/simple.js @@ -40,7 +40,8 @@ function validFunctionThree(a,b) { var my_global_var1 = 33; -function extra.validFunctionFour(a,b) {} +var extra = {} +extra.validFunctionFour = function(a,b) {} //pseudo-module setup testlib = {} diff --git a/tests/ctags/simple.js.tags b/tests/ctags/simple.js.tags index 704fb1b52e..3919539593 100644 --- a/tests/ctags/simple.js.tags +++ b/tests/ctags/simple.js.tags @@ -10,6 +10,8 @@ core variable: testlib :: core executeQueryStringÌ128ÎDatabaseÖ0 method: Database :: executeQueryString +extraÌ16384Ö0 +variable: extra extrasÌ1ÎtestlibÖ0 class: testlib :: extras getHalfOfÌ16Í(num1, num2, num3)Ö0 @@ -22,6 +24,8 @@ invalidInnerFunction function: invalidInnerFunction(a,b) my_global_var1Ì16384Ö0 variable: my_global_var1 +my_global_var2Ì16384Ö0 +variable: my_global_var2 my_global_var3Ì16384Ö0 variable: my_global_var3 my_global_var4Ì16384Ö0 @@ -34,6 +38,8 @@ testlib variable: testlib validFunctionFiveÌ16Í(a,b)ÎtestlibÖ0 function: testlib :: validFunctionFive(a,b) +validFunctionFourÌ16Í(a,b)ÎextraÖ0 +function: extra :: validFunctionFour(a,b) validFunctionOneÌ16Í(a,b)Ö0 function: validFunctionOne(a,b) validFunctionSixÌ16Í(a,b)Îtestlib.coreÖ0