diff --git a/ctags/Makefile.am b/ctags/Makefile.am index eceed2fba4..c0cd8eaeb8 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -1,6 +1,7 @@ AM_CPPFLAGS = \ -I$(srcdir)/main \ -I$(srcdir)/parsers \ + -I$(srcdir)/dsl \ -DEXTERNAL_PARSER_LIST_FILE=\"$(top_srcdir)/src/tagmanager/tm_parsers.h\" \ -DG_LOG_DOMAIN=\"CTags\" AM_CFLAGS = \ @@ -58,11 +59,17 @@ parsers = \ parsers/geany_vhdl.c # skip cmd.c and mini-geany.c which define main() +# also skip lregex-pcre2.c which we don't use libctags_la_SOURCES = \ + dsl/optscript.c \ + dsl/optscript.h \ + dsl/es.c \ + dsl/es.h \ main/args.c \ main/args_p.h \ main/colprint.c \ main/colprint_p.h \ + main/CommonPrelude.c \ main/ctags.h \ main/debug.c \ main/debug.h \ @@ -99,6 +106,7 @@ libctags_la_SOURCES = \ main/lregex.c \ main/lregex.h \ main/lregex_p.h \ + main/lregex-default.c \ main/lxpath.c \ main/lxpath.h \ main/lxpath_p.h \ @@ -144,6 +152,8 @@ libctags_la_SOURCES = \ main/routines.c \ main/routines.h \ main/routines_p.h \ + main/script.c \ + main/script_p.h \ main/seccomp.c \ main/selectors.c \ main/selectors.h \ diff --git a/ctags/dsl/es.c b/ctags/dsl/es.c new file mode 100644 index 0000000000..74959d3983 --- /dev/null +++ b/ctags/dsl/es.c @@ -0,0 +1,3332 @@ +/* + Copyright (c) 2009 Masatake YAMATO + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ + +#if defined (HAVE_CONFIG_H) +# include +#endif + +#include "es.h" + + +#include +#include +#include +#include + +#include + +static int es_debug = 0; + +typedef struct _EsInteger EsInteger; +typedef struct _EsReal EsReal; +typedef struct _EsBoolean EsBoolean; +typedef struct _EsString EsString; +typedef struct _EsSingleton EsSingleton; +typedef struct _EsSymbol EsSymbol; +typedef struct _EsError EsError; +typedef struct _EsCons EsCons; +typedef struct _EsRegex EsRegex; +typedef struct _EsPointer EsPointer; + +struct _EsObject +{ + EsType type; + union + { + int ref_count; + EsSingleton* next; + }; +}; + +struct _EsInteger +{ + EsObject base; + int value; +}; + +struct _EsReal +{ + EsObject base; + double value; +}; + +struct _EsBoolean +{ + EsObject base; + int value; +}; + +struct _EsString +{ + EsObject base; + char* value; +}; + +struct _EsSingleton +{ + EsObject base; + char* quark; +}; + +struct _EsSymbol +{ + EsSingleton base; + void *data; +}; + +struct _EsError +{ + EsSingleton base; + EsObject *object; +}; + +struct _EsCons +{ + EsObject base; + EsObject* car; + EsObject* cdr; +}; + +struct _EsRegex +{ + EsObject base; + regex_t *code; + char* literal; + int case_insensitive; +}; + +struct _EsPointer +{ + EsObject base; + void *ptr; + char fat []; +}; + +enum EsObjectFlag +{ + ES_OBJECT_FLAG_ATOM = 1 << 0, +}; + +typedef struct _EsObjectClass EsObjectClass; +struct _EsObjectClass +{ + size_t size; + void (* free) (EsObject* object); + int (* equal) (const EsObject* self, const EsObject* other); + void (* print) (const EsObject* object, MIO* fp); + unsigned flags; + EsSingleton **obarray; + const char* name; +}; + + +static void es_nil_free(EsObject* object); +static int es_nil_equal(const EsObject* self, const EsObject* other); +static void es_nil_print(const EsObject* object, MIO* fp); + +static void es_integer_free(EsObject* object); +static int es_integer_equal(const EsObject* self, const EsObject* other); +static void es_integer_print(const EsObject* object, MIO* fp); + +static void es_real_free(EsObject* object); +static int es_real_equal(const EsObject* self, const EsObject* other); +static void es_real_print(const EsObject* object, MIO* fp); + +static void es_boolean_free(EsObject* object); +static int es_boolean_equal(const EsObject* self, const EsObject* other); +static void es_boolean_print(const EsObject* object, MIO* fp); + +static void es_string_free(EsObject* object); +static int es_string_equal(const EsObject* self, const EsObject* other); +static void es_string_print(const EsObject* self, MIO* fp); + +static void es_symbol_free(EsObject* object); +static int es_symbol_equal(const EsObject* self, const EsObject* other); +static void es_symbol_print(const EsObject* object, MIO* fp); + +static void es_cons_free(EsObject* object); +static int es_cons_equal(const EsObject* self, const EsObject* other); +static void es_cons_print(const EsObject* object, MIO* fp); + +static void es_regex_free(EsObject* object); +static int es_regex_equal(const EsObject* self, const EsObject* other); +static void es_regex_print(const EsObject* object, MIO* fp); + +static void es_error_free(EsObject* object); +static int es_error_equal(const EsObject* self, const EsObject* other); +static void es_error_print(const EsObject* object, MIO* fp); + +static void es_pointer_free(EsObject* object); +static int es_pointer_equal(const EsObject* self, const EsObject* other); +static void es_pointer_print(const EsObject* object, MIO* fp); + +static EsSingleton* es_obarray_intern(EsType type, const char* name); +static const char* es_singleton_get (EsSingleton *singleton); +static unsigned int hash(const char* keyarg); +#define OBARRAY_SIZE 83 +static EsSingleton *symbol_obarray[OBARRAY_SIZE]; +static EsSingleton *error_obarray [OBARRAY_SIZE]; + +static EsObjectClass es_nil_class = { + .size = 0, + .free = es_nil_free, + .equal = es_nil_equal, + .print = es_nil_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = NULL, + .name = "nil", +}; + +static EsObjectClass es_integer_class = { + .size = sizeof(EsInteger), + .free = es_integer_free, + .equal = es_integer_equal, + .print = es_integer_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = NULL, + .name = "integer", +}; + +static EsObjectClass es_real_class = { + .size = sizeof(EsReal), + .free = es_real_free, + .equal = es_real_equal, + .print = es_real_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = NULL, + .name = "real", +}; + +static EsObjectClass es_boolean_class = { + .size = sizeof(EsBoolean), + .free = es_boolean_free, + .equal = es_boolean_equal, + .print = es_boolean_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = (void*)1, + .name = "boolean", +}; + +static EsObjectClass es_symbol_class = { + .size = sizeof(EsSymbol), + .free = es_symbol_free, + .equal = es_symbol_equal, + .print = es_symbol_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = symbol_obarray, + .name = "symbol", +}; + +static EsObjectClass es_string_class = { + .size = sizeof(EsString), + .free = es_string_free, + .equal = es_string_equal, + .print = es_string_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = NULL, + .name = "string", +}; + +static EsObjectClass es_cons_class = { + .size = sizeof(EsCons), + .free = es_cons_free, + .equal = es_cons_equal, + .print = es_cons_print, + .flags = 0, + .obarray = NULL, + .name = "cons", +}; + +static EsObjectClass es_regex_class = { + .size = sizeof(EsRegex), + .free = es_regex_free, + .equal = es_regex_equal, + .print = es_regex_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = NULL, + .name = "regex", +}; + +static EsObjectClass es_error_class = { + .size = sizeof(EsError), + .free = es_error_free, + .equal = es_error_equal, + .print = es_error_print, + .flags = ES_OBJECT_FLAG_ATOM, + .obarray = error_obarray, + .name = "error", +}; + + +#define ES_TYPE_CLASS_MAX 32 +static int classes_count = ES_TYPE_FOREIGNER_START; +static EsObjectClass *classes[ES_TYPE_CLASS_MAX] = { + [ES_TYPE_NIL] = &es_nil_class, + [ES_TYPE_INTEGER] = &es_integer_class, + [ES_TYPE_REAL] = &es_real_class, + [ES_TYPE_BOOLEAN] = &es_boolean_class, + [ES_TYPE_SYMBOL] = &es_symbol_class, + [ES_TYPE_STRING] = &es_string_class, + [ES_TYPE_CONS] = &es_cons_class, + [ES_TYPE_REGEX] = &es_regex_class, + [ES_TYPE_ERROR] = &es_error_class, +}; + + + +static MIO *mio_stdout (void) +{ + static MIO *out; + + if (out == NULL) + out = mio_new_fp (stdout, NULL); + + return out; +} + +static MIO *mio_stdin (void) +{ + static MIO *out; + + if (out == NULL) + out = mio_new_fp (stdin, NULL); + + return out; +} + +static MIO *mio_stderr (void) +{ + static MIO *out; + + if (out == NULL) + out = mio_new_fp (stderr, NULL); + + return out; +} + + + +static EsObjectClass* +class_of(const EsObject* object) +{ + return (classes[es_object_get_type(object)]); +} + +static EsObject* +es_object_new(EsType type) +{ + EsObject* r; + + + r = calloc(1, (classes[type])->size); + if (r == NULL) + return ES_ERROR_MEMORY; + r->type = type; + r->ref_count = 1; + + if (es_debug) + mio_printf(mio_stderr(), ";; new{%s}: 0x%p\n", + (classes[type])->name, + r); + + return r; +} + +static void +es_object_free(EsObject* object) +{ + memset(object, 0, class_of(object)->size); + free(object); +} + +static int +es_object_type_p(const EsObject* object, EsType type) +{ + return es_object_get_type(object) == type; +} + +const char* es_type_get_name (int t) +{ + return (classes[t]->name); +} + +EsType +es_object_get_type (const EsObject* object) +{ + return object? object->type: ES_TYPE_NIL; +} + +EsObject* +es_object_ref (EsObject* object) +{ + if (object) + { + if (class_of(object)->obarray) + return object; + + if (es_debug) + mio_printf(mio_stderr(), ";; ref{%s}: [%d]0x%p\n", + class_of(object)->name, + object->ref_count, + object); + object->ref_count++; + } + return object; +} + +void +es_object_unref (EsObject* object) +{ + + if (object) + { + if (class_of(object)->obarray) + return; + + if (object->ref_count == 0) + if ((1 || es_debug)) + { + mio_printf(mio_stderr(), "*** ref_count < 0: 0x%p ***\n", object); + mio_printf(mio_stderr(), "*** BOOSTING while(1). ***\n"); + while (1); + } + + object->ref_count--; + if (es_debug) + mio_printf(mio_stderr(), ";; unref{%s}: [%d]0x%p\n", + class_of(object)->name, + object->ref_count, object); + if (object->ref_count == 0) + { + if (es_debug) + mio_printf(mio_stderr(), ";; free{%s}: 0x%p\n", + class_of(object)->name, + object); + class_of(object)->free(object); + } + } +} + +void +es_object_unref_batch (EsObject* array[], + unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) + { + es_object_unref(array[i]); + array[i] = es_nil; + } +} + +int +es_object_equal (const EsObject* self, + const EsObject* other) +{ + if (self == other) + return 1; + + return class_of(self)->equal(self, other); +} + + +int +es_atom (const EsObject* object) +{ + return class_of(object)->flags & ES_OBJECT_FLAG_ATOM; +} + + +/* + * Nil + */ +int +es_null(const EsObject* object) +{ + return (object == es_nil)? 1: 0; +} + +static void +es_nil_free(EsObject* object) +{ + /* DO NOTHING */ +} + +static int +es_nil_equal(const EsObject* self, const EsObject* other) +{ + return es_null(other); +} + +static void +es_nil_print(const EsObject* object, MIO* fp) +{ + mio_puts(fp, "()"); +} + +/* + * Integer + */ +EsObject* +es_integer_new (int value) +{ + EsObject* r; + + r = es_object_new(ES_TYPE_INTEGER); + ((EsInteger*)r)->value = value; + return r; +} + +int +es_integer_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_INTEGER); +} + +int +es_integer_get (const EsObject* object) +{ + if (es_integer_p(object)) + return ((EsInteger *)object)->value; + else + { + mio_printf(mio_stderr(), ";; es_integer_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return -1; + } +} + +static void +es_integer_free(EsObject* object) +{ + es_object_free(object); +} + +static int +es_integer_equal(const EsObject* self, const EsObject* other) +{ + return ((es_integer_p(other)) + && (es_integer_get(self) == es_integer_get(other)))? 1: 0; +} + +static void +es_integer_print(const EsObject* object, MIO* fp) +{ + mio_printf(fp, "%d", es_integer_get(object)); +} + + +/* + * Real + */ +EsObject* +es_real_new (double value) +{ + EsObject* r; + + r = es_object_new(ES_TYPE_REAL); + ((EsReal*)r)->value = value; + return r; +} + +int +es_real_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_REAL); +} + +double +es_real_get (const EsObject* object) +{ + if (es_real_p(object)) + return ((EsReal *)object)->value; + else + { + mio_printf(mio_stderr(), ";; es_real_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return -1; + } +} + +static void +es_real_free(EsObject* object) +{ + es_object_free(object); +} + +static int +es_real_equal(const EsObject* self, const EsObject* other) +{ + return ((es_real_p(other)) + /* TODO: Too restricted? */ + && (es_real_get(self) == es_real_get(other)))? 1: 0; +} + +static void +es_real_print(const EsObject* object, MIO* fp) +{ + mio_printf(fp, "%f", es_real_get(object)); +} + +/* + * Use Integer as Real + */ +int +es_number_p (const EsObject* object) +{ + return (es_integer_p(object) || es_real_p(object))? 1: 0; +} + +double +es_number_get (const EsObject* object) +{ + double r; + + switch(es_object_get_type(object)) + { + case ES_TYPE_INTEGER: + r = (double)es_integer_get(object); + break; + case ES_TYPE_REAL: + r = es_real_get(object); + break; + default: + mio_printf(mio_stderr(), ";; es_number_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + r = -1.0; + break; + } + return r; +} + + +/* + * Boolean + */ +EsObject* +es_boolean_new (int value) +{ + static EsObject* T; + static EsObject* F; + + if (!T) + { + T = es_object_new(ES_TYPE_BOOLEAN); + ((EsBoolean*)T)->value = 1; + } + if (!F) + { + F = es_object_new(ES_TYPE_BOOLEAN); + ((EsBoolean*)F)->value = 0; + } + + return value? T: F; +} + +int +es_boolean_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_BOOLEAN); +} + +int +es_boolean_get (const EsObject* object) +{ + if (es_boolean_p(object)) + return ((EsBoolean *)object)->value; + else + { + mio_printf(mio_stderr(), ";; es_boolean_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return -1; + } +} + +static void +es_boolean_free(EsObject* object) +{ + /* Do nothing */ +} + +static int +es_boolean_equal(const EsObject* self, const EsObject* other) +{ + return (self == other)? 1: 0; +} + +static void +es_boolean_print(const EsObject* object, MIO* fp) +{ + mio_printf(fp, "#%c", (es_boolean_get(object)? 't': 'f')); +} + +/* + * Singleton + */ +static EsSingleton* +es_obarray_intern(EsType type, const char* name) +{ + unsigned int hv; + EsSingleton** obarray; + EsSingleton* s; + EsSingleton* tmp; + + + obarray = (classes[type])->obarray; + if (!obarray) + return NULL; + + hv = hash(name); + tmp = obarray[hv]; + + s = NULL; + while (tmp) + { + if (!strcmp(tmp->quark, name)) + { + s = tmp; + break; + } + else + tmp = ((EsObject *)tmp)->next; + } + + if (!s) + { + s = (EsSingleton*) es_object_new(type); + s->quark = strdup(name); + tmp = obarray[hv]; + obarray[hv] = s; + ((EsObject *)s)->next = tmp; + } + + return s; + +} + +static const char* +es_singleton_get (EsSingleton *singleton) +{ + return singleton->quark; +} + + +/* + * Symbol + */ +static unsigned char get_char_class(int c); + + +EsObject* +es_symbol_intern (const char* name) +{ + EsSingleton* r; + + r = es_obarray_intern(ES_TYPE_SYMBOL, name); + return (EsObject*)r; +} + +int +es_symbol_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_SYMBOL); +} + +const char* +es_symbol_get (const EsObject* object) +{ + if (es_symbol_p(object)) + return es_singleton_get((EsSingleton*)object); + else + { + mio_printf(mio_stderr(), ";; es_symbol_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return NULL; + } +} + +void* es_symbol_set_data (const EsObject* object, void *data) +{ + if (es_symbol_p(object)) + { + void* old_data; + + old_data = ((EsSymbol*)object)->data; + ((EsSymbol*)object)->data = data; + return old_data; + } + else + { + mio_printf(mio_stderr(), ";; es_symbol_set_data, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return NULL; + } +} + +void* es_symbol_get_data (const EsObject* object) +{ + if (es_symbol_p(object)) + return ((EsSymbol*)object)->data; + else + { + mio_printf(mio_stderr(), ";; es_symbol_get_data, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return NULL; + } +} + +static void +es_symbol_free(EsObject* object) +{ + /* DO NOTHING */ +} + +static int +es_symbol_equal(const EsObject* self, const EsObject* other) +{ + return (self == other)? 1: 0; +} + +static void +es_symbol_print(const EsObject* object, MIO* fp) +{ + const char* string; + size_t len; + char c; + unsigned char cc; + unsigned char mask; + int needs_bar; + int i; + + string = es_symbol_get(object); + if (!string) + return; + + len = strlen(string); + if (len == 0) + needs_bar = 1; + + c = string[0]; + cc = get_char_class(c); + mask = 0x1; + needs_bar = (cc & mask)? 1: 0; + if (!needs_bar) + { + /* 0 => 1? */ + mask = 0x2; + for (i = 0; i< len; i++) + { + c = string[i]; + cc = get_char_class(c); + needs_bar = (cc & mask)? 1: 0; + if (needs_bar) + break; + } + + } + + if (needs_bar) + mio_printf(fp, "|"); + + for (i = 0; i < len; i++) + { + c = string[i]; + if (c == '\\' || c == '|') + mio_printf(fp, "\\"); + mio_printf(fp, "%c", c); + } + + if (needs_bar) + mio_printf(fp, "|"); +} + + +/* + * symbol.c - symbol implementation + * + * Copyright (c) 2000-2007 Shiro Kawai + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the authors nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: symbol.c,v 1.40 2007/09/13 12:30:28 shirok Exp $ + */ +/* table of special chars. + bit 0: bad char for symbol to begin with + bit 1: bad char for symbol to contain + bit 2: bad char for symbol, and should be written as \nnn + bit 3: bad char for symbol, and should be written as \c + bit 4: may be escaped when case fold mode +*/ +static char symbol_special[] = { + /* NUL .... */ + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + /* .... */ + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + /* ! " # $ % & ' ( ) * + , - . / */ + 3, 0, 3, 3, 0, 0, 0, 3, 3, 3, 0, 1, 3, 1, 1, 0, + /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 0, 0, 0, 0, + /* @ A B C D E F G H I J K L M N O */ + 1, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + /* P Q R S T U V W X Y Z [ \ ] ^ _ */ + 16,16,16,16,16,16,16,16,16,16,16,3, 11,3, 0, 0, + /* ` a b c d e f g h i j k l m n o */ + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* p q r s t u v w x y z { | } ~ ^? */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 11,3, 0, 7 +}; + +/* symbol_special[':'] was 1 in the symbol.c of Gauche. + However I modified it to 0. + Because a keyword is a just a symbol started from `:' + in Es. */ +static unsigned char +get_char_class(int c) +{ + return (c < 0)? 0xff: symbol_special[c]; +} + +/* + * String + */ +EsObject* +es_string_new (const char* value) +{ + EsObject* r; + + r = es_object_new(ES_TYPE_STRING); + ((EsString*)r)->value = strdup(value); + return r; +} + +EsObject* +es_string_newL (const char* value, size_t len) +{ + EsObject* r; + + r = es_object_new(ES_TYPE_STRING); + if (es_error_p (r)) + return r; + + void * v = malloc (len + 1); + if (v == NULL) + { + ((EsString*)r)->value = NULL; + es_object_free (r); + return ES_ERROR_MEMORY; + } + memcpy (v, value, len); + ((char *)v)[len] = '\0'; + ((EsString*)r)->value = v; + return r; +} + +int +es_string_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_STRING); +} + +const char* +es_string_get (const EsObject* object) +{ + if (es_string_p(object)) + return ((EsString *)object)->value; + else + { + mio_printf(mio_stderr(), ";; es_string_get, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return NULL; + } +} + +static void +es_string_free(EsObject* object) +{ + if (es_string_p(object)) + { + free(((EsString*) object)->value); + ((EsString*) object)->value = NULL; + es_object_free(object); + } + else + { + mio_printf(mio_stderr(), ";; Internal error: \n"); + mio_printf(mio_stderr(), ";;es_string_free, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + } +} + + +static int +es_string_equal(const EsObject* self, const EsObject* other) +{ + if (es_string_p(other)) + { + return (!strcmp(es_string_get(self), es_string_get(other))); + } + else + return 0; +} + +static void +es_string_print(const EsObject* object, MIO* fp) +{ + const char* string; + char c; + size_t len; + int i; + + + string = es_string_get(object); + len = strlen(string); + + mio_printf(fp, "\""); + + for (i = 0; i < len; i++) + { + char cc; + + c = string[i]; + switch (c) + { + case '\n': + cc = 'n'; + break; + case '\t': + cc = 't'; + break; + case '\r': + cc = 'r'; + break; + case '\f': + cc = 'f'; + break; + default: + cc = 0; + break; + } + if (cc) + { + mio_printf(fp, "\\"); + mio_printf(fp, "%c", cc); + continue; + } + + if (c == '\\' || c == '"') + mio_printf(fp, "\\"); + mio_printf(fp, "%c", c); + } + + mio_printf(fp, "\""); +} + +/* + * Cons + */ +EsObject* +es_cons (EsObject* car, EsObject* cdr) +{ + EsObject* r; + + if (!es_list_p(cdr)) + { + /* This library doesn't permit to dotted list. */ + return es_nil; + } + + + r = es_object_new(ES_TYPE_CONS); + if (es_error_p (r)) + return r; + if (es_debug) + { + mio_printf(mio_stderr(), ";; cons[0x%p] = (0x%p . 0x%p)\n", r, car, cdr); + /* es_print(car, mio_stderr()); + mio_putc('\n', mio_stderr()); + es_print(cdr, mio_stderr()); + mio_putc('\n', mio_stderr()); */ + } + ((EsCons*)r)->car = es_object_ref(car); + ((EsCons*)r)->cdr = es_object_ref(cdr); + + return r; +} + +int +es_cons_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_CONS); +} + +int +es_list_p (const EsObject* object) +{ + EsType t; + + t = es_object_get_type(object); + return (t == ES_TYPE_NIL || t == ES_TYPE_CONS); +} + +EsObject* +es_car (const EsObject* object) +{ + if (es_cons_p(object)) + return ((EsCons*)object)->car; + else if (es_null(object)) + return es_nil; + else + { + mio_printf(mio_stderr(), ";; es_car, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return es_nil; + } +} + +EsObject* +es_cdr (const EsObject* object) +{ + if (es_cons_p(object)) + return ((EsCons*)object)->cdr; + else if (es_null(object)) + return es_nil; + else + { + mio_printf(mio_stderr(), ";; es_cdr, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return es_nil; + } +} + +static void +es_cons_free(EsObject* object) +{ + EsCons* cons; + + if (es_cons_p(object)) + { + cons = ((EsCons*)object); + + es_object_unref(cons->car); + cons->car = NULL; + + es_object_unref(cons->cdr); + cons->cdr = NULL; + es_object_free(object); + } + else if (es_null(object)) + ; /* DO NOTHING */ + else + { + mio_printf(mio_stderr(), ";; Internal error: \n"); + mio_printf(mio_stderr(), ";; es_cons_free, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + } +} + +static int +es_cons_equal(const EsObject* self, const EsObject* other) +{ + return (es_null(other) + || (!es_cons_p(other)) + || (!es_object_equal(es_car(self), es_car(other))) + || (!es_object_equal(es_cdr(self), es_cdr(other)))) + ? 0 + : 1; +} + +static void +es_cons_print(const EsObject* object, MIO* fp) +{ + EsObject* car; + EsObject* cdr; + + mio_printf(fp, "("); + while(!es_null(object)) + { + car = es_car(object); + cdr = es_cdr(object); + + es_print(car, fp); + if (es_cons_p(cdr)) + mio_putc(fp, ' '); + else if (!es_null(cdr)) + { + mio_printf(mio_stderr(), ";; es_cons_print, dotted list given: "); + mio_putc(mio_stderr(), '\n'); + } + object = cdr; + } + mio_printf(fp, ")"); +} + +static EsObject* es_cons_reverse_rec(EsObject* cdr, + EsObject* car, + EsObject* gathered); + +static EsObject* +es_cons_reverse (EsObject* cons) +{ + /* g_return_val_if_fail (es_null(cons) || es_cons_p(cons), es_nil); + g_return_val_if_fail (!es_cproc_dotted_p(cons), es_nil); */ + + if (es_null(cons)) + return es_nil; + else + return es_cons_reverse_rec(es_cdr(cons), + es_car(cons), + es_nil); +} + +EsObject* +es_reverse (EsObject* cons) +{ + return es_cons_reverse(cons); +} + +static EsObject* +es_cons_reverse_rec(EsObject* cdr, + EsObject* car, + EsObject* gathered) +{ + EsObject* cons; + EsObject* o; + + cons = es_cons(car, o = gathered); + es_object_unref(o); + + if (es_null(cdr)) + return cons; + else + return es_cons_reverse_rec(es_cdr(cdr), + es_car(cdr), + cons); +} + +/* + * Regex + */ +EsObject* +es_regex_compile (const char* pattern_literal, int case_insensitive) +{ + EsObject* r; + regex_t *code; + int err; + int flag = REG_EXTENDED | REG_NEWLINE + | (case_insensitive? REG_ICASE: 0); + + code = malloc(sizeof(regex_t)); + if (!code) + return ES_ERROR_MEMORY; + + err = regcomp(code, pattern_literal, + flag); + if (err) + { +#if 0 +/* TODO: This should be reported to caller. */ + char errmsg [256]; + regerror (err, code, errmsg, 256); +#endif + regfree (code); + free (code); + return ES_ERROR_REGEX; + } + + r = es_object_new(ES_TYPE_REGEX); + ((EsRegex*)r)->code = code; + ((EsRegex*)r)->literal = strdup(pattern_literal); + if (!((EsRegex*)r)->literal) + { + regfree(((EsRegex*)r)->code); + free(((EsRegex*)r)->code); + es_object_free(r); + return ES_ERROR_MEMORY; + } + ((EsRegex*)r)->case_insensitive = case_insensitive; + return r; +} + +int +es_regex_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_REGEX); +} + +static void es_regex_free(EsObject* object) +{ + free(((EsRegex*)object)->literal); + regfree(((EsRegex*)object)->code); + free(((EsRegex*)object)->code); + es_object_free(object); +} + +static int +es_regex_equal(const EsObject* self, const EsObject* other) +{ + return (es_regex_p (other) + && (strcmp (((EsRegex*)self)->literal, + ((EsRegex*)other)->literal) == 0) + && (((EsRegex*)self)->case_insensitive == + ((EsRegex*)other)->case_insensitive)); +} + +static void +es_regex_print(const EsObject* object, MIO* fp) +{ + mio_puts(fp, "#/"); + const char *s = ((EsRegex*)object)->literal; + while (*s) + { + if (*s == '/') + mio_putc(fp, '\\'); + mio_putc(fp, *s); + s++; + } + mio_putc(fp, '/'); + if (((EsRegex*)object)->case_insensitive) + mio_putc(fp, 'i'); +} + +EsObject* +es_regex_exec (const EsObject* regex, + const EsObject* str) +{ + return regexec (((EsRegex*)regex)->code, es_string_get (str), + 0, NULL, 0)? es_false: es_true; +} + +/* + * Error + */ +EsObject* +es_error_intern (const char* name) +{ + EsSingleton* r; + + r = es_obarray_intern(ES_TYPE_ERROR, name); + return (EsObject*)r; +} + +int +es_error_p (const EsObject* object) +{ + return es_object_type_p(object, ES_TYPE_ERROR); +} + +const char* +es_error_name (const EsObject* object) +{ + if (es_error_p(object)) + return es_singleton_get((EsSingleton *)object); + else + { + mio_printf(mio_stderr(), ";; es_error_name, Wrong type argument: "); + es_print(object, mio_stderr()); + mio_putc(mio_stderr(), '\n'); + return NULL; + } +} + +EsObject* +es_error_set_object (EsObject* error, EsObject* object) +{ + EsError *e = (EsError *)error; + if (e->object) + es_object_unref (e->object); + + e->object = es_object_ref (object); + return error; +} + +EsObject* +es_error_get_object (const EsObject* error) +{ + EsError *e = (EsError *)error; + return e->object; +} + +static void +es_error_free(EsObject* object) +{ + /* DO NOTHING */ +} + +static int +es_error_equal(const EsObject* self, const EsObject* other) +{ + return (self == other)? 1: 0; +} + +static void +es_error_print(const EsObject* object, MIO* fp) +{ + const char* string; + EsError *e = (EsError *)object; + + string = es_error_name(object); + mio_printf(fp, "#%s:", string); + es_print (e->object, fp); +} + +/* + * Foreigner + */ +typedef struct _EsPointerClass EsPointerClass; +struct _EsPointerClass +{ + EsObjectClass base; + + size_t fat_size; + EsObject *(* init_fat) (void *fat, void * ptr, void *extra); + + void (* free_ptr) (void *); + int (* equal_ptr) (const void*, const void*); + void (* print_ptr) (const void*, MIO *); + + + void (* free_fatptr) (void *, void *); + int (* equal_fatptr) (const void*, const void*, + const void*, const void*); + void (* print_fatptr) (const void*, const void*, MIO *); +}; + +static EsType +es_type_define_pointer_full(const char *name, + size_t fat_size, + EsObject *(* initfat_fn) (void *fat, void * ptr, void *extra), + void (* freefn) (void *), + int (* equalfn) (const void*, const void*), + void (* printfn) (const void*, MIO *), + void (* freefn_fat) (void * ptr, void *fat), + int (* equalfn_fat) (const void* ptr_a, const void* fat_a, + const void* ptr_b, const void* fat_b), + void (* printfn_fat) (const void* ptr, const void *fat, MIO *)) +{ + EsType t = ES_TYPE_NIL; + if (classes_count >= ES_TYPE_CLASS_MAX) + return t; + + EsPointerClass *c = calloc (1, sizeof (EsPointerClass)); + if (c == NULL) + return t; + + c->fat_size = fat_size; + c->init_fat = initfat_fn; + c->free_ptr = freefn; + c->equal_ptr = equalfn; + c->print_ptr = printfn; + c->free_fatptr = freefn_fat; + c->equal_fatptr = equalfn_fat; + c->print_fatptr = printfn_fat; + + c->base.size = sizeof (EsPointer) + c->fat_size; + c->base.free = es_pointer_free; + c->base.equal = es_pointer_equal; + c->base.print = es_pointer_print; + c->base.flags = ES_OBJECT_FLAG_ATOM; + c->base.name = strdup (name); + if (c->base.name == NULL) + { + free (c); + return t; + } + + t = classes_count++; + classes [t] = (EsObjectClass *)c; + + return t; +} + +EsType +es_type_define_pointer(const char *name, + void (* freefn) (void *), + int (* equalfn) (const void*, const void*), + void (* printfn) (const void*, MIO *)) +{ + + return es_type_define_pointer_full (name, 0, NULL, + freefn, equalfn, printfn, + NULL, NULL, NULL); +} + +EsType +es_type_define_fatptr (const char *name, + size_t fat_size, + EsObject *(* initfat_fn) (void *fat, void * ptr, void *extra), + void (* freefn) (void * ptr, void *fat), + int (* equalfn) (const void* ptr_a, const void* fat_a, + const void* ptr_b, const void* fat_b), + void (* printfn) (const void* ptr, const void *fat, MIO *)) +{ + return es_type_define_pointer_full (name, fat_size, initfat_fn, + NULL, NULL, NULL, + freefn, equalfn, printfn); +} + +static void es_pointer_free(EsObject* object) +{ + EsObjectClass *c = class_of(object); + if (((EsPointer*)object)->ptr) + { + if (((EsPointerClass *)c)->free_fatptr) + ((EsPointerClass *)c)->free_fatptr (((EsPointer*)object)->ptr, + ((EsPointer*)object)->fat); + else if (((EsPointerClass *)c)->free_ptr) + ((EsPointerClass *)c)->free_ptr (((EsPointer*)object)->ptr); + } + es_object_free (object); +} + +static int es_pointer_equal(const EsObject* self, const EsObject* other) +{ + if (es_object_get_type (self) != es_object_get_type (other)) + return 0; + + EsPointerClass *c = (EsPointerClass *)class_of(self); + void *self_ptr = ((EsPointer *)self)->ptr; + void *other_ptr = ((EsPointer *)other)->ptr; + + if (c->fat_size == 0 && self_ptr == other_ptr) + return 1; + + if (self_ptr == NULL) + return 0; + + if (c->equal_fatptr) + return c->equal_fatptr (self_ptr, ((EsPointer*)self)->fat, + other_ptr, ((EsPointer*)other)->fat); + else if (c->equal_ptr) + return c->equal_ptr (self_ptr, other_ptr); + return 0; +} + +static void es_pointer_print(const EsObject* object, MIO* fp) +{ + EsObjectClass *c = class_of(object); + if (((EsPointerClass *)c)->print_fatptr) + { + ((EsPointerClass *)c)->print_fatptr (((EsPointer *)object)->ptr, + ((EsPointer *)object)->fat, + fp); + } + else if (((EsPointerClass *)c)->print_ptr) + { + ((EsPointerClass *)c)->print_ptr (((EsPointer *)object)->ptr, fp); + } + else + { + mio_puts(fp, "#<"); + mio_puts(fp, c->name); + mio_putc(fp, ' '); + mio_printf(fp, "(%p, %p)", object, ((EsPointer *)object)->ptr); + mio_putc(fp, '>'); + } +} + +static EsObject* +es_pointer_new_common (EsType type, void *ptr) +{ + EsObject *r; + + r = es_object_new (type); + if (es_error_p (r)) + return r; + + ((EsPointer *)r)->ptr = ptr; + return r; +} + +/* + * Pointer + */ +EsObject* +es_pointer_new (EsType type, void *ptr) +{ + EsObject *r = es_pointer_new_common (type, ptr); + if (es_error_p (r)) + return r; + + if (((EsPointerClass *) (classes [type]))->fat_size > 0) + memset(((EsPointer *)r)->fat, 0, + ((EsPointerClass *) (classes [type]))->fat_size); + return r; +} + +void* +es_pointer_get (const EsObject *object) +{ + return ((EsPointer *)object)->ptr; +} + +void* +es_pointer_take (EsObject *object) +{ + void *r = ((EsPointer *)object)->ptr; + ((EsPointer *)object)->ptr = NULL; + return r; +} + +/* + * Fat pointer + */ +EsObject* +es_fatptr_new (EsType type, void *ptr, void *extra) +{ + EsObject *r = es_pointer_new_common (type, ptr); + if (es_error_p (r)) + return r; + + if (((EsPointerClass *) (classes [type]))->fat_size > 0) + { + if (((EsPointerClass *) (classes [type]))->init_fat) + { + EsObject *f = (* ((EsPointerClass *) (classes [type]))->init_fat) + (((EsPointer *)r)->fat, ptr, extra); + if (es_error_p (f)) + { + es_object_free (r); + return f; + } + } + else if (extra) + memcpy (((EsPointer *)r)->fat, extra, + ((EsPointerClass *) (classes [type]))->fat_size); + else + memset(((EsPointer *)r)->fat, 0, + ((EsPointerClass *) (classes [type]))->fat_size); + } + return r; +} + +void* +es_fatptr_get (const EsObject *object) +{ + EsObjectClass *c = class_of(object); + if (((EsPointerClass *)c)->fat_size == 0) + return NULL; + + return ((EsPointer *)object)->fat; +} + + + +/* http://www.cse.yorku.ca/~oz/hash.html */ +static unsigned long +djb2(unsigned char *str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + return hash; +} + +static unsigned int hash(const char* keyarg) +{ + return ((unsigned int)djb2((unsigned char *)keyarg)) % OBARRAY_SIZE; +} + +/* + * Print + */ +void +es_print (const EsObject* object, + MIO* out) +{ + class_of(object)->print(object, out? out: mio_stdout()); +} + + +char* +es_print_to_string (EsObject* object) +{ + char *bp; + size_t size; + MIO* out; + + + out = mio_new_memory (NULL, 0, realloc, NULL); + if (!out) + { + /* TODO: Report error */ + return NULL; + } + + es_print(object, out); + bp = (char *)mio_memory_get_data (out, &size); + mio_unref(out); + + return bp; +} + +static const char* comment_prefix = ";; "; +void +es_comment (const char* comment, MIO* out) +{ + const char* p; + const char* c; + + p = comment_prefix? comment_prefix: ";; "; + c = comment? comment: ""; + out = out? out: mio_stdout(); + + /* "" + => ;; + + "a" + => ;; a + + "a\n" + => ;; a + + + "a\nb" + => ;; a + ;; b + + "a\nb\n" + => ;; a + ;;b + + + */ + while (1) + { + mio_puts(out, p); + + while(1) + { + if (*c == '\0') + { + mio_putc(out, '\n'); + return; + } + else + { + mio_putc(out, *c++); + if (*(c - 1) == '\n') + break; + } + } + } +} + +char* +es_comment_to_string (const char* comment) +{ + char *bp; + size_t size; + MIO* out; + + out = mio_new_memory (NULL, 0, realloc, NULL); + if (!out) + { + /* TODO: Report error */ + return NULL; + } + + es_comment(comment, out); + bp = (char *)mio_memory_get_data (out, &size); + mio_unref(out); + + return bp; +} + + + + +/* + * Read + */ +typedef struct _Token Token; +struct _Token +{ + char* buffer; + size_t filled; + size_t allocated; +}; +static Token* token_new (char seed); +static void token_free (Token* token); +static Token* token_append(Token* token, char c); + +static Token eof_token; +#define EOF_TOKEN (&eof_token) +static Token open_paren_token; +#define OPEN_PAREN_TOKEN (&open_paren_token) +static Token close_paren_token; +#define CLOSE_PAREN_TOKEN (&close_paren_token) + +static Token* get_token (MIO* in); +static void skip_to_newline(MIO* in); +static int is_whitespace (char c); +static int is_paren_open (char c); +static int is_paren_close (char c); +static int is_comment_start (char c); +static int is_string_start (char c); +static int is_fence_start (char c); +static int is_reader_macro_prefix(char c); + +typedef +int (*TerminalDetector) (int c); + +static int is_string_end (int c); +static int is_fence_end (int c); +static int is_separator (int c); + +static Token* get_sequence (MIO* fp, + Token* seed, + TerminalDetector is_terminator, + int include_terminator); +static Token* get_string (MIO* fp, char seed); +static Token* get_escaped_symbol(MIO* fp, char seed); +static Token* get_symbol (MIO* fp, char seed); +static Token* get_regex (MIO* fp); +static void inject_regex_flag (Token* t, char c); + +static EsObject* fill_list (MIO* fp); +static EsObject* make_atom (Token* token); +static EsObject* make_string (char* t); +static EsObject* make_symbol (char* t, + int is_wrapped); +static EsObject* make_boolean (int b); +static int is_integer (const char* t, + int* i); +static EsObject* make_integer (int i); +static int is_real (const char* t, + double* d); +static EsObject* make_real (double d); +static EsObject* make_regex (const char *pat, + int case_insensitive); + + +EsObject* +es_read (MIO* in) +{ + Token* t; + EsObject* r; + + + in = in? in: mio_stdin(); + + t = get_token(in); + + if (t == NULL) + return ES_READER_ERROR; + else if (t == EOF_TOKEN) + return ES_READER_EOF; + else if (t == OPEN_PAREN_TOKEN) + r = fill_list(in); + else if (t == CLOSE_PAREN_TOKEN) + return ES_READER_ERROR; + else + r = make_atom(t); + + token_free(t); + + return r; +} + + +static Token* +get_token(MIO* in) +{ + Token* t; + + int c; + while (1) + { + c = mio_getc(in); + + if (c == EOF) + { + t = EOF_TOKEN; + break; + } + else + { + char c0; + + c0 = (char)c; + + if (is_whitespace(c0)) + continue; + else if (is_comment_start(c0)) + { + skip_to_newline(in); + /* TODO */ + continue; + } + else if (is_paren_open(c0)) + { + t = OPEN_PAREN_TOKEN; + break; + } + else if (is_paren_close(c0)) + { + t = CLOSE_PAREN_TOKEN; + break; + } + else if (is_string_start(c0)) + { + t = get_string(in, c0); + break; + } + else if (is_fence_start(c0)) + { + t = get_escaped_symbol(in, c0); + break; + } + else if (is_reader_macro_prefix(c0)) + { + c = mio_getc(in); + if (c == EOF) + { + t = get_symbol(in, c0); + break; + } + else if (c == '/') + { + t = get_regex(in); + break; + } + else + { + mio_ungetc (in, c); + t = get_symbol(in, c0); + break; + } + } + else + { + t = get_symbol(in, c0); + break; + } + } + } + + return t; +} + +static int +is_whitespace (char c) +{ + static const char* const whitespace_chars = " \t\n\r\f"; + + return strchr(whitespace_chars, c)? 1: 0; +} + +static int +is_paren_open (char c) +{ + return (c == '(')? 1: 0; +} + +static int +is_paren_close (char c) +{ + return (c == ')')? 1: 0; +} + +static int +is_comment_start (char c) +{ + return (c == ';')? 1: 0; +} + +static int +is_string_start (char c) +{ + return (c == '"')? 1: 0; +} + +static int +is_fence_start (char c) +{ + return (c == '|')? 1: 0; +} + +static int +is_reader_macro_prefix(char c) +{ + return (c == '#')? 1: 0; +} + +static void +skip_to_newline (MIO* fp) +{ + int c; + + + while (1) + { + char c0; + + + c = mio_getc(fp); + if (c == EOF) + break; + + c0 = (char)c; + if (c0 == '\n') + break; + } +} + +static int +is_string_end (int c) +{ + return ((char)(c) == '"')? 1: 0; +} + +static int +is_fence_end (int c) +{ + return ((char)(c) == '|')? 1: 0; +} + +static int +is_separator (int c) +{ + if (c == EOF) + return 1; + else + { + char c0; + + + c0 = (char)(c); + if (is_whitespace(c0) + || is_comment_start(c0) + || is_paren_open(c0) + || is_paren_close(c0) + || is_string_start(c0) + || is_fence_start(c0)) + return 1; + } + + return 0; +} + +static Token* +get_string (MIO* fp, + char seed) +{ + Token* t; + + t = token_new(seed); + return get_sequence(fp, t, is_string_end, 1); +} + +static Token* +get_escaped_symbol (MIO* fp, + char seed) +{ + Token* t; + + t = token_new(seed); + return get_sequence(fp, t, is_fence_end, 1); +} + +static Token* +get_symbol (MIO* fp, + char seed) +{ + Token* t; + + t = token_new(seed); + return get_sequence(fp, t, is_separator, 0); +} + +static Token* +get_regex (MIO* fp) +{ + Token *t; + t = token_new('#'); + if (!t) + return NULL; + + if (!token_append(t, '/')) + return NULL; + + /* Inject a placeholder representing + * case-{in}sesitive. */ + if (!token_append(t, ' ')) + return NULL; + + int c; + int in_escape = 0; + while (1) + { + c = mio_getc(fp); + if (EOF == c) + { + /* TODO: Propagate the error to upper layer. */ + mio_printf(mio_stderr(), + ";; unexpected termination during parsing regex pattern\n"); + token_free (t); + return NULL; + } + + char c0 = c; + if (in_escape) + { + in_escape = 0; + + if (c0 == 'n') + c0 = '\n'; + else if (c0 == 't') + c0 = '\t'; + else if (c0 != '/') + { + if (!token_append(t, '\\')) + return NULL; + } + + if (!token_append(t, c0)) + return NULL; + } + else if (c0 == '\\') + in_escape = 1; + else if (c0 == '/') + { + c = mio_getc(fp); + if (c == 'i') + /* Refill the placeholder. */ + inject_regex_flag (t, 'i'); + else if (c != EOF) + mio_ungetc (fp, c); + break; + } + else + if (!token_append(t, c0)) + return NULL; + } + return t; +} + +static void +dump_token (MIO* stream, const char* prefix, Token* seed) +{ + const char* buf; + int i; + char c; + + + buf = seed->buffer; + mio_printf(stream, "%s", prefix); + for (i = 0; i < ( seed->filled - 1 ); i++) + { + c = buf[i]; + mio_putc(stream, c); + if (buf[i] == '\n') + mio_printf(stream, "%s", prefix); + } + mio_putc(mio_stderr(), '\n'); +} + +static Token* +get_sequence (MIO* fp, + Token* seed, + TerminalDetector is_terminator, + int include_terminator) +{ + int c; + int in_escape; + + in_escape = 0; + while (1) + { + c = mio_getc(fp); + if (EOF == c) + { + if (in_escape) + { + /* + throw ReadError("no character after escape character: " + seed); + */ + mio_printf(mio_stderr(), ";; no character after escape character:\n"); + { + seed = token_append(seed, '\\'); + dump_token(mio_stderr(), "; ", seed); + } + token_free(seed); + return NULL; + } + else if (is_terminator(c)) + break; + else + { + /* + throw ReadError("got EOF during reading a sequence: " + seed); + */ + mio_printf(mio_stderr(), ";; got EOF during reading a sequence: \n"); + dump_token(mio_stderr(), "; ", seed); + token_free(seed); + return NULL; + } + } + else + { + char c0; + + + c0 = (char)(c); + if (in_escape) + { + switch (c0) + { + case 'n': c0 = '\n'; break; + case 't': c0 = '\t'; break; + case 'r': c0 = '\r'; break; + case 'f': c0 = '\f'; break; + default: break; + } + seed = token_append(seed, c0); + in_escape = 0; + continue; + } + else if (c0 == '\\') + { + in_escape = 1; + continue; + } + else if (is_terminator(c)) + { + if (include_terminator) + seed = token_append(seed, c0); + else + { + if (mio_ungetc(fp, c) == EOF) + { + token_free(seed); + return NULL; + } + } + break; + } + else + { + seed = token_append(seed, c0); + in_escape = 0; + continue; + } + } + } + return seed; +} + + +/* + (let ((total-length 0) + (count-symbol 0)) + (mapatoms (lambda (s) (setq total-length (+ total-length (length (symbol-name s))) + count-symbol (+ 1 count-symbol) + ))) + (/ total-length count-symbol)) => 15 +*/ +#define DEFAULT_TOKEN_LENGHT 16 +static Token* +token_new (char seed) +{ + Token *t; + + + t = malloc(sizeof(Token)); + if (!t) + return NULL; + + t->buffer = calloc(1, sizeof(char) * DEFAULT_TOKEN_LENGHT); + if (!t->buffer) + { + free(t); + return NULL; + } + + t->filled = 0; + t->buffer[t->filled++] = seed; + t->buffer[t->filled++] = '\0'; + t->allocated = DEFAULT_TOKEN_LENGHT; + + return t; +} + +static void +token_free (Token* token) +{ + if ((token == NULL) + || (token == EOF_TOKEN) + || (token == OPEN_PAREN_TOKEN) + || (token == CLOSE_PAREN_TOKEN)) + return; + + + free(token->buffer); + token->buffer = NULL; + free(token); +} + +static Token* +token_append(Token* t, char c) +{ + size_t d; + + + d = t->allocated - t->filled; + if (d < 1) + { + char* tmp; + + tmp = t->buffer; + t->buffer = realloc(t->buffer, t->allocated *= 2); + if (!t->buffer) + { + t->buffer = tmp; + token_free(t); + return NULL; + } + } + + t->buffer[t->filled - 1] = c; + t->buffer[t->filled++] = '\0'; + + return t; +} + +/* We use the third character of buffer + * as a flag representing an option for + * regex pattern. + * + * 'i': case_insensitive + */ +static void +inject_regex_flag(Token* t, char c) +{ + t->buffer [2] = c; +} + +static EsObject* +fill_list (MIO* fp) +{ + EsObject* r; + Token* t; + + r = es_nil; + while(1) + { + t = get_token(fp); + if (t == NULL) + { + es_object_unref(r); + return ES_READER_ERROR; + } + else if (t == EOF_TOKEN) + { + es_object_unref(r); + return ES_READER_ERROR; + } + else if (t == CLOSE_PAREN_TOKEN) + { + EsObject* tmp; + + tmp = es_cons_reverse(r); + es_object_unref(r); + r = tmp; + break; + } + else if (t == OPEN_PAREN_TOKEN) + { + EsObject* car; + EsObject* cdr; + + car = fill_list(fp); + if (es_error_p(car)) + { + es_object_unref(r); + r = car; + break; + } + + cdr = r; + r = es_cons(car, cdr); + es_object_unref(car); + es_object_unref(cdr); + + continue; + } + else + { + EsObject* car; + EsObject* cdr; + + car = make_atom(t); + token_free(t); + + if (es_error_p (car)) + { + es_object_unref(r); + r = car; + break; + } + + cdr = r; + r = es_cons(car, cdr); + es_object_unref(car); + es_object_unref(cdr); + + continue; + } + } + + return r; +} + + +static EsObject* +make_atom (Token* token) +{ + EsObject* r; + char* t; + + int i; + double d; + + + t = token->buffer; + + if (t[0] == '"') + r = make_string(t); + else if (t[0] == '|') + r = make_symbol(t, 1); + else if (strcmp(t, "#t") == 0) + r = make_boolean(1); + else if (strcmp(t, "#f") == 0) + r = make_boolean(0); + else if ((strncmp(t, "#/", 2) == 0) + && t[2] != '\0') + r = make_regex (t + 3, (t[2] == 'i')); + else if (is_integer(t, &i)) + { + r = make_integer(i); + } + else if (is_real(t, &d)) + { + r = make_real(d); + } + else + r = make_symbol(t, 0); + + return r; +} + +static EsObject* +make_string (char* t) +{ + size_t len; + + + len = strlen(t); + t[(len - 1)] = '\0'; + return es_string_new(t + 1); +} + +static EsObject* +make_symbol (char* t, + int is_wrapped) +{ + if (is_wrapped) + { + size_t len; + + len = strlen(t); + t[(len - 1)] = '\0'; + t = t + 1; + } + + return es_symbol_intern(t); +} + + +static EsObject* +make_boolean (int b) +{ + return es_boolean_new(b); +} + +static int +is_integer (const char* cstr, + int* i) +{ + char* endptr; + long r; + + endptr = NULL; + errno = 0; + r = strtol(cstr, &endptr, 10); + + if (errno || (endptr == cstr)) + return 0; + else if (*endptr != '\0') + return 0; + + if ((r > INT_MAX) || r < INT_MIN) + { + /* TODO: What I should do? + TODO: Set error */ + /* + throw ReadError("Too large integer for `int': " + r); + */ + mio_printf(mio_stderr(), ";; is_integer, Integer out of range: %s\n", cstr); + return 0; + } + + *i = r; + return 1; +} + +static EsObject* +make_integer (int i) +{ + return es_integer_new(i); +} + +static int +is_real (const char* cstr, + double* d) +{ + char* endptr; + + endptr = NULL; + errno = 0; + *d = strtod(cstr, &endptr); + + if (errno || (endptr == cstr)) + return 0; + else if (*endptr != '\0') + return 0; + + /* TODO: INF, NAN... */ + return 1; +} + +static EsObject* +make_real (double d) +{ + return es_real_new(d); +} + +static EsObject* +make_regex (const char *pat, + int case_insensitive) +{ + return es_regex_compile(pat, case_insensitive); +} + +EsObject* +es_read_from_string(const char* buf, + const char** saveptr) +{ + MIO* in; + EsObject* o; + + + /* IN is opend in "r" mode and the stream pointed by + IN is short-lived here. */ + in = mio_new_memory((void *)buf, strlen(buf), NULL, NULL); + o = es_read(in); + if (saveptr) + *saveptr = buf + mio_tell(in); + mio_unref(in); + + return o; +} + + + +typedef struct _EsAutounrefPool EsAutounrefPool; +typedef struct _EsChain EsChain; + +struct _EsChain +{ + EsObject* object; + EsChain* next; +}; + +struct _EsAutounrefPool +{ + EsAutounrefPool * parent_pool; + EsChain* chain; +}; + +static EsAutounrefPool * currrent_pool; + +static EsAutounrefPool* es_autounref_pool_new(void); +static void es_autounref_pool_free(EsAutounrefPool* pool); +static EsChain* es_chain_new(EsObject* object); +static void es_chain_free(EsChain* chain); + + +void +es_autounref_pool_push(void) +{ + EsAutounrefPool* r; + + r = es_autounref_pool_new(); + r->parent_pool = currrent_pool; + currrent_pool = r; +} + +void +es_autounref_pool_pop (void) +{ + EsAutounrefPool *tmp; + + tmp = currrent_pool; + currrent_pool = tmp->parent_pool; + + es_autounref_pool_free(tmp); +} + +static void +es_autounref_pool_free(EsAutounrefPool* pool) +{ + pool->parent_pool = NULL; + es_chain_free(pool->chain); + pool->chain = NULL; + + free(pool); +} + +EsObject* +es_object_autounref (EsObject* object) +{ + EsChain* r; + + r = es_chain_new(object); + r->next = currrent_pool->chain; + currrent_pool->chain = r; + + return object; +} + +static EsAutounrefPool* +es_autounref_pool_new(void) +{ + EsAutounrefPool* r; + + r = calloc(1, sizeof(EsAutounrefPool)); + return r; +} + +static EsChain* +es_chain_new(EsObject *object) +{ + EsChain* r; + + r = calloc(1, sizeof(EsChain)); + r->object = object; + return r; +} + +static void +es_chain_free(EsChain *chain) +{ + EsChain *tmp; + + while(chain) + { + tmp = chain; + chain = chain->next; + + es_object_unref(tmp->object); + tmp->object = NULL; + tmp->next = NULL; + free(tmp); + } +} + + +#include +static EsObject* es_list_va(EsObject* object, va_list *ap); + +EsObject* +es_list(EsObject* object,...) +{ + EsObject* r; + va_list ap; + + va_start(ap, object); + r = es_list_va(object, &ap); + va_end(ap); + + return r; +} + +static EsObject* +es_list_va(EsObject* object, va_list *ap) +{ + EsObject* r; + EsObject* p; + EsObject* tmp; + + r = es_nil; + p = object; + es_autounref_pool_push(); + do { + if (p == ES_READER_EOF) + break; + + r = es_cons((p), es_object_autounref(r)); + p = va_arg(*ap, EsObject *); + } while(1); + es_autounref_pool_pop(); + + tmp = r; + r = es_cons_reverse(r); + es_object_unref(tmp); + + return r; +} + + +static EsObject* es_append0(EsObject* tail, EsObject* body); +static EsObject* es_append1(EsObject* tail, EsObject* body0); + +EsObject* +es_append(EsObject* list,...) +{ + EsObject *r; + EsObject *tmp; + EsObject *tail; + EsObject *body; + va_list ap; + + + va_start(ap, list); + r = es_list_va(list, &ap); + va_end(ap); + + tmp = r; + r = es_cons_reverse(r); + es_object_unref(tmp); + + /* r */ + tail = es_car(r); + body = es_cdr(r); + tmp = r; + r = es_append0(tail, body); + es_object_unref(tmp); + + return r; +} + +static EsObject* +es_append0(EsObject* tail, EsObject* body) +{ + if (es_null(body)) + return tail; + else + { + EsObject* car; + + car = es_cons_reverse(es_car(body)); + tail = es_append1(tail, car); + es_object_unref(car); + body = es_cdr(body); + return es_append0(tail, body); + } +} + +static EsObject* +es_append1(EsObject* tail, EsObject* body0) +{ + if (es_null(body0)) + return es_object_ref(tail); + else + { + EsObject* car; + EsObject* r; + + car = es_car(body0); + tail = es_cons(car, tail); + + r = es_append1(tail, es_cdr(body0)); + es_object_unref(tail); + return r; + } +} + + + +static EsObject* pattern_d = NULL; +static EsObject* pattern_f = NULL; +static EsObject* pattern_F = NULL; +static EsObject* pattern_s = NULL; +static EsObject* pattern_S = NULL; +static EsObject* pattern_b = NULL; +static EsObject* pattern_rest = NULL; +static EsObject* pattern_unquote = NULL; +static EsObject* pattern_splice = NULL; + +static EsObject* pattern_i_d = NULL; +static EsObject* pattern_i_f = NULL; +static EsObject* pattern_i_F = NULL; +static EsObject* pattern_i_s = NULL; +static EsObject* pattern_i_S = NULL; +static EsObject* pattern_i_b = NULL; +static EsObject* pattern_i_rest = NULL; +static EsObject* pattern_i_unquote = NULL; + +static void +pattern_init(void) +{ + if (!pattern_d) (pattern_d = es_symbol_intern("%d")); + if (!pattern_f) (pattern_f = es_symbol_intern("%f")); + if (!pattern_F) (pattern_F = es_symbol_intern("%F")); + if (!pattern_s) (pattern_s = es_symbol_intern("%s")); + if (!pattern_S) (pattern_S = es_symbol_intern("%S")); + if (!pattern_b) (pattern_b = es_symbol_intern("%b")); + if (!pattern_rest) (pattern_rest = es_symbol_intern("%@")); + if (!pattern_unquote) (pattern_unquote = es_symbol_intern("%,")); + if (!pattern_splice) (pattern_splice = es_symbol_intern("%,@")); + + if (!pattern_i_d) (pattern_i_d = es_symbol_intern("%_d")); + if (!pattern_i_f) (pattern_i_f = es_symbol_intern("%_f")); + if (!pattern_i_F) (pattern_i_F = es_symbol_intern("%_F")); + if (!pattern_i_s) (pattern_i_s = es_symbol_intern("%_s")); + if (!pattern_i_S) (pattern_i_S = es_symbol_intern("%_S")); + if (!pattern_i_b) (pattern_i_b = es_symbol_intern("%_b")); + if (!pattern_i_rest) (pattern_i_rest = es_symbol_intern("%_@")); + if (!pattern_i_unquote) (pattern_i_unquote = es_symbol_intern("%_,")); +} + +static EsObject* +es_vrealize_atom(EsObject* fmt_object, va_list *ap) +{ + if (fmt_object == pattern_d) + return es_integer_new(va_arg(*ap, int)); + else if (fmt_object == pattern_f) + { + double x = va_arg(*ap, double); + mio_printf(mio_stderr(), "=>%f\n", x); + return es_real_new(x); + } + else if (fmt_object == pattern_s) + return es_string_new(va_arg(*ap, char *)); + else if (fmt_object == pattern_S) + return es_symbol_intern(va_arg(*ap, char *)); + else if (fmt_object == pattern_b) + return es_boolean_new(va_arg(*ap, int)); + else if ((fmt_object == pattern_unquote) + || (fmt_object == pattern_splice)) + return es_object_ref(va_arg(*ap, EsObject*)); + else + return es_object_ref(fmt_object); +} + +static EsObject* +es_vrealize(EsObject* fmt_object, va_list *ap) +{ + pattern_init(); + + if (es_cons_p(fmt_object)) + { + EsObject* car; + EsObject* cdr; + EsObject* kar; + EsObject* kdr; + EsObject* r; + + car = es_car(fmt_object); + + if (car == pattern_rest) + r = es_object_ref(va_arg(*ap, EsObject*)); + else + { + cdr = es_cdr(fmt_object); + + kar = es_vrealize(car, ap); + kdr = es_vrealize(cdr, ap); + + if (car == pattern_splice) + { + if (es_cons_p(kar)) + r = es_append(kar, kdr, ES_READER_EOF); + else + { + /* TODO: error */ + char *fmt; + + mio_printf(mio_stderr(), + ";; an atom is passed for splice format:\n"); + fmt = es_print_to_string(fmt_object); + mio_printf(mio_stderr(), ";; => %s\n", fmt); + free(fmt); + r = es_nil; + } + } + else + r = es_cons(kar, kdr); + + es_object_unref(kar); + es_object_unref(kdr); + } + return r; + } + else + return es_vrealize_atom(fmt_object, ap); +} + +EsObject* +es_realize (EsObject* fmt_object,...) +{ + EsObject* object; + va_list ap; + + if (es_error_p(fmt_object)) + return es_object_ref(fmt_object); + + va_start(ap, fmt_object); + object = es_vrealize(fmt_object, &ap); + va_end(ap); + + return object; +} + +EsObject* +es_srealize (const char* fmt,...) +{ + EsObject* fmt_object; + EsObject* object; + va_list ap; + + fmt_object = es_read_from_string(fmt, NULL); + if (es_error_p(fmt_object)) + return fmt_object; + + va_start(ap, fmt); + object = es_vrealize(fmt_object, &ap); + va_end(ap); + + es_object_unref(fmt_object); + + return object; +} + +EsObject* es_map (EsObject * (*fn) (EsObject *, void *), + EsObject *list, void *user_data) +{ + if (es_null (list)) + return list; + + EsObject *c = es_car (list); + c = fn (c, user_data); + if (es_error_p (c)) + return c; + + EsObject *r = es_map (fn, es_cdr (list), user_data); + if (es_error_p (r)) + { + es_object_unref (c); + return r; + } + + EsObject *o = es_cons (c, r); + es_object_unref (r); + es_object_unref (c); + + return o; +} + +EsObject* es_foreach (EsObject * (*fn) (EsObject *, void *), + EsObject *list, void *user_data) +{ + if (es_null (list)) + return es_false; + + for (EsObject *c = list; !es_null (c); c = es_cdr (c)) + { + EsObject *r = fn (es_car (c), user_data); + if (!es_object_equal (r, es_false)) + return r; + } + + return es_false; +} + +EsObject* es_fold (EsObject * (*kons) (EsObject *, EsObject *, void *), + EsObject * knil, EsObject * list, void *user_data) +{ + EsObject *r = knil; + + es_autounref_pool_push(); + while (!es_null (list)) + { + EsObject *e = es_car (list); + list = es_cdr (list); + + r = (* kons) (e, (r == knil) ? r : es_object_autounref (r), + user_data); + if (es_error_p (r)) + break; + } + es_autounref_pool_pop(); + + return r; +} + +static EsObject* +es_vmatch_atom_input(EsObject* input, EsObject* fmt_object, va_list *ap) +{ + return ES_READER_ERROR; +} + +static EsObject* +es_vmatch_atom_fmt(EsObject* input, EsObject* fmt_object, va_list *ap) +{ + if (fmt_object == pattern_unquote) + *(va_arg(*ap, EsObject**)) = /* es_object_ref */(input); + else if (fmt_object == pattern_i_unquote) + ; + else + return ES_READER_ERROR; + + return fmt_object; +} + +static EsObject* +es_vmatch_atom(EsObject* input, EsObject* fmt_object, va_list *ap) +{ + if (fmt_object == pattern_d) + { + if (es_integer_p(input)) + *(va_arg(*ap, int*)) = es_integer_get(input); + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_d) + { + if (es_integer_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_f) + { + if (es_real_p(input)) + *(va_arg(*ap, double*)) = es_real_get(input); + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_f) + { + if (es_real_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_F) + { + if (es_integer_p(input)) + { + int i; + + i = es_integer_get(input); + *(va_arg(*ap, double*)) = (double)i; + } + else if (es_real_p(input)) + { + *(va_arg(*ap, double*)) = es_real_get(input); + } + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_F) + { + if (es_integer_p(input) || es_real_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_s) + { + if (es_string_p(input)) + *(va_arg(*ap, const char**)) = /* strdup */(es_string_get(input)); + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_s) + { + if (es_string_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_S) + { + if (es_symbol_p(input)) + *(va_arg(*ap, const char**)) = /* strdup */(es_symbol_get(input)); + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_S) + { + if (es_symbol_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_b) + { + if (es_boolean_p(input)) + *(va_arg(*ap, int*)) = es_boolean_get(input); + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_i_b) + { + if (es_boolean_p(input)) + ; + else + return ES_READER_ERROR; + } + else if (fmt_object == pattern_unquote) + *(va_arg(*ap, EsObject**)) = /* es_object_ref */(input); + else if (fmt_object == pattern_i_unquote) + ; + else if (es_object_equal(fmt_object, input)) + ; + else + return ES_READER_ERROR; + + return fmt_object; +} + +static void +recover(EsObject* fmt_object, va_list *aq) +{ + if (es_cons_p(fmt_object)) + { + recover(es_car(fmt_object), aq); + recover(es_cdr(fmt_object), aq); + } + else + { + if (fmt_object == pattern_s + || fmt_object == pattern_S) + { + char **s; + + s = va_arg(*aq, char **); + (void)/* free */(*s); + + *s = NULL; + } + else if (fmt_object == pattern_rest + || fmt_object == pattern_unquote) + { + EsObject** o; + + o = va_arg(*aq, EsObject**); + (void)/* es_object_unref */(*o); + *o = NULL; + } + } +} + +static EsObject* +es_vmatch(EsObject* input, EsObject* fmt_object, va_list *ap) +{ + pattern_init(); + + if (es_cons_p(fmt_object) && es_cons_p(input)) + { + EsObject* fmt_car; + EsObject* fmt_cdr; + EsObject* i_car; + EsObject* i_cdr; + + EsObject* r_car; + EsObject* r_cdr; + + va_list aq; + + fmt_car = es_car(fmt_object); + + if (fmt_car == pattern_rest) + { + *(va_arg(*ap, EsObject**)) = /* es_object_ref */(input); + return fmt_car; + } + else if (fmt_car == pattern_i_rest) + { + return fmt_car; + } + + fmt_cdr = es_cdr(fmt_object); + + i_car = es_car(input); + i_cdr = es_cdr(input); + + va_copy(aq, *ap); + r_car = es_vmatch(i_car, fmt_car, ap); + if (es_error_p(r_car)) + { + va_end(aq); + return r_car; + } + + r_cdr = es_vmatch(i_cdr, fmt_cdr, ap); + if (es_error_p(r_cdr)) + { + recover(fmt_car, &aq); + va_end(aq); + return r_cdr; + } + va_end(aq); + return r_cdr; + } + else if (es_cons_p(fmt_object)) + { + return es_vmatch_atom_input(input, fmt_object, ap); + } + else if (es_cons_p(input)) + { + if (fmt_object == pattern_rest) + { + *(va_arg(*ap, EsObject**)) = /* es_object_ref */(input); + return fmt_object; + } + else if (fmt_object == pattern_i_rest) + return fmt_object; + else + return es_vmatch_atom_fmt(input, fmt_object, ap); + } + else + { + return es_vmatch_atom(input, fmt_object, ap); + } +} + +int +es_match(EsObject* input, EsObject* fmt_object,...) +{ + EsObject* object; + va_list ap; + + va_start(ap, fmt_object); + object = es_vmatch(input, fmt_object, &ap); + va_end(ap); + + return !(es_error_p(object)); +} + +int +es_smatch (EsObject* input, const char* fmt,...) +{ + int r; + EsObject* object; + EsObject* fmt_object; + va_list ap; + + fmt_object = es_read_from_string(fmt, NULL); + if (es_error_p(fmt_object)) + return 0; + + va_start(ap, fmt); + object = es_vmatch(input, fmt_object, &ap); + va_end(ap); + + r = !(es_error_p(object)); + es_object_unref(fmt_object); + + return r; +} + +EsObject* +es_pget (EsObject* plist, EsObject* key, EsObject* default_value) +{ + if (es_cons_p(plist)) + { + EsObject* car; + EsObject* cdr; + EsObject* cadr; + EsObject* cddr; + + car = es_car(plist); + cdr = es_cdr(plist); + + if (es_cons_p(cdr)) + { + cadr = es_car(cdr); + cddr = es_cdr(cdr); + + if (es_object_equal(car, key)) + return cadr; + else + return es_pget(cddr, key, default_value); + } + else + return ES_READER_ERROR; + } + else + return default_value; +} diff --git a/ctags/dsl/es.h b/ctags/dsl/es.h new file mode 100644 index 0000000000..79bfe2af93 --- /dev/null +++ b/ctags/dsl/es.h @@ -0,0 +1,274 @@ +/* + Copyright (c) 2009 Masatake YAMATO + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ + +#ifndef __ES_LANG_C_STDC99_H__ +#define __ES_LANG_C_STDC99_H__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + +#include "mio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum _EsType { + ES_TYPE_NIL, + ES_TYPE_INTEGER, + ES_TYPE_REAL, + ES_TYPE_BOOLEAN, + ES_TYPE_SYMBOL, + ES_TYPE_STRING, + ES_TYPE_CONS, + ES_TYPE_REGEX, + /* ... */ + ES_TYPE_ERROR, + /* ... */ + ES_TYPE_BUILTIN_LAST = ES_TYPE_ERROR, + ES_TYPE_FOREIGNER_START, +}; +typedef enum _EsType EsType; + +struct _EsObject; +typedef struct _EsObject EsObject; + +const char* es_type_get_name (int t); + +EsType es_object_get_type (const EsObject* object); + + +EsObject* es_object_ref (EsObject* object); +void es_object_unref (EsObject* object); +void es_object_unref_batch (EsObject* array[], + unsigned int count); +EsObject* es_object_autounref (EsObject* object); + +int es_object_equal (const EsObject* self, + const EsObject* other); + +int es_atom (const EsObject* object); + +#define ES_ERROR_MEMORY es_error_intern("MEMORY-EXHAUSTED") +#define ES_ERROR_REGEX es_error_intern("WRONG-REGEX-SYNTAX") + +/* + * Nil + */ +#define es_nil ((EsObject*)0) +int es_null (const EsObject* object); + +/* + * Integer + */ +EsObject* es_integer_new (int value); +int es_integer_p (const EsObject* object); +int es_integer_get (const EsObject* object); + + +/* + * Real + */ +EsObject* es_real_new (double value); +int es_real_p (const EsObject* object); +double es_real_get (const EsObject* object); + + +/* + * Use Integer as Real + */ +int es_number_p (const EsObject* object); +double es_number_get (const EsObject* object); + + +/* + * Boolean + */ +#define es_true (es_boolean_new(1)) +#define es_false (es_boolean_new(0)) +EsObject* es_boolean_new (int value); +int es_boolean_p (const EsObject* object); +int es_boolean_get (const EsObject* object); + +/* + * String + */ +EsObject* es_string_new (const char* value); +EsObject* es_string_newL (const char* value, size_t length); +int es_string_p (const EsObject* object); +const char* es_string_get (const EsObject* object); + + +/* + * Symbol + */ +EsObject* es_symbol_intern (const char* name); +int es_symbol_p (const EsObject* object); +const char* es_symbol_get (const EsObject* object); + +void* es_symbol_set_data (const EsObject* object, void *data); +void* es_symbol_get_data (const EsObject* object); + +/* + * Error + */ + +EsObject* es_error_intern (const char* name); +int es_error_p (const EsObject* object); +const char* es_error_name (const EsObject* object); +EsObject* es_error_set_object (EsObject* error, EsObject* object); +EsObject* es_error_get_object (const EsObject* error); + + +/* + * Cons + */ +EsObject* es_cons (EsObject* car, EsObject* cdr); +int es_cons_p (const EsObject* object); +int es_list_p (const EsObject* object); +EsObject* es_car (const EsObject* object); +EsObject* es_cdr (const EsObject* object); + + +/* + * Regex + */ +EsObject* es_regex_compile (const char* pat, + int case_insensitive); +int es_regex_p (const EsObject* object); +EsObject* es_regex_exec (const EsObject* regex, + const EsObject* str); + +/* + * Foreign pointer + */ +EsType es_type_define_pointer (const char *name, + void (* freefn) (void *), + int (* equalfn) (const void*, const void*), + void (* printfn) (const void*, MIO *)); + +/* If the type has sized fat area, the area is filled with zero. */ +EsObject* es_pointer_new (EsType type, void *ptr); +void* es_pointer_get (const EsObject *object); +void* es_pointer_take (EsObject *object); + +/* + * Fatptr: Foreign pointer with extra data + * + * init_fat () returns es_true if the initialization ends successfully. + * In failure case, init_fat () returns an error object. + */ +EsType es_type_define_fatptr (const char *name, + size_t fat_size, + EsObject *(* initfat_fn) (void *fat, void * ptr, void *extra), + void (* freefn) (void * ptr, void *fat), + int (* equalfn) (const void* ptr_a, const void* fat_a, + const void* ptr_b, const void* fat_b), + void (* printfn) (const void* ptr, const void *fat, MIO *)); +/* If initfat_fn is given in the type, the new fat area will is + * initialized with the method. + * If initfat_fn is not given, and extra is not NULL, the contents + * pointed by extra is copied to the fat area. + * If initfat_fn is not given, and extra is NULL, the fat area + * is filled with zero as es_pointer_new does. */ +EsObject* es_fatptr_new (EsType type, void *ptr, void *extra); +/* Access to the fat area. Use es_pointer_get to access the pointer. */ +void* es_fatptr_get (const EsObject *object); + +/* + * Print + */ +void es_print (const EsObject* object, + MIO* out); +char* es_print_to_string (EsObject* object); + +/* + * Read + */ +EsObject* es_read (MIO* in); +EsObject* es_read_from_string(const char* in, + const char** saveptr); + +#define ES_READER_ERROR es_error_intern("READ-ERROR") +#define ES_READER_EOF es_error_intern("EOF") + +/* + * Comment + */ +void es_comment (const char* comment, + MIO* out); +char* es_comment_to_string (const char* comment); + +/* + * Autounref pool + */ +void es_autounref_pool_push(void); +void es_autounref_pool_pop (void); + + + +/* + * List builders + */ +EsObject* es_list (EsObject* object,...); +EsObject* es_append (EsObject* list,...); +EsObject* es_reverse (EsObject* cons); + +#define ES_PROC_UNIMPLEMENTED es_error_intern("PROC-UNIMPLEMENTED") +EsObject* es_realize (EsObject* fmt_object,...); +EsObject* es_srealize (const char* fmt,...); + +/* The value returned from FN treated as if it is returned from + * a *_new function. es_map may call es_object_unref() for the value. + * The value returned from es_map should be treated as if it is + * returned from a *_new function. The caller must free the returned + * value. + */ +EsObject* es_map (EsObject * (*fn) (EsObject *, void *), + EsObject *list, void *user_data); + +/* Unlike es_map, the value returnd from FN is not accumulated. + * If FN returns a value other than #f, es_foreach stops the + * iteration immediately and returns the value. + */ +EsObject* es_foreach (EsObject * (*fn) (EsObject *, void *), + EsObject *list, void *user_data); + +/* The memory management of es_map is also applicable to es_fold. */ +EsObject* es_fold (EsObject * (*kons) (EsObject *, EsObject *, void *), + EsObject * knil, EsObject * list, void *user_data); + +/* + * Rich element accessors + */ +int es_match (EsObject* input, EsObject* fmt_object,...); +int es_smatch (EsObject* input, const char* fmt,...); + + +EsObject* es_pget (EsObject* plist, EsObject* key, EsObject* default_value); + +#ifdef __cplusplus +} +#endif + +#endif /* Not def: __ES_LANG_C_STDC_H__ */ diff --git a/ctags/dsl/optscript.c b/ctags/dsl/optscript.c new file mode 100644 index 0000000000..f37e128c46 --- /dev/null +++ b/ctags/dsl/optscript.c @@ -0,0 +1,4422 @@ +// for x in $(grep ^declop ~/var/ctags/dsl/optscript.c | sed -e 's/declop(\([^)]*\));/\1/'); do grep -q $x Tmain/optscript.d/*.ps || echo $x; done +/* + * Copyright (c) 2020, Masatake YAMATO + * Copyright (c) 2020, 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. + */ + + +#include "general.h" + +#include "debug.h" +#include "es.h" +#include "htable.h" +#include "optscript.h" +#include "ptrarray.h" +#include "routines.h" +#include "vstring.h" + +#include +#include + + +struct sOptVM +{ + ptrArray *ostack; + ptrArray *dstack; + ptrArray *estack; + + int dstack_protection; + MIO *in; + MIO *out; + MIO *err; + + EsObject *error; + + int print_depth; + int read_depth; + char *prompt; + void *app_data; +}; + +typedef struct sOperatorFat +{ + EsObject *name; + int arity; + const char *help_str; +} OperatorFat; + +typedef struct sOperatorExtra +{ + const char *name; + int arity; + const char *help_str; +} OperatorExtra; + +typedef OptOperatorFn Operator; + +typedef enum eAttr { + ATTR_READABLE = 1 << 0, + ATTR_WRITABLE = 1 << 1, + ATTR_EXECUTABLE = 1 << 2, +} Attr; + +typedef struct sDictFat +{ + unsigned int attr; +} DictFat; + +typedef struct sArrayFat +{ + unsigned int attr; +} ArrayFat; + +typedef struct sStringFat +{ + unsigned int attr; +} StringFat; + +typedef struct sNameFat +{ + unsigned int attr; +} NameFat; + +static EsObject* opt_system_dict; + +int OPT_TYPE_ARRAY; +int OPT_TYPE_DICT; +int OPT_TYPE_OPERATOR; +int OPT_TYPE_STRING; +int OPT_TYPE_NAME; +int OPT_TYPE_MARK; + +static EsObject *OPT_ERR_UNDEFINED; +static EsObject *OPT_ERR_SYNTAX; +EsObject *OPT_ERR_UNDERFLOW; +EsObject *OPT_ERR_TYPECHECK; +EsObject *OPT_ERR_RANGECHECK; +static EsObject *OPT_ERR_DICTSTACKUNDERFLOW; +static EsObject *OPT_ERR_UNMATCHEDMARK; +static EsObject *OPT_ERR_INTERNALERROR; +static EsObject *OPT_ERR_END_PROC; +static EsObject *OPT_ERR_INVALIDEXIT; +static EsObject *OPT_ERR_STOPPED; +EsObject *OPT_ERR_QUIT; +static EsObject *OPT_ERR_INVALIDACCESS; +static EsObject *OPT_ERR_INTOVERFLOW; + +static EsObject* OPT_MARK_ARRAY; +static EsObject* OPT_MARK_DICT; +static EsObject* OPT_MARK_MARK; + +static EsObject* OPT_KEY_newerror; +static EsObject* OPT_KEY_errorname; +static EsObject* OPT_KEY_command; +static EsObject* OPT_KEY_ostack; +static EsObject* OPT_KEY_estack; +static EsObject* OPT_KEY_dstack; + +/* Naming conversions + * + * Opt|OPT + * ===================================================================== + * exported as part of the library API + * + * optscript and ctags may refer these names. + * + * + * _... + * ===================================================================== + * functions released to PS datatypes + * PS datatypes are array, dict, operator, ... + * + * _es_... + * --------------------------------------------------------------------- + * functions for representing the PS datatype object as EsObject object + * + * _op_... + * --------------------------------------------------------------------- + * functions for accessing the datatype object from vm internal purpose + * + * + * op_... + * ===================================================================== + * functions implementing operators + * + * + * vm_... + * ===================================================================== + * the rest VM related functions + * + */ + +static EsObject* array_new (unsigned int attr); + +static EsObject* array_es_init_fat (void *fat, void *ptr, void *extra); +static void array_es_free (void *ptr, void *fat); +static int array_es_equal (const void *a, + const void *afat, + const void *b, + const void *bfat); +static void array_es_print (const void *ptr, const void *fat, MIO *out); + +static void array_op_add (EsObject* array, EsObject* elt); +static unsigned int array_op_length (const EsObject* array); +static EsObject* array_op_get (const EsObject* array, unsigned int n); +static void array_op_put (EsObject* array, unsigned int n, EsObject *obj); + + +static EsObject* dict_new (unsigned int size, unsigned int attr); + +static EsObject* dict_es_init_fat (void *fat, void *ptr, void *extra); +static void dict_es_free (void *ptr, void *fat); +static int dict_es_equal (const void *a, + const void *afat, + const void *b, + const void *bfat); +static void dict_es_print (const void *ptr, const void *fat, MIO *out); + + +static void dict_op_def (EsObject* dict, EsObject *key, EsObject *val); +static bool dict_op_undef (EsObject* dict, EsObject *key); +static bool dict_op_known_and_get (EsObject* dict, EsObject *key, EsObject **val); +static void dict_op_clear (EsObject* dict); + + +static EsObject* operator_new (Operator op, const char *name, int arity, const char *help_str); + +static EsObject* operator_es_init_fat (void *fat, void *ptr, void *extra); +static void operator_es_print (const void *ptr, const void *fat, MIO *out); +static void operator_es_free (void *ptr, void *fat); + + +static EsObject* string_new (vString *vstr); + +static EsObject* string_es_init_fat (void *fat, void *ptr, void *extra); +static void string_es_free (void *ptr, void *fat); +static int string_es_equal (const void *a, + const void *afat, + const void *b, + const void *bfat); +static void string_es_print (const void *ptr, const void *fat, MIO *out); + + +static EsObject* name_new (EsObject* symbol, unsigned int attr); +static EsObject* name_newS (const char*s, unsigned int attr); +static EsObject* name_newS_cb (const char*s, void *attr); + +static EsObject* name_es_init_fat (void *fat, void *ptr, void *extra); +static void name_es_print (const void *ptr, const void *fat, MIO *out); +static void name_es_free (void *ptr, void *fat); +static int name_es_equal (const void *a, + const void *afat, + const void *b, + const void *bfat); + + +static EsObject* mark_new (const char* mark); + +static void mark_es_print (const void *ptr, MIO *out); +static void mark_es_free (void *ptr); +static int mark_es_equal (const void *a, const void *b); + +static EsObject* vm_read (OptVM *vm); +static EsObject* vm_call_operator (OptVM *vm, EsObject *op); +static EsObject* vm_call_proc (OptVM *vm, EsObject *proc); +static void vm_print (OptVM *vm, EsObject *o); +static void vm_print_full (OptVM *vm, EsObject *o, bool string_as_is, int dict_recursion); +static void vm_help (OptVM *vm, MIO *out, struct OptHelpExtender *extop, void *data); +static void vm_record_stop (OptVM *vm, EsObject *cmd); +static void vm_record_error (OptVM *vm, EsObject *e, EsObject *cmd); +static void vm_report_error (OptVM *vm, EsObject *e); +static void vm_bind_proc (OptVM *vm, ptrArray *proc); + +static void vm_ostack_push (OptVM *vm, EsObject *o); +static EsObject* vm_ostack_pop (OptVM *vm); +static unsigned int vm_ostack_count (OptVM *vm); +static EsObject* vm_ostack_top (OptVM *vm); +static EsObject* vm_ostack_peek (OptVM *vm, int index_from_top); +static int vm_ostack_counttomark (OptVM *vm); + +static void vm_dict_def (OptVM *vm, EsObject *key, EsObject *val); + +/* Returns the dictionary where the value for the key is found. + * val can be NULL. */ +static EsObject* vm_dstack_known_and_get (OptVM *vm, EsObject *key, EsObject **val); +static void vm_dstack_push (OptVM *vm, EsObject *o); +/* FIXME: return type */ +static int vm_dstack_count (OptVM *vm); +static EsObject* vm_dstack_pop (OptVM *vm); +static void vm_dstack_clear (OptVM *vm); + +static EsObject* vm_estack_push (OptVM *vm, EsObject *p); +static EsObject* vm_estack_pop (OptVM *vm); + +#define declop(OP) \ + static EsObject* op_##OP(OptVM *vm, EsObject *name) + + +#define defOP(DICT, FN, NAME, ARITY, HELP) \ + dict_op_def (DICT, \ + es_object_autounref(es_symbol_intern (NAME)), \ + es_object_autounref(operator_new (FN, NAME, ARITY, HELP))) + +#define defop(DICT, NAME, ARITY, HELP) \ + defOP (DICT, op_##NAME, #NAME, ARITY, HELP) + +static EsObject* op__print_objdict_rec (OptVM *vm, EsObject *name); +static EsObject* op__print_objdict (OptVM *vm, EsObject *name); +static EsObject* op__print_object (OptVM *vm, EsObject *name); +static EsObject* op__print (OptVM *vm, EsObject *name); +static EsObject* op__make_array (OptVM *vm, EsObject *name); +static EsObject* op__make_dict (OptVM *vm, EsObject *name); + +/* non-standard operator */ +declop(_help); + +/* tested in pstack.ps */ +declop(pstack); + +/* error related non-standard operators */ +declop(_newerror); +declop(_errorname); + +/* Operators for operand stack manipulation + * tested in stack.ps */ +declop(pop); +declop(exch); +declop(dup); +declop(index); +declop(roll); +declop(clear); +declop(count); +declop(mark); +declop(cleartomark); +declop(counttomark); + +/* Arithmetic Operators + tested in arithmetic.ps */ +declop(add); +declop(idiv); +declop(mod); +declop(mul); +declop(sub); +declop(abs); +declop(neg); + +/* Operators for array manipulation + tested in array.ps */ +declop(array); +declop(astore); +declop(aload); + +/* Operators for dictionary manipulation + * tested in dict.ps */ +declop(dict); +declop(begin); +declop(end); +declop(def); +declop(load); +declop(undef); +declop(known); +declop(where); +declop(currentdict); +declop(countdictstack); +declop(store); +declop(dictstack); +declop(cleardictstack); + +/* Operators for string manipulation + tested in string.ps */ +/* -anchorsearch, -search, -token */ +declop(string); +declop(_strstr); +declop(_strrstr); +declop(_strchr); +declop(_strrchr); +declop(_strpbrk); + +/* Relation, logical, and bit operators + tested in relalogbit.ps */ +declop(eq); +declop(ne); +declop(true); +declop(false); +declop(and); +declop(or); +declop(xor); +declop(not); +declop(bitshift); +declop(ge); +declop(gt); +declop(le); +declop(lt); + +/* Operators for control flow + * tested in control.ps */ +declop(exec); +declop(if); +declop(ifelse); +declop(repeat); +declop(loop); +declop(exit); +declop(stop); +declop(stopped); +declop(for); +declop(quit); +declop(countexecstack); +declop(execstack); +/* ?start */ + +/* Operators for type, attribute and their conversion + * tested in typeattrconv.ps */ +declop(type); +declop(cvn); +/* cvlit, cvx, xcheck, executeonly, noacess, readonly, + rcheck, wcheck, cvi, cvr, cvrs, cvs,... */ + +/* Operators for Virtual Memory Operators */ +/* ?save, ?restore */ + +/* Misc operators + * tested in misc.ps */ +declop(null); +declop(bind); + +/* Methods for compound objects + tested in compound.ps */ +declop(length); +declop(copy); +declop(get); +declop(put); +declop(forall); +declop(putinterval); +declop(_copyinterval); +/* -getinterval .... */ + + +/* + * Public functions + */ + +int +opt_init (void) +{ + OPT_TYPE_ARRAY = es_type_define_fatptr ("arraytype", + sizeof (ArrayFat), + array_es_init_fat, + array_es_free, + array_es_equal, + array_es_print); + OPT_TYPE_DICT = es_type_define_fatptr ("dicttype", + sizeof (DictFat), + dict_es_init_fat, + dict_es_free, + dict_es_equal, + dict_es_print); + OPT_TYPE_OPERATOR = es_type_define_fatptr ("operatortype", + sizeof (OperatorFat), + operator_es_init_fat, + operator_es_free, + NULL, + operator_es_print); + OPT_TYPE_STRING = es_type_define_fatptr ("stringtype", + sizeof (StringFat), + string_es_init_fat, + string_es_free, + string_es_equal, + string_es_print); + OPT_TYPE_NAME = es_type_define_fatptr ("nametype", + sizeof (NameFat), + name_es_init_fat, + name_es_free, + name_es_equal, + name_es_print); + OPT_TYPE_MARK = es_type_define_pointer ("marktype", + mark_es_free, + mark_es_equal, + mark_es_print); + + OPT_ERR_UNDEFINED = es_error_intern ("undefined"); + OPT_ERR_SYNTAX = es_error_intern ("syntaxerror"); + OPT_ERR_UNDERFLOW = es_error_intern ("stackunderflow"); + OPT_ERR_TYPECHECK = es_error_intern ("typecheck"); + OPT_ERR_RANGECHECK = es_error_intern ("rangecheck"); + OPT_ERR_DICTSTACKUNDERFLOW = es_error_intern ("dictstackunderflow"); + OPT_ERR_UNMATCHEDMARK = es_error_intern ("unmatchedmark"); + OPT_ERR_INTERNALERROR = es_error_intern ("internalerror"); + OPT_ERR_END_PROC = es_error_intern ("}"); + OPT_ERR_INVALIDEXIT = es_error_intern ("invalidexit"); + OPT_ERR_STOPPED = es_error_intern ("stopped"); + OPT_ERR_QUIT = es_error_intern ("quit"); + OPT_ERR_INVALIDACCESS = es_error_intern ("invalidaccess"); + OPT_ERR_INTOVERFLOW = es_error_intern ("intoverflow"); + + es_symbol_intern ("true"); + es_symbol_intern ("false"); + es_symbol_intern ("null"); + + OPT_MARK_ARRAY = mark_new ("["); + OPT_MARK_DICT = mark_new ("<<"); + OPT_MARK_MARK = mark_new ("mark"); + + opt_system_dict = dict_new (101, ATTR_READABLE); + + es_autounref_pool_push (); + + defOP (opt_system_dict, op__print_objdict_rec,"====", 1, "any === -"); + defOP (opt_system_dict, op__print_objdict, "===", 1, "any === -"); + defOP (opt_system_dict, op__print_object, "==", 1, "any == -"); + defOP (opt_system_dict, op__print, "=", 1, "any == -"); + + 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, _help, 0, "- _HELP -"); + defop (opt_system_dict, pstack, 0, "|- any1 ... anyn PSTACK |- any1 ... anyn"); + + defop (opt_system_dict, _newerror, 0, "- _NEWERROR bool"); + defop (opt_system_dict, _errorname, 0, "- _ERRORNAME error:name|null"); + + defop (opt_system_dict, pop, 1, "any POP -"); + defop (opt_system_dict, exch, 2, "any1 any2 EXCH any2 any1"); + defop (opt_system_dict, dup, 1, "any DUP any any"); + defop (opt_system_dict, index, 1, "anyn ... any0 n INDEX anyn ... any0 anyn"); + defop (opt_system_dict, roll, 2, "any_n-1 ... any0 n j ROLL any_(j-1)_mod_n ... any_n-1 ... any_j_mod_n"); + defop (opt_system_dict, clear, 0, "|- any1 ... anyn CLEAR |-"); + defop (opt_system_dict, count, 0, "|- any1 ... anyn COUNT any1 ... anyn n"); + defop (opt_system_dict, mark, 0, "- MARK mark"); + defop (opt_system_dict, cleartomark, 1, "mark any1 ... anyn CLEARTOMARK -"); + defop (opt_system_dict, counttomark, 1, "mark any1 ... anyn COUNTTOMARK mark any1 ... anyn n"); + + defop (opt_system_dict, add, 2, "int1 int2 ADD int"); + defop (opt_system_dict, idiv, 2, "int1 int2 IDIV int"); + defop (opt_system_dict, mod, 2, "int1 int1 MOD int"); + defop (opt_system_dict, mul, 2, "int1 int2 MUL int"); + defop (opt_system_dict, sub, 2, "int1 int2 SUB int"); + defop (opt_system_dict, abs, 1, "int1 ABS int2"); + defop (opt_system_dict, neg, 1, "int1 NEG int2"); + + defop (opt_system_dict, array, 1, "int ARRAY array"); + defop (opt_system_dict, astore, 1, "any0 ... any_n_1 array ASTORE array"); + defop (opt_system_dict, aload, 1, "array ALOAD any0 ... any_n-1 array"); + + defop (opt_system_dict, eq, 2, "any1 any2 EQ bool"); + defop (opt_system_dict, ne, 2, "any1 any2 NE bool"); + defop (opt_system_dict, true, 0, "- TRUE true"); + defop (opt_system_dict, false, 0, "- FALSE false"); + defop (opt_system_dict, ge, 2, "int1 int2 GE bool%" + "string1 string2 GE bool"); + defop (opt_system_dict, gt, 2, "int1 int2 GT bool%" + "string1 string2 GT bool"); + defop (opt_system_dict, le, 2, "int1 int2 LE bool%" + "string1 string2 LE bool"); + defop (opt_system_dict, lt, 2, "int1 int2 LT bool%" + "string1 string2 LT bool"); + defop (opt_system_dict, and, 2, "bool1 bool2 AND bool3%" + "int1 int2 AND int3"); + defop (opt_system_dict, or, 2, "bool1 bool2 OR bool3%" + "int1 int2 OR int3"); + defop (opt_system_dict, xor, 2, "bool1 bool2 XOR bool3%" + "int1 int2 XOR int3"); + defop (opt_system_dict, not, 1, "bool1|int1 NOT bool2|int2"); + defop (opt_system_dict, bitshift, 2, "int1 shift BITSHIFT int2"); + + defop (opt_system_dict, dict, 1, "int DICT dict"); + defop (opt_system_dict, begin, 1, "dict BEGIN -"); + defop (opt_system_dict, end, 0, "- END -"); + defop (opt_system_dict, def, 2, "key value DEF -"); + defop (opt_system_dict, load, 1, "key LOAD value"); + defop (opt_system_dict, undef, 2, "dict key UNDEF -"); + defop (opt_system_dict, known, 2, "dict key KNOWN bool"); + defop (opt_system_dict, where, 1, "key WHERE dict true%key WHERE false"); + defop (opt_system_dict, store, 2, "key value STORE -"); + defop (opt_system_dict, currentdict, 0, "- CURRENTDICT dict"); + defop (opt_system_dict, countdictstack, 0, "- COUNTDICTSTACK int"); + defop (opt_system_dict, dictstack, 1, "array DICTSTACK array"); + defop (opt_system_dict, cleardictstack, 0, "- CLEARDICTSTACK -"); + + defop (opt_system_dict, string, 1, "int STRING -"); + defop (opt_system_dict, _strstr, 2, "string seek _STRSTR string offset true%" + "string seek _STRSTR string false"); + defop (opt_system_dict, _strrstr, 2, "string seek _STRRSTR string offset true%" + "string seek _STRRSTR string false"); + defop (opt_system_dict, _strchr, 2, "string chr _STRCHR string offset true%" + "string chr _STRCHR string false"); + defop (opt_system_dict, _strrchr, 2, "string chr _STRRCHR string offset true%" + "string chr _STRRCHR string false"); + defop (opt_system_dict, _strpbrk, 2, "string accept _STRPBRK string offset true%" + "string accept _STRPBRK string false"); + + defop (opt_system_dict, exec, 1, "any EXEC -"); + defop (opt_system_dict, if, 2, "bool proc IF -"); + defop (opt_system_dict, ifelse, 3, "bool proc_t proc_f IFELSE -"); + defop (opt_system_dict, repeat, 2, "int proc REPEAT -"); + defop (opt_system_dict, loop, 1, "proc LOOP -"); + defop (opt_system_dict, exit, 0, "- EXIT -"); + defop (opt_system_dict, stop, 0, "- STOP -"); + defop (opt_system_dict, stopped, 1, "any STOPPED bool"); + defop (opt_system_dict, for, 4, "initial increment limit proc FOR -"); + defop (opt_system_dict, quit, 0, "- quit -"); + defop (opt_system_dict, countexecstack, 0, "- countexecstack int"); + defop (opt_system_dict, execstack, 1, "array EXECSTACK array"); + + defop (opt_system_dict, type, 1, "any TYPE name"); + defop (opt_system_dict, cvn, 1, "string CVN name"); + + defop (opt_system_dict, null, 0, "- NULL null"); + defop (opt_system_dict, bind, 1, "proc BIND proc"); + + defop (opt_system_dict, copy, 1, "any1 ... anyn n COPY any1 ... anyn any1 ... anyn%" + "array1 array2 COPY array2%" + "dict1 dict2 COPY dict2%" + "string1 string2 COPY string2"); + defop (opt_system_dict, length, 1, "array LENGTH int%" + "dict LENGTH int%" + "string LENGTH int"); + defop (opt_system_dict, get, 2, "array index GET any%" + "dict key GET any%" + "string int GET int"); + defop (opt_system_dict, put, 3, "array index any PUT -%" + "dict key any PUT -%" + "string index int PUT -"); + defop (opt_system_dict, forall, 2, "array proc FORALL -%" + "dict proc FORALL -%" + "string proc FORALL -"); + defop (opt_system_dict, putinterval, 3, "array1 index array2 PUTINTERVAL -%" + "string1 index string2 PUTINTERVAL -"); + defop (opt_system_dict, _copyinterval, 4, "array1 index count array2 _COPYINTERVAL array2%" + "string1 index count string2 _COPYINTERVAL string2"); + +#define defKey(S) OPT_KEY_##S = es_symbol_intern(#S) + defKey(newerror); + defKey(errorname); + defKey(command); + defKey(ostack); + defKey(estack); + defKey(dstack); + + es_autounref_pool_pop (); + + return 0; +} + +OptVM * +opt_vm_new (MIO *in, MIO *out, MIO *err) +{ + OptVM *vm = xCalloc (1, OptVM); + + vm->in = mio_ref (in); + vm->out = mio_ref (out); + vm->err = mio_ref (err); + + EsObject *tmp; + + tmp = array_new (0); + vm->ostack = (ptrArray *)es_pointer_take (tmp); + es_object_unref (tmp); + + tmp = array_new (0); + vm->dstack = (ptrArray *)es_pointer_take (tmp); + es_object_unref (tmp); + + tmp = array_new (0); + vm->estack = (ptrArray *)es_pointer_take (tmp); + es_object_unref (tmp); + + vm->dstack_protection = 0; + vm_dstack_push (vm, opt_system_dict); + vm->dstack_protection++; + + vm->error = dict_new (6, ATTR_READABLE|ATTR_WRITABLE); + + vm->print_depth = 0; + vm->read_depth = 0; + vm->prompt = NULL; + + return vm; +} + +void +opt_vm_clear (OptVM *vm) +{ + ptrArrayClear (vm->estack); + ptrArrayClear (vm->ostack); + vm_dstack_clear (vm); + vm->app_data = NULL; + dict_op_clear (vm->error); +} + +void +opt_vm_delete (OptVM *vm) +{ + ptrArrayDelete (vm->estack); + ptrArrayDelete (vm->dstack); + ptrArrayDelete (vm->ostack); + es_object_unref (vm->error); + + mio_unref (vm->err); + mio_unref (vm->out); + mio_unref (vm->in); + eFree (vm); +} + +EsObject * +opt_dict_new (unsigned int size) +{ + return dict_new (size, ATTR_READABLE|ATTR_WRITABLE); +} + +bool +opt_dict_known_and_get_cstr (EsObject *dict, const char* name, EsObject **val) +{ + if (es_object_get_type (dict) != OPT_TYPE_DICT) + return false; + + EsObject *sym = es_symbol_intern (name); + return dict_op_known_and_get (dict, sym, val); +} + +bool +opt_dict_foreach (EsObject *dict, bool (* fn) (EsObject *, EsObject *, void*), void *data) +{ + if (es_object_get_type (dict) != OPT_TYPE_DICT) + return false; + + hashTable *htable = es_pointer_get (dict); + return hashTableForeachItem (htable, (hashTableForeachFunc) fn, data); +} + +void +opt_dict_def (EsObject *dict, EsObject *sym, EsObject *val) +{ + Assert (!es_null(sym)); + dict_op_def (dict, sym, val); +} + +bool +opt_dict_undef (EsObject *dict, EsObject *sym) +{ + Assert (!es_null(sym)); + return dict_op_undef (dict, sym); +} + +void +opt_dict_clear (EsObject *dict) +{ + Assert (es_object_get_type (dict) == OPT_TYPE_DICT); + dict_op_clear (dict); +} + +EsObject * +opt_array_new (void) +{ + return array_new (ATTR_READABLE | ATTR_WRITABLE); +} + +EsObject * +opt_array_get (const EsObject *array, unsigned int index) +{ + return array_op_get (array, index); +} + +void +opt_array_put (EsObject *array, unsigned int index, EsObject *obj) +{ + array_op_put (array, index, obj); +} + +void +opt_array_add (EsObject *array, EsObject* elt) +{ + array_op_add (array, elt); +} + +unsigned int +opt_array_length(const EsObject *array) +{ + return array_op_length (array); +} + +void +opt_vm_dstack_push (OptVM *vm, EsObject *dict) +{ + vm_dstack_push (vm, dict); + vm->dstack_protection++; +} + +void +opt_vm_dstack_pop (OptVM *vm) +{ + vm->dstack_protection--; + vm_dstack_pop (vm); +} + +EsObject* +opt_vm_ostack_top (OptVM *vm) +{ + return vm_ostack_top (vm); +} + +EsObject* +opt_vm_ostack_peek (OptVM *vm, int index_from_top) +{ + return vm_ostack_peek (vm, index_from_top); +} + +EsObject* +opt_vm_ostack_pop (OptVM *vm) +{ + return vm_ostack_pop (vm); +} + +void +opt_vm_ostack_push (OptVM *vm, EsObject *obj) +{ + vm_ostack_push (vm, obj); +} + +unsigned int +opt_vm_ostack_count (OptVM *vm) +{ + return vm_ostack_count (vm); +} + +static EsObject* +vm_eval (OptVM *vm, EsObject * o) +{ + EsObject *r = es_false; + + if (es_error_p (o)) + { + r = o; + goto out; + } + else if (es_object_get_type (o) == OPT_TYPE_NAME) + { + unsigned int attr = ((NameFat *)es_fatptr_get (o))->attr; + if (attr & ATTR_EXECUTABLE) + { + EsObject *sym = es_pointer_get (o); + EsObject *val = es_nil; + EsObject *dict = vm_dstack_known_and_get (vm, sym, &val); + + if (es_object_get_type (dict) == OPT_TYPE_DICT) + { + int t = es_object_get_type (val); + if (t == OPT_TYPE_OPERATOR) + r = vm_call_operator (vm, val); + else if (t == OPT_TYPE_ARRAY + && (((ArrayFat *)es_fatptr_get (val))->attr & ATTR_EXECUTABLE)) + r = vm_call_proc (vm, val); + else + { + vm_ostack_push (vm, val); + r = es_false; + } + + if (es_error_p (r)) + goto out; + } + else + { + r = es_error_set_object (OPT_ERR_UNDEFINED, o); + vm_record_error (vm, r, o); /* TODO */ + goto out; + } + } + else + vm_ostack_push (vm, o); + } + else if (es_object_get_type (o) == OPT_TYPE_OPERATOR) + { + r = vm_call_operator (vm, o); + goto out; + } + else + vm_ostack_push (vm, o); + out: + return r; +} + +EsObject* +opt_vm_read (OptVM *vm, MIO *in) +{ + EsObject *e; + MIO *tmp; + if (in) + { + tmp = vm->in; + vm->in = in; + } + e = vm_read (vm); + if (in) + vm->in = tmp; + return e; +} + +EsObject * +opt_vm_eval (OptVM *vm, EsObject *obj) +{ + return vm_eval (vm, obj); +} + +void +opt_vm_report_error (OptVM *vm, EsObject *eobj, MIO *err) +{ + MIO *tmp; + + if (err) + { + tmp = vm->err; + vm->err = err; + } + vm_report_error (vm, eobj); + if (err) + vm->err = tmp; +} + +char* +opt_vm_set_prompt (OptVM *vm, char *prompt) +{ + char *tmp = vm->prompt; + vm->prompt = prompt; + return tmp; +} + +void +opt_vm_print_prompt (OptVM *vm) +{ + if (vm->prompt && vm->read_depth == 0) + { + mio_puts (vm->err, vm->prompt); + unsigned int c = ptrArrayCount (vm->ostack); + + if (c > 0) + mio_printf (vm->err, "<%u> ", c); + else + mio_printf (vm->err, "> "); + } +} + +void* +opt_vm_get_app_data (OptVM *vm) +{ + return vm->app_data; +} + +void* +opt_vm_set_app_data (OptVM *vm, void *app_data) +{ + void *tmp = vm->app_data; + vm->app_data = app_data; + return tmp; +} + +int +opt_vm_help (OptVM *vm, MIO *out, struct OptHelpExtender *extop, void *data) +{ + vm_help (vm, out? out: vm->out, extop, data); + return 0; +} + +EsObject * +opt_operator_new (OptOperatorFn op, const char *name, int arity, const char *help_str) +{ + return operator_new (op, name, arity, help_str); +} + +EsObject *opt_string_new_from_cstr (const char *cstr) +{ + vString *vstr = vStringNewInit (cstr? cstr: ""); + return string_new (vstr); +} + +const char* opt_string_get_cstr (const EsObject *str) +{ + vString *vstr = es_pointer_get (str); + return vStringValue (vstr); +} + +EsObject *opt_name_new_from_cstr (const char *cstr) +{ + return name_newS (cstr, ATTR_READABLE); +} + +const char* opt_name_get_cstr (const EsObject *name) +{ + if (es_object_get_type (name) == OPT_TYPE_NAME) + name = es_pointer_get (name); + if (!es_symbol_p (name)) + return NULL; + return es_symbol_get (name); +} + + +/* + * VM + */ +static void +vm_read_skip_comment(OptVM *vm) +{ + while (true) + { + int c = mio_getc (vm->in); + if (c == EOF || c == '\n' || c == '\r') + { + if (c != EOF) + opt_vm_print_prompt (vm); + return; + } + } +} + +#define is_meta_char(c) ((c) == '%' \ + || (c) == '/' \ + || (c) == '(' \ + || (c) == '{' \ + || (c) == '}' \ + || (c) == '[' \ + || (c) == ']' \ + || (c) == '<' \ + || (c) == '>') + +static EsObject* +vm_read_char (OptVM *vm) +{ + int c = mio_getc (vm->in); + int i; + + if (c == EOF) + return OPT_ERR_SYNTAX; + else if (c == '\\') + { + c = mio_getc (vm->in); + int i; + switch (c) + { + case 't': + i = '\t'; + break; + case 'n': + i = '\n'; + break; + case 'f': + i = '\f'; + break; + case 'r': + i = '\r'; + break; + case 'v': + i = '\v'; + break; + case ' ': + case '_': + i = ' '; + break; + case '\\': + i = '\\'; + break; + default: + return OPT_ERR_SYNTAX; + } + c = mio_getc (vm->in); + if (!(c == EOF || isspace (c) || is_meta_char (c))) + return OPT_ERR_SYNTAX; + mio_ungetc (vm->in, c); + return es_integer_new (i); + } + else if (isgraph(c)) + { + i = c; + + c = mio_getc (vm->in); + if (!(c == EOF || isspace (c) || is_meta_char (c))) + return OPT_ERR_SYNTAX; + mio_ungetc (vm->in, c); + + return es_integer_new (i); + } + else + return OPT_ERR_SYNTAX; +} + +static EsObject* +vm_read_string (OptVM *vm) +{ + int depth = 0; + vString *s = vStringNew (); + while (true) + { + int c = mio_getc (vm->in); + if (c == ')') + { + if (depth == 0) + return string_new (s); + vStringPut (s, c); + depth--; + } + else if (c == '(') + { + vStringPut (s, c); + depth++; + } + else if (c == '\\') + { + c = mio_getc (vm->in); + switch (c) + { + case EOF: + vStringDelete (s); + return OPT_ERR_SYNTAX; + case 'n': + vStringPut (s, '\n'); + break; + case 't': + vStringPut (s, '\t'); + break; + case 'r': + vStringPut (s, '\r'); + break; + case 'f': + vStringPut (s, '\f'); + break; + case 'v': + vStringPut (s, '\v'); + break; + case '\\': + case '(': + case ')': + vStringPut (s, c); + break; + default: + vStringPut (s, c); + break; + ; + } + } + else if (c == EOF) + { + vStringDelete (s); + return OPT_ERR_SYNTAX; + } + else + vStringPut (s, c); + } +} + +static EsObject* +vm_read_generic(OptVM *vm, int c, + EsObject * (* make_object) (const char *, void *), + void *data) +{ + vString *name = vStringNew (); + vStringPut (name, c); + + while (1) + { + c = mio_getc (vm->in); + if (c == EOF) + break; + else if (isspace (c) || is_meta_char (c)) + { + mio_ungetc (vm->in, c); + break; + } + else + vStringPut (name, c); + } + EsObject *n = make_object (vStringValue (name), data); + vStringDelete (name); + return n; +} + +static EsObject* +vm_read_name (OptVM *vm, int c, unsigned int attr) +{ + return vm_read_generic (vm, c, name_newS_cb, &attr); +} + +struct name_or_number_data { + unsigned int attr; + bool negative; +}; + +static EsObject* +name_or_number_new (const char* s, void *data) +{ + struct name_or_number_data *d = data; + + bool number = true; + const char *t = s; + while (*t) + { + if (!isdigit ((int)*t)) + { + number = false; + break; + } + t++; + } + if (number) + { + int n; + if (strToInt (s, 10, &n)) + return es_integer_new (n * ((d->negative)? -1: 1)); + else + return OPT_ERR_INTOVERFLOW; + } + else + return name_newS_cb (s, &d->attr); +} + +static EsObject* +vm_read_name_or_number (OptVM *vm, int c, unsigned int attr, bool negative) +{ + struct name_or_number_data data = { + .attr = attr, + .negative = negative, + }; + + return vm_read_generic (vm, c, name_or_number_new, &data); +} + +static EsObject* +vm_read_quoted (OptVM *vm) +{ + bool immediate = false; + + int c = mio_getc (vm->in); + switch (c) + { + case '/': + immediate = true; + c = mio_getc (vm->in); + break; + default: + break; + } + + EsObject *s = vm_read_name (vm, c, ATTR_READABLE); + if (immediate) + { + EsObject *q; + + EsObject *val = es_nil; + EsObject *dict = vm_dstack_known_and_get (vm, s, &val); + if (es_object_get_type (dict) == OPT_TYPE_DICT) + q = es_object_ref (val); + else + { + q = es_error_set_object (OPT_ERR_UNDEFINED, s); + vm_record_error (vm, q, s); /* TODO */ + } + es_object_unref (s); + return q; + } + else + return s; +} + +static EsObject* +vm_read_proc (OptVM *vm) +{ + EsObject *proc = array_new (ATTR_EXECUTABLE|ATTR_READABLE); + + vm->read_depth++; + while (true) + { + EsObject *o = vm_read (vm); + if (es_object_equal (o, OPT_ERR_END_PROC)) + { + break; + } + else if (es_error_p (o)) + { + es_object_unref (proc); + proc = o; + break; + } + else + { + array_op_add (proc, o); + es_object_unref (o); + } + } + vm->read_depth--; + return proc; +} + +static EsObject* +vm_read (OptVM *vm) +{ + while (true) + { + int c = mio_getc (vm->in); + if (c == EOF) + return es_object_ref (ES_READER_EOF); + else if (c == '\n' || c == '\r') + { + opt_vm_print_prompt (vm); + continue; + } + else if (isspace (c)) + continue; + else if (c == '%') + { + vm_read_skip_comment (vm); + continue; + } + else if (isdigit (c)) + { + return vm_read_name_or_number (vm, c, ATTR_EXECUTABLE|ATTR_READABLE, + false); + } + else if (c == '-' || c == '+') + { + bool negative = (c == '-'); + c = mio_getc (vm->in); + if (isdigit (c)) + return vm_read_name_or_number (vm, c, ATTR_EXECUTABLE|ATTR_READABLE, + negative); + else + { + mio_ungetc (vm->in, c); + return vm_read_name_or_number (vm, '-', ATTR_EXECUTABLE|ATTR_READABLE, + false); + } + } + else if (c == '/') + return vm_read_quoted (vm); + else if (c == '(') + return vm_read_string (vm); + else if (c == '{') + return vm_read_proc (vm); + else if (c == '}') + { + if (vm->read_depth) + return OPT_ERR_END_PROC; + else + return OPT_ERR_SYNTAX; + } + else if (c == '[' || c == ']') + { + const char name[2] = { [0] = c, [1] = '\0' }; + EsObject *s = es_symbol_intern (name); + EsObject *n = name_new (s, ATTR_EXECUTABLE|ATTR_READABLE); + return n; + } + else if (c == '<' || c == '>') + { + int c0 = mio_getc (vm->in); + if (c != c0) + return OPT_ERR_SYNTAX; + + const char name [3] = { [0] = c, [1] = c, [2] = '\0' }; + EsObject *s = es_symbol_intern (name); + EsObject *n = name_new (s, ATTR_EXECUTABLE|ATTR_READABLE); + return n; + } + else if (c == '?') + return vm_read_char (vm); + else + return vm_read_name (vm, c, ATTR_EXECUTABLE|ATTR_READABLE); + } +} + +static void +vm_ostack_push (OptVM *vm, EsObject *o) +{ + ptrArrayAdd (vm->ostack, es_object_ref (o)); +} + +static EsObject* +vm_ostack_pop (OptVM *vm) +{ + unsigned int c = vm_ostack_count (vm); + + if (c > 0) + { + ptrArrayDeleteLast (vm->ostack); + return es_false; + } + + return OPT_ERR_UNDERFLOW; +} + +static unsigned int +vm_ostack_count (OptVM *vm) +{ + return ptrArrayCount (vm->ostack); +} + +static int +vm_ostack_counttomark (OptVM *vm) +{ + unsigned int c = ptrArrayCount (vm->ostack); + unsigned int i; + + if (c == 0) + return -1; + + for (i = c; i > 0; i--) + { + EsObject *elt = ptrArrayItem (vm->ostack, i - 1); + if (es_object_get_type (elt) == OPT_TYPE_MARK) + break; + } + + if (i == 0) + return -1; + + int r = (c - i); + if (r < 0) /* FIXME */ + r = -1; + return r; +} + +static EsObject* +vm_ostack_top (OptVM *vm) +{ + if (ptrArrayCount (vm->ostack) > 0) + return ptrArrayLast (vm->ostack); + return OPT_ERR_UNDERFLOW; +} + +static EsObject* +vm_ostack_peek (OptVM *vm, int index_from_top) +{ + unsigned int c = ptrArrayCount (vm->ostack); + if (c > (unsigned int)index_from_top) + { + unsigned int i = (c - ((unsigned int)index_from_top)) - 1; + Assert (i < c); + return ptrArrayItem (vm->ostack, i); + } + return OPT_ERR_UNDERFLOW; +} + +static EsObject* +vm_dstack_known_and_get (OptVM *vm, EsObject *key, EsObject **val) +{ + if (es_object_get_type (key) == OPT_TYPE_NAME) + key = es_pointer_get (key); + + int c = ptrArrayCount (vm->dstack); + + for (int i = c - 1; i >= 0; i--) + { + EsObject *d = ptrArrayItem (vm->dstack, i); + if (dict_op_known_and_get (d, key, val)) + return d; + } + return es_false; +} + +static void +vm_dict_def (OptVM *vm, EsObject *key, EsObject *val) +{ + Assert (!es_null(key)); + dict_op_def (ptrArrayLast(vm->dstack), key, val); +} + +static void +vm_dstack_push (OptVM *vm, EsObject *o) +{ + ptrArrayAdd (vm->dstack, es_object_ref (o)); +} + +static int +vm_dstack_count (OptVM *vm) +{ + return ptrArrayCount (vm->dstack); +} + +static EsObject* +vm_dstack_pop (OptVM *vm) +{ + if (vm_dstack_count (vm) <= vm->dstack_protection) + return OPT_ERR_DICTSTACKUNDERFLOW; + ptrArrayDeleteLast (vm->dstack); + return es_false; +} + +static void +vm_dstack_clear (OptVM *vm) +{ + while (ptrArrayCount (vm->dstack) > 1) + ptrArrayDeleteLast (vm->dstack); + + vm->dstack_protection = 1; +} + +static EsObject* +vm_call_operator (OptVM *vm, EsObject *op) +{ + EsObject *r; + + Operator operator = es_pointer_get (op); + OperatorFat *ofat = es_fatptr_get (op); + + vm_estack_push (vm, op); + + if (ofat->arity > 0) + { + unsigned int c = ptrArrayCount (vm->ostack); + if (c < (unsigned int)ofat->arity) + { + vm_estack_pop (vm); + vm_record_error (vm, OPT_ERR_UNDERFLOW, op); + return OPT_ERR_UNDERFLOW; + } + } + + r = (* operator) (vm, ofat->name); + if (es_error_p (r)) + { + vm_estack_pop (vm); + if (es_object_equal (OPT_ERR_STOPPED, r)) + vm_record_stop (vm, op); + else + vm_record_error (vm, r, op); + return r; + } + + vm_estack_pop (vm); + return es_false; +} + +static EsObject* +vm_call_proc (OptVM *vm, EsObject *proc) +{ + ptrArray *a = es_pointer_get (proc); + unsigned int c = ptrArrayCount (a); + + vm_estack_push (vm, proc); + for (unsigned int i = 0; i < c; i++) + { + EsObject *o = ptrArrayItem (a, i); + EsObject* e = vm_eval (vm, o); + if (es_error_p (e)) + { + vm_estack_pop (vm); /* ??? */ + return e; + } + } + vm_estack_pop (vm); + + return es_false; +} + +static EsObject* +vm_estack_push (OptVM *vm, EsObject *p) +{ + ptrArrayAdd (vm->estack, es_object_ref (p)); + return es_false; +} + +static EsObject* +vm_estack_pop (OptVM *vm) +{ + if (ptrArrayCount (vm->estack) < 1) + return OPT_ERR_INTERNALERROR; + ptrArrayDeleteLast (vm->estack); + return es_false; +} + +static void +insert_spaces (MIO *mio, int n) +{ + while (n-- > 0) + mio_putc(mio, ' '); +} + +struct htable_print_data { + OptVM *vm; + int dict_recursion; +}; + +static bool +htable_print_entry (const void *key, void *val, void *user_data) +{ + struct htable_print_data *data = user_data; + + vm_print_full (data->vm, (EsObject *)key, false, data->dict_recursion); + mio_putc (data->vm->out, ' '); + vm_print_full (data->vm, (EsObject *)val, false, data->dict_recursion); + + return true; +} + +static bool +htable_print_entries (const void *key, void *val, void *user_data) +{ + struct htable_print_data *data = user_data; + + insert_spaces (data->vm->out, data->vm->print_depth * 2); + htable_print_entry (key, val, user_data); + mio_putc (data->vm->out, '\n'); + + return true; +} + +static void +vm_print (OptVM *vm, EsObject *elt) +{ + vm_print_full (vm, elt, false, 0); +} + +static void +vm_print_full(OptVM *vm, EsObject *elt, bool string_as_is, int dict_recursion) +{ + if (es_object_equal (elt, es_true)) + mio_puts (vm->out, "true"); + else if (es_object_equal (elt, es_false)) + mio_puts (vm->out, "false"); + else if (es_object_equal (elt, es_nil)) + mio_puts (vm->out, "null"); + else if (es_error_p (elt)) + { + mio_putc (vm->out, '/'); + mio_puts (vm->out, es_error_name (elt)); + } + else if (es_object_get_type (elt) == OPT_TYPE_DICT) + { + hashTable *d = es_pointer_get (elt); + + struct htable_print_data data = { + .vm = vm, + .dict_recursion = dict_recursion - 1, + }; + + if (dict_recursion) + { + switch (hashTableCountItem (d)) + { + case 0: + mio_puts(vm->out, "<<>> "); + break; + case 1: + mio_puts(vm->out, "<<"); + hashTableForeachItem (d, htable_print_entry, &data); + mio_puts(vm->out, ">> "); + break; + default: + mio_puts(vm->out, "<<\n"); + vm->print_depth++; + hashTableForeachItem (d, htable_print_entries, &data); + vm->print_depth--; + insert_spaces (vm->out, vm->print_depth*2); + mio_puts(vm->out, ">> "); + break; + } + } + else + { + mio_printf (vm->out, "-dict:%u-", + hashTableCountItem (d)); + } + } + else if (es_object_get_type (elt) == OPT_TYPE_ARRAY) + { + ArrayFat *afat = (ArrayFat *)es_fatptr_get (elt); + ptrArray *a = (ptrArray *)es_pointer_get (elt); + unsigned int c = ptrArrayCount (a); + int is_proc = (afat->attr & ATTR_EXECUTABLE)? 1: 0; + + mio_putc (vm->out, is_proc? '{': '['); + vm->print_depth += is_proc; + for (unsigned int i = 0; i < c; i++) + { + vm_print_full (vm, (EsObject *)ptrArrayItem (a, i), false, dict_recursion); + if (i != c - 1) + mio_putc (vm->out, ' '); + } + vm->print_depth -= is_proc; + mio_putc (vm->out, is_proc? '}': ']'); + } + else if (es_object_get_type (elt) == OPT_TYPE_STRING && string_as_is) + { + const char *cstr = opt_string_get_cstr (elt); + mio_puts (vm->out, cstr); + } + else if ((es_object_get_type (elt) == OPT_TYPE_NAME || es_symbol_p (elt)) + && string_as_is) + { + const char *cstr = opt_name_get_cstr (elt); + mio_puts (vm->out, cstr); + } + else if (es_symbol_p (elt) && (! string_as_is)) + { + mio_putc (vm->out, '/'); + es_print (elt, vm->out); + } + else + es_print (elt, vm->out); +} + +static bool +collect_operators (const void *key, void *value, void *user_data) +{ + ptrArray *a = user_data; + EsObject *op = value; + + if (es_object_get_type (op) == OPT_TYPE_OPERATOR) + { + OperatorFat *ofat = es_fatptr_get (op); + if (ofat->help_str) + ptrArrayAdd (a, op); + } + return true; +} + +static const char* +callable_get_name (const EsObject *callable) +{ + if (es_object_get_type (callable) == OPT_TYPE_OPERATOR) + { + const OperatorFat *ofat_callable = es_fatptr_get (callable); + return es_symbol_get (ofat_callable->name); + } + else + return opt_name_get_cstr(callable); +} + +static int +compare_callable_by_name (const void *a, const void *b) +{ + const char *str_a = callable_get_name (a); + const char *str_b = callable_get_name (b); + + return strcmp (str_a, str_b); +} + +static void +vm_help (OptVM *vm, MIO *out, struct OptHelpExtender *extop, void *data) +{ + unsigned int c = ptrArrayCount (vm->dstack); + + ptrArray *a = ptrArrayNew (NULL); + for (unsigned int i = 0; i < c; i++) + { + hashTable *t = es_pointer_get (ptrArrayItem (vm->dstack, i)); + hashTableForeachItem (t, collect_operators, a); + } + if (extop) + extop->add (a, data); + + ptrArraySort (a, compare_callable_by_name); + + unsigned int ca = ptrArrayCount (a); + size_t maxlen = 0; + for (unsigned int i = 0; i < ca; i++) + { + EsObject* obj = ptrArrayItem (a, i); + const char *name = callable_get_name (obj); + + size_t l = strlen (name); + if (l > maxlen) + maxlen = l; + } + + for (unsigned int i = 0; i < ca; i++) + { + EsObject* obj = ptrArrayItem (a, i); + const char *name = NULL; + const char *help_str_head = NULL; + const char *help_str_original = NULL; + + if (es_object_get_type (obj) == OPT_TYPE_OPERATOR) + { + OperatorFat *ofat = es_fatptr_get (obj); + name = es_symbol_get (ofat->name); + help_str_head = ofat->help_str; + } + else if (extop) + { + name = opt_name_get_cstr (obj); + help_str_head = extop->get_help_str (obj, data); + } + help_str_original = help_str_head; + + if (name == NULL || help_str_head == NULL) + continue; + + while (help_str_head) + { + const char *next = strpbrk (help_str_head, "%\n"); + const char *label = (help_str_head == help_str_original)? name: NULL; + if (next) + { + char *tmp = eStrndup (help_str_head, next - help_str_head); + bool desc = (tmp[0] == ':'); + mio_printf (out, "%*s%s%s\n", + (int)maxlen, label? label: "", + ((desc || (label == NULL))? " ": " -> "), + (desc? tmp + 1: tmp)); + eFree ((char *)tmp); + help_str_head = next + 1; + while (*help_str_head && isspace ((unsigned char)*help_str_head)) + help_str_head++; + } + else + { + if (*help_str_head != '\0') + { + bool desc = (help_str_head[0] == ':'); + mio_printf (out, "%*s%s%s\n", + (int)maxlen, label? label: "", + ((desc || (label == NULL))? " ": " -> "), + (desc? help_str_head + 1: help_str_head)); + } + help_str_head = NULL; + } + } + } + + ptrArrayDelete (a); +} + +static EsObject * +array_new_from_stack (ptrArray *src) +{ + EsObject *dst = array_new (0); + ptrArray *a = (ptrArray *)es_pointer_get (dst); + for (unsigned int i = 0; i < ptrArrayCount(src); i++) + ptrArrayAdd (a, es_object_ref (ptrArrayItem (src, i))); + return dst; +} + +static void +vm_record_stop (OptVM *vm, EsObject *cmd) +{ + dict_op_def (vm->error, OPT_KEY_command, cmd); + dict_op_def (vm->error, OPT_KEY_errorname, es_nil); + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + /* OPT_KEY_{o,e,o}stack are kept as is. */ +} + +static void +vm_record_error (OptVM *vm, EsObject *e, EsObject *cmd) +{ + EsObject *newerror = es_nil; + if (dict_op_known_and_get (vm->error, OPT_KEY_newerror, &newerror) + && es_object_equal (newerror, es_true)) + return; + + dict_op_def (vm->error, OPT_KEY_newerror, es_true); + dict_op_def (vm->error, OPT_KEY_errorname, e); + dict_op_def (vm->error, OPT_KEY_command, cmd); + + EsObject *a; + + a = array_new_from_stack (vm->ostack); + dict_op_def (vm->error, OPT_KEY_ostack, a); + es_object_unref (a); + + a = array_new_from_stack (vm->estack); + dict_op_def (vm->error, OPT_KEY_estack, a); + es_object_unref (a); + + a = array_new_from_stack (vm->dstack); + dict_op_def (vm->error, OPT_KEY_dstack, a); + es_object_unref (a); +} + +static void +vm_report_error (OptVM *vm, EsObject *e) +{ + MIO *out = vm->out; + vm->out = vm->err; + mio_puts (vm->err, "Error: "); + + EsObject *newerror = es_nil; + if (!dict_op_known_and_get (vm->error, OPT_KEY_newerror, &newerror)) + { + vm_print (vm, e); + mio_putc (vm->err, '\n'); + goto out; + } + + if (es_object_equal (newerror, es_false)) + { + vm_print (vm, e); + mio_putc (vm->err, '\n'); + goto out; + } + + if (!dict_op_known_and_get (vm->error, OPT_KEY_errorname, &e)) + { + vm_print (vm, OPT_ERR_INTERNALERROR); + mio_putc (vm->err, '\n'); + goto out; + } + + vm_print (vm, e); + + EsObject *command = es_nil; + dict_op_known_and_get (vm->error, OPT_KEY_command, &command); + EsObject *attached_object = es_error_get_object (e); + + if (!es_null (attached_object)) + { + mio_puts (vm->err, " in "); + vm_print (vm, attached_object); + } + else if (!es_null (command)) + { + mio_puts (vm->err, " in "); + vm_print (vm, command); + command = es_nil; + } + mio_putc (vm->err, '\n'); + + EsObject *ostack = es_nil; + if (dict_op_known_and_get (vm->error, OPT_KEY_ostack, &ostack)) + { + mio_puts (vm->err, "Operand stack:\n"); + mio_puts (vm->err, "top|"); + ptrArray *a = es_pointer_get (ostack); + for (unsigned int i = ptrArrayCount (a); i > 0; i--) + { + EsObject *o = ptrArrayItem (a, i - 1); + mio_puts (vm->err, " "); + vm_print (vm, o); + } + } + mio_puts (vm->err, " |bottom\n"); + + EsObject *estack = es_nil; + if (dict_op_known_and_get (vm->error, OPT_KEY_estack, &estack)) + { + mio_puts (vm->err, "Execution stack:\n"); + mio_puts (vm->err, "top|"); + + if (!es_null (command)) + { + mio_puts (vm->err, " "); + vm_print (vm, command); + } + + ptrArray *a = es_pointer_get (estack); + for (unsigned int i = ptrArrayCount (a); i > 0; i--) + { + EsObject *o = ptrArrayItem (a, i - 1); + mio_puts (vm->err, " "); + vm_print (vm, o); + } + } + mio_puts (vm->err, " |bottom\n"); + + EsObject *dstack = es_nil; + if (dict_op_known_and_get (vm->error, OPT_KEY_dstack, &dstack)) + { + mio_puts (vm->err, "Dictionary stack:\n"); + mio_puts (vm->err, "top|"); + ptrArray *a = es_pointer_get (dstack); + for (unsigned int i = ptrArrayCount (a); i > 0; i--) + { + EsObject *o = ptrArrayItem (a, i - 1); + mio_puts (vm->err, " "); + vm_print (vm, o); + } + } + mio_puts (vm->err, " |bottom\n"); + + out: + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + vm->out = out; +} + +static void +vm_bind_proc (OptVM *vm, ptrArray *proc) +{ + unsigned int c = ptrArrayCount (proc); + for (unsigned int i = 0; i < c; i++) + { + EsObject *x = ptrArrayItem (proc, i); + + if (es_object_get_type (x) == OPT_TYPE_ARRAY) + vm_bind_proc (vm, es_pointer_get (x)); + else if (es_object_get_type (x) == OPT_TYPE_NAME) + { + if (!(((NameFat *)es_fatptr_get (x))->attr + & ATTR_EXECUTABLE)) + continue; + + EsObject* val = NULL; + EsObject *r = vm_dstack_known_and_get (vm, x, &val); + if (es_object_get_type (r) == OPT_TYPE_DICT) + { + if (es_object_get_type (val) == OPT_TYPE_OPERATOR) + ptrArrayUpdate (proc, i, es_object_ref (val), es_nil); + } + } + } +} + + +/* + * Array + */ +static EsObject* +array_new (unsigned int attr) +{ + ptrArray *a = ptrArrayNew ((ptrArrayDeleteFunc)es_object_unref); + return es_fatptr_new (OPT_TYPE_ARRAY, a, &attr); +} + +static EsObject* +array_es_init_fat (void *fat, void *ptr, void *extra) +{ + ArrayFat *a = fat; + a->attr = *((unsigned int *)extra); + return es_false; +} + +static void +array_es_free (void *ptr, void *fat) +{ + if (ptr) + ptrArrayDelete ((ptrArray *)ptr); +} + +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) + return 1; + else + return 0; +} + +static void +array_es_print (const void *ptr, const void *fat, MIO *out) +{ + unsigned int c = ptrArrayCount ((ptrArray *)ptr); + ArrayFat *a = (ArrayFat *)fat; + mio_printf (out, "%c%c%c count: %u", + (a->attr & ATTR_READABLE) ? 'r': '-', + (a->attr & ATTR_WRITABLE) ? 'w': '-', + (a->attr & ATTR_EXECUTABLE)? 'x': '-', + c); +} + +static void +array_op_add (EsObject* array, EsObject* elt) +{ + ptrArray *a = es_pointer_get (array); + ptrArrayAdd (a, es_object_ref (elt)); +} + +static unsigned int +array_op_length (const EsObject* array) +{ + ptrArray *a = es_pointer_get (array); + return ptrArrayCount (a); +} + +static EsObject* +array_op_get (const EsObject* array, unsigned int n) +{ + ptrArray *a = es_pointer_get (array); + unsigned int len = ptrArrayCount (a); + if (n >= len) + return OPT_ERR_RANGECHECK; + return ptrArrayItem (a, n); +} + +static void +array_op_put (EsObject* array, unsigned int n, EsObject *obj) +{ + ptrArray *a = es_pointer_get (array); + ptrArrayUpdate (a, n, + es_object_ref (obj), es_nil); +} + + +/* + * Dictionary + */ +static unsigned int +opt_es_hash (const void * const key) +{ + const EsObject *k = key; + + if (es_integer_p (key)) + return hashInthash (key); + else if (es_boolean_p (key)) + return es_object_equal (key, es_true)? 1: 0; + + return hashPtrhash (k); +} + +static bool +opt_es_eq (const void* a, const void* b) +{ + return es_object_equal (a, b); +} + +static EsObject* +dict_new (unsigned int size, unsigned int attr) +{ + hashTable *t = hashTableNew (size, + opt_es_hash, + opt_es_eq, + (hashTableDeleteFunc)es_object_unref, + (hashTableDeleteFunc)es_object_unref); + hashTableSetValueForUnknownKey (t, t, NULL); + return es_fatptr_new (OPT_TYPE_DICT, t, &attr); +} + +static EsObject* +dict_es_init_fat (void *fat, void *ptr, void *extra) +{ + DictFat *a = fat; + a->attr = *((unsigned int *)extra); + return es_false; +} + +static void +dict_es_free (void *ptr, void *fat) +{ + if (ptr) + hashTableDelete ((hashTable *)ptr); +} + +static int +dict_es_equal (const void *a, const void *afat, const void *b, const void *bfat) +{ + if (a == b) + return 1; + return 0; +} + +static void +dict_es_print (const void *ptr, const void *fat, MIO *out) +{ + unsigned int c = hashTableCountItem ((hashTable *)ptr); + DictFat *a = (DictFat *)fat; + mio_printf (out, "%c%c%c count: %u", + (a->attr & ATTR_READABLE) ? 'r': '-', + (a->attr & ATTR_WRITABLE) ? 'w': '-', + (a->attr & ATTR_EXECUTABLE)? 'x': '-', + c); +} + +static void +dict_op_def (EsObject* dict, EsObject *key, EsObject *val) +{ + hashTable *t = es_pointer_get (dict); + Assert (t); + Assert (!es_null (key)); + + if (es_object_get_type (key) == OPT_TYPE_NAME) + key = es_pointer_get (key); + + key = es_object_ref (key); + val = es_object_ref (val); + + hashTableUpdateItem (t, key, val); +} + +static bool +dict_op_undef (EsObject *dict, EsObject *key) +{ + hashTable *t = es_pointer_get (dict); + Assert (t); + + if (es_object_get_type (key) == OPT_TYPE_NAME) + key = es_pointer_get (key); + + /* TODO: handle the case key == NULL */ + return hashTableDeleteItem (t, key); +} + +static bool +dict_op_known_and_get(EsObject* dict, EsObject *key, EsObject **val) +{ + hashTable *t = es_pointer_get (dict); + Assert (t); + + if (es_object_get_type (key) == OPT_TYPE_STRING) + { + const char * cstr = opt_string_get_cstr (key); + key = es_symbol_intern (cstr); + } + + if (es_object_get_type (key) == OPT_TYPE_NAME) + key = es_pointer_get (key); + + void *tmp = hashTableGetItem (t, key); + if (tmp == t) + return false; + + if (val) + *val = tmp; + return true; +} + +static void +dict_op_clear (EsObject* dict) +{ + hashTable *h = es_pointer_get (dict); + Assert (h); + + hashTableClear (h); +} + + +/* + * Operator + */ +static EsObject* +operator_new (Operator op, const char *name, int arity, const char *help_str) +{ + OperatorExtra extra = { .name = name, .arity = arity, .help_str = help_str }; + return es_fatptr_new (OPT_TYPE_OPERATOR, op, &extra); +} + +static EsObject* +operator_es_init_fat (void *fat, void *ptr, void *extra) +{ + OperatorFat *ofat = fat; + + if (!extra) + { + ofat->name = NULL; + return es_true; + } + + OperatorExtra *oextra = extra; + const char *name = oextra->name; + EsObject *o = es_symbol_intern (name); + + if (es_error_p (o)) + return o; + ofat->name = o; + ofat->arity = oextra->arity; + ofat->help_str = oextra->help_str? eStrdup (oextra->help_str): NULL; + return es_true; +} + +static void +operator_es_free (void *ptr, void *fat) +{ + OperatorFat *ofat = fat; + if (ofat->help_str) + eFree ((char *)ofat->help_str); +} + +static void +operator_es_print (const void *ptr, const void *fat, MIO *out) +{ + OperatorFat *ofat = (OperatorFat *)fat; + mio_printf (out, "--%s--", es_symbol_get (ofat->name)); +} + +/* + * String + */ +static EsObject* +string_new (vString *vstr) +{ + unsigned int attr = ATTR_READABLE|ATTR_WRITABLE; + + if (vstr == NULL) + vstr = vStringNew (); + + return es_fatptr_new (OPT_TYPE_STRING, vstr, &attr); +} + +static EsObject* +string_es_init_fat (void *fat, void *ptr, void *extra) +{ + StringFat *s = fat; + s->attr = *((unsigned int *)extra); + return es_false; +} + +static void +string_es_free (void *ptr, void *fat) +{ + if (ptr) + vStringDelete (ptr); +} + +static int +string_es_equal (const void *a, + const void *afat, + const void *b, + const void *bfat) +{ + if (!strcmp (vStringValue ((vString *)a), + vStringValue ((vString *)b))) + return 1; + return 0; +} + + +static void +string_es_print (const void *ptr, const void *fat, MIO *out) +{ + char *v = vStringValue ((vString *)ptr); + + mio_putc (out, '('); + while (*v != '\0') + { + switch (*v) + { + case '(': + case ')': + case '\\': + mio_putc (out, '\\'); + mio_putc (out, *v); + break; + case '\n': + mio_putc (out, '\\'); + mio_putc (out, 'n'); + break; + case '\r': + mio_putc (out, '\\'); + mio_putc (out, 'r'); + break; + case '\t': + mio_putc (out, '\\'); + mio_putc (out, 't'); + break; + case '\f': + mio_putc (out, '\\'); + mio_putc (out, 'f'); + break; + case '\v': + mio_putc (out, '\\'); + mio_putc (out, 'v'); + break; + default: + mio_putc (out, *v); + } + v++; + } + mio_putc (out, ')'); +} + + +/* + * Name + */ +static EsObject* +name_new (EsObject* symbol, unsigned int attr) +{ + return es_fatptr_new (OPT_TYPE_NAME, + es_object_ref (symbol), &attr); +} + +static EsObject* +name_newS (const char*s, unsigned int attr) +{ + EsObject *sym = es_symbol_intern (s); + return name_new (sym, attr); +} + +static EsObject* name_newS_cb (const char*s, void *attr) +{ + return name_newS (s, *((unsigned int *)attr)); +} + +static EsObject* +name_es_init_fat (void *fat, void *ptr, void *extra) +{ + ArrayFat *a = fat; + a->attr = *((unsigned int *)extra); + return es_false; +} + +static void +name_es_print (const void *ptr, const void *fat, MIO *out) +{ + const EsObject *symbol = ptr; + const NameFat *qfat = fat; + if (!(qfat->attr & ATTR_EXECUTABLE)) + mio_putc (out, '/'); + const char *name = es_symbol_get (symbol); + mio_puts (out, name); +} + +static void +name_es_free (void *ptr, void *fat) +{ + if (ptr) + es_object_unref (ptr); +} + +static int +name_es_equal (const void *a, const void *afat, + const void *b, const void *bfat) +{ + const EsObject * asym = a; + const EsObject * bsym = b; + return es_object_equal (asym, bsym); +} + +/* + * Mark + */ +static EsObject* +mark_new (const char* mark) +{ + return es_pointer_new (OPT_TYPE_MARK, + eStrdup (mark)); +} + +static void +mark_es_print (const void *ptr, MIO *out) +{ + if (ptr == NULL || (strcmp (ptr, "mark") == 0)) + mio_printf (out, "-mark-"); + else + mio_printf (out, "-mark:%s-", (char *)ptr); +} + +static void +mark_es_free (void *ptr) +{ + if (ptr) + eFree (ptr); +} + +static int +mark_es_equal (const void *a, const void *b) +{ + return 1; +} + + +/* + * Operator implementations + */ +#define GEN_PRINTER(NAME, BODY) \ + static EsObject* \ + NAME(OptVM *vm, EsObject *name) \ + { \ + EsObject * elt = ptrArrayRemoveLast (vm->ostack); \ + BODY; \ + mio_putc (vm->out, '\n'); \ + es_object_unref (elt); \ + return es_false; \ + } + +GEN_PRINTER(op__print_objdict_rec, vm_print_full (vm, elt, false, 10)) +GEN_PRINTER(op__print_objdict, vm_print_full (vm, elt, false, 1)) +GEN_PRINTER(op__print_object, vm_print_full (vm, elt, false, 0)) +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) + return OPT_ERR_UNMATCHEDMARK; + + unsigned int count = vm_ostack_count (vm); + EsObject *a = array_new (ATTR_READABLE | ATTR_WRITABLE); + for (int i = (int)(count - n); i < count; i++) + { + EsObject *elt = ptrArrayItem (vm->ostack, i); + array_op_add (a, elt); + } + + ptrArrayDeleteLastInBatch (vm->ostack, n + 1); + vm_ostack_push (vm, a); + es_object_unref (a); + return es_false; +} + +static EsObject* +op__make_dict (OptVM *vm, EsObject *name) +{ + int n = vm_ostack_counttomark (vm); + if (n < 0) + return OPT_ERR_UNMATCHEDMARK; + + if (n % 2) + return OPT_ERR_RANGECHECK; + + for (int i = 0; i < (n / 2); i++) + { + EsObject *key = ptrArrayItemFromLast (vm->ostack, 2 * i + 1); + + if (es_object_get_type (key) != OPT_TYPE_NAME + && es_object_get_type (key) != OPT_TYPE_STRING + && !es_integer_p (key) && !es_boolean_p (key)) + return OPT_ERR_TYPECHECK; + } + + EsObject *d = dict_new (n % 2 + 1, ATTR_READABLE|ATTR_WRITABLE); /* FIXME: + 1 */ + for (int i = 0; i < (n / 2); i++) + { + EsObject *val = ptrArrayLast (vm->ostack); + EsObject *key = ptrArrayItemFromLast (vm->ostack, 1); + bool converted = false; + + if (es_object_get_type (key) == OPT_TYPE_STRING) + { + const char *cstr = opt_string_get_cstr (key); + key = opt_name_new_from_cstr (cstr); + converted = true; + } + dict_op_def (d, key, val); + if (converted) + es_object_unref (key); + + ptrArrayDeleteLastInBatch (vm->ostack, 2); + } + ptrArrayDeleteLast (vm->ostack); /* Remove the mark */ + vm_ostack_push (vm, d); + es_object_unref (d); + return es_false; +} + +static EsObject* +op__help (OptVM *vm, EsObject *name) +{ + vm_help (vm, vm->out, NULL, NULL); + return es_false; +} + +static EsObject* +op_pstack (OptVM *vm, EsObject *name) +{ + unsigned int c = vm_ostack_count (vm); + + for (unsigned int i = c; i > 0; i--) + { + EsObject * elt = ptrArrayItem (vm->ostack, i - 1); + vm_print (vm, elt); + mio_putc (vm->out, '\n'); + } + return es_false; +} + +static EsObject* +op__newerror (OptVM *vm, EsObject *name) +{ + EsObject *newerror; + if (dict_op_known_and_get (vm->error, OPT_KEY_newerror, &newerror)) + vm_ostack_push (vm, newerror); + else + vm_ostack_push (vm, es_false); + return es_false; +} + +static EsObject* +op__errorname (OptVM *vm, EsObject *name) +{ + EsObject *errorname; + if (dict_op_known_and_get (vm->error, OPT_KEY_errorname, &errorname)) + { + EsObject *sym = es_nil; + if (!es_null (errorname)) + { + const char *cstr = es_error_name(errorname); + sym = opt_name_new_from_cstr (cstr); + } + vm_ostack_push (vm, sym); + if (!es_null (errorname)) + es_object_unref (sym); + } + else + vm_ostack_push (vm, es_nil); + return es_false; +} + +static EsObject* +op_quit (OptVM *vm, EsObject *name) +{ + int c = mio_getc (vm->in); + if (!(c == '\n' || c == '\r' || c == EOF)) + mio_ungetc (vm->in, c); + return OPT_ERR_QUIT; +} + +static EsObject* +op_countexecstack (OptVM *vm, EsObject *name) +{ + unsigned int c = ptrArrayCount (vm->estack); + int n = c; + + if (n < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + + EsObject *nobj = es_integer_new (n); + vm_ostack_push (vm, nobj); + es_object_unref (nobj); + + return es_false; +} + +static EsObject* +op__stack_common (OptVM *vm, EsObject *name, ptrArray *stack, EsObject *dstarrayobj, + bool ignoreLast) +{ + unsigned int c = ptrArrayCount (stack); + ptrArray *a = es_pointer_get (dstarrayobj); + + if (ignoreLast && c == 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + + ptrArrayClear (a); + for (unsigned int i = 0; i < c - (ignoreLast? 1: 0); i++) + { + EsObject *d = ptrArrayItem (stack, i); + ptrArrayAdd (a, es_object_ref (d)); + } + + return es_false; +} + +static EsObject* +op_execstack (OptVM *vm, EsObject *name) +{ + EsObject *obj = ptrArrayLast (vm->ostack); + if (es_object_get_type (obj) != OPT_TYPE_ARRAY) + return OPT_ERR_TYPECHECK; + + return op__stack_common (vm, name, vm->estack, obj, true); +} + + +/* + * Operators for operand stack manipulation + */ +static EsObject* +op_pop (OptVM *vm, EsObject *name) +{ + ptrArrayDeleteLast (vm->ostack); + return es_false; +} + +static EsObject* +op_exch (OptVM *vm, EsObject *name) +{ + EsObject * top = ptrArrayRemoveLast (vm->ostack); + EsObject * next = ptrArrayRemoveLast (vm->ostack); + ptrArrayAdd (vm->ostack, top); + ptrArrayAdd (vm->ostack, next); + return es_false; +} + +static EsObject* +op_dup (OptVM *vm, EsObject *name) +{ + EsObject * top = vm_ostack_top (vm); + if (es_error_p (top)) + return top; + vm_ostack_push (vm, top); + return es_false; +} + +static bool +dict_copy_cb (const void *key, void *value, void *user_data) +{ + hashTable *dst = user_data; + hashTablePutItem (dst, es_object_ref ((void *)key), es_object_ref (value)); + return true; +} + +static EsObject* +op__copy_compound (OptVM *vm, EsObject *name, unsigned int c, EsObject *obj2) +{ + int t = es_object_get_type (obj2); + if (!(t == OPT_TYPE_ARRAY || t == OPT_TYPE_DICT || t == OPT_TYPE_STRING)) + return OPT_ERR_TYPECHECK; + + if (c < 2) + return OPT_ERR_UNDERFLOW; + + EsObject *obj1 = ptrArrayItemFromLast (vm->ostack, 1); + if (es_object_get_type (obj1) != t) + return OPT_ERR_TYPECHECK; + + if (t == OPT_TYPE_ARRAY) + { + ptrArray *a1 = es_pointer_get (obj1); + ptrArray *a2 = es_pointer_get (obj2); + ptrArrayClear (a2); + unsigned int len = ptrArrayCount (a1); + for (unsigned int i = 0; i < len; i++) + { + EsObject *o = ptrArrayItem (a1, i); + ptrArrayAdd (a2, es_object_ref (o)); + } + } + else if (t == OPT_TYPE_DICT) + { + hashTable *ht1 = es_pointer_get (obj1); + hashTable *ht2 = es_pointer_get (obj2); + hashTableClear (ht2); + hashTableForeachItem (ht1, dict_copy_cb, ht2); + } + else + { + vString *str1 = es_pointer_get (obj1); + vString *str2 = es_pointer_get (obj2); + vStringCopy (str2, str1); + } + + ptrArrayRemoveLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + ptrArrayAdd (vm->ostack, obj2); + return es_false; +} + +static EsObject* +op_copy (OptVM *vm, EsObject *name) +{ + unsigned int c = vm_ostack_count (vm); + + if (c > 0) + { + EsObject * nobj = ptrArrayLast(vm->ostack); + + + if (!es_integer_p (nobj)) + return op__copy_compound (vm, name, c, nobj); + + int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; + + c--; + + if (((int)c) - n < 0) + return OPT_ERR_UNDERFLOW; + + ptrArrayDeleteLast(vm->ostack); + + for (int i = c - n; i < c; i++) + { + EsObject * elt = ptrArrayItem (vm->ostack, i); + vm_ostack_push (vm, elt); + } + return es_false; + } + return OPT_ERR_UNDERFLOW; +} + +static EsObject* +op_index (OptVM *vm, EsObject *name) +{ + unsigned int c = vm_ostack_count (vm); + + EsObject * nobj = ptrArrayLast(vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; + if (c < (unsigned int)(n + 2)) + return OPT_ERR_UNDERFLOW; + + ptrArrayDeleteLast (vm->ostack); + + EsObject * elt = ptrArrayItem (vm->ostack, c - n - 2); + vm_ostack_push (vm, elt); + return es_false; + + return OPT_ERR_UNDERFLOW; +} + +static EsObject* +op_roll (OptVM *vm, EsObject *name) +{ + unsigned int c = vm_ostack_count (vm); + + EsObject *jobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (jobj)) + return OPT_ERR_TYPECHECK; + int j = es_integer_get (jobj); + + EsObject *nobj = ptrArrayItemFromLast (vm->ostack, 1); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (nobj); + + if ((((int)c) - 2) < n) + return OPT_ERR_UNDERFLOW; + + ptrArrayDeleteLastInBatch (vm->ostack, 2); + if (j == 0) + return es_false; + + unsigned int indx = c - 2 - n; + EsObject *p; + if (j > 0) + { + while (j-- != 0) + { + p = ptrArrayRemoveLast (vm->ostack); + ptrArrayInsertItem (vm->ostack, indx, p); + } + } + else + { + while (j++ != 0) + { + p = ptrArrayRemoveItem(vm->ostack, indx); + ptrArrayAdd (vm->ostack, p); + } + + } + + return es_false; +} + +static EsObject* +op_clear (OptVM *vm, EsObject *name) +{ + ptrArrayClear (vm->ostack); + + return es_false; +} + +static EsObject* +op_count (OptVM *vm, EsObject *name) +{ + unsigned int c = ptrArrayCount (vm->ostack); + + EsObject *n = es_integer_new ((int)c); + ptrArrayAdd (vm->ostack, n); + + return es_false; +} + +static EsObject* +op_mark (OptVM *vm, EsObject *name) +{ + EsObject *mark; + if (es_object_equal (name, es_symbol_intern ("["))) + mark = OPT_MARK_ARRAY; + else if (es_object_equal (name, es_symbol_intern ("<<"))) + mark = OPT_MARK_DICT; + else + mark = OPT_MARK_MARK; + vm_ostack_push (vm, mark); + + return es_false; +} + +static EsObject* +op_cleartomark (OptVM *vm, EsObject *name) +{ + int r = vm_ostack_counttomark (vm); + + if (r < 0) + return OPT_ERR_UNMATCHEDMARK; + + if (r < 0) + return OPT_ERR_UNMATCHEDMARK; + + for (int i = 0; i <= r; i++) + ptrArrayDeleteLast (vm->ostack); + return es_false; +} + +static EsObject* +op_counttomark (OptVM *vm, EsObject *name) +{ + int r = vm_ostack_counttomark (vm); + + if (r < 0) + return OPT_ERR_UNMATCHEDMARK; + + ptrArrayAdd (vm->ostack, es_integer_new (r)); + return es_false; +} + + +/* + * Arithmetic Operators + */ +#define INTEGER_BINOP(OP) \ + EsObject *n0obj = ptrArrayLast (vm->ostack); \ + if (!es_integer_p (n0obj)) \ + return OPT_ERR_TYPECHECK; \ + int n0 = es_integer_get (n0obj); \ + \ + EsObject *n1obj = ptrArrayItemFromLast (vm->ostack, 1); \ + if (!es_integer_p (n1obj)) \ + return OPT_ERR_TYPECHECK; \ + int n1 = es_integer_get (n1obj); \ + \ + EsObject *r = es_integer_new (n1 OP n0); \ + if (es_error_p (r)) \ + return r; \ + \ + ptrArrayDeleteLastInBatch (vm->ostack, 2); \ + ptrArrayAdd (vm->ostack, r); \ + return es_false + +static EsObject* +op_add (OptVM *vm, EsObject *name) +{ + INTEGER_BINOP(+); +} + +static EsObject* +op_idiv (OptVM *vm, EsObject *name) +{ + INTEGER_BINOP(/); +} + +static EsObject* +op_mod (OptVM *vm, EsObject *name) +{ + INTEGER_BINOP(%); +} + +static EsObject* +op_mul (OptVM *vm, EsObject *name) +{ + INTEGER_BINOP(*); +} + +static EsObject* +op_sub (OptVM *vm, EsObject *name) +{ + INTEGER_BINOP(-); +} + +static EsObject* +op_abs (OptVM *vm, EsObject *name) +{ + EsObject *nobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get(nobj); + if (n >= 0) + return es_false; + + EsObject *r = es_integer_new (-n); + if (es_error_p (r)) + return r; + ptrArrayDeleteLast (vm->ostack); + ptrArrayAdd (vm->ostack, r); + return es_false; +} + +static EsObject* +op_neg (OptVM *vm, EsObject *name) +{ + EsObject *nobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + int n = es_integer_get(nobj); + EsObject *r = es_integer_new (-n); + if (es_error_p (r)) + return r; + ptrArrayDeleteLast (vm->ostack); + ptrArrayAdd (vm->ostack, r); + return es_false; +} + + +/* + * Operators for array manipulation + */ +static EsObject* +op_array (OptVM *vm, EsObject *name) +{ + EsObject *nobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; + + ptrArrayDeleteLast (vm->ostack); + + EsObject *array = array_new (ATTR_WRITABLE|ATTR_READABLE); + ptrArray *a = es_pointer_get (array); + for (int i = 0; i < n; i++) + ptrArrayAdd (a, es_nil); + vm_ostack_push (vm, array); + es_object_unref (array); + + return es_false; +} + +static EsObject* +op_astore (OptVM *vm, EsObject *name) +{ + EsObject *array = ptrArrayLast (vm->ostack); + if (es_object_get_type (array) != OPT_TYPE_ARRAY) + return OPT_ERR_TYPECHECK; + + unsigned int c = ptrArrayCount (vm->ostack); + ptrArray *a = es_pointer_get (array); + unsigned int l = ptrArrayCount (a); + + if (l == 0) + return es_false; + + /* +1 is for the array itself. */ + if (c < (l + 1)) + return OPT_ERR_UNDERFLOW; + + ptrArrayClear (a); + ptrArrayRemoveLast (vm->ostack); + + int i = l - 1; + if (i < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + for (; i >= 0; i--) + { + EsObject * o = ptrArrayItemFromLast (vm->ostack, i); + ptrArrayAdd (a, es_object_ref (o)); + } + + ptrArrayDeleteLastInBatch (vm->ostack, l); + vm_ostack_push (vm, array); + es_object_unref (array); + return es_false; +} + +static EsObject* +op_aload (OptVM *vm, EsObject *name) +{ + EsObject *array = ptrArrayLast (vm->ostack); + if (es_object_get_type (array) != OPT_TYPE_ARRAY) + return OPT_ERR_TYPECHECK; + ptrArray *a = es_pointer_get (array); + + ptrArrayRemoveLast (vm->ostack); + unsigned int c = ptrArrayCount (a); + for (unsigned int i = 0; i < c; i++) + { + EsObject *o = ptrArrayItem (a, i); + vm_ostack_push (vm, o); + } + vm_ostack_push (vm, array); + es_object_unref (array); + return es_false; +} + + +/* + * Operators for dictionary manipulation + */ +static EsObject* +op_dict (OptVM *vm, EsObject *name) +{ + EsObject *nobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (nobj); + if (n < 1) + return OPT_ERR_RANGECHECK; + + ptrArrayDeleteLast (vm->ostack); + + EsObject *dict = dict_new (n, ATTR_READABLE|ATTR_WRITABLE); + vm_ostack_push (vm, dict); + es_object_unref (dict); + + return es_false; +} + +static EsObject* +op_def (OptVM *vm, EsObject *name) +{ + EsObject *val = ptrArrayLast (vm->ostack); + EsObject *key = ptrArrayItemFromLast (vm->ostack, 1); + /* TODO */ + if (es_object_get_type (key) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + vm_dict_def (vm, key, val); + + ptrArrayDeleteLastInBatch(vm->ostack, 2); + + return es_false; +} + +static EsObject* +op_undef (OptVM *vm, EsObject *name) +{ + EsObject *key = ptrArrayLast (vm->ostack); + EsObject *dict = ptrArrayItemFromLast (vm->ostack, 1); + + if (es_object_get_type (key) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + if (es_object_get_type (dict) != OPT_TYPE_DICT) + return OPT_ERR_TYPECHECK; + + unsigned int attr = ((DictFat *)es_fatptr_get (dict))->attr; + if (!(attr & ATTR_WRITABLE)) + return OPT_ERR_INVALIDACCESS; + + if (!dict_op_undef (dict, key)) + return es_error_set_object (OPT_ERR_UNDEFINED, key); + + ptrArrayDeleteLastInBatch (vm->ostack, 2); + return es_false; +} + +static EsObject* +op_begin (OptVM *vm, EsObject *name) +{ + EsObject *d = ptrArrayLast (vm->ostack); + if (es_object_get_type (d) != OPT_TYPE_DICT) + return OPT_ERR_TYPECHECK; + + vm_dstack_push (vm, d); + ptrArrayDeleteLast (vm->ostack); + + return es_false; +} + +static EsObject* +op_end (OptVM *vm, EsObject *name) +{ + return vm_dstack_pop (vm); +} + +static EsObject* +op_currentdict (OptVM *vm, EsObject *name) +{ + EsObject *dict = ptrArrayLast (vm->dstack); + + vm_ostack_push (vm, dict); + + return es_false; +} + +static EsObject* +op_countdictstack (OptVM *vm, EsObject *name) +{ + unsigned int c = ptrArrayCount (vm->dstack); + int n = c; + + if (n < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + + EsObject *nobj = es_integer_new (n); + vm_ostack_push (vm, nobj); + es_object_unref (nobj); + + return es_false; +} + +static EsObject* +op_dictstack (OptVM *vm, EsObject *name) +{ + EsObject *obj = ptrArrayLast (vm->ostack); + if (es_object_get_type (obj) != OPT_TYPE_ARRAY) + return OPT_ERR_TYPECHECK; + + return op__stack_common (vm, name, vm->dstack, obj, false); +} + +static EsObject* +op_cleardictstack (OptVM *vm, EsObject *name) +{ + unsigned int d = ptrArrayCount (vm->dstack) - vm->dstack_protection; + ptrArrayDeleteLastInBatch (vm->dstack, d); + return es_false; +} + +static EsObject* +op_where (OptVM *vm, EsObject *name) +{ + EsObject *key = ptrArrayLast (vm->ostack); + if (es_object_get_type (key) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + EsObject *dict = vm_dstack_known_and_get (vm, key, NULL); + ptrArrayDeleteLast (vm->ostack); + + if (es_object_get_type (dict) != OPT_TYPE_DICT) + { + vm_ostack_push (vm, es_false); + return es_false; + } + else + { + vm_ostack_push (vm, dict); + vm_ostack_push (vm, es_true); + return es_false; + } +} + +static EsObject* +op_known (OptVM *vm, EsObject *name) +{ + EsObject *key = ptrArrayLast (vm->ostack); + EsObject *dict = ptrArrayItemFromLast (vm->ostack, 1); + + if (es_object_get_type (dict) != OPT_TYPE_DICT) + return OPT_ERR_TYPECHECK; + + EsObject *b = dict_op_known_and_get (dict, key, NULL) + ? es_true + : es_false; + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, b); + + return false; +} + +static EsObject* +op_store (OptVM *vm, EsObject *name) +{ + EsObject *val = ptrArrayLast (vm->ostack); + EsObject *key = ptrArrayItemFromLast (vm->ostack, 1); + + if (es_null (key)) + return OPT_ERR_TYPECHECK; + if (es_object_get_type (key) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + EsObject *dict = vm_dstack_known_and_get (vm, key, NULL); + if (es_object_get_type (dict) != OPT_TYPE_DICT) + vm_dict_def (vm, key, val); + else if (!(((DictFat *)es_fatptr_get (dict))->attr & ATTR_WRITABLE)) + return OPT_ERR_INVALIDACCESS; + else + dict_op_def (dict, key, val); + + ptrArrayDeleteLastInBatch(vm->ostack, 2); + return es_false; +} + +static EsObject* +op_load (OptVM *vm, EsObject *name) +{ + EsObject *key = ptrArrayLast (vm->ostack); + EsObject *val = NULL; + EsObject *dict = vm_dstack_known_and_get (vm, key, &val); + + if (es_object_get_type (dict) != OPT_TYPE_DICT) + return es_error_set_object (OPT_ERR_UNDEFINED, key); + else + { + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, val); + return es_false; + } +} + + +/* + * Operators for string manipulation + */ +static EsObject* +op_string (OptVM *vm, EsObject *name) +{ + EsObject *nobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; + + vString *s = vStringNew (); + + while (n-- > 0) + vStringPut (s, ' '); + + EsObject *sobj = string_new (s); + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, sobj); + es_object_unref (sobj); + return es_false; +} + +static EsObject* +op__strstr_common (OptVM *vm, EsObject *name, bool fromTail) +{ + EsObject *seekobj = ptrArrayLast (vm->ostack); + EsObject *strobj = ptrArrayItemFromLast (vm->ostack, 1); + + if (es_object_get_type (strobj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + if (es_object_get_type (seekobj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + + vString *stringv = es_pointer_get (strobj); + vString *seekv = es_pointer_get (seekobj); + + if (vStringLength (stringv) < vStringLength (seekv)) + { + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, es_false); + return es_false; + } + + const char *stringc = vStringValue (stringv); + const char *seekc = vStringValue (seekv); + char *tmp = (fromTail? strrstr: strstr) (stringc, seekc); + + if (tmp == NULL) + { + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, es_false); + return es_false; + } + + unsigned int ud = tmp - stringc; + int d = (int)ud; + if (d < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + + ptrArrayDeleteLast (vm->ostack); + EsObject* dobj = es_integer_new (d); + vm_ostack_push (vm, dobj); + es_object_unref (dobj); + vm_ostack_push (vm, es_true); + return es_false; +} + +static EsObject* +op__strstr (OptVM *vm, EsObject *name) +{ + return op__strstr_common (vm, name, false); +} + +static EsObject* +op__strrstr (OptVM *vm, EsObject *name) +{ + return op__strstr_common (vm, name, true); +} + +static EsObject* +op__strchr_common (OptVM *vm, EsObject *name, bool fromTail) +{ + EsObject *chrobj = ptrArrayLast (vm->ostack); + EsObject *strobj = ptrArrayItemFromLast (vm->ostack, 1); + + if (! es_integer_p (chrobj)) + return OPT_ERR_TYPECHECK; + + unsigned int chr = (unsigned int)es_integer_get (chrobj); + /* 0 is unacceptable. */ + if (! (0 < chr && chr < 256)) + return OPT_ERR_RANGECHECK; + + if (es_object_get_type (strobj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + + vString *strv = es_pointer_get (strobj); + const char *str = vStringValue (strv); + + char * p = (fromTail? strrchr: strchr) (str, (int)chr); + if (p) + { + int d = p - str; + if (d < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + ptrArrayDeleteLast (vm->ostack); + EsObject *dobj = es_integer_new (d); + vm_ostack_push (vm, dobj); + es_object_unref (dobj); + vm_ostack_push (vm, es_true); + return es_false; + } + else + { + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, es_false); + return es_false; + } +} + +static EsObject* +op__strchr (OptVM *vm, EsObject *name) +{ + return op__strchr_common (vm, name, false); +} + +static EsObject* +op__strrchr (OptVM *vm, EsObject *name) +{ + return op__strchr_common (vm, name, true); +} + +static EsObject* +op__strpbrk (OptVM *vm, EsObject *name) +{ + EsObject *acceptobj = ptrArrayLast (vm->ostack); + EsObject *strobj = ptrArrayItemFromLast (vm->ostack, 1); + + if (es_object_get_type (strobj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + if (es_object_get_type (acceptobj) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + + vString *strv = es_pointer_get (strobj); + vString *acceptv = es_pointer_get (acceptobj); + + const char *str = vStringValue (strv); + char *p = strpbrk (str, vStringValue (acceptv)); + if (p) + { + int d = p - str; + if (d < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + ptrArrayDeleteLast (vm->ostack); + EsObject *dobj = es_integer_new (d); + vm_ostack_push (vm, dobj); + es_object_unref (dobj); + vm_ostack_push (vm, es_true); + return es_false; + } + else + { + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, es_false); + return es_false; + } +} + + +/* + * Relation, logical, and bit operators + */ +static EsObject* +op__eq_full (OptVM *vm, EsObject *name, bool inversion) +{ + EsObject *a = ptrArrayItemFromLast (vm->ostack, 0); + EsObject *b = ptrArrayItemFromLast (vm->ostack, 1); + + bool eq = opt_es_eq (a, b); + EsObject *r = (inversion? (!eq): eq)? es_true: es_false; + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, r); + return es_false; +} + + +/* + * Relation, logical, and bit operators + */ +static EsObject* +op_eq (OptVM *vm, EsObject *name) +{ + op__eq_full (vm, name, false); + return es_false; + +} + +static EsObject* +op_ne (OptVM *vm, EsObject *name) +{ + op__eq_full (vm, name, true); + return es_false; + +} + +static EsObject* +op_true (OptVM *vm, EsObject *name) +{ + vm_ostack_push (vm, es_true); + return es_false; + +} + +static EsObject* +op_false (OptVM *vm, EsObject *name) +{ + vm_ostack_push (vm, es_false); + return es_false; +} + +#define CMP_OP(OP) \ + EsObject *o0 = ptrArrayLast (vm->ostack); \ + EsObject *o1 = ptrArrayItemFromLast (vm->ostack, 1); \ + EsObject *r; \ + \ + if (es_integer_p (o0)) \ + { \ + if (!es_integer_p (o1)) \ + return OPT_ERR_TYPECHECK; \ + \ + int i0 = es_integer_get (o0); \ + int i1 = es_integer_get (o1); \ + r = es_boolean_new (i1 OP i0); \ + } \ + else if (es_object_get_type (o0) == OPT_TYPE_STRING) \ + { \ + if (es_object_get_type (o1) != OPT_TYPE_STRING) \ + return OPT_ERR_TYPECHECK; \ + vString *vs0 = es_pointer_get (o0); \ + vString *vs1 = es_pointer_get (o1); \ + const char *s0 = vStringValue (vs0); \ + const char *s1 = vStringValue (vs1); \ + int d = strcmp (s1, s0); \ + r = es_boolean_new (d OP 0); \ + } \ + else \ + return OPT_ERR_TYPECHECK; \ + ptrArrayDeleteLastInBatch (vm->ostack, 2); \ + vm_ostack_push (vm, r); \ + es_object_unref (r); \ + return es_false + +static EsObject* +op_ge (OptVM *vm, EsObject *name) +{ + CMP_OP (>=); +} + +static EsObject* +op_gt (OptVM *vm, EsObject *name) +{ + CMP_OP (>); +} + +static EsObject* +op_le (OptVM *vm, EsObject *name) +{ + CMP_OP (<=); +} + +static EsObject* +op_lt (OptVM *vm, EsObject *name) +{ + CMP_OP (<); +} + +#define LOGBIT_OP(LOGOP, BITOP) \ + EsObject *o0 = ptrArrayLast (vm->ostack); \ + EsObject *o1 = ptrArrayItemFromLast (vm->ostack, 1); \ + EsObject *r; \ + \ + if (es_boolean_p (o0)) \ + { \ + if (!es_boolean_p (o1)) \ + return OPT_ERR_TYPECHECK; \ + bool b0 = es_boolean_get (o0); \ + bool b1 = es_boolean_get (o1); \ + bool b = b0 LOGOP b1; \ + r = es_boolean_new (b); \ + } \ + else if (es_integer_p (o0)) \ + { \ + if (!es_integer_p (o1)) \ + return OPT_ERR_TYPECHECK; \ + int i0 = es_integer_get (o0); \ + int i1 = es_integer_get (o1); \ + int i = i0 BITOP i1; \ + r = es_integer_new (i); \ + } \ + else \ + return OPT_ERR_TYPECHECK; \ + \ + ptrArrayDeleteLastInBatch (vm->ostack, 2); \ + vm_ostack_push (vm, r); \ + es_object_unref (r); \ + return es_false; + +static EsObject* +op_and (OptVM *vm, EsObject *name) +{ + LOGBIT_OP (&&, &); +} + +static EsObject* +op_or (OptVM *vm, EsObject *name) +{ + LOGBIT_OP (||, |); +} + +static EsObject* +op_xor (OptVM *vm, EsObject *name) +{ + LOGBIT_OP (!=, ^); +} + +static EsObject* +op_not (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayLast (vm->ostack); + EsObject *r; + + if (es_boolean_p (o)) + r = es_boolean_new (!es_boolean_get (o)); + else if (es_integer_p (o)) + r = es_integer_new (~ es_integer_get (o)); + else + return OPT_ERR_TYPECHECK; + + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, r); + es_object_unref (r); + return es_false; +} + +static EsObject* +op_bitshift (OptVM *vm, EsObject *name) +{ + EsObject *shiftobj = ptrArrayLast (vm->ostack); + if (!es_integer_p (shiftobj)) + return OPT_ERR_TYPECHECK; + + EsObject *iobj = ptrArrayItemFromLast (vm->ostack, 1); + if (!es_integer_p (iobj)) + return OPT_ERR_TYPECHECK; + + int shift = es_integer_get (shiftobj); + int i = es_integer_get (iobj); + + EsObject *r; + if (i == 0 || shift == 0) + r = es_object_ref (iobj); + else if (shift > 0) + r = es_integer_new (i << shift); + else + r = es_integer_new (i >> -shift); + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, r); + es_object_unref (r); + + return es_false; +} + + +/* + * Operators for control flow + */ +static EsObject* +op_exec (OptVM *vm, EsObject *name) +{ + EsObject *x = ptrArrayRemoveLast (vm->ostack); + + EsObject *e; + if (es_object_get_type (x) == OPT_TYPE_ARRAY + && (((ArrayFat *)es_fatptr_get (x))->attr & ATTR_EXECUTABLE)) + e = vm_call_proc (vm, x); + else + e = vm_eval (vm, x); + + es_object_unref (x); + return e; +} + +static EsObject* +op_if (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (proc) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *b = ptrArrayItemFromLast (vm->ostack, 1); + if (!es_boolean_p (b)) + return OPT_ERR_TYPECHECK; + + if (es_object_equal (b, es_false)) + { + ptrArrayDeleteLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + return es_false; + } + + es_object_ref (proc); + ptrArrayDeleteLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + EsObject *e = vm_call_proc (vm, proc); + es_object_unref (proc); + + return e; +} + +static EsObject* +op_ifelse (OptVM *vm, EsObject *name) +{ + EsObject *procf = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (procf) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (procf))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *proct = ptrArrayItemFromLast (vm->ostack, 1); + if (!((es_object_get_type (proct) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proct))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *b = ptrArrayItemFromLast (vm->ostack, 2); + if (!es_boolean_p (b)) + return OPT_ERR_TYPECHECK; + + EsObject *p = (es_object_equal (b, es_false))? procf: proct; + + es_object_ref (p); + ptrArrayDeleteLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + EsObject *e = vm_call_proc (vm, p); + es_object_unref (p); + + return e; +} + +static EsObject* +op_loop (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (proc) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + es_object_ref (proc); + ptrArrayDeleteLast (vm->ostack); + + EsObject *e; + while (true) + { + e = vm_call_proc (vm, proc); + if (es_object_equal (e, OPT_ERR_INVALIDEXIT)) + { + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + e = es_false; + break; + } + else if (es_error_p (e)) + break; + } + es_object_unref (proc); + return e; +} + +static EsObject* +op_exit (OptVM *vm, EsObject *name) +{ + return OPT_ERR_INVALIDEXIT; +} + +static EsObject* +op_repeat (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (proc) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *nobj = ptrArrayItemFromLast (vm->ostack, 1); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (nobj); + if (n < 0) + return OPT_ERR_RANGECHECK; + + es_object_ref (proc); + ptrArrayDeleteLast (vm->ostack); + ptrArrayDeleteLast (vm->ostack); + + EsObject *e = es_false;; + for (int i = 0; i < n; i++) + { + e = vm_call_proc (vm, proc); + if (es_object_equal (e, OPT_ERR_INVALIDEXIT)) + { + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + e = es_false; + break; + } + else if (es_error_p (e)) + break; + } + es_object_unref (proc); + return e; +} + +static EsObject* +op_stop (OptVM *vm, EsObject *name) +{ + return OPT_ERR_STOPPED; +} + +static EsObject* +op_stopped (OptVM *vm, EsObject *name) +{ + EsObject *e = op_exec (vm, name); + vm_ostack_push (vm, es_error_p (e)? es_true: es_false); + return es_false; +} + +static EsObject* +op_for (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (proc) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *limitobj = ptrArrayItemFromLast (vm->ostack, 1); + if (! es_integer_p (limitobj)) + return OPT_ERR_TYPECHECK; + int limit = es_integer_get (limitobj); + + EsObject *incrementobj = ptrArrayItemFromLast (vm->ostack, 2); + if (! es_integer_p (incrementobj)) + return OPT_ERR_TYPECHECK; + int increment = es_integer_get (incrementobj); + + EsObject *initialobj = ptrArrayItemFromLast (vm->ostack, 3); + if (! es_integer_p (initialobj)) + return OPT_ERR_TYPECHECK; + int initial = es_integer_get (initialobj); + + ptrArrayRemoveLast (vm->ostack); + ptrArrayDeleteLastInBatch (vm->ostack, 3); + + EsObject *r = es_false; + for (int i = initial; + (increment >= 0) ? (i <= limit) : (i >= limit); + i += increment) + { + EsObject *iobj = es_integer_new (i); + vm_ostack_push (vm, iobj); + r = vm_call_proc (vm, proc); + es_object_unref (iobj); + + if (es_object_equal (r, OPT_ERR_INVALIDEXIT)) + { + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + r = es_false; + break; + } + if (es_error_p (r)) + break; + } + es_object_unref (proc); + return r; +} + + +/* + * Operators for type, attribute and their conversion + */ +static EsObject* +op_type (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayRemoveLast (vm->ostack); + const char *n; + + if (o == es_nil) + n = "nulltype"; + else if (es_boolean_p (o)) + n = "booleantype"; + else if (es_integer_p (o)) + n = "integertype"; + else + { + int t = es_object_get_type (o); + n = es_type_get_name (t); + } + + EsObject *p = name_newS (n, ATTR_EXECUTABLE|ATTR_READABLE); + vm_ostack_push (vm, p); + es_object_unref (p); + es_object_unref (o); + return es_false; +} + +static EsObject* +op_cvn (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayLast (vm->ostack); + if (es_object_get_type (o) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + + vString *vstr = es_pointer_get (o); + const char *cstr = vStringValue (vstr); + StringFat *sfat = es_fatptr_get (o); + EsObject *n = name_newS (cstr, sfat->attr); + ptrArrayDeleteLast (vm->ostack); + vm_ostack_push (vm, n); + es_object_unref (n); + return es_false; +} + + +/* + * Misc operators + */ +static EsObject* +op_null (OptVM *vm, EsObject *name) +{ + vm_ostack_push (vm, es_nil); + return es_false; +} + +static EsObject* +op_bind (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!((es_object_get_type (proc) == OPT_TYPE_ARRAY) + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + vm_bind_proc (vm, es_pointer_get (proc)); + return es_false; +} + + +/* + * Methods for compound objects + */ +static EsObject* +op_length (OptVM *vm, EsObject *name) +{ + EsObject *o = ptrArrayLast (vm->ostack); + unsigned int c; + + int t = es_object_get_type (o); + if (t == OPT_TYPE_ARRAY) + { + ptrArray *a = es_pointer_get (o); + c = ptrArrayCount (a); + } + else if (t == OPT_TYPE_DICT) + { + hashTable *h = es_pointer_get (o); + c = hashTableCountItem (h); + } + else if (t == OPT_TYPE_STRING) + { + vString *s = es_pointer_get (o); + c = (unsigned int)vStringLength (s); + } + else if (t == OPT_TYPE_NAME) + { + EsObject *sym = es_pointer_get (o); + const char* cstr = es_symbol_get (sym); + c = (unsigned int) strlen (cstr); + } + else + return OPT_ERR_TYPECHECK; + + int n = c; + if (n < 0) + return OPT_ERR_INTERNALERROR; /* TODO: integer overflow */ + + ptrArrayDeleteLast (vm->ostack); + EsObject *nobj = es_integer_new (n); + vm_ostack_push (vm, nobj); + es_object_unref (nobj); + + return es_false; +} + +static EsObject* +op__get_array (OptVM *vm, EsObject *name, + EsObject *k, EsObject *obj) +{ + if (!es_integer_p (k)) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (k); + if (n < 0) + return OPT_ERR_RANGECHECK; + EsObject *r = array_op_get (obj, (unsigned int)n); + if (es_error_p (r)) + return r; + es_object_ref (r); + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, r); + es_object_unref (r); + return es_false; +} + +static EsObject* +op__get_dict (OptVM *vm, EsObject *name, + EsObject *k, EsObject *obj) +{ + EsObject *v = NULL; + if (!dict_op_known_and_get (obj, k, &v)) + return es_error_set_object (OPT_ERR_UNDEFINED, k); + es_object_ref (v); + ptrArrayDeleteLastInBatch (vm->ostack, 2); + vm_ostack_push (vm, v); + es_object_unref (v); + return es_false; +} + +static EsObject* +op__get_str (OptVM *vm, EsObject *name, + EsObject *k, EsObject *obj) +{ + if (!es_integer_p (k)) + return OPT_ERR_TYPECHECK; + int n = es_integer_get (k); + if (n < 0) + return OPT_ERR_RANGECHECK; + vString *s = es_pointer_get (obj); + unsigned int len = vStringLength (s); + if ((unsigned int)n >= len) + return OPT_ERR_RANGECHECK; + unsigned char chr = vStringChar (s, n); + ptrArrayDeleteLastInBatch (vm->ostack, 2); + EsObject *chrobj = es_integer_new (chr); + vm_ostack_push (vm, chrobj); + es_object_unref (chrobj); + return es_false; +} + +static EsObject* +op_get (OptVM *vm, EsObject *name) +{ + EsObject *k = ptrArrayLast (vm->ostack); + EsObject *obj = ptrArrayItemFromLast (vm->ostack, 1); + + int t = es_object_get_type (obj); + if (t == OPT_TYPE_ARRAY) + return op__get_array (vm, name, k, obj); + else if (t == OPT_TYPE_DICT) + return op__get_dict (vm, name, k, obj); + else if (t == OPT_TYPE_STRING) + return op__get_str (vm, name, k, obj); + + return OPT_ERR_TYPECHECK; +} + +static EsObject* +op__put_array (OptVM *vm, EsObject *name, + EsObject *v, EsObject *k, EsObject *array) +{ + if (!es_integer_p (k)) + return OPT_ERR_TYPECHECK; + int index = es_integer_get (k); + if (index < 0) + return OPT_ERR_RANGECHECK; + + array_op_put (array, (unsigned int)index, v); + ptrArrayDeleteLastInBatch (vm->ostack, 3); + return es_false; +} + +static EsObject* +op__put_dict (OptVM *vm, EsObject *name, + EsObject *v, EsObject *k, EsObject *dict) +{ + EsObject *key = k; + + if (es_null (key)) + return OPT_ERR_TYPECHECK; + + if (es_object_get_type (key) == OPT_TYPE_STRING) + { + const char *cstr = opt_string_get_cstr (key); + key = opt_name_new_from_cstr (cstr); + } + + if (es_object_get_type (key) != OPT_TYPE_NAME + && !es_integer_p (key) && !es_boolean_p (key)) + return OPT_ERR_TYPECHECK; + + dict_op_def (dict, key, v); + if (key != k) + es_object_unref (key); + ptrArrayDeleteLastInBatch (vm->ostack, 3); + return es_false; +} + +static EsObject* +op__put_str (OptVM *vm, EsObject *name, + EsObject *v, EsObject *k, EsObject *str) +{ + if (!es_integer_p (v)) + return OPT_ERR_TYPECHECK; + int c = es_integer_get (v); + if (!(c >= 0 && c < 256)) + return OPT_ERR_RANGECHECK; + if (!es_integer_p (k)) + return OPT_ERR_TYPECHECK; + int index = es_integer_get (k); + if (index < 0) + return OPT_ERR_RANGECHECK; + + vString *vstr = es_pointer_get (str); + size_t len = vStringLength (vstr); + if (len > (size_t)index) + { + if (c == 0) + vStringTruncate (vstr, (size_t)index); + else + vStringChar(vstr, index) = (char)c; + } + else + { + size_t d = index - len; + for (size_t i = 0; i < d; i++) + vStringPut (vstr, ' '); + if (c != 0) + vStringPut (vstr, (char)c); + } + + ptrArrayDeleteLastInBatch (vm->ostack, 3); + return es_false; +} + +static EsObject* +op_put (OptVM *vm, EsObject *name) +{ + EsObject *v = ptrArrayLast (vm->ostack); + EsObject *k = ptrArrayItemFromLast (vm->ostack, 1); + EsObject *obj = ptrArrayItemFromLast (vm->ostack, 2); + + int t = es_object_get_type (obj); + if (t == OPT_TYPE_ARRAY) + return op__put_array (vm, name, v, k, obj); + else if (t == OPT_TYPE_DICT) + return op__put_dict (vm, name, v, k, obj); + else if (t == OPT_TYPE_STRING) + return op__put_str (vm, name, v, k, obj); + + return OPT_ERR_TYPECHECK; +} + +static EsObject* +op__forall_array (OptVM *vm, EsObject *name, + EsObject *proc, EsObject *obj) +{ + 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++) + { + EsObject *o = ptrArrayItem (a, i); + es_object_ref (o); + vm_ostack_push (vm, o); + e = vm_call_proc (vm, proc); + es_object_unref (o); + if (es_error_p (e)) + break; + } + + return e; +} + +struct dictForallData { + OptVM *vm; + EsObject *proc; + EsObject *err; +}; + +static bool +dict_forall_cb (const void *key, void *value, void *user_data) +{ + bool r = true; + EsObject *k = (EsObject *)key; + EsObject *v = value; + struct dictForallData *d = user_data; + + /* TODO */ + if (es_symbol_p (k)) + k = name_new (k, ATTR_READABLE); + else + es_object_ref ((EsObject *)k); + es_object_ref (v); + + vm_ostack_push (d->vm, (EsObject *)k); + vm_ostack_push (d->vm, v); + EsObject *e = vm_call_proc (d->vm, d->proc); + if (es_error_p (e)) + { + d->err = e; + r = false; + } + es_object_unref ((EsObject *)k); + es_object_unref (v); + + return r; +} + +static EsObject* +op__forall_dict (OptVM *vm, EsObject *name, + EsObject *proc, EsObject *obj) +{ + EsObject *r = es_false;; + hashTable *ht = es_pointer_get (obj); + struct dictForallData data = { + .vm = vm, + .proc = proc + }; + + if (!hashTableForeachItem (ht, dict_forall_cb, &data)) + r = data.err; + + return r; +} + +static EsObject* +op__forall_string (OptVM *vm, EsObject *name, + EsObject *proc, EsObject *obj) +{ + 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++) + { + unsigned char chr = vStringChar (s, i); + EsObject *o = es_integer_new (chr); + vm_ostack_push (vm, o); + es_object_unref (o); + e = vm_call_proc (vm, proc); + if (es_error_p (e)) + break; + } + + return e; +} + +static EsObject* +op_forall (OptVM *vm, EsObject *name) +{ + EsObject *proc = ptrArrayLast (vm->ostack); + if (!(es_object_get_type (proc) == OPT_TYPE_ARRAY + && (((ArrayFat *)es_fatptr_get (proc))->attr & ATTR_EXECUTABLE))) + return OPT_ERR_TYPECHECK; + + EsObject *obj = ptrArrayItemFromLast (vm->ostack, 1); + + int t = es_object_get_type (obj); + EsObject * (* proc_driver) (OptVM *, EsObject *, + EsObject *, EsObject *) = NULL; + if (t == OPT_TYPE_ARRAY) + proc_driver = op__forall_array; + else if (t == OPT_TYPE_DICT) + proc_driver = op__forall_dict; + else if (t == OPT_TYPE_STRING) + proc_driver = op__forall_string; + else + return OPT_ERR_TYPECHECK; + + ptrArrayRemoveLast (vm->ostack); + ptrArrayRemoveLast (vm->ostack); + EsObject *e = (*proc_driver) (vm, name, proc, obj); + es_object_unref (proc); + es_object_unref (obj); + + if (es_object_equal (e, OPT_ERR_INVALIDEXIT)) + { + dict_op_def (vm->error, OPT_KEY_newerror, es_false); + e = es_false; + } + return e; +} + +static EsObject* +op__putinterval_array (OptVM *vm, EsObject *name, + ptrArray *srca, int index, ptrArray *dsta) +{ + unsigned int dlen = ptrArrayCount (dsta); + unsigned int slen = ptrArrayCount (srca); + if (dlen > index) + { + if ((dlen - index) <= slen) + { + ptrArrayDeleteLastInBatch (dsta, dlen - index); + 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, + es_object_ref (ptrArrayItem (srca, i)), + es_nil); + return es_false; + } + } + else if (dlen == index) + { + for (unsigned int i = 0; i < slen; i++) + ptrArrayAdd (dsta, es_object_ref (ptrArrayItem (srca, i))); + return es_false; + } + else + return OPT_ERR_RANGECHECK; +} + +static EsObject* +op__putinterval_string (OptVM *vm, EsObject *name, + vString *srcv, int index, vString *dstv) +{ + size_t dlen = vStringLength (dstv); + if (dlen > index) + { + size_t slen = vStringLength (srcv); + if ((dlen - index) <= slen) + { + vStringTruncate (dstv, (size_t)index); + vStringCat (dstv, srcv); + return es_false; + } + else + { + for (size_t i = 0; i < slen; i++) + vStringChar (dstv, index + i) = vStringChar (srcv, i); + return es_false; + } + } + else if (dlen == index) + { + vStringCat (dstv, srcv); + return es_false; + } + else + return OPT_ERR_RANGECHECK; +} + +static EsObject* +op_putinterval (OptVM *vm, EsObject *name) +{ + EsObject *src = ptrArrayLast (vm->ostack); + EsObject *indexobj = ptrArrayItemFromLast (vm->ostack, 1); + EsObject *dst = ptrArrayItemFromLast (vm->ostack, 2); + + int t = es_object_get_type (src); + if (t == OPT_TYPE_ARRAY || t == OPT_TYPE_STRING) + { + if (!es_integer_p (indexobj)) + return OPT_ERR_TYPECHECK; + if (es_object_get_type (dst) != t) + return OPT_ERR_TYPECHECK; + } + else + return OPT_ERR_TYPECHECK; + + int index = es_integer_get (indexobj); + if (index < 0) + return OPT_ERR_RANGECHECK; + + EsObject *r; + if (t == OPT_TYPE_ARRAY) + r = op__putinterval_array (vm, name, + es_pointer_get (src), + index, + es_pointer_get (dst)); + else + r = op__putinterval_string (vm, name, + es_pointer_get (src), + index, + es_pointer_get (dst)); + + if (!es_error_p (r)) + ptrArrayDeleteLastInBatch (vm->ostack, 3); + + return r; +} + +static EsObject* +op__copyinterval_array (OptVM *vm, EsObject *name, + ptrArray *dsta, + int count, + int index, + ptrArray *srca) +{ + unsigned long srcl = ptrArrayCount (srca); + + if ((unsigned long)index > srcl) + return OPT_ERR_RANGECHECK; + + if ((unsigned long)(index + count) > srcl) + return OPT_ERR_RANGECHECK; + + for (unsigned int i = (unsigned int)index; i < index + count; i++) + ptrArrayAdd (dsta, es_object_ref (ptrArrayItem (srca, i))); + return es_false; +} + +static EsObject* +op__copyinterval_string (OptVM *vm, EsObject *name, + vString *dsts, + int count, + int index, + vString *srcs) +{ + size_t srcl = vStringLength (srcs); + + if ((size_t)index > srcl) + return OPT_ERR_RANGECHECK; + + if ((size_t)(index + count) > srcl) + return OPT_ERR_RANGECHECK; + + vStringNCatSUnsafe (dsts, vStringValue (srcs) + index, (size_t)count); + return es_false; +} + +static EsObject* +op__copyinterval (OptVM *vm, EsObject *name) +{ + EsObject *dstobj = ptrArrayLast (vm->ostack); + EsObject *countobj = ptrArrayItemFromLast (vm->ostack, 1); + EsObject *indexobj = ptrArrayItemFromLast (vm->ostack, 2); + EsObject *srcobj = ptrArrayItemFromLast (vm->ostack, 3); + + int t = es_object_get_type (dstobj); + if (! (t == OPT_TYPE_ARRAY || t == OPT_TYPE_STRING)) + return OPT_ERR_TYPECHECK; + if (t != es_object_get_type (srcobj)) + return OPT_ERR_TYPECHECK; + + if (!es_integer_p (countobj)) + return OPT_ERR_TYPECHECK; + if (!es_integer_p (indexobj)) + return OPT_ERR_TYPECHECK; + + int count = es_integer_get (countobj); + if (count < 0) + return OPT_ERR_RANGECHECK; + + int index = es_integer_get (indexobj); + if (index < 0) + return OPT_ERR_RANGECHECK; + + EsObject* r; + if (t == OPT_TYPE_ARRAY) + r = op__copyinterval_array (vm, name, + es_pointer_get (dstobj), + count, + index, + es_pointer_get (srcobj)); + else + r = op__copyinterval_string (vm, name, + es_pointer_get (dstobj), + count, + index, + es_pointer_get (srcobj)); + + if (es_error_p (r)) + return r; + + es_object_ref (dstobj); + ptrArrayDeleteLastInBatch (vm->ostack, 4); + vm_ostack_push (vm, dstobj); + es_object_unref (dstobj); + return r; +} diff --git a/ctags/dsl/optscript.h b/ctags/dsl/optscript.h new file mode 100644 index 0000000000..8a2b122879 --- /dev/null +++ b/ctags/dsl/optscript.h @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2020, Masatake YAMATO +* Copyright (c) 2020, 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. +*/ + +#ifndef OPTSCRIPT_H +#define OPTSCRIPT_H + +#include "general.h" + +#include "es.h" +#include "mio.h" +#include "ptrarray.h" + +typedef struct sOptVM OptVM; +typedef EsObject* (* OptOperatorFn) (OptVM *, EsObject *); + +struct OptHelpExtender { + void (* add) (ptrArray *, void *); + const char* (* get_help_str) (EsObject *, void *); +}; + +int opt_init (void); + +OptVM *opt_vm_new (MIO *in, MIO *out, MIO *err); +void opt_vm_delete (OptVM *vm); + +EsObject *opt_vm_read (OptVM *vm, MIO *in); +EsObject *opt_vm_eval (OptVM *vm, EsObject *obj); +void opt_vm_report_error (OptVM *vm, EsObject *eobj, MIO *err); + +void *opt_vm_set_app_data (OptVM *vm, void *app_data); +void *opt_vm_get_app_data (OptVM *vm); + +char *opt_vm_set_prompt (OptVM *vm, char *prompt); +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_dstack_push (OptVM *vm, EsObject *dict); +void opt_vm_dstack_pop (OptVM *vm); + +EsObject* opt_vm_ostack_top (OptVM *vm); +EsObject* opt_vm_ostack_peek (OptVM *vm, int index_from_top); +EsObject* opt_vm_ostack_pop (OptVM *vm); +void opt_vm_ostack_push (OptVM *vm, EsObject *obj); +unsigned int opt_vm_ostack_count (OptVM *vm); + +EsObject *opt_dict_new (unsigned int size); +bool opt_dict_known_and_get_cstr (EsObject *dict, const char* name, EsObject **val); +bool opt_dict_foreach (EsObject *dict, bool (* fn) (EsObject *, EsObject *, void*), void *data); +void opt_dict_def (EsObject *dict, EsObject *sym, EsObject *val); +bool opt_dict_undef (EsObject *dict, EsObject *sym); +void opt_dict_clear (EsObject *dict); + +EsObject *opt_array_new (void); +EsObject *opt_array_get (const EsObject *array, unsigned int index); +void opt_array_put (EsObject *array, unsigned int index, EsObject *obj); +void opt_array_add (EsObject *array, EsObject *elt); + +unsigned int opt_array_length(const EsObject *array); + +EsObject *opt_operator_new (OptOperatorFn op, const char *name, int arity, const char *help_str); + +EsObject *opt_string_new_from_cstr (const char *cstr); +const char* opt_string_get_cstr (const EsObject *str); + +EsObject *opt_name_new_from_cstr (const char *cstr); +const char* opt_name_get_cstr (const EsObject *name); + +extern EsObject *OPT_ERR_TYPECHECK; +extern EsObject *OPT_ERR_QUIT; +extern EsObject *OPT_ERR_RANGECHECK; +extern EsObject *OPT_ERR_UNDERFLOW; + +extern int OPT_TYPE_ARRAY; +extern int OPT_TYPE_DICT; +extern int OPT_TYPE_OPERATOR; +extern int OPT_TYPE_STRING; +extern int OPT_TYPE_NAME; +extern int OPT_TYPE_MARK; + +#endif diff --git a/ctags/main/CommonPrelude.c b/ctags/main/CommonPrelude.c new file mode 100644 index 0000000000..8def2544cf --- /dev/null +++ b/ctags/main/CommonPrelude.c @@ -0,0 +1,162 @@ +const char ctagsCommonPrelude []= +"%\n" +"% Copyright (c) 2021, Masatake YAMATO\n" +"% Copyright (c) 2021, Red Hat, Inc.\n" +"%\n" +"% This source code is released for free distribution under the terms of the\n" +"% GNU General Public License version 2 or (at your option) any later version.\n" +"%\n" +"\n" +"%\n" +"% The documentation table\n" +"%\n" +"\n" +"% __PROCDOCS:dict\n" +"/__procdocs 30 dict def\n" +"\n" +"% name value __BDEF -\n" +"/__bdef { bind def } bind def\n" +"\n" +"% doc:string key:name any:val __DOCDEF -\n" +"/__bddef {\n" +" 1 index exch __bdef\n" +" exch __procdocs 3 1 roll put\n" +"} __bdef\n" +"\n" +"\n" +"%\n" +"% procedures\n" +"%\n" +"(any n:int _NDUP any1 ... anyn)\n" +"/_ndup { { dup } repeat } __bddef\n" +"\n" +"(x:any x:any _DEDUP x:any\n" +" x:any y:any _DEDUP x:any y:any)\n" +"/_dedup {\n" +" count 1 gt {\n" +" 2 copy eq {\n" +" pop\n" +" } if\n" +" } if\n" +"} __bddef\n" +"\n" +"(space:int space:int _DEDUP_SPACES space:int\n" +" otherthanspace:int space:int _DEDUP_SPACES otherthanspace:int space:int)\n" +"/_dedup_spaces {\n" +" count 0 gt {\n" +" dup ?\\_ eq {\n" +" _dedup\n" +" } if\n" +" } if\n" +"} __bddef\n" +"\n" +"% 32 32 _dedup_spaces pstack clear (---) ==\n" +"% 32 41 _dedup_spaces pstack clear (---) ==\n" +"% 41 32 _dedup_spaces pstack clear (---) ==\n" +"% 32 _dedup_spaces pstack clear (---) ==\n" +"% 41 _dedup_spaces pstack clear (---) ==\n" +"% quit\n" +"\n" +"/__buildstring {\n" +" {\n" +" counttomark dup 1 eq {\n" +" pop exch pop\n" +" exit\n" +" } {\n" +" -1 roll 1 index exch _putlast!\n" +" } ifelse\n" +" } loop\n" +"} __bdef\n" +"\n" +"(mark char:int|substring:string... _BUILDSTRING string)\n" +"/_buildstring {\n" +" 0 string __buildstring\n" +"} __bddef\n" +"\n" +"(string char:int|string _PUTLAST! -)\n" +"/_putlast! {\n" +" 1 index length exch\n" +" dup type /integertype eq {\n" +" put\n" +" } {\n" +" putinterval\n" +" } ifelse\n" +"} __bddef\n" +"\n" +"(target:string fromto:str _TR! -)\n" +"/_tr! {\n" +" %\n" +" % () is not allowed.\n" +" % The reason must be be documented.\n" +" %\n" +" 0 string\n" +" % str [int int] str'\n" +" 2 index {\n" +" % str [int int] str' int\n" +" dup 3 index 0 get\n" +" % str [int int] str' int int int\n" +" eq {% str [int int] str' int\n" +" pop\n" +" dup 2 index 1 get _putlast!\n" +" } {% str [int int] str' int\n" +" 1 index exch _putlast!\n" +" } ifelse\n" +" } forall\n" +" % str [int int] str'\n" +" exch pop\n" +" 0 exch putinterval\n" +"} __bddef\n" +"\n" +"(string _NORMALIZE_SPACES! -)\n" +"/_normalize_spaces! {\n" +" dup\n" +" dup (\\n ) _tr!\n" +" dup (\\t ) _tr!\n" +" dup (\\r ) _tr!\n" +" dup (\\f ) _tr!\n" +" dup (\\v ) _tr!\n" +" mark exch { _dedup_spaces } forall _buildstring\n" +" exch copy pop\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" +"(tag:int _SCOPEREF -)\n" +"/_scoperef {\n" +" _scopetop not { 0 } if scope:\n" +"} __bddef\n" +"\n" +"(tag:int _SCOPEPUSH -)\n" +"/_scopepush {\n" +" dup _scoperef _scopeset\n" +"} __bddef\n" +"\n" +"(string _ISSTRING string true\n" +" any:!string _ISSTRING false)\n" +"/_isstring {\n" +" dup type /stringtype eq {\n" +" true\n" +" } {\n" +" pop false\n" +" } ifelse\n" +"} __bddef\n" +"\n" +"(array key _AMEMBER true|fales)\n" +"/_amember {\n" +" false 3 1 roll\n" +" % false array key\n" +" exch {\n" +" % false key elt\n" +" 1 index\n" +" % false key elt key\n" +" eq {\n" +" % false key\n" +" exch pop true exch\n" +" exit\n" +" } if\n" +" % false key\n" +" } forall\n" +" pop\n" +"} __bddef\n" +; diff --git a/ctags/main/args.c b/ctags/main/args.c index 60ba288716..3bf012b011 100644 --- a/ctags/main/args.c +++ b/ctags/main/args.c @@ -145,8 +145,8 @@ static char* nextFileLine (FILE* const fp) c = ungetc (c, fp); } vStringStripTrailing (vs); - result = xMalloc (vStringLength (vs) + 1, char); vStringStripLeading (vs); + result = xMalloc (vStringLength (vs) + 1, char); strcpy (result, vStringValue (vs)); } vStringDelete (vs); @@ -161,18 +161,120 @@ static bool isCommentLine (char* line) return (*line == '#'); } +static bool isOptscriptLine (char *line) +{ + size_t len = strlen (line); + if (len < 2) + return false; + if (line [len - 1] == '{' && line [len - 2] == '{') + return true; + return false; +} + +static char* nextOptscriptLines (FILE* const fp, char *line) +{ + vString *vstr = vStringNewInit (line); + vStringPut (vstr, '\n'); + eFree (line); + + /* \n}}, \n=>1, }=>2, }=>3 */ + int endMarkers = 0; + int c; + while (true) + { + c = fgetc (fp); + if (c == EOF) + break; + + if (c == '\r' || c == '\n') + { + if (c == '\r') + { + c = fgetc (fp); + if (c != '\n') + { + ungetc(c, fp); + c = '\n'; + } + } + if (c == '\n') + { + vStringPut (vstr, c); + if (endMarkers != 1) + endMarkers = 1; + } + } + else if (c == '}') + { + vStringPut (vstr, c); + if (endMarkers == 1 || endMarkers == 2) + endMarkers++; + if (endMarkers == 3) + break; + } + else + { + endMarkers = 0; + vStringPut (vstr, c); + } + } + + if (c == EOF) + { + switch (endMarkers) + { + case 0: + vStringPut (vstr, '\n'); + /* Fall through */ + case 1: + vStringPut (vstr, '}'); + /* Fall through */ + case 2: + vStringPut (vstr, '}'); + default: + break; + } + } + + c = fgetc (fp); + while (c != EOF) + { + if (c == '\n') + break; + if (c == '\r') + { + c = fgetc (fp); + if (c == '\n') + break; + ungetc (c, fp); + } + c = fgetc (fp); + } + return vStringDeleteUnwrap (vstr); +} + static char* nextFileLineSkippingComments (FILE* const fp) { char* result; bool comment; + bool optscript; do { result = nextFileLine (fp); - comment = (result && isCommentLine (result)); + comment = false; + optscript = false; + if (result) + { + comment = isCommentLine (result); + optscript = isOptscriptLine (result); + } if (comment) eFree (result); + else if (optscript) + result = nextOptscriptLines (fp, result); } while (comment); + return result; } diff --git a/ctags/main/dependency.c b/ctags/main/dependency.c index 13f27aac77..9f4767cb22 100644 --- a/ctags/main/dependency.c +++ b/ctags/main/dependency.c @@ -145,17 +145,18 @@ extern void notifyInputStart (void) { subparser *s; - foreachSubparser(s, false) - { - langType lang = getSubparserLanguage (s); - notifyLanguageRegexInputStart (lang); + /* for running prelude of optlib */ + langType lang = getInputLanguage (); + notifyLanguageRegexInputStart (lang); + foreachSubparser(s, true) + { + enterSubparser(s); if (s->inputStart) - { - enterSubparser(s); s->inputStart (s); - leaveSubparser(); - } + /* propagate the event recursively */ + notifyInputStart (); + leaveSubparser(); } } @@ -163,18 +164,19 @@ extern void notifyInputEnd (void) { subparser *s; - foreachSubparser(s, false) + foreachSubparser(s, true) { + enterSubparser(s); + /* propagate the event recursively */ + notifyInputEnd (); if (s->inputEnd) - { - enterSubparser(s); s->inputEnd (s); - leaveSubparser(); - } - - langType lang = getSubparserLanguage (s); - notifyLanguageRegexInputEnd (lang); + leaveSubparser(); } + + /* for running sequel of optlib */ + langType lang = getInputLanguage (); + notifyLanguageRegexInputEnd (lang); } extern void notifyMakeTagEntry (const tagEntryInfo *tag, int corkIndex) @@ -292,6 +294,19 @@ extern slaveParser *getFirstSlaveParser (struct slaveControlBlock *scb) return NULL; } +extern subparser *getLanguageSubparser (langType sublang, + bool including_none_crafted_parser) +{ + subparser *s; + + foreachSubparser (s, including_none_crafted_parser) + { + if (getSubparserLanguage (s) == sublang) + return s; + } + return NULL; +} + extern struct colprintTable * subparserColprintTableNew (void) { return colprintTableNew ("L:NAME", "L:BASEPARSER", "L:DIRECTIONS", NULL); diff --git a/ctags/main/entry.c b/ctags/main/entry.c index d07de24ef7..048745b21b 100644 --- a/ctags/main/entry.c +++ b/ctags/main/entry.c @@ -203,6 +203,9 @@ extern void makeFileTag (const char *const fileName) tag.extensionFields.endLine = getInputLineNumber (); } + if (isFieldEnabled (FIELD_EPOCH)) + tag.extensionFields.epoch = getInputFileMtime (); + makeTagEntry (&tag); } @@ -1066,8 +1069,6 @@ static tagEntryInfoX *copyTagEntry (const tagEntryInfo *const tag, slot->name = eStrdup (slot->name); if (slot->extensionFields.access) slot->extensionFields.access = eStrdup (slot->extensionFields.access); - if (slot->extensionFields.fileScope) - slot->extensionFields.fileScope = eStrdup (slot->extensionFields.fileScope); if (slot->extensionFields.implementation) slot->extensionFields.implementation = eStrdup (slot->extensionFields.implementation); if (slot->extensionFields.inheritance) @@ -1144,8 +1145,6 @@ static void deleteTagEnry (void *data) if (slot->extensionFields.access) eFree ((char *)slot->extensionFields.access); - if (slot->extensionFields.fileScope) - eFree ((char *)slot->extensionFields.fileScope); if (slot->extensionFields.implementation) eFree ((char *)slot->extensionFields.implementation); if (slot->extensionFields.inheritance) @@ -1450,6 +1449,7 @@ static int queueTagEntry(const tagEntryInfo *const tag) corkIndex = (int)ptrArrayAdd (TagFile.corkQueue, entry); entry->corkIndex = corkIndex; + entry->slot.inCorkQueue = 1; return corkIndex; } @@ -1652,12 +1652,17 @@ extern size_t countEntryInCorkQueue (void) return ptrArrayCount (TagFile.corkQueue); } +extern void markTagPlaceholder (tagEntryInfo *e, bool placeholder) +{ + e->placeholder = placeholder; +} + extern int makePlaceholder (const char *const name) { tagEntryInfo e; initTagEntry (&e, name, KIND_GHOST_INDEX); - e.placeholder = 1; + markTagPlaceholder(&e, true); /* * makePlaceholder may be called even before reading any bytes @@ -1766,6 +1771,13 @@ extern int makeQualifiedTagEntry (const tagEntryInfo *const e) return r; } +extern void setTagPositionFromTag (tagEntryInfo *const dst, + const tagEntryInfo *const src) +{ + dst->lineNumber = src->lineNumber; + dst->filePosition = src->filePosition; +} + static void initTagEntryFull (tagEntryInfo *const e, const char *const name, unsigned long lineNumber, langType langType_, @@ -1802,6 +1814,8 @@ static void initTagEntryFull (tagEntryInfo *const e, const char *const name, if (roleBits) markTagExtraBit (e, XTAG_REFERENCE_TAGS); + e->extensionFields.nth = NO_NTH_FIELD; + if (doesParserRunAsGuest ()) markTagExtraBit (e, XTAG_GUEST); if (doesSubparserRun ()) @@ -1885,8 +1899,9 @@ static void markTagExtraBitFull (tagEntryInfo *const tag, xtagType extra, int n = countXtags () - XTAG_COUNT; tag->extraDynamic = xCalloc ((n / 8) + 1, uint8_t); - PARSER_TRASH_BOX(tag->extraDynamic, eFree); - markTagExtraBit (tag, extra); + if (!tag->inCorkQueue) + PARSER_TRASH_BOX(tag->extraDynamic, eFree); + markTagExtraBitFull (tag, extra, mark); return; } @@ -1914,7 +1929,6 @@ extern bool isTagExtraBitMarked (const tagEntryInfo *const tag, xtagType extra) index = (extra / 8); offset = (extra % 8); slot = tag->extra; - } else if (!tag->extraDynamic) return false; @@ -1924,14 +1938,13 @@ extern bool isTagExtraBitMarked (const tagEntryInfo *const tag, xtagType extra) index = ((extra - XTAG_COUNT) / 8); offset = ((extra - XTAG_COUNT) % 8); slot = tag->extraDynamic; - } return !! ((slot [ index ]) & (1 << offset)); } extern bool isTagExtra (const tagEntryInfo *const tag) { - for (unsigned int i = 0; i < XTAG_COUNT; i++) + for (unsigned int i = 0; i < countXtags(); i++) if (isTagExtraBitMarked (tag, i)) return true; return false; @@ -2010,15 +2023,28 @@ extern void tagFilePosition (MIOPos *p) "failed to get file position of the tag file\n"); } -extern void setTagFilePosition (MIOPos *p) +extern void setTagFilePosition (MIOPos *p, bool truncation) { /* mini-geany doesn't set TagFile.mio. */ if (TagFile.mio == NULL) return; + + long t0 = 0; + if (truncation) + t0 = mio_tell (TagFile.mio); + if (mio_setpos (TagFile.mio, p) == -1) error (FATAL|PERROR, "failed to set file position of the tag file\n"); + + if (truncation) + { + long t1 = mio_tell (TagFile.mio); + if (!mio_try_resize (TagFile.mio, (size_t)t1)) + error (FATAL|PERROR, + "failed to truncate the tag file %ld -> %ld\n", t0, t1); + } } extern const char* getTagFileDirectory (void) diff --git a/ctags/main/entry.h b/ctags/main/entry.h index 335ec01e74..71bd07d84f 100644 --- a/ctags/main/entry.h +++ b/ctags/main/entry.h @@ -16,6 +16,7 @@ #include "types.h" #include +#include #include "field.h" #include "xtag.h" @@ -23,6 +24,8 @@ #include "ptrarray.h" #include "nestlevel.h" +#include "script_p.h" + /* * MACROS */ @@ -45,13 +48,14 @@ struct sTagEntryInfo { 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; /* This is just a part of scope context. + 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. */ 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 */ const char* pattern; /* pattern for locating input line @@ -67,7 +71,6 @@ struct sTagEntryInfo { struct { const char* access; - const char* fileScope; const char* implementation; const char* inheritance; @@ -96,6 +99,9 @@ struct sTagEntryInfo { const char* xpath; #endif unsigned long endLine; + time_t epoch; +#define NO_NTH_FIELD -1 + short nth; } extensionFields; /* list of extension fields*/ /* `usedParserFields' tracks how many parser own fields are @@ -141,6 +147,7 @@ extern bool isRoleAssigned(const tagEntryInfo *const e, int roleIndex); extern int makeQualifiedTagEntry (const tagEntryInfo *const e); +extern void setTagPositionFromTag (tagEntryInfo *const dst, const tagEntryInfo *const src); #define CORK_NIL 0 tagEntryInfo *getEntryInCorkQueue (int n); @@ -162,8 +169,9 @@ void registerEntry (int corkIndex); * under the scope. * * If FUNC returns false, this function returns false. - * If FUNC never returns false, this func returns true. - * If FUNC is not called because no node for NAME in the symbol table. + * 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. */ bool foreachEntriesInScope (int corkIndex, const char *name, /* or NULL */ @@ -256,6 +264,7 @@ extern void attachParserFieldToCorkEntry (int index, fieldType ftype, const char extern const char* getParserFieldValueForType (tagEntryInfo *const tag, fieldType ftype); extern int makePlaceholder (const char *const name); +extern void markTagPlaceholder (tagEntryInfo *e, bool placeholder); /* Marking all tag entries entries under the scope specified * with index recursively. diff --git a/ctags/main/entry_p.h b/ctags/main/entry_p.h index 6172eaf528..3809294de8 100644 --- a/ctags/main/entry_p.h +++ b/ctags/main/entry_p.h @@ -39,7 +39,7 @@ extern unsigned long numTagsTotal(void); extern unsigned long maxTagsLine(void); extern void invalidatePatternCache(void); extern void tagFilePosition (MIOPos *p); -extern void setTagFilePosition (MIOPos *p); +extern void setTagFilePosition (MIOPos *p, bool truncation); extern const char* getTagFileDirectory (void); extern void getTagScopeInformation (tagEntryInfo *const tag, const char **kind, const char **name); diff --git a/ctags/main/error.c b/ctags/main/error.c index bc5493dd34..13213da016 100644 --- a/ctags/main/error.c +++ b/ctags/main/error.c @@ -35,7 +35,8 @@ extern bool stderrDefaultErrorPrinter (const errorSelection selection, va_list ap, void *data CTAGS_ATTR_UNUSED) { fprintf (stderr, "%s: %s", getExecutableName (), - selected (selection, WARNING) ? "Warning: " : ""); + selected (selection, WARNING) ? "Warning: " : + selected (selection, NOTICE) ? "Notice: " : ""); vfprintf (stderr, format, ap); if (selected (selection, PERROR)) { @@ -56,6 +57,9 @@ extern void error (const errorSelection selection, va_list ap; bool shouldExit; + if (Option.quiet && selected (selection, NOTICE)) + return; + va_start (ap, format); shouldExit = (* errorPrinter) (selection, format, ap, errorPrinterData); va_end (ap); @@ -77,6 +81,8 @@ bool jsonErrorPrinter (const errorSelection selection, const char *const format, json_t *response = json_object (); json_object_set_new (response, "_type", json_string ("error")); json_object_set_new (response, "message", json_string (reason)); + if (selected (selection, NOTICE)) + json_object_set_new (response, "notice", json_true ()); if (selected (selection, WARNING)) json_object_set_new (response, "warning", json_true ()); if (selected (selection, FATAL)) diff --git a/ctags/main/field.c b/ctags/main/field.c index 22a97d01a5..7f1b21ac19 100644 --- a/ctags/main/field.c +++ b/ctags/main/field.c @@ -31,6 +31,8 @@ #include "writer_p.h" #include "xtag_p.h" +#include "optscript.h" + #define FIELD_NULL_LETTER_CHAR '-' #define FIELD_NULL_LETTER_STRING "-" @@ -67,13 +69,14 @@ static const char *renderFieldExtras (const tagEntryInfo *const tag, const char static const char *renderFieldXpath (const tagEntryInfo *const tag, const char *value, vString* b); static const char *renderFieldScopeKindName(const tagEntryInfo *const tag, const char *value, vString* b); static const char *renderFieldEnd (const tagEntryInfo *const tag, const char *value, vString* b); +static const char *renderFieldEpoch (const tagEntryInfo *const tag, const char *value, vString* b); +static const char *renderFieldNth (const tagEntryInfo *const tag, const char *value, vString* b); static bool doesContainAnyCharInName (const tagEntryInfo *const tag, const char *value, const char *chars); static bool doesContainAnyCharInInput (const tagEntryInfo *const tag, const char*value, const char *chars); static bool doesContainAnyCharInFieldScope (const tagEntryInfo *const tag, const char *value, const char *chars); static bool doesContainAnyCharInSignature (const tagEntryInfo *const tag, const char *value, const char *chars); -static bool isLanguageFieldAvailable (const tagEntryInfo *const tag); static bool isTyperefFieldAvailable (const tagEntryInfo *const tag); static bool isFileFieldAvailable (const tagEntryInfo *const tag); static bool isInheritsFieldAvailable (const tagEntryInfo *const tag); @@ -83,150 +86,372 @@ static bool isSignatureFieldAvailable (const tagEntryInfo *const tag); static bool isExtrasFieldAvailable (const tagEntryInfo *const tag); static bool isXpathFieldAvailable (const tagEntryInfo *const tag); static bool isEndFieldAvailable (const tagEntryInfo *const tag); - - -#define DEFINE_FIELD(L, N, V, H, DT, RE) \ - DEFINE_FIELD_FULL (L, N, V, H, NULL, DT, RE, NULL, NULL) -#define DEFINE_FIELD_FULL(L, N, V, H, A, DT, RE, RN, DCAC) \ - { \ - .letter = L, \ - .name = N, \ - .description = H, \ - .enabled = V, \ - .render = RE, \ - .renderNoEscaping= RN, \ - .doesContainAnyChar = DCAC, \ - .isValueAvailable = A, \ - .dataType = DT, \ - } +static bool isEpochAvailable (const tagEntryInfo *const tag); +static bool isNthAvailable (const tagEntryInfo *const tag); + +static EsObject* getFieldValueForName (const tagEntryInfo *, const fieldDefinition *); +static EsObject* setFieldValueForName (tagEntryInfo *, const fieldDefinition *, const EsObject *); +static EsObject* getFieldValueForInput (const tagEntryInfo *, const fieldDefinition *); +static EsObject* getFieldValueForKind (const tagEntryInfo *, const fieldDefinition *); +static EsObject* getFieldValueForTyperef (const tagEntryInfo *, const fieldDefinition *); +static EsObject* setFieldValueForTyperef (tagEntryInfo *, const fieldDefinition *, const EsObject *); +static EsObject* checkFieldValueForTyperef (const fieldDefinition *, const EsObject *); +static EsObject* getFieldValueForScope (const tagEntryInfo *, const fieldDefinition *); +static EsObject* setFieldValueForScope (tagEntryInfo *, const fieldDefinition *, const EsObject *); +static EsObject* checkFieldValueForScope (const fieldDefinition *, const EsObject *); +static EsObject* getFieldValueForExtras (const tagEntryInfo *, const fieldDefinition *); +static EsObject* getFieldValueForSignature (const tagEntryInfo *, const fieldDefinition *); +static EsObject* setFieldValueForSignature (tagEntryInfo *, const fieldDefinition *, const EsObject *); +static EsObject* getFieldValueForRoles (const tagEntryInfo *, const fieldDefinition *); +static EsObject* getFieldValueForLineCommon (const tagEntryInfo *, const fieldDefinition *); +static EsObject* checkFieldValueForLineCommon (const fieldDefinition *, const EsObject *); +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) static fieldDefinition fieldDefinitionsFixed [] = { - /* FIXED FIELDS */ - DEFINE_FIELD_FULL ('N', "name", true, - "tag name", - NULL, - FIELDTYPE_STRING, - renderFieldName, renderFieldNameNoEscape, - doesContainAnyCharInName), - DEFINE_FIELD_FULL ('F', "input", true, - "input file", - NULL, - FIELDTYPE_STRING, - renderFieldInput, renderFieldInputNoEscape, - doesContainAnyCharInInput), - DEFINE_FIELD ('P', "pattern", true, - "pattern", - FIELDTYPE_STRING|FIELDTYPE_BOOL, - renderFieldPattern), + [FIELD_NAME] = { + .letter = 'N', + .name = "name", + .description = "tag name", + .enabled = true, + .render = renderFieldName, + .renderNoEscaping = renderFieldNameNoEscape, + .doesContainAnyChar = doesContainAnyCharInName, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + .getterValueType = NULL, + .getValueObject = getFieldValueForName, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = setFieldValueForName, + }, + [FIELD_INPUT_FILE] = { + .letter = 'F', + .name = "input", + .description = "input file", + .enabled = true, + .render = renderFieldInput, + .renderNoEscaping = renderFieldInputNoEscape, + .doesContainAnyChar = doesContainAnyCharInInput, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + .getterValueType = NULL, + .getValueObject = getFieldValueForInput, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = NULL, + }, + [FIELD_PATTERN] = { + .letter = 'P', + .name = "pattern", + .description = "pattern", + .enabled = true, + .render = renderFieldPattern, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING|FIELDTYPE_BOOL, + }, }; static fieldDefinition fieldDefinitionsExuberant [] = { - DEFINE_FIELD ('C', "compact", false, - "compact input line (used only in xref output)", - FIELDTYPE_STRING, - renderFieldCompactInputLine), - - /* EXTENSION FIELDS */ - DEFINE_FIELD_FULL ('a', "access", false, - "Access (or export) of class members", - isAccessFieldAvailable, - FIELDTYPE_STRING, - renderFieldAccess, NULL, NULL), - DEFINE_FIELD_FULL ('f', "file", true, - "File-restricted scoping", - isFileFieldAvailable, - FIELDTYPE_BOOL, - renderFieldFile, NULL, NULL), - DEFINE_FIELD_FULL ('i', "inherits", false, - "Inheritance information", - isInheritsFieldAvailable, - FIELDTYPE_STRING|FIELDTYPE_BOOL, - renderFieldInherits, NULL, NULL), - DEFINE_FIELD ('K', NULL, false, - "Kind of tag in long-name form", - FIELDTYPE_STRING, - renderFieldKindName), - DEFINE_FIELD ('k', NULL, true, - "Kind of tag in one-letter form", - FIELDTYPE_STRING, - renderFieldKindLetter), - DEFINE_FIELD_FULL ('l', "language", false, - "Language of input file containing tag", - isLanguageFieldAvailable, - FIELDTYPE_STRING, - renderFieldLanguage, NULL, NULL), - DEFINE_FIELD_FULL ('m', "implementation", false, - "Implementation information", - isImplementationFieldAvailable, - FIELDTYPE_STRING, - renderFieldImplementation, NULL, NULL), - DEFINE_FIELD ('n', "line", false, - "Line number of tag definition", - FIELDTYPE_INTEGER, - renderFieldLineNumber), - DEFINE_FIELD_FULL ('S', "signature", false, - "Signature of routine (e.g. prototype or parameter list)", - isSignatureFieldAvailable, - FIELDTYPE_STRING, - renderFieldSignature, renderFieldSignatureNoEscape, - doesContainAnyCharInSignature), - DEFINE_FIELD_FULL ('s', NULL, true, - "[tags output] scope (kind:name) of tag definition, [xref and json output] name of scope", - NULL, - FIELDTYPE_STRING, - renderFieldScope, renderFieldScopeNoEscape, - doesContainAnyCharInFieldScope), - DEFINE_FIELD_FULL ('t', "typeref", true, - "Type and name of a variable or typedef", - isTyperefFieldAvailable, - FIELDTYPE_STRING, - renderFieldTyperef, NULL, NULL), - DEFINE_FIELD ('z', "kind", false, - "[tags output] prepend \"kind:\" to k/ (or K/) field output, [xref and json output] kind in long-name form", - FIELDTYPE_STRING, - /* Following renderer is for handling --_xformat=%{kind}; - and is not for tags output. */ - renderFieldKindName), + [FIELD_COMPACT_INPUT_LINE - FIELD_ECTAGS_START] = { + .letter = 'C', + .name = "compact", + .description = "compact input line (used only in xref output)", + .enabled = false, + .render = renderFieldCompactInputLine, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_FILE_SCOPE - FIELD_ECTAGS_START] = { + .letter = 'f', + .name = "file", + .description = "File-restricted scoping", + .enabled = true, + .render = renderFieldFile, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isFileFieldAvailable, + .dataType = FIELDTYPE_BOOL, + }, + [FIELD_KIND_LONG - FIELD_ECTAGS_START] = { + .letter = 'K', + .name = NULL, + .description = "Kind of tag in long-name form", + .enabled = false, + .render = renderFieldKindName, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_KIND - FIELD_ECTAGS_START] = { + .letter ='k', + .name = NULL, + .description = "Kind of tag in one-letter form", + .enabled = true, + .render = renderFieldKindLetter, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_LANGUAGE - FIELD_ECTAGS_START] = { + .letter = 'l', + .name = "language", + .description = "Language of input file containing tag", + .enabled = false, + .render = renderFieldLanguage, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_LINE_NUMBER - FIELD_ECTAGS_START] = { + .letter = 'n', + .name = "line", + .description = "Line number of tag definition", + .enabled = false, + .render = renderFieldLineNumber, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_INTEGER, + .getterValueType = "int", + .getValueObject = getFieldValueForLineCommon, + .setterValueType = "matchlok|int", + .checkValueForSetter= checkFieldValueForLineCommon, + .setValueObject = setFieldValueForLineCommon, + }, + [FIELD_SCOPE - FIELD_ECTAGS_START] = { + .letter = 's', + .name = NULL, + .description = "[tags output] scope (kind:name) of tag definition, [xref and json output] name of scope", + .enabled = true, + .render = renderFieldScope, + .renderNoEscaping = renderFieldScopeNoEscape, + .doesContainAnyChar = doesContainAnyCharInFieldScope, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_TYPE_REF - FIELD_ECTAGS_START] = { + .letter = 't', + .name = "typeref", + .description = "Type and name of a variable or typedef", + .enabled = true, + .render = renderFieldTyperef, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isTyperefFieldAvailable, + .dataType = FIELDTYPE_STRING, + .getterValueType = "[string string]", + .getValueObject = getFieldValueForTyperef, + .setterValueType = "[string string]|string|index:int|false", + .checkValueForSetter= checkFieldValueForTyperef, + .setValueObject = setFieldValueForTyperef, + }, + [FIELD_KIND_KEY - FIELD_ECTAGS_START] = { + .letter = 'z', + .name = "kind", + .description = "[tags output] prepend \"kind:\" to k/ (or K/) field output, [xref and json output] kind in long-name form", + .enabled = false, + /* Following renderer is for handling --_xformat=%{kind}; + and is not for tags output. */ + .render = renderFieldKindName, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + .getterValueType = "name", + .getValueObject = getFieldValueForKind, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = NULL, + }, + [FIELD_INHERITANCE - FIELD_ECTAGS_START] = { + .letter = 'i', + .name = "inherits", + .description = "Inheritance information", + .enabled = false, + .render = renderFieldInherits, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isInheritsFieldAvailable, + .dataType = FIELDTYPE_STRING|FIELDTYPE_BOOL, + .getterValueType = NULL, + .getValueObject = NULL, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = setFieldValueForInherits, + }, + [FIELD_ACCESS - FIELD_ECTAGS_START] = { + .letter = 'a', + .name = "access", + .description = "Access (or export) of class members", + .enabled = false, + .render = renderFieldAccess, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isAccessFieldAvailable, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_IMPLEMENTATION - FIELD_ECTAGS_START] = { + .letter = 'm', + .name = "implementation", + .description = "Implementation information", + .enabled = false, + .render = renderFieldImplementation, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isImplementationFieldAvailable, + .dataType = FIELDTYPE_STRING, + + }, + [FIELD_SIGNATURE - FIELD_ECTAGS_START] = { + .letter = 'S', + .name = "signature", + .description = "Signature of routine (e.g. prototype or parameter list)", + .enabled = false, + .render = renderFieldSignature, + .renderNoEscaping = renderFieldSignatureNoEscape, + .doesContainAnyChar = doesContainAnyCharInSignature, + .isValueAvailable = isSignatureFieldAvailable, + .dataType = FIELDTYPE_STRING, + .getterValueType = NULL, + .getValueObject = getFieldValueForSignature, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = setFieldValueForSignature, + }, }; static fieldDefinition fieldDefinitionsUniversal [] = { - DEFINE_FIELD ('r', "roles", false, - "Roles", - FIELDTYPE_STRING, - renderFieldRoles), - DEFINE_FIELD ('R', NULL, false, - "Marker (R or D) representing whether tag is definition or reference", - FIELDTYPE_STRING, - renderFieldRefMarker), - DEFINE_FIELD_FULL ('Z', "scope", false, - "[tags output] prepend \"scope:\" key to s/scope field output, [xref and json output] the same as s/ field", - NULL, - FIELDTYPE_STRING, - /* Following renderer is for handling --_xformat=%{scope}; - and is not for tags output. */ - renderFieldScope, renderFieldScopeNoEscape, - doesContainAnyCharInFieldScope), - DEFINE_FIELD_FULL ('E', "extras", false, - "Extra tag type information", - isExtrasFieldAvailable, - FIELDTYPE_STRING, - renderFieldExtras, NULL, NULL), - DEFINE_FIELD_FULL ('x', "xpath", false, - "xpath for the tag", - isXpathFieldAvailable, - FIELDTYPE_STRING, - renderFieldXpath, NULL, NULL), - DEFINE_FIELD ('p', "scopeKind", false, - "[tags output] no effect, [xref and json output] kind of scope in long-name form", - FIELDTYPE_STRING, - renderFieldScopeKindName), - DEFINE_FIELD_FULL ('e', "end", false, - "end lines of various items", - isEndFieldAvailable, - FIELDTYPE_INTEGER, - renderFieldEnd, NULL, NULL), + [FIELD_REF_MARK - FIELDS_UCTAGS_START] = { + .letter = 'R', + .name = NULL, + .description = "Marker (R or D) representing whether tag is definition or reference", + .enabled = false, + .render = renderFieldRefMarker, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_SCOPE_KEY - FIELDS_UCTAGS_START] = { + .letter = 'Z', + .name = "scope", + .description = "[tags output] prepend \"scope:\" key to s/scope field output, [xref and json output] the same as s/ field", + .enabled = false, + /* Following renderer is for handling --_xformat=%{scope}; + and is not for tags output. */ + .render = renderFieldScope, + .renderNoEscaping = renderFieldScopeNoEscape, + .doesContainAnyChar = doesContainAnyCharInFieldScope, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + .getterValueType = "int", + .getValueObject = getFieldValueForScope, + .setterValueType = "int", + .checkValueForSetter= checkFieldValueForScope, + .setValueObject = setFieldValueForScope, + }, + [FIELD_SCOPE_KIND_LONG - FIELDS_UCTAGS_START] = { + .letter = 'p', + .name = "scopeKind", + .description = "[tags output] no effect, [xref and json output] kind of scope in long-name form", + .enabled = false, + .render = renderFieldScopeKindName, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_ROLES - FIELDS_UCTAGS_START] = { + .letter = 'r', + .name = "roles", + .description = "Roles", + .enabled = false, + .render = renderFieldRoles, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = NULL, + .dataType = FIELDTYPE_STRING, + .getterValueType = "[ role1:name ... rolen:name ]", + .getValueObject = getFieldValueForRoles, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = NULL, + }, + [FIELD_EXTRAS - FIELDS_UCTAGS_START] = { + .letter = 'E', + .name = "extras", + .description = "Extra tag type information", + .enabled = false, + .render = renderFieldExtras, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isExtrasFieldAvailable, + .dataType = FIELDTYPE_STRING, + .getterValueType = "[ extra1:name ... extran:name ]", + .getValueObject = getFieldValueForExtras, + .setterValueType = NULL, + .checkValueForSetter= NULL, + .setValueObject = NULL, + }, + [FIELD_XPATH - FIELDS_UCTAGS_START] = { + .letter = 'x', + .name = "xpath", + .description = "xpath for the tag", + .enabled = false, + .render = renderFieldXpath, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isXpathFieldAvailable, + .dataType = FIELDTYPE_STRING, + }, + [FIELD_END_LINE - FIELDS_UCTAGS_START] = { + .letter = 'e', + .name = "end", + .description = "end lines of various items", + .enabled = false, + .render = renderFieldEnd, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isEndFieldAvailable, + .dataType = FIELDTYPE_INTEGER, + .getterValueType = "int", + .getValueObject = getFieldValueForLineCommon, + .setterValueType = "matchlok|int", + .checkValueForSetter= checkFieldValueForLineCommon, + .setValueObject = setFieldValueForLineCommon, + + }, + [FIELD_EPOCH - FIELDS_UCTAGS_START] = { + .letter = 'T', + .name = "epoch", + .description = "the last modified time of the input file (only for F/file kind tag)", + .enabled = true, + .render = renderFieldEpoch, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isEpochAvailable, + .dataType = FIELDTYPE_INTEGER, + }, + [FIELD_NTH - FIELDS_UCTAGS_START] = { + .letter = 'o', + .name = "nth", + .description = "the order in the parent scope", + .enabled = false, + .render = renderFieldNth, + .renderNoEscaping = NULL, + .doesContainAnyChar = NULL, + .isValueAvailable = isNthAvailable, + .dataType = FIELDTYPE_INTEGER, + }, }; @@ -258,6 +483,7 @@ extern void initFieldObjects (void) fobj->nameWithPrefix = fobj->def->name; fobj->language = LANG_IGNORE; fobj->sibling = FIELD_UNKNOWN; + fobj->def->ftype = i + fieldObjectUsed; } fieldObjectUsed += ARRAY_SIZE (fieldDefinitionsFixed); @@ -269,6 +495,7 @@ extern void initFieldObjects (void) fobj->nameWithPrefix = fobj->def->name; fobj->language = LANG_IGNORE; fobj->sibling = FIELD_UNKNOWN; + fobj->def->ftype = i + fieldObjectUsed; } fieldObjectUsed += ARRAY_SIZE (fieldDefinitionsExuberant); @@ -293,6 +520,7 @@ extern void initFieldObjects (void) fobj->nameWithPrefix = NULL; fobj->language = LANG_IGNORE; fobj->sibling = FIELD_UNKNOWN; + fobj->def->ftype = i + fieldObjectUsed; } fieldObjectUsed += ARRAY_SIZE (fieldDefinitionsUniversal); @@ -704,32 +932,51 @@ static const char *renderFieldLineNumber (const tagEntryInfo *const tag, return vStringValue (b); } -static const char *renderFieldRoles (const tagEntryInfo *const tag, - const char *value CTAGS_ATTR_UNUSED, - vString* b) +struct renderRoleData { + vString* b; + int nRoleWritten; +}; + +static void renderRoleByIndex (const tagEntryInfo *const tag, int roleIndex, void *data) +{ + struct renderRoleData *rdata = data; + + if (!isLanguageRoleEnabled (tag->langType, tag->kindIndex, roleIndex)) + return; + + if (rdata->nRoleWritten > 0) + vStringPut(rdata->b, ','); + + const roleDefinition * role = getTagRole(tag, roleIndex); + renderRole (role, rdata->b); + rdata->nRoleWritten++; +} + +static roleBitsType foreachRoleBits (const tagEntryInfo *const tag, + void (* fn) (const tagEntryInfo *const, int, void *), + void *data) { roleBitsType rbits = tag->extensionFields.roleBits; - const roleDefinition * role; - if (rbits) - { - int roleCount = countLanguageRoles (tag->langType, tag->kindIndex); - int nRoleWritten = 0; + if (!rbits) + return rbits; - for (int roleIndex = 0; roleIndex < roleCount; roleIndex++) - { - if (((rbits >> roleIndex) & (roleBitsType)1) - && isLanguageRoleEnabled (tag->langType, tag->kindIndex, roleIndex)) - { - if (nRoleWritten > 0) - vStringPut(b, ','); + int roleCount = countLanguageRoles (tag->langType, tag->kindIndex); - role = getTagRole(tag, roleIndex); - renderRole (role, b); - nRoleWritten++; - } - } + for (int roleIndex = 0; roleIndex < roleCount; roleIndex++) + { + if ((rbits >> roleIndex) & (roleBitsType)1) + fn (tag, roleIndex, data); } - else + return rbits; +} + +static const char *renderFieldRoles (const tagEntryInfo *const tag, + const char *value CTAGS_ATTR_UNUSED, + vString* b) +{ + struct renderRoleData data = { .b = b, .nRoleWritten = 0 }; + + if (!foreachRoleBits (tag, renderRoleByIndex, &data)) vStringCatS (b, ROLE_DEFINITION_NAME); return vStringValue (b); } @@ -743,7 +990,10 @@ static const char *renderFieldLanguage (const tagEntryInfo *const tag, if (Option.lineDirectives && (tag->sourceLangType != LANG_IGNORE)) l = getLanguageName(tag->sourceLangType); else + { + Assert (tag->langType != LANG_IGNORE); l = getLanguageName(tag->langType); + } return renderAsIs (b, WITH_DEFUALT_VALUE(l)); } @@ -878,9 +1128,31 @@ static const char *renderFieldEnd (const tagEntryInfo *const tag, return NULL; } -static bool isLanguageFieldAvailable (const tagEntryInfo *const tag) +static const char *renderFieldEpoch (const tagEntryInfo *const tag, + const char *value, vString* b) { - return (tag->langType == LANG_IGNORE)? false: true; +#define buf_len 21 + static char buf[buf_len]; + + if (snprintf (buf, buf_len, "%lld", (long long)tag->extensionFields.epoch) > 0) + return renderAsIs (b, buf); + else + return NULL; +#undef buf_len +} + +static const char *renderFieldNth (const tagEntryInfo *const tag, + const char *value, vString* b) +{ +#define buf_len 12 + static char buf[buf_len]; + + if (tag->extensionFields.nth > NO_NTH_FIELD + && snprintf (buf, buf_len, "%d", (int)tag->extensionFields.nth) > 0) + return renderAsIs (b, buf); + else + return NULL; +#undef buf_len } static bool isTyperefFieldAvailable (const tagEntryInfo *const tag) @@ -917,13 +1189,12 @@ 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; - else if (tag->extraDynamic) - return true; - } return false; } @@ -942,46 +1213,39 @@ static bool isEndFieldAvailable (const tagEntryInfo *const tag) return (tag->extensionFields.endLine != 0)? true: false; } +static bool isEpochAvailable (const tagEntryInfo *const tag) +{ + return (tag->kindIndex == KIND_FILE_INDEX) + ? true + : false; +} + +static bool isNthAvailable (const tagEntryInfo *const tag) +{ + Assert (tag->langType >= NO_NTH_FIELD); + return (tag->extensionFields.nth != NO_NTH_FIELD)? true: false; +} + extern bool isFieldEnabled (fieldType type) { return getFieldObject(type)->def->enabled; } -extern bool enableField (fieldType type, bool state, bool warnIfFixedField) +extern bool enableField (fieldType type, bool state) { fieldDefinition *def = getFieldObject(type)->def; bool old = def->enabled; - if (writerDoesTreatFieldAsFixed (type)) - { - if ((!state) && warnIfFixedField) - { - if (def->name && def->letter != NUL_FIELD_LETTER) - error(WARNING, "Cannot disable fixed field: '%c'{%s}", - def->letter, def->name); - else if (def->name) - error(WARNING, "Cannot disable fixed field: {%s}", - def->name); - else if (def->letter != NUL_FIELD_LETTER) - error(WARNING, "Cannot disable fixed field: '%c'", - getFieldObject(type)->def->letter); - else - AssertNotReached(); - } - } - else - { - getFieldObject(type)->def->enabled = state; + getFieldObject(type)->def->enabled = state; - if (isCommonField (type)) - verbose ("enable field \"%s\": %s\n", + if (isCommonField (type)) + verbose ("enable field \"%s\": %s\n", getFieldObject(type)->def->name, (state? "yes": "no")); - else - verbose ("enable field \"%s\"<%s>: %s\n", + else + verbose ("enable field \"%s\"<%s>: %s\n", getFieldObject(type)->def->name, getLanguageName (getFieldOwner(type)), (state? "yes": "no")); - } return old; } @@ -1000,6 +1264,11 @@ extern unsigned int getFieldDataType (fieldType type) return getFieldObject(type)->def->dataType; } +extern bool isFieldValueAvailableAlways (fieldType type) +{ + return getFieldObject(type)->def->isValueAvailable == NULL; +} + extern bool doesFieldHaveRenderer (fieldType type, bool noEscaping) { if (noEscaping) @@ -1101,11 +1370,14 @@ extern int defineField (fieldDefinition *def, langType language) #define FIELD_COL_LANGUAGE 3 #define FIELD_COL_JSTYPE 4 #define FIELD_COL_FIXED 5 -#define FIELD_COL_DESCRIPTION 6 +#define FIELD_COL_OPERATOR 6 +#define FIELD_COL_DESCRIPTION 7 + extern struct colprintTable * fieldColprintTableNew (void) { return colprintTableNew ("L:LETTER", "L:NAME", "L:ENABLED", - "L:LANGUAGE", "L:JSTYPE", "L:FIXED", "L:DESCRIPTION", NULL); + "L:LANGUAGE", "L:JSTYPE", "L:FIXED", + "L:OP", "L:DESCRIPTION", NULL); } static void fieldColprintAddLine (struct colprintTable *table, int i) @@ -1140,6 +1412,13 @@ static void fieldColprintAddLine (struct colprintTable *table, int i) } colprintLineAppendColumnCString (line, typefields); colprintLineAppendColumnBool (line, writerDoesTreatFieldAsFixed (i)); + + char operator[] = {'-', '-', '\0'}; + if (fdef->getValueObject) + operator[0] = 'r'; + if (fdef->setValueObject) + operator[1] = 'w'; + colprintLineAppendColumnCString (line, operator); colprintLineAppendColumnCString (line, fdef->description); } @@ -1235,3 +1514,380 @@ extern void fieldColprintTablePrint (struct colprintTable *table, colprintTableSort (table, fieldColprintCompareLines); colprintTablePrint (table, 0, withListHeader, machinable, fp); } + +extern const char * getFieldGetterValueType (fieldType type) +{ + fieldObject *fobj = getFieldObject (type); + return (fobj? fobj->def->getterValueType: NULL); +} + +extern EsObject * getFieldValue (fieldType type, const tagEntryInfo *tag) +{ + fieldObject* fobj; + + fobj = getFieldObject (type); + if (fobj && fobj->def->getValueObject) + return fobj->def->getValueObject (tag, fobj->def); + return es_nil; +} + +extern bool hasFieldGetter (fieldType type) +{ + fieldObject *fobj = getFieldObject (type); + return (fobj && fobj->def->getValueObject); +} + +extern const char * getFieldSetterValueType (fieldType type) +{ + fieldObject *fobj = getFieldObject (type); + return (fobj? fobj->def->setterValueType: NULL); +} + +extern EsObject * setFieldValue (fieldType type, tagEntryInfo *tag, const EsObject *val) +{ + fieldObject *fobj; + + fobj = getFieldObject (type); + if (fobj && fobj->def->setValueObject) + return fobj->def->setValueObject (tag, fobj->def, val); + return es_false; +} + +extern bool hasFieldSetter (fieldType type) +{ + fieldObject *fobj = getFieldObject (type); + return (fobj && fobj->def->setValueObject); +} + + +extern bool hasFieldValueCheckerForSetter (fieldType type) +{ + fieldObject *fobj = getFieldObject (type); + return (fobj && fobj->def->checkValueForSetter); +} + +extern EsObject* checkFieldValueForSetter (fieldType type, const EsObject *val) +{ + fieldObject *fobj = getFieldObject (type); + return fobj->def->checkValueForSetter (fobj->def, val); +} + +static EsObject* getFieldValueForName (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + return opt_string_new_from_cstr (tag->name); +} + +static EsObject* setFieldValueForName (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *val) +{ + eFree ((char*) tag->name); + const char *cstr = opt_string_get_cstr (val); + tag->name = eStrdup (cstr); + return es_false; +} + +static EsObject* getFieldValueForInput (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + return opt_string_new_from_cstr (tag->inputFileName); +} + +static EsObject* getFieldValueForKind (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + const char *kind_name = getLanguageKindName (tag->langType, tag->kindIndex); + return opt_name_new_from_cstr (kind_name); +} + +static EsObject* getFieldValueForTyperef (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + if (tag->extensionFields.typeRef [0] == NULL + && tag->extensionFields.typeRef [1] == NULL) + return es_nil; + + EsObject *a = opt_array_new (); + EsObject *e0 = tag->extensionFields.typeRef [0] + ? opt_string_new_from_cstr(tag->extensionFields.typeRef [0]) + : es_false; + EsObject *e1 = tag->extensionFields.typeRef [1] + ? opt_string_new_from_cstr(tag->extensionFields.typeRef [1]) + : es_false; + opt_array_put (a, 0, e0); + opt_array_put (a, 1, e1); + es_object_unref (e0); + es_object_unref (e1); + return a; +} + +static EsObject* setFieldValueForTyperef (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + const char *tmp[2] = {NULL, NULL}; + + for (int i = 0 ; i < 2; i++) + { + if (tag->extensionFields.typeRef [i]) + tmp [i] = tag->extensionFields.typeRef [i]; + } + + if (es_boolean_p (obj)) + { + for (int i = 0 ; i < 2; i++) + { + if (tag->extensionFields.typeRef [i]) + tag->extensionFields.typeRef [i] = NULL; + } + } + else if (es_object_get_type (obj) == OPT_TYPE_ARRAY) + { + for (int i = 0 ; i < 2; i++) + { + EsObject *e = opt_array_get (obj, i); + if (es_boolean_p (e)) + { + if (tag->extensionFields.typeRef [i]) + tag->extensionFields.typeRef [i] = NULL; + } + else if (es_object_get_type (e) == OPT_TYPE_STRING) + { + tag->extensionFields.typeRef [i] = eStrdup (opt_string_get_cstr (e)); + } + } + } + else if (es_object_get_type (obj) == OPT_TYPE_STRING) + { + const char *str = opt_string_get_cstr (obj); + tag->extensionFields.typeRef [0] = eStrdup ("typename"); + tag->extensionFields.typeRef [1] = eStrdup (str); + } + else if (es_integer_p (obj)) + { + int index = es_integer_get (obj); + tagEntryInfo *e = getEntryInCorkQueue (index); + if (e) + { + const char *name = e->name; + const char *kindName = getLanguageKindName (e->langType, e->kindIndex); + + tag->extensionFields.typeRef [0] = eStrdup (kindName); + tag->extensionFields.typeRef [1] = eStrdup (name); + } + } + else + { + AssertNotReached(); + return OPT_ERR_TYPECHECK; + } + + for (int i = 0; i < 2; i++) + if (tmp [i]) + eFree ((char*)tmp[i]); + + return es_false; +} + +static EsObject* checkFieldValueForTyperef (const fieldDefinition *fdef, const EsObject *obj) +{ + if (es_boolean_p (obj)) + { + if (!es_object_equal (es_false, obj)) + return OPT_ERR_TYPECHECK; + } + else if (es_object_get_type (obj) == OPT_TYPE_ARRAY) + { + if (opt_array_length (obj) != 2) + return OPT_ERR_TYPECHECK; + + for (unsigned int i = 0; i < 2; i++) + { + EsObject *e = opt_array_get (obj, i); + if (es_object_get_type (e) != OPT_TYPE_STRING) + return OPT_ERR_TYPECHECK; + } + } + else if (es_object_get_type (obj) == OPT_TYPE_STRING) + ; + else if (es_integer_p (obj)) + { + int index = es_integer_get (obj); + if (index >= countEntryInCorkQueue ()) + return OPTSCRIPT_ERR_NOTAGENTRY; + } + else + return OPT_ERR_TYPECHECK; + return es_false; +} + +static EsObject* getFieldValueForScope (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + return es_integer_new (tag->extensionFields.scopeIndex); +} + +static EsObject* setFieldValueForScope (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + int index = es_integer_get (obj); + if (index < countEntryInCorkQueue ()) + { + tag->extensionFields.scopeIndex = index; + return es_false; + } + + return OPTSCRIPT_ERR_NOTAGENTRY; +} + +static EsObject* checkFieldValueForScope (const fieldDefinition *fdef, const EsObject *obj) +{ + if (!es_integer_p (obj)) + return OPT_ERR_TYPECHECK; + + if (es_integer_get (obj) < 0) + return OPT_ERR_RANGECHECK; + + return es_false; +} + +static EsObject* getFieldValueForExtras (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + if (!isTagExtra (tag)) + return es_nil; + + EsObject* a = opt_array_new (); + + for (int i = 0; i < countXtags (); i++) + { + if (!isTagExtraBitMarked (tag, i)) + continue; + + langType lang = getXtagOwner (i); + const char *lang_name = (lang == LANG_IGNORE) + ? NULL + : getLanguageName (lang); + const char *extra_name = getXtagName (i); + + EsObject *extra; + if (lang_name == NULL) + extra = opt_name_new_from_cstr (extra_name); + else + { + vString *tmp = vStringNewInit (lang_name); + vStringPut (tmp, '.'); + vStringCatS (tmp, extra_name); + extra = opt_name_new_from_cstr (vStringValue (tmp)); + vStringDelete (tmp); + } + opt_array_add (a, extra); + es_object_unref (extra); + } + return a; +} + +static EsObject* getFieldValueForSignature (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + if (!tag->extensionFields.signature) + return es_nil; + return (opt_name_new_from_cstr (tag->extensionFields.signature)); +} + +static EsObject* setFieldValueForSignature (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + if (tag->extensionFields.signature) + eFree ((char *)tag->extensionFields.signature); + + const char *str = opt_string_get_cstr (obj); + tag->extensionFields.signature = eStrdup (str); + return es_false; +} + +static void makeRolesArray (const tagEntryInfo *const tag, int roleIndex, void *data) +{ + EsObject *a = data; + + const roleDefinition *role = getTagRole (tag, roleIndex); + EsObject *r = opt_name_new_from_cstr (role->name); + opt_array_add (a, r); + es_object_unref (r); +} + +static EsObject* getFieldValueForRoles (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + EsObject *a = opt_array_new (); + + if (!foreachRoleBits (tag, makeRolesArray, a)) + { + EsObject *r = opt_name_new_from_cstr (ROLE_DEFINITION_NAME); + opt_array_add (a, r); + es_object_unref (r); + } + return a; +} + +static EsObject* getFieldValueForLineCommon (const tagEntryInfo *tag, const fieldDefinition *fdef) +{ + if (fdef->ftype == FIELD_END_LINE) + return ((int)tag->extensionFields.endLine == 0) + ? es_nil + : es_integer_new ((int)tag->extensionFields.endLine); + else + return ((int)tag->lineNumber == 0) + ? es_nil + : es_integer_new ((int)tag->lineNumber); +} + +static EsObject* checkFieldValueForLineCommon (const fieldDefinition *fdef, const EsObject *obj) +{ + return es_false; +} + +static EsObject* setFieldValueForLineCommon (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + int l; + if (es_object_get_type (obj) == OPT_TYPE_MATCHLOC) + { + matchLoc *loc = es_pointer_get (obj); + l = loc->line; + } + else if (es_integer_p (obj)) + { + l = es_integer_get (obj); + if (l < 1) + return OPT_ERR_RANGECHECK; + + /* If the new line number is too large, + we cannot fill tag->filePosition wit + getInputFilePositionForLine(); */ + if (fdef->ftype == FIELD_LINE_NUMBER + && l < getInputLineNumber()) + return OPT_ERR_RANGECHECK; + } + else + return OPT_ERR_TYPECHECK; + + if (fdef->ftype == FIELD_END_LINE) + tag->extensionFields.endLine = l; + else + { + tag->lineNumber = l; + tag->filePosition = getInputFilePositionForLine (l); + } + + return es_false; +} + +static EsObject* setFieldValueForInherits (tagEntryInfo *tag, const fieldDefinition *fdef, const EsObject *obj) +{ + if (es_object_get_type (obj) == OPT_TYPE_STRING) + { + if (tag->extensionFields.inheritance) + eFree ((void *)tag->extensionFields.inheritance); + const char *str = opt_string_get_cstr (obj); + tag->extensionFields.inheritance = eStrdup (str); + } + else if (es_object_equal (es_false, obj)) + { + if (tag->extensionFields.inheritance) + { + eFree ((void *)tag->extensionFields.inheritance); + tag->extensionFields.inheritance = NULL; + } + } + else + return OPT_ERR_RANGECHECK; /* true is not acceptable. */ + + return es_false; +} diff --git a/ctags/main/field.h b/ctags/main/field.h index 4213ab6232..6b7c02f20f 100644 --- a/ctags/main/field.h +++ b/ctags/main/field.h @@ -20,6 +20,7 @@ #include "types.h" #include "vstring.h" +#include "optscript.h" /* * DATA DECLARATIONS @@ -32,32 +33,41 @@ typedef enum eFieldType { /* extension field content control */ FIELD_NAME, FIELD_INPUT_FILE, FIELD_PATTERN, - FIELD_COMPACT_INPUT_LINE, + + FIELD_ECTAGS_START, + FIELD_COMPACT_INPUT_LINE = FIELD_ECTAGS_START, /* EXTENSION FIELDS */ - FIELD_EXTENSION_START, - FIELD_ACCESS = FIELD_EXTENSION_START, - FIELD_FILE_SCOPE, - FIELD_INHERITANCE, + FIELD_JSON_LOOP_START, + FIELD_FILE_SCOPE = FIELD_JSON_LOOP_START, FIELD_KIND_LONG, FIELD_KIND, FIELD_LANGUAGE, - FIELD_IMPLEMENTATION, FIELD_LINE_NUMBER, - FIELD_SIGNATURE, FIELD_SCOPE, FIELD_TYPE_REF, FIELD_KIND_KEY, + FIELD_ECTAGS_LOOP_START, + FIELD_INHERITANCE = FIELD_ECTAGS_LOOP_START, + FIELD_ACCESS, + FIELD_IMPLEMENTATION, + FIELD_SIGNATURE, + FIELD_ECTAGS_LOOP_LAST = FIELD_SIGNATURE, - /* EXTENSION FIELDS NEWLY INTRODUCED IN UCTAGS */ - FIELD_ROLES, - FIELD_REF_MARK, + /* Extension fields newly introduced in Universal Ctags. */ + FIELDS_UCTAGS_START, + FIELD_REF_MARK = FIELDS_UCTAGS_START, FIELD_SCOPE_KEY, + FIELD_SCOPE_KIND_LONG, + FIELD_UCTAGS_LOOP_START, + FIELD_ROLES = FIELD_UCTAGS_LOOP_START, FIELD_EXTRAS, FIELD_XPATH, - FIELD_SCOPE_KIND_LONG, FIELD_END_LINE, - FIELD_BUILTIN_LAST = FIELD_END_LINE, + FIELD_EPOCH, + FIELD_NTH, + + FIELD_BUILTIN_LAST = FIELD_NTH, } fieldType ; #define fieldDataTypeFalgs "sib" /* used in --list-fields */ @@ -90,6 +100,15 @@ struct sFieldDefinition { bool (* isValueAvailable) (const tagEntryInfo *const); + const char * getterValueType; + 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 *); + fieldDataType dataType; /* used in json output */ unsigned int ftype; /* Given from the main part */ diff --git a/ctags/main/field_p.h b/ctags/main/field_p.h index 204068370b..fd56bd865f 100644 --- a/ctags/main/field_p.h +++ b/ctags/main/field_p.h @@ -18,6 +18,7 @@ #include "general.h" #include "colprint_p.h" #include "field.h" +#include "optscript.h" /* * DATA DECLARATIONS @@ -43,14 +44,14 @@ extern fieldType getFieldTypeForOption (char letter); internally, each parser is not initialized. `LANG_IGNORE' is a bit faster. */ extern fieldType getFieldTypeForName (const char *name); extern fieldType getFieldTypeForNameAndLanguage (const char *fieldName, langType language); -extern bool enableField (fieldType type, bool state, bool warnIfFixedField); +extern bool enableField (fieldType type, bool state); extern bool isCommonField (fieldType type); extern int getFieldOwner (fieldType type); extern const char* getFieldDescription (fieldType type); extern const char* getFieldName (fieldType type); extern unsigned char getFieldLetter (fieldType type); extern unsigned int getFieldDataType (fieldType type); -extern void printFields (int language); +extern bool isFieldValueAvailableAlways (fieldType type); /* Whether the field specified with TYPE has a method for rendering in the current format. */ @@ -77,4 +78,14 @@ extern void fieldColprintAddLanguageLines (struct colprintTable *table, langType extern void fieldColprintTablePrint (struct colprintTable *table, bool withListHeader, bool machinable, FILE *fp); +/* tag is assumed that it is in the cork queue. */ +extern EsObject * getFieldValue (fieldType type, const tagEntryInfo *tag); +extern bool hasFieldGetter (fieldType type); +extern const char * getFieldGetterValueType (fieldType type); +extern EsObject * setFieldValue (fieldType type, tagEntryInfo *tag, const EsObject *val); +extern bool hasFieldSetter (fieldType type); +extern const char * getFieldSetterValueType (fieldType type); +extern bool hasFieldValueCheckerForSetter (fieldType type); +extern EsObject *checkFieldValueForSetter (fieldType type, const EsObject *val); + #endif /* CTAGS_MAIN_FIELD_PRIVATE_H */ diff --git a/ctags/main/flags.c b/ctags/main/flags.c index b5931eaa7e..abe9ae0aa8 100644 --- a/ctags/main/flags.c +++ b/ctags/main/flags.c @@ -21,18 +21,24 @@ #include "vstring.h" #include "routines.h" -void flagsEval (const char* flags_original, flagDefinition* defs, unsigned int ndefs, void* data) +extern const char *flagsEval (const char* flags_original, flagDefinition* defs, unsigned int ndefs, void* data) { unsigned int i, j; char *flags; + const char *optscript = NULL; if (!flags_original) - return; + return NULL; flags = eStrdup (flags_original); for (i = 0 ; flags [i] != '\0' ; ++i) { - if (flags [i] == LONG_FLAGS_OPEN) + if (flags [i] == LONG_FLAGS_OPEN && flags [i + 1] == LONG_FLAGS_OPEN) + { + optscript = flags_original + i; + break; + } + else if (flags [i] == LONG_FLAGS_OPEN) { const char* aflag = flags + i + 1; char* needle_close_paren = strchr(aflag, LONG_FLAGS_CLOSE); @@ -74,6 +80,7 @@ void flagsEval (const char* flags_original, flagDefinition* defs, unsigned int n defs[j].shortProc(flags[i], data); } eFree (flags); + return optscript; } extern struct colprintTable * flagsColprintTableNew (void) diff --git a/ctags/main/flags_p.h b/ctags/main/flags_p.h index 786bf9a61a..21668f6922 100644 --- a/ctags/main/flags_p.h +++ b/ctags/main/flags_p.h @@ -27,7 +27,8 @@ typedef struct sFlagDefinition { const char *description; } flagDefinition; -extern void flagsEval (const char* flags, flagDefinition* defs, unsigned int ndefs, void* data); +/* Return {{optscript}} part. */ +extern const char* flagsEval (const char* flags, flagDefinition* defs, unsigned int ndefs, void* data); extern struct colprintTable * flagsColprintTableNew (void); extern void flagsColprintAddDefinitions (struct colprintTable *table, flagDefinition* def, unsigned int ndefs); extern void flagsColprintTablePrint (struct colprintTable *table, diff --git a/ctags/main/fmt.c b/ctags/main/fmt.c index d7c99289dd..4569058af8 100644 --- a/ctags/main/fmt.c +++ b/ctags/main/fmt.c @@ -230,13 +230,13 @@ static fmtElement** queueTagField (fmtElement **last, long width, bool truncatio else cur->spec.field.raw_fmtstr = NULL; - enableField (ftype, true, false); + enableField (ftype, true); if (language == LANG_AUTO) { fieldType ftype_next = ftype; while ((ftype_next = nextSiblingField (ftype_next)) != FIELD_UNKNOWN) - enableField (ftype_next, true, false); + enableField (ftype_next, true); } cur->printer = printTagField; diff --git a/ctags/main/htable.c b/ctags/main/htable.c index c13daa9823..0ebfbd815f 100644 --- a/ctags/main/htable.c +++ b/ctags/main/htable.c @@ -43,8 +43,10 @@ struct sHashTable { unsigned int size; hashTableHashFunc hashfn; hashTableEqualFunc equalfn; - hashTableFreeFunc keyfreefn; - hashTableFreeFunc valfreefn; + hashTableDeleteFunc keyfreefn; + hashTableDeleteFunc valfreefn; + void *valForNotUnknownKey; + hashTableDeleteFunc valForNotUnknownKeyfreefn; }; struct chainTracker { @@ -65,18 +67,27 @@ static hentry* entry_new (void *key, void *value, hentry* next) return entry; } -static hentry* entry_destroy (hentry* entry, - hashTableFreeFunc keyfreefn, - hashTableFreeFunc valfreefn) +static void entry_reset (hentry* entry, + void *newkey, + void *newval, + hashTableDeleteFunc keyfreefn, + hashTableDeleteFunc valfreefn) { - hentry* tmp; - if (keyfreefn) keyfreefn (entry->key); if (valfreefn) valfreefn (entry->value); - entry->key = NULL; - entry->value = NULL; + entry->key = newkey; + entry->value = newval; +} + +static hentry* entry_destroy (hentry* entry, + hashTableDeleteFunc keyfreefn, + hashTableDeleteFunc valfreefn) +{ + hentry* tmp; + + entry_reset (entry, NULL, NULL, keyfreefn, valfreefn); tmp = entry->next; eFree (entry); @@ -84,14 +95,15 @@ static hentry* entry_destroy (hentry* entry, } static void entry_reclaim (hentry* entry, - hashTableFreeFunc keyfreefn, - hashTableFreeFunc valfreefn) + hashTableDeleteFunc keyfreefn, + hashTableDeleteFunc valfreefn) { while (entry) entry = entry_destroy (entry, keyfreefn, valfreefn); } -static void *entry_find (hentry* entry, const void* const key, hashTableEqualFunc equalfn) +static void *entry_find (hentry* entry, const void* const key, hashTableEqualFunc equalfn, + void *valForNotUnknownKey) { while (entry) { @@ -99,11 +111,11 @@ static void *entry_find (hentry* entry, const void* const key, hashTableEqualFun return entry->value; entry = entry->next; } - return NULL; + return valForNotUnknownKey; } static bool entry_delete (hentry **entry, const void *key, hashTableEqualFunc equalfn, - hashTableFreeFunc keyfreefn, hashTableFreeFunc valfreefn) + hashTableDeleteFunc keyfreefn, hashTableDeleteFunc valfreefn) { while (*entry) { @@ -112,7 +124,22 @@ static bool entry_delete (hentry **entry, const void *key, hashTableEqualFunc e *entry = entry_destroy (*entry, keyfreefn, valfreefn); return true; } + entry = &((*entry)->next); + } + return false; +} +static bool entry_update (hentry *entry, void *key, void *value, hashTableEqualFunc equalfn, + hashTableDeleteFunc keyfreefn, hashTableDeleteFunc valfreefn) +{ + while (entry) + { + if (equalfn (key, entry->key)) + { + entry_reset (entry, key, value, keyfreefn, valfreefn); + return true; + } + entry = entry->next; } return false; } @@ -131,8 +158,8 @@ static bool entry_foreach (hentry *entry, hashTableForeachFunc proc, void *user extern hashTable *hashTableNew (unsigned int size, hashTableHashFunc hashfn, hashTableEqualFunc equalfn, - hashTableFreeFunc keyfreefn, - hashTableFreeFunc valfreefn) + hashTableDeleteFunc keyfreefn, + hashTableDeleteFunc valfreefn) { hashTable *htable; @@ -144,16 +171,21 @@ extern hashTable *hashTableNew (unsigned int size, htable->equalfn = equalfn; htable->keyfreefn = keyfreefn; htable->valfreefn = valfreefn; + htable->valForNotUnknownKey = NULL; + htable->valForNotUnknownKeyfreefn = NULL; return htable; } -extern hashTable* hashTableIntNew (unsigned int size, - hashTableHashFunc hashfn, - hashTableEqualFunc equalfn, - hashTableFreeFunc keyfreefn) +extern void hashTableSetValueForUnknownKey (hashTable *htable, + void *val, + hashTableDeleteFunc valfreefn) { - return hashTableNew (size, hashfn, equalfn, keyfreefn, NULL); + if (htable->valfreefn) + htable->valfreefn (htable->valForNotUnknownKey); + + htable->valForNotUnknownKey = val; + htable->valForNotUnknownKeyfreefn = valfreefn; } extern void hashTableDelete (hashTable *htable) @@ -163,6 +195,8 @@ extern void hashTableDelete (hashTable *htable) hashTableClear (htable); + if (htable->valForNotUnknownKeyfreefn) + htable->valForNotUnknownKeyfreefn (htable->valForNotUnknownKey); eFree (htable->table); eFree (htable); } @@ -196,7 +230,7 @@ extern void* hashTableGetItem (hashTable *htable, const void * key) unsigned int i; i = htable->hashfn (key) % htable->size; - return entry_find(htable->table[i], key, htable->equalfn); + return entry_find(htable->table[i], key, htable->equalfn, htable->valForNotUnknownKey); } extern bool hashTableDeleteItem (hashTable *htable, const void *key) @@ -208,9 +242,21 @@ extern bool hashTableDeleteItem (hashTable *htable, const void *key) htable->equalfn, htable->keyfreefn, htable->valfreefn); } +extern bool hashTableUpdateItem (hashTable *htable, void *key, void *value) +{ + unsigned int i; + + i = htable->hashfn (key) % htable->size; + bool r = entry_update(htable->table[i], key, value, + htable->equalfn, htable->keyfreefn, htable->valfreefn); + if (!r) + htable->table[i] = entry_new(key, value, htable->table[i]); + return r; +} + extern bool hashTableHasItem (hashTable *htable, const void *key) { - return hashTableGetItem (htable, key)? true: false; + return hashTableGetItem (htable, key) == htable->valForNotUnknownKey? false: true; } extern bool hashTableForeachItem (hashTable *htable, hashTableForeachFunc proc, void *user_data) @@ -258,7 +304,7 @@ static bool count (const void *const key CTAGS_ATTR_UNUSED, void *value CTAGS_AT return true; } -extern int hashTableCountItem (hashTable *htable) +extern unsigned int hashTableCountItem (hashTable *htable) { int c = 0; hashTableForeachItem (htable, count, &c); diff --git a/ctags/main/htable.h b/ctags/main/htable.h index 19cfd7cf0f..7e14874385 100644 --- a/ctags/main/htable.h +++ b/ctags/main/htable.h @@ -30,7 +30,7 @@ typedef struct sHashTable hashTable; typedef unsigned int (* hashTableHashFunc) (const void * const key); typedef bool (* hashTableEqualFunc) (const void* a, const void* b); -typedef void (* hashTableFreeFunc) (void * ptr); +typedef void (* hashTableDeleteFunc) (void * ptr); /* To continue interation, return true. * To break interation, return false. */ @@ -51,8 +51,18 @@ bool hashInteq (const void * a, const void * b); extern hashTable* hashTableNew (unsigned int size, hashTableHashFunc hashfn, hashTableEqualFunc equalfn, - hashTableFreeFunc keyfreefn, - hashTableFreeFunc valfreefn); + hashTableDeleteFunc keyfreefn, + hashTableDeleteFunc valfreefn); + +/* By default, hashTableGetItem() returns NULL for a unknown key. + * It means you cannot store NULL as a value for a key. + * With hashTableSetValueForUnknownKey(), you can specific + * an alternative address representing the value for for unknown + * keys. + */ +extern void hashTableSetValueForUnknownKey (hashTable *htable, + void *val, + hashTableDeleteFunc valfreefn); extern void hashTableDelete (hashTable *htable); extern void hashTableClear (hashTable *htable); @@ -60,6 +70,7 @@ 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); /* Return true if proc never returns false; proc returns true for all * the items, or htable holds no item. @@ -74,12 +85,8 @@ extern bool hashTableForeachItem (hashTable *htable, hashTableForeachFunc * key. */ extern bool hashTableForeachItemOnChain (hashTable *htable, const void *key, hashTableForeachFunc proc, void *user_data); -extern int hashTableCountItem (hashTable *htable); +extern unsigned int hashTableCountItem (hashTable *htable); -extern hashTable* hashTableIntNew (unsigned int size, - hashTableHashFunc hashfn, - hashTableEqualFunc equalfn, - hashTableFreeFunc keyfreefn); #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/kind_p.h b/ctags/main/kind_p.h index cbaace74e1..de51f8c673 100644 --- a/ctags/main/kind_p.h +++ b/ctags/main/kind_p.h @@ -13,6 +13,7 @@ */ #include "general.h" +#include "kind.h" #include "vstring.h" diff --git a/ctags/main/lregex-default.c b/ctags/main/lregex-default.c new file mode 100644 index 0000000000..ae60d1fb39 --- /dev/null +++ b/ctags/main/lregex-default.c @@ -0,0 +1,127 @@ +/* +* Copyright (c) 2000-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 applying regular expression matching. +* +* The code for utilizing the Gnu regex package with regards to processing the +* regex option and checking for regex matches was adapted from routines in +* Gnu etags. +*/ + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include +#include "lregex_p.h" + +/* +* FUNCTION DECLARATIONS +*/ +static int match (struct regexBackend *backend, + void *code, const char *input, size_t size, + regmatch_t pmatch[BACK_REFERENCE_COUNT]); +static regexCompiledCode compile (struct regexBackend *backend, + const char *const regexp, + int flags); +static void delete_code (void *code); +static void set_icase_flag (int *flags); + +/* +* DATA DEFINITIONS +*/ +static struct regexBackend defaultRegexBackend = { + .fdefs = NULL, + .fdef_count = 0, + .set_icase_flag = set_icase_flag, + .compile = compile, + .match = match, + .delete_code = delete_code, +}; + +/* +* FUNCTOIN DEFINITIONS +*/ +extern void basic_regex_flag_short (char c, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %c", c); + + desc->backend = &defaultRegexBackend; + desc->flags = (desc->regptype == REG_PARSER_MULTI_TABLE)? 0: REG_NEWLINE; +} + +extern void basic_regex_flag_long (const char* const s, const char* const unused CTAGS_ATTR_UNUSED, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %s", s); + + basic_regex_flag_short ('b', data); +} + +extern void extend_regex_flag_short (char c, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %c", c); + + desc->backend = &defaultRegexBackend; + desc->flags = REG_EXTENDED; + desc->flags |= (desc->regptype == REG_PARSER_MULTI_TABLE)? 0: REG_NEWLINE; +} + +extern void extend_regex_flag_long (const char* const s, const char* const unused CTAGS_ATTR_UNUSED, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %s", s); + + extend_regex_flag_short('e', data); +} + +static void delete_code (void *code) +{ + regex_t *regex_code = code; + regfree (regex_code); + eFree (regex_code); +} + +static regexCompiledCode compile (struct regexBackend *backend, + const char *const regexp, + int flags) +{ + regex_t *regex_code = xMalloc (1, regex_t); + int errcode = regcomp (regex_code, regexp, flags); + if (errcode != 0) + { + char errmsg[256]; + regerror (errcode, regex_code, errmsg, 256); + error (WARNING, "regcomp: %s", errmsg); + regfree (regex_code); + eFree (regex_code); + return (regexCompiledCode) { .backend = NULL, .code = NULL }; + } + return (regexCompiledCode) { .backend = &defaultRegexBackend, .code = regex_code }; +} + +static int match (struct regexBackend *backend, + void *code, const char *input, size_t size CTAGS_ATTR_UNUSED, + regmatch_t pmatch[BACK_REFERENCE_COUNT]) +{ + return regexec ((regex_t *)code, input, BACK_REFERENCE_COUNT, pmatch, 0); +} + +static void set_icase_flag (int *flags) +{ + *flags |= REG_ICASE; +} diff --git a/ctags/main/lregex-pcre2.c b/ctags/main/lregex-pcre2.c new file mode 100644 index 0000000000..322670f51c --- /dev/null +++ b/ctags/main/lregex-pcre2.c @@ -0,0 +1,140 @@ +/* +* Copyright (c) 2021, Red Hat, Inc. +* Copyright (c) 2021, 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 applying regular expression matching. +* +* The code for utilizing the Gnu regex package with regards to processing the +* regex option and checking for regex matches was adapted from routines in +* Gnu etags. +*/ + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#ifdef HAVE_PCRE2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include +#endif + +#include "lregex_p.h" +#include "trashbox.h" + +#include + +/* +* FUNCTION DECLARATIONS +*/ +static int match (struct regexBackend *backend, + void *code, const char *input, size_t size, + regmatch_t pmatch[BACK_REFERENCE_COUNT]); +static regexCompiledCode compile (struct regexBackend *backend, + const char *const regexp, + int flags); +static void delete_code (void *code); +static void set_icase_flag (int *flags); + +/* +* DATA DEFINITIONS +*/ +static struct regexBackend pcre2RegexBackend = { + .fdefs = NULL, + .fdef_count = 0, + .set_icase_flag = set_icase_flag, + .compile = compile, + .match = match, + .delete_code = delete_code, +}; + +/* +* FUNCTOIN DEFINITIONS +*/ +extern void pcre2_regex_flag_short (char c, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %c", c); + + desc->backend = &pcre2RegexBackend; + desc->flags = (desc->regptype == REG_PARSER_MULTI_TABLE)? PCRE2_DOTALL: PCRE2_MULTILINE; +} + +extern void pcre2_regex_flag_long (const char* const s, const char* const unused CTAGS_ATTR_UNUSED, void* data) +{ + struct flagDefsDescriptor *desc = data; + + if (desc->backend) + error (FATAL, "regex backed is specified twice: %s", s); + + pcre2_regex_flag_short ('p', data); +} + +static void delete_code (void *code) +{ + pcre2_code_free (code); +} + +static regexCompiledCode compile (struct regexBackend *backend, + const char *const regexp, + int flags) +{ + int errornumber; + PCRE2_SIZE erroroffset; + pcre2_code *regex_code = pcre2_compile((PCRE2_SPTR)regexp, + PCRE2_ZERO_TERMINATED, + (uint32_t) flags, + &errornumber, + &erroroffset, + NULL); + if (regex_code == NULL) + { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errornumber, buffer, sizeof(buffer)); + error (WARNING, "PCRE2 compilation failed at offset %d: %s", (int)erroroffset, + buffer); + return (regexCompiledCode) { .backend = NULL, .code = NULL }; + } + return (regexCompiledCode) { .backend = &pcre2RegexBackend, .code = regex_code }; +} + +static int match (struct regexBackend *backend, + void *code, const char *input, size_t size, + regmatch_t pmatch[BACK_REFERENCE_COUNT]) +{ + static pcre2_match_data *match_data; + if (match_data == NULL) + { + match_data = pcre2_match_data_create (BACK_REFERENCE_COUNT, NULL); + DEFAULT_TRASH_BOX (match_data, pcre2_match_data_free); + } + + int rc = pcre2_match (code, (PCRE2_SPTR)input, size, + 0, 0, match_data, NULL); + if (rc > 0) + { + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); + if (ovector[0] <= ovector[1]) + { + memset (pmatch, 0, sizeof(pmatch[0]) * BACK_REFERENCE_COUNT); + for (int i = 0; i < BACK_REFERENCE_COUNT; i++) + { + pmatch [i].rm_so = (i < rc)? ovector [2*i] : -1; + pmatch [i].rm_eo = (i < rc)? ovector [2*i+1]: -1; + + } + return 0; + } + } + return 1; +} + +static void set_icase_flag (int *flags) +{ + *flags |= PCRE2_CASELESS; +} diff --git a/ctags/main/lregex.c b/ctags/main/lregex.c index 56175fd8d0..e36193154e 100644 --- a/ctags/main/lregex.c +++ b/ctags/main/lregex.c @@ -23,7 +23,6 @@ #ifdef HAVE_SYS_TYPES_H # include /* declare off_t (not known to regex.h on FreeBSD) */ #endif -#include #include @@ -35,12 +34,15 @@ #include "htable.h" #include "kind.h" #include "options.h" +#include "optscript.h" #include "parse_p.h" #include "promise.h" #include "read.h" #include "read_p.h" #include "routines.h" #include "routines_p.h" +#include "script_p.h" +#include "trace.h" #include "trashbox.h" #include "xtag_p.h" @@ -50,9 +52,6 @@ static bool regexAvailable = false; * MACROS */ -/* Back-references \0 through \9 */ -#define BACK_REFERENCE_COUNT 10 - /* The max depth of taction=enter/leave stack */ #define MTABLE_STACK_MAX_DEPTH 64 @@ -64,6 +63,7 @@ static bool regexAvailable = false; Tmain cases. */ #define MTABLE_MOTIONLESS_MAX (MTABLE_STACK_MAX_DEPTH + 1) +#define DEFAULT_REGEX_BACKEND "e" /* * DATA DECLARATIONS @@ -137,7 +137,7 @@ struct mTableActionSpec { }; typedef struct { - regex_t *pattern; + regexCompiledCode pattern; enum pType type; bool exclusive; bool accept_empty_name; @@ -172,6 +172,9 @@ typedef struct { char *message_string; } message; + char *optscript_src; + EsObject *optscript; + int refcount; } regexPattern; @@ -206,6 +209,17 @@ struct guestRequest { struct boundaryInRequest boundary[2]; }; +typedef struct { + const char *line; + const char *start; + const regexPattern* const patbuf; + const regmatch_t* const pmatch; + int nmatch; + struct mTableActionSpec taction; + bool advanceto; + unsigned int advanceto_delta; +} scriptWindow; + struct lregexControlBlock { int currentScope; ptrArray *entries [2]; @@ -215,12 +229,21 @@ struct lregexControlBlock { struct guestRequest *guest_req; + EsObject *local_dict; + + ptrArray *hook[SCRIPT_HOOK_MAX]; + ptrArray *hook_code[SCRIPT_HOOK_MAX]; + langType owner; + + scriptWindow *window; }; /* * DATA DEFINITIONS */ +static OptVM *optvm; +static EsObject *lregex_dict = es_nil; /* * FUNCTION DEFINITIONS @@ -237,6 +260,15 @@ static bool guestRequestIsFilled(struct guestRequest *); static void guestRequestClear (struct guestRequest *); static void guestRequestSubmit (struct guestRequest *); +static EsObject *scriptRead (OptVM *vm, const char *src); +static void scriptSetup (OptVM *vm, struct lregexControlBlock *lcb, int corkIndex, scriptWindow *window); +static EsObject* scriptEval (OptVM *vm, EsObject *optscript); +static void scriptEvalHook (OptVM *vm, struct lregexControlBlock *lcb, enum scriptHook hook); +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 void deleteTable (void *ptrn) { struct regexTable *t = ptrn; @@ -261,9 +293,7 @@ static void deletePattern (regexPattern *p) if (p->refcount > 0) return; - regfree (p->pattern); - eFree (p->pattern); - p->pattern = NULL; + p->pattern.backend->delete_code (p->pattern.code); if (p->type == PTRN_TAG) { @@ -285,6 +315,11 @@ static void deletePattern (regexPattern *p) if (p->anonymous_tag_prefix) eFree (p->anonymous_tag_prefix); + if (p->optscript) + es_object_unref (p->optscript); + if (p->optscript_src) + eFree (p->optscript_src); + eFree (p); } @@ -304,6 +339,13 @@ extern struct lregexControlBlock* allocLregexControlBlock (parserDefinition *par lcb->tables = ptrArrayNew(deleteTable); lcb->tstack = ptrArrayNew(NULL); lcb->guest_req = guestRequestNew (); + lcb->local_dict = es_nil; + + for (int i = 0; i< SCRIPT_HOOK_MAX; i++) + { + lcb->hook[i] = ptrArrayNew (eFree); + lcb->hook_code[i] = ptrArrayNew ((ptrArrayDeleteFunc)es_object_unref); + } lcb->owner = parser->id; return lcb; @@ -327,6 +369,18 @@ extern void freeLregexControlBlock (struct lregexControlBlock* lcb) guestRequestDelete (lcb->guest_req); lcb->guest_req = NULL; + es_object_unref (lcb->local_dict); + lcb->local_dict = es_nil; + + for (int i = 0; i < SCRIPT_HOOK_MAX; i++) + { + ptrArrayDelete (lcb->hook[i]); + lcb->hook[i] = NULL; + + ptrArrayDelete (lcb->hook_code[i]); + lcb->hook_code[i] = NULL; + } + eFree (lcb); } @@ -335,11 +389,11 @@ extern void freeLregexControlBlock (struct lregexControlBlock* lcb) */ static void initRegexTag (tagEntryInfo *e, - const vString* const name, int kindIndex, int roleIndex, int scopeIndex, int placeholder, + const char * name, int kindIndex, int roleIndex, int scopeIndex, int placeholder, unsigned long line, MIOPos *pos, int xtag_type) { - Assert (name != NULL && ((vStringLength (name) > 0) || placeholder)); - initRefTagEntry (e, vStringValue (name), kindIndex, roleIndex); + Assert (name != NULL && ((name[0] != '\0') || placeholder)); + initRefTagEntry (e, name, kindIndex, roleIndex); e->extensionFields.scopeIndex = scopeIndex; e->placeholder = !!placeholder; if (line) @@ -364,7 +418,7 @@ static void initRegexTag (tagEntryInfo *e, * Returns pointer to terminating separator. Works in place. Null * terminates name string. */ -static char* scanSeparators (char* name, enum regexParserType regptype) +static char* scanSeparators (char* name, bool multiline) { char sep = name [0]; char *copyto = name; @@ -378,9 +432,7 @@ static char* scanSeparators (char* name, enum regexParserType regptype) *copyto++ = sep; else if (*name == 't') *copyto++ = '\t'; - else if ((regptype == REG_PARSER_MULTI_LINE - || (regptype == REG_PARSER_MULTI_TABLE)) - && *name == 'n') + else if (multiline && *name == 'n') *copyto++ = '\n'; else { @@ -420,7 +472,8 @@ static bool parseTagRegex ( bool result = false; const int separator = (unsigned char) regexp [0]; - *name = scanSeparators (regexp, regptype); + *name = scanSeparators (regexp, (regptype == REG_PARSER_MULTI_LINE + || regptype == REG_PARSER_MULTI_TABLE)); if (*regexp == '\0') error (WARNING, "empty regexp"); else if (**name != separator) @@ -436,10 +489,28 @@ static bool parseTagRegex ( { /* * first----------V third------------V - * --regex-=/regexp/replacement/[kind-spec/][flags] + * --regex-=/regexp/replacement/[kind-spec/][flags][{{\n...\n}}] * second----------------^ fourth---------------^ */ + /* + * The following code assumes "{{\n" is never used in flags. + * If the input comes from the command line or an optlib file, + * this assumption is always correct; a new line character is never + * put at the middle (or end) of the input. + * + * TODO: How about the input comes from the source code translated + * by optlib2c? + */ + char *script = strstr (third, "{{\n"); + if (script) + { + /* The script part should not be unescaed by scanSeparators(). + * By spitting the string, we can hide the script part from + * scanSeparators(). */ + script [0] = '\0'; + } + char* const fourth = scanSeparators (third, false); if (*fourth == separator) { @@ -452,6 +523,21 @@ static bool parseTagRegex ( *flags = third; *kinds = NULL; } + + if (script) + { + Assert (*flags); + + char *end = *flags + strlen (*flags); + script [0] = '{'; + if (end != script) + { + size_t len = strlen (script); + memmove (end, script, len); + end [len] = '\0'; + } + } + result = true; } } @@ -552,12 +638,14 @@ static regexPattern * refPattern (regexPattern * ptrn) return ptrn; } -static regexPattern * newPattern (regex_t* const pattern, +static regexPattern * newPattern (regexCompiledCode* const pattern, enum regexParserType regptype) { regexPattern *ptrn = xCalloc(1, regexPattern); - ptrn->pattern = pattern; + ptrn->pattern.backend = pattern->backend; + ptrn->pattern.code = pattern->code; + ptrn->exclusive = false; ptrn->accept_empty_name = false; ptrn->regptype = regptype; @@ -571,6 +659,10 @@ static regexPattern * newPattern (regex_t* const pattern, ptrn->u.tag.roleBits = 0; ptrn->refcount = 1; + + ptrn->optscript = NULL; + ptrn->optscript_src = NULL; + return ptrn; } @@ -584,7 +676,7 @@ static regexTableEntry * newRefPatternEntry (regexTableEntry * other) return entry; } -static regexTableEntry * newEntry (regex_t* const pattern, +static regexTableEntry * newEntry (regexCompiledCode* const pattern, enum regexParserType regptype) { regexTableEntry *entry = xCalloc (1, regexTableEntry); @@ -594,7 +686,7 @@ static regexTableEntry * newEntry (regex_t* const pattern, static regexPattern* addCompiledTagCommon (struct lregexControlBlock *lcb, int table_index, - regex_t* const pattern, + regexCompiledCode* const pattern, enum regexParserType regptype) { regexTableEntry *entry = newEntry (pattern, regptype); @@ -1200,7 +1292,12 @@ static void patternEvalFlags (struct lregexControlBlock *lcb, if (regptype == REG_PARSER_SINGLE_LINE) flagsEval (flags, prePtrnFlagDef, ARRAY_SIZE(prePtrnFlagDef), &ptrn->exclusive); - flagsEval (flags, commonSpecFlagDef, ARRAY_SIZE(commonSpecFlagDef), &commonFlagData); + const char * optscript = flagsEval (flags, commonSpecFlagDef, ARRAY_SIZE(commonSpecFlagDef), &commonFlagData); + if (optscript) + { + ptrn->optscript = scriptRead (optvm, optscript); + ptrn->optscript_src = eStrdup (optscript); + } if (regptype == REG_PARSER_SINGLE_LINE || regptype == REG_PARSER_MULTI_TABLE) flagsEval (flags, scopePtrnFlagDef, ARRAY_SIZE(scopePtrnFlagDef), &ptrn->scopeActions); @@ -1224,7 +1321,7 @@ static void patternEvalFlags (struct lregexControlBlock *lcb, static regexPattern *addCompiledTagPattern (struct lregexControlBlock *lcb, int table_index, - enum regexParserType regptype, regex_t* const pattern, + enum regexParserType regptype, regexCompiledCode* const pattern, const char* const name, char kindLetter, const char* kindName, char *const description, const char* flags, bool kind_explicitly_defined, @@ -1242,7 +1339,7 @@ static regexPattern *addCompiledTagPattern (struct lregexControlBlock *lcb, return ptrn; } -static regexPattern *addCompiledCallbackPattern (struct lregexControlBlock *lcb, regex_t* const pattern, +static regexPattern *addCompiledCallbackPattern (struct lregexControlBlock *lcb, regexCompiledCode* const pattern, const regexCallback callback, const char* flags, bool *disabled, void *userData) @@ -1259,33 +1356,21 @@ static regexPattern *addCompiledCallbackPattern (struct lregexControlBlock *lcb, return ptrn; } - -static void regex_flag_basic_short (char c CTAGS_ATTR_UNUSED, void* data) -{ - int* cflags = data; - *cflags &= ~REG_EXTENDED; -} - -static void regex_flag_basic_long (const char* const s CTAGS_ATTR_UNUSED, const char* const unused CTAGS_ATTR_UNUSED, void* data) -{ - regex_flag_basic_short ('b', data); -} - -static void regex_flag_extend_short (char c CTAGS_ATTR_UNUSED, void* data) -{ - int* cflags = data; - *cflags |= REG_EXTENDED; -} - -static void regex_flag_extend_long (const char* const c CTAGS_ATTR_UNUSED, const char* const unused CTAGS_ATTR_UNUSED, void* data) -{ - regex_flag_extend_short('e', data); -} +static flagDefinition backendFlagDefs[] = { + { 'b', "basic", basic_regex_flag_short, basic_regex_flag_long, + NULL, "interpreted as a Posix basic regular expression."}, + { 'e', "extend", extend_regex_flag_short, extend_regex_flag_long, + NULL, "interpreted as a Posix extended regular expression (default)"}, +#ifdef HAVE_PCRE2 + { 'p', "pcre2", pcre2_regex_flag_short, pcre2_regex_flag_long, + NULL, "use pcre2 regex engine"}, +#endif +}; static void regex_flag_icase_short (char c CTAGS_ATTR_UNUSED, void* data) { - int* cflags = data; - *cflags |= REG_ICASE; + struct flagDefsDescriptor *desc = data; + desc->backend->set_icase_flag (&desc->flags); } static void regex_flag_icase_long (const char* s CTAGS_ATTR_UNUSED, const char* const unused CTAGS_ATTR_UNUSED, void* data) @@ -1293,44 +1378,57 @@ static void regex_flag_icase_long (const char* s CTAGS_ATTR_UNUSED, const char* regex_flag_icase_short ('i', data); } - -static flagDefinition regexFlagDefs[] = { - { 'b', "basic", regex_flag_basic_short, regex_flag_basic_long, - NULL, "interpreted as a Posix basic regular expression."}, - { 'e', "extend", regex_flag_extend_short, regex_flag_extend_long, - NULL, "interpreted as a Posix extended regular expression (default)"}, +static flagDefinition backendCommonRegexFlagDefs[] = { { 'i', "icase", regex_flag_icase_short, regex_flag_icase_long, NULL, "applied in a case-insensitive manner"}, }; -static regex_t* compileRegex (enum regexParserType regptype, - const char* const regexp, const char* const flags) + +static struct flagDefsDescriptor choose_backend (const char *flags, enum regexParserType regptype, bool error_if_no_backend) { - int cflags = REG_EXTENDED | REG_NEWLINE; + struct flagDefsDescriptor desc = { + .backend = NULL, + .flags = 0, + .regptype = regptype, + }; - if (regptype == REG_PARSER_MULTI_TABLE) - cflags &= ~REG_NEWLINE; + if (flags) + flagsEval (flags, + backendFlagDefs, + ARRAY_SIZE(backendFlagDefs), + &desc); + + /* Choose the default backend. */ + if (desc.backend == NULL) + { + if (flags && error_if_no_backend) + error (FATAL, "No sunch backend for the name: \"%s\"", flags); - regex_t *result; - int errcode; + flagsEval (DEFAULT_REGEX_BACKEND, + backendFlagDefs, + ARRAY_SIZE(backendFlagDefs), + &desc); + } + return desc; +} +static regexCompiledCode compileRegex (enum regexParserType regptype, + const char* const regexp, const char* const flags) +{ + struct flagDefsDescriptor desc = choose_backend (flags, regptype, false); + + /* Evaluate backend specific flags */ flagsEval (flags, - regexFlagDefs, - ARRAY_SIZE(regexFlagDefs), - &cflags); + desc.backend->fdefs, + desc.backend->fdef_count, + &desc.flags); - result = xMalloc (1, regex_t); - errcode = regcomp (result, regexp, cflags); - if (errcode != 0) - { - char errmsg[256]; - regerror (errcode, result, errmsg, 256); - error (WARNING, "regcomp %s: %s", regexp, errmsg); - regfree (result); - eFree (result); - result = NULL; - } - return result; + flagsEval (flags, + backendCommonRegexFlagDefs, + ARRAY_SIZE (backendCommonRegexFlagDefs), + &desc); + + return desc.backend->compile (desc.backend, regexp, desc.flags); } @@ -1446,11 +1544,17 @@ static void fillEndLineFieldOfUpperScopes (struct lregexControlBlock *lcb, unsig } } +static bool hasNameSlot (const regexPattern* const patbuf) +{ + return (patbuf->u.tag.name_pattern[0] != '\0' + || patbuf->anonymous_tag_prefix); +} + static void matchTagPattern (struct lregexControlBlock *lcb, const char* line, const regexPattern* const patbuf, const regmatch_t* const pmatch, - off_t offset) + off_t offset, scriptWindow *window) { vString *const name = (patbuf->u.tag.name_pattern[0] != '\0') ? substitute (line, @@ -1520,7 +1624,7 @@ static void matchTagPattern (struct lregexControlBlock *lcb, kind = patbuf->u.tag.kindIndex; roleBits = patbuf->u.tag.roleBits; - initRegexTag (&e, name, kind, ROLE_DEFINITION_INDEX, scope, placeholder, + initRegexTag (&e, vStringValue (name), kind, ROLE_DEFINITION_INDEX, scope, placeholder, ln, ln == 0? NULL: &pos, patbuf->xtagType); if (field_trashbox == NULL) @@ -1569,6 +1673,16 @@ static void matchTagPattern (struct lregexControlBlock *lcb, if (patbuf->scopeActions & SCOPE_PUSH) lcb->currentScope = n; + if (n != CORK_NIL && window) + { + 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); + es_object_unref (e); + scriptTeardown (optvm, lcb); + } + vStringDelete (name); } @@ -1694,19 +1808,41 @@ static bool matchRegexPattern (struct lregexControlBlock *lcb, if (patbuf->disabled && *(patbuf->disabled)) return false; - match = regexec (patbuf->pattern, vStringValue (line), - BACK_REFERENCE_COUNT, pmatch, 0); + match = patbuf->pattern.backend->match (patbuf->pattern.backend, + patbuf->pattern.code, vStringValue (line), + vStringLength (line), + pmatch); + if (match == 0) { result = true; entry->statistics.match++; + scriptWindow window = { + .line = vStringValue (line), + .start = 0, + .patbuf = patbuf, + .pmatch = pmatch, + .nmatch = BACK_REFERENCE_COUNT, + .advanceto = false, + }; + + if (patbuf->optscript && (! hasNameSlot (patbuf))) + { + 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); + es_object_unref (e); + scriptTeardown (optvm, lcb); + } if (hasMessage(patbuf)) printMessage(lcb->owner, patbuf, 0, vStringValue (line), pmatch); if (patbuf->type == PTRN_TAG) { - matchTagPattern (lcb, vStringValue (line), patbuf, pmatch, 0); + matchTagPattern (lcb, vStringValue (line), patbuf, pmatch, 0, + (patbuf->optscript && hasNameSlot (patbuf))? &window: NULL); if (guest->lang.type != GUEST_LANG_UNKNOWN) { @@ -1759,8 +1895,11 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, current = start = vStringValue (allLines); do { - match = regexec (patbuf->pattern, current, - BACK_REFERENCE_COUNT, pmatch, 0); + match = patbuf->pattern.backend->match (patbuf->pattern.backend, + patbuf->pattern.code, current, + vStringLength (allLines) - (current - start), + pmatch); + if (match != 0) { entry->statistics.unmatch++; @@ -1774,9 +1913,29 @@ static bool matchMultilineRegexPattern (struct lregexControlBlock *lcb, - start; entry->statistics.match++; + scriptWindow window = { + .line = current, + .start = start, + .patbuf = patbuf, + .pmatch = pmatch, + .nmatch = BACK_REFERENCE_COUNT, + .advanceto = false, + }; + + if (patbuf->optscript && (! hasNameSlot (patbuf))) + { + 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); + es_object_unref (e); + scriptTeardown (optvm, lcb); + } + if (patbuf->type == PTRN_TAG) { - matchTagPattern (lcb, current, patbuf, pmatch, offset); + matchTagPattern (lcb, current, patbuf, pmatch, offset, + (patbuf->optscript && hasNameSlot (patbuf))? &window: NULL); result = true; } else if (patbuf->type == PTRN_CALLBACK) @@ -1852,10 +2011,22 @@ extern void notifyRegexInputStart (struct lregexControlBlock *lcb) ptrArrayClear (lcb->tstack); guestRequestClear (lcb->guest_req); + + opt_vm_dstack_push (optvm, lregex_dict); + + 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); + 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); + opt_dict_clear (lcb->local_dict); unsigned long endline = getInputLineNumber (); fillEndLineFieldOfUpperScopes (lcb, endline); } @@ -1877,30 +2048,36 @@ extern void findRegexTags (void) findRegexTagsMainloop (fileReadLineDriver); } -static bool hasScopeActionInRegex0(ptrArray *entries) +static bool doesExpectCorkInRegex0(ptrArray *entries) { for (unsigned int i = 0; i < ptrArrayCount(entries); i++) { regexTableEntry *entry = ptrArrayItem(entries, i); Assert (entry && entry->pattern); - if (entry->pattern->scopeActions) + if (entry->pattern->scopeActions + || entry->pattern->optscript + ) return true; } return false; } -extern bool hasScopeActionInRegex (struct lregexControlBlock *lcb) +extern bool doesExpectCorkInRegex (struct lregexControlBlock *lcb) { ptrArray *entries; entries = lcb->entries[REG_PARSER_SINGLE_LINE]; - if (hasScopeActionInRegex0 (entries)) + if (doesExpectCorkInRegex0 (entries)) + return true; + + entries = lcb->entries[REG_PARSER_MULTI_LINE]; + if (doesExpectCorkInRegex0 (entries)) return true; for (unsigned int i = 0; i < ptrArrayCount(lcb->tables); i++) { struct regexTable *table = ptrArrayItem(lcb->tables, i); - if (hasScopeActionInRegex0 (table->entries)) + if (doesExpectCorkInRegex0 (table->entries)) return true; } @@ -1944,9 +2121,21 @@ static regexPattern *addTagRegexInternal (struct lregexControlBlock *lcb, if (!regexAvailable) return NULL; - regex_t* const cp = compileRegex (regptype, regex, flags); - if (cp == NULL) + regexCompiledCode cp = compileRegex (regptype, regex, flags); + if (cp.code == NULL) + { + error (WARNING, "pattern: %s", regex); + if (table_index != TABLE_INDEX_UNUSED) + { + struct regexTable *table = ptrArrayItem (lcb->tables, table_index); + error (WARNING, "table: %s[%u]", table->name, ptrArrayCount (table->entries)); + error (WARNING, "language: %s", getLanguageName (lcb->owner)); + } + else + error (WARNING, "language: %s[%u]", getLanguageName (lcb->owner), + ptrArrayCount (lcb->entries[regptype])); return NULL; + } char kindLetter; char* kindName; @@ -2011,7 +2200,7 @@ static regexPattern *addTagRegexInternal (struct lregexControlBlock *lcb, } regexPattern *rptr = addCompiledTagPattern (lcb, table_index, - regptype, cp, name, + regptype, &cp, name, kindLetter, kindName, description, flags, explictly_defined, disabled); @@ -2027,6 +2216,7 @@ static regexPattern *addTagRegexInternal (struct lregexControlBlock *lcb, || rptr->anonymous_tag_prefix || regptype == REG_PARSER_MULTI_TABLE || rptr->guest.lang.type != GUEST_LANG_UNKNOWN + || rptr->optscript ) rptr->accept_empty_name = true; else @@ -2083,13 +2273,17 @@ extern void addCallbackRegex (struct lregexControlBlock *lcb, return; - regex_t* const cp = compileRegex (REG_PARSER_SINGLE_LINE, regex, flags); - if (cp != NULL) + regexCompiledCode cp = compileRegex (REG_PARSER_SINGLE_LINE, regex, flags); + if (cp.code == NULL) { - regexPattern *rptr = addCompiledCallbackPattern (lcb, cp, callback, flags, - disabled, userData); - rptr->pattern_string = escapeRegexPattern(regex); + error (WARNING, "pattern: %s", regex); + error (WARNING, "language: %s", getLanguageName (lcb->owner)); + return; } + + regexPattern *rptr = addCompiledCallbackPattern (lcb, &cp, callback, flags, + disabled, userData); + rptr->pattern_string = escapeRegexPattern(regex); } static void addTagRegexOption (struct lregexControlBlock *lcb, @@ -2182,49 +2376,79 @@ extern void processTagRegexOption (struct lregexControlBlock *lcb, * Regex option parsing */ -extern void printRegexFlags (bool withListHeader, bool machinable, FILE *fp) +extern void printRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp) { - struct colprintTable * table; - - table = flagsColprintTableNew (); + struct colprintTable * table = flagsColprintTableNew (); - flagsColprintAddDefinitions (table, regexFlagDefs, ARRAY_SIZE (regexFlagDefs)); - flagsColprintAddDefinitions (table, prePtrnFlagDef, ARRAY_SIZE (prePtrnFlagDef)); - flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); - flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); - flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + if (flags && *flags != '\0') + { + /* Print backend specific flags. + * This code is just stub because there is no backend having a specific flag. + * The help message for this option is not updated. */ + struct flagDefsDescriptor desc = choose_backend (flags, REG_PARSER_SINGLE_LINE, true); + flagsColprintAddDefinitions (table, desc.backend->fdefs, desc.backend->fdef_count); + } + else + { + flagsColprintAddDefinitions (table, backendFlagDefs, ARRAY_SIZE(backendFlagDefs)); + flagsColprintAddDefinitions (table, backendCommonRegexFlagDefs, ARRAY_SIZE(backendCommonRegexFlagDefs)); + flagsColprintAddDefinitions (table, prePtrnFlagDef, ARRAY_SIZE (prePtrnFlagDef)); + flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); + flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); + flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + } flagsColprintTablePrint (table, withListHeader, machinable, fp); colprintTableDelete(table); } -extern void printMultilineRegexFlags (bool withListHeader, bool machinable, FILE *fp) +extern void printMultilineRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp) { - struct colprintTable * table; - - table = flagsColprintTableNew (); + struct colprintTable * table = flagsColprintTableNew (); - flagsColprintAddDefinitions (table, regexFlagDefs, ARRAY_SIZE (regexFlagDefs)); - flagsColprintAddDefinitions (table, multilinePtrnFlagDef, ARRAY_SIZE (multilinePtrnFlagDef)); - flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); - flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + if (flags && *flags != '\0') + { + /* Print backend specific flags. + * This code is just stub because there is no backend having a specific flag. + * The help message for this option is not updated. */ + struct flagDefsDescriptor desc = choose_backend (flags, REG_PARSER_MULTI_LINE, true); + flagsColprintAddDefinitions (table, desc.backend->fdefs, desc.backend->fdef_count); + } + else + { + flagsColprintAddDefinitions (table, backendFlagDefs, ARRAY_SIZE(backendFlagDefs)); + flagsColprintAddDefinitions (table, backendCommonRegexFlagDefs, ARRAY_SIZE(backendCommonRegexFlagDefs)); + flagsColprintAddDefinitions (table, multilinePtrnFlagDef, ARRAY_SIZE (multilinePtrnFlagDef)); + flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); + flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + } flagsColprintTablePrint (table, withListHeader, machinable, fp); colprintTableDelete(table); } -extern void printMultitableRegexFlags (bool withListHeader, bool machinable, FILE *fp) +extern void printMultitableRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp) { - struct colprintTable * table; + struct colprintTable * table = flagsColprintTableNew (); - table = flagsColprintTableNew (); - - flagsColprintAddDefinitions (table, regexFlagDefs, ARRAY_SIZE (regexFlagDefs)); - flagsColprintAddDefinitions (table, multilinePtrnFlagDef, ARRAY_SIZE (multilinePtrnFlagDef)); - flagsColprintAddDefinitions (table, multitablePtrnFlagDef, ARRAY_SIZE (multitablePtrnFlagDef)); - flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); - flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); - flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + if (flags && *flags != '\0') + { + /* Print backend specific flags. + * This code is just stub because there is no backend having a specific flag. + * The help message for this option is not updated. */ + struct flagDefsDescriptor desc = choose_backend (flags, REG_PARSER_MULTI_TABLE, true); + flagsColprintAddDefinitions (table, desc.backend->fdefs, desc.backend->fdef_count); + } + else + { + flagsColprintAddDefinitions (table, backendFlagDefs, ARRAY_SIZE(backendFlagDefs)); + flagsColprintAddDefinitions (table, backendCommonRegexFlagDefs, ARRAY_SIZE(backendCommonRegexFlagDefs)); + flagsColprintAddDefinitions (table, multilinePtrnFlagDef, ARRAY_SIZE (multilinePtrnFlagDef)); + flagsColprintAddDefinitions (table, multitablePtrnFlagDef, ARRAY_SIZE (multitablePtrnFlagDef)); + flagsColprintAddDefinitions (table, guestPtrnFlagDef, ARRAY_SIZE (guestPtrnFlagDef)); + flagsColprintAddDefinitions (table, scopePtrnFlagDef, ARRAY_SIZE (scopePtrnFlagDef)); + flagsColprintAddDefinitions (table, commonSpecFlagDef, ARRAY_SIZE (commonSpecFlagDef)); + } flagsColprintTablePrint (table, withListHeader, machinable, fp); colprintTableDelete(table); @@ -2232,7 +2456,8 @@ extern void printMultitableRegexFlags (bool withListHeader, bool machinable, FIL extern void freeRegexResources (void) { - /* TODO: SHOULD BE REMOVED */ + es_object_unref (lregex_dict); + opt_vm_delete (optvm); } extern bool regexNeedsMultilineBuffer (struct lregexControlBlock *lcb) @@ -2406,6 +2631,10 @@ static struct regexTable * matchMultitableRegexTable (struct lregexControlBlock for (unsigned int i = 0; i < ptrArrayCount(table->entries); i++) { regexTableEntry *entry = ptrArrayItem(table->entries, i); + if ((entry->pattern->xtagType != XTAG_UNKNOWN) + && (!isXtagEnabled (entry->pattern->xtagType))) + continue; + regexPattern *ptrn = entry->pattern; struct guestSpec *guest = &ptrn->guest; @@ -2453,21 +2682,45 @@ static struct regexTable * matchMultitableRegexTable (struct lregexControlBlock if (ptrn->disabled && *(ptrn->disabled)) continue; - match = regexec (ptrn->pattern, current, - BACK_REFERENCE_COUNT, pmatch, 0); - + match = ptrn->pattern.backend->match (ptrn->pattern.backend, + ptrn->pattern.code, current, + vStringLength(start) - (current - cstart), + pmatch); 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, + .patbuf = ptrn, + .pmatch = pmatch, + .nmatch = BACK_REFERENCE_COUNT, + .advanceto = false, + }; + initTaction (&window.taction); + + if (ptrn->optscript && (! hasNameSlot (ptrn))) + { + scriptSetup (optvm, lcb, CORK_NIL, &window); + EsObject *e = scriptEval (optvm, ptrn->optscript); + if (es_error_p (e)) + error (WARNING, "error when evaluating: %s", ptrn->optscript_src); + es_object_unref (e); + scriptTeardown (optvm, lcb); + } if (ptrn->type == PTRN_TAG) { - struct mTableActionSpec *taction = &(ptrn->taction); + matchTagPattern (lcb, current, ptrn, pmatch, offset_for_tag, + (ptrn->optscript && hasNameSlot (ptrn))? &window: NULL); + + struct mTableActionSpec *taction = (window.taction.action == TACTION_NOP) + ? &(ptrn->taction) + : &window.taction; - matchTagPattern (lcb, current, ptrn, pmatch, - (current - + pmatch [ptrn->mgroup.forLineNumberDetermination].rm_so) - - cstart); BEGIN_VERBOSE(vfp); { fprintf(vfp, "result: matched %d bytes\n", (int)(pmatch[0].rm_eo)); @@ -2487,9 +2740,12 @@ static struct regexTable * matchMultitableRegexTable (struct lregexControlBlock guestRequestClear (lcb->guest_req); } - delta = (ptrn->mgroup.nextFromStart - ? pmatch [ptrn->mgroup.forNextScanning].rm_so - : pmatch [ptrn->mgroup.forNextScanning].rm_eo); + if (window.advanceto) + delta = window.advanceto_delta; + else + delta = (ptrn->mgroup.nextFromStart + ? pmatch [ptrn->mgroup.forNextScanning].rm_so + : pmatch [ptrn->mgroup.forNextScanning].rm_eo); *offset += delta; switch (taction->action) @@ -2719,6 +2975,9 @@ static int makePromiseForAreaSpecifiedWithOffsets (const char *parser, unsigned long startLineOffset = getInputFileOffsetForLine (startLine); unsigned long endLineOffset = getInputFileOffsetForLine (endLine); + Assert(startOffset >= startLineOffset); + Assert(endOffset >= endLineOffset); + return makePromise (parser, startLine, startOffset - startLineOffset, endLine, endOffset - endLineOffset, @@ -2764,6 +3023,72 @@ static void guestRequestSubmit (struct guestRequest *r) r->boundary[BOUNDARY_END].offset); } +/* + * Script related functions + */ + +/* This functions expects { code }} as input. + * Be care that curly brackets must be unbalanced. + */ +static EsObject *scriptRead (OptVM *vm, const char *src) +{ + size_t len = strlen (src); + Assert (len > 2); + Assert (src[len - 1] == '}'); + Assert (src[len - 2] == '}'); + + EsObject *obj = optscriptRead (vm, src + 1, len - 1 - 1); + if (es_error_p (obj)) + error (FATAL, "failed in loading an optscript: %s", src); + return obj; +} + +extern EsObject* scriptEval (OptVM *vm, EsObject *optscript) +{ + return optscriptEval (vm, optscript); +} + +static void scriptEvalHook (OptVM *vm, struct lregexControlBlock *lcb, enum scriptHook hook) +{ + if (ptrArrayCount (lcb->hook_code[hook]) == 0) + { + for (int i = 0; i < ptrArrayCount (lcb->hook[hook]); i++) + { + const char *src = ptrArrayItem (lcb->hook[hook], i); + EsObject *code = scriptRead (vm, src); + if (es_error_p (code)) + error (FATAL, "error when reading hook[%d] code: %s", hook, src); + ptrArrayAdd (lcb->hook_code[hook], es_object_ref (code)); + es_object_unref (code); + } + } + for (int i = 0; i < ptrArrayCount (lcb->hook_code[hook]); i++) + { + EsObject *code = ptrArrayItem (lcb->hook_code[hook], i); + EsObject * e = optscriptEval (vm, code); + if (es_error_p (e)) + error (WARNING, "error when evaluating hook[%d] code: %s", + hook, (char *)ptrArrayItem (lcb->hook[i], i)); + } +} + +static void scriptSetup (OptVM *vm, struct lregexControlBlock *lcb, int corkIndex, scriptWindow *window) +{ + lcb->window = window; + optscriptSetup (vm, lcb->local_dict, corkIndex); +} + +static void scriptTeardown (OptVM *vm, struct lregexControlBlock *lcb) +{ + optscriptTeardown (vm, lcb->local_dict); + lcb->window = NULL; +} + +extern void addOptscriptToHook (struct lregexControlBlock *lcb, enum scriptHook hook, const char *code) +{ + ptrArrayAdd (lcb->hook[hook], eStrdup (code)); +} + /* Return true if available. */ extern bool checkRegex (void) { @@ -2781,5 +3106,950 @@ extern bool checkRegex (void) /* We are using bundled regex engine. */ regexAvailable = true; #endif + return regexAvailable; } + +static EsObject *OPTSCRIPT_ERR_UNKNOWNKIND; + +/* name:str kind:name loc _TAG tag + * name:str kind:name _TAG tag */ +static EsObject* lrop_make_tag (OptVM *vm, EsObject *name) +{ + 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) + { + if (opt_vm_ostack_count (vm) < 3) + 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) + return OPT_ERR_TYPECHECK; + if (opt_vm_ostack_count (vm) < 2) + 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) + return OPT_ERR_TYPECHECK; + EsObject *kind_sym = es_pointer_get (kind); + const char *kind_str = es_symbol_get (kind_sym); + kindDefinition* kind_def = getLanguageKindForName (getInputLanguage (), + kind_str); + if (!kind_def) + return OPTSCRIPT_ERR_UNKNOWNKIND; + int kind_index = kind_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_DEFINITION_INDEX, CORK_NIL, 0, + loc? loc->line: 0, loc? &loc->pos: NULL, XTAG_UNKNOWN); + EsObject *obj = es_pointer_new (OPT_TYPE_TAG, e); + if (es_error_p (obj)) + return obj; + + while (index-- > 0) + opt_vm_ostack_pop (vm); + + opt_vm_ostack_push (vm, obj); + es_object_unref (obj); + return es_false; +} + +static EsObject *OPTSCRIPT_ERR_UNKNOWNROLE; + +static EsObject* lrop_make_reftag (OptVM *vm, EsObject *name) +{ + 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) + { + if (opt_vm_ostack_count (vm) < 4) + 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) + 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; + + 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); + if (!role_def) + 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; + + while (index-- > 0) + opt_vm_ostack_pop (vm); + + opt_vm_ostack_push (vm, obj); + es_object_unref (obj); + return es_false; +} + +/* tag COMMIT int */ +static EsObject* lrop_commit_tag (OptVM *vm, EsObject *name) +{ + EsObject *tag = opt_vm_ostack_top (vm); + if (es_object_get_type (tag) != OPT_TYPE_TAG) + return OPT_ERR_TYPECHECK; + + tagEntryInfo *e = es_pointer_get (tag); + int corkIndex = makeTagEntry (e); + EsObject *n = es_integer_new (corkIndex); + if (es_error_p (n)) + return n; + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, n); + es_object_unref (n); + return es_false; +} + +static EsObject* lrop_get_match_loc (OptVM *vm, EsObject *name) +{ + + bool start; + EsObject *group; + + if (opt_vm_ostack_count (vm) < 1) + return OPT_ERR_UNDERFLOW; + + EsObject *tmp = opt_vm_ostack_top (vm); + + if (es_object_get_type (tmp) == ES_TYPE_INTEGER) + { + group = tmp; + start = true; + } + else + { + EsObject *pos = tmp; + + static EsObject *start_name, *end_name; + if (!start_name) + { + start_name = opt_name_new_from_cstr ("start"); + end_name = opt_name_new_from_cstr ("end"); + } + + if (es_object_equal (pos, start_name)) + start = true; + else if (es_object_equal (pos, end_name)) + start = false; + else + return OPT_ERR_TYPECHECK; + + if (opt_vm_ostack_count (vm) < 2) + return OPT_ERR_UNDERFLOW; + + group = opt_vm_ostack_peek (vm, 1); + if (es_object_get_type (group) != ES_TYPE_INTEGER) + return OPT_ERR_TYPECHECK; + } + + int g = es_integer_get (group); + if (g < 1) + return OPT_ERR_RANGECHECK; + + struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + scriptWindow *window = lcb->window; + + matchLoc *mloc = make_mloc (window, g, start); + if (mloc == NULL) + return OPT_ERR_RANGECHECK; + + EsObject * mlocobj = es_pointer_new (OPT_TYPE_MATCHLOC, mloc); + if (es_error_p (mlocobj)) + { + eFree (mloc); + return mlocobj; + } + + if (group != tmp) + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, mlocobj); + es_object_unref (mlocobj); + return es_false; +} + +static matchLoc* make_mloc_from_tagEntryInfo(tagEntryInfo *e) +{ + matchLoc *mloc = xMalloc (1, matchLoc); + mloc->delta = 0; + mloc->line = e->lineNumber; + mloc->pos = e->filePosition; + + return mloc; +} + +static EsObject* lrop_get_tag_loc (OptVM *vm, EsObject *name) +{ + EsObject *nobj = opt_vm_ostack_top (vm); + + if (es_object_get_type (nobj) != ES_TYPE_INTEGER) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get(nobj); + if (! (CORK_NIL < n && n < countEntryInCorkQueue())) + return OPT_ERR_RANGECHECK; + + tagEntryInfo *e = getEntryInCorkQueue (n); + if (e == NULL) + return OPT_ERR_TYPECHECK; /* ??? */ + + matchLoc *mloc = make_mloc_from_tagEntryInfo (e); + EsObject * mlocobj = es_pointer_new (OPT_TYPE_MATCHLOC, mloc); + if (es_error_p (mlocobj)) + { + eFree (mloc); + return mlocobj; + } + + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, mlocobj); + es_object_unref (mlocobj); + return es_false; +} + +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; + const char *cstr = make_match_string (window, i); + if (!cstr) + { + for (; npop > 0; npop--) + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, es_false); + return es_false; + } + EsObject *str = opt_string_new_from_cstr (cstr); + eFree ((void *)cstr); + + for (; npop > 0; npop--) + opt_vm_ostack_pop (vm); + + opt_vm_ostack_push (vm, str); + es_object_unref (str); + return es_false; +} + +/* Handles \1, \2, ... */ +static EsObject* lrop_get_match_string_named_group (OptVM *vm, EsObject *name) +{ + void * data = es_symbol_get_data (name); + int i = HT_PTR_TO_INT (data); + + return lrop_get_match_string_common (vm, i, 0); +} + +static EsObject* lrop_get_match_string_gorup_on_stack (OptVM *vm, EsObject *name) +{ + EsObject *group = opt_vm_ostack_top (vm); + if (!es_integer_p (group)) + return OPT_ERR_TYPECHECK; + + int g = es_integer_get (group); + if (g < 1) + return OPT_ERR_RANGECHECK; + + EsObject *r = lrop_get_match_string_common (vm, g, 1); + if (es_error_p (r)) + return r; + + r = opt_vm_ostack_top (vm); + if (es_object_get_type (r) == OPT_TYPE_STRING) + opt_vm_ostack_push (vm, es_true); + return es_false; +} + +static char* make_match_string (scriptWindow *window, int group) +{ + if (window == NULL + || 0 >= group + || window->nmatch <= group + || window->pmatch [group].rm_so == -1) + return NULL; + + const int len = window->pmatch [group].rm_eo - window->pmatch [group].rm_so; + const char *start = window->line + window->pmatch [group].rm_so; + + return eStrndup (start, len); +} + +static matchLoc *make_mloc (scriptWindow *window, int group, bool start) +{ + if (window == NULL + || 0 > group + || window->nmatch <= group + || window->pmatch [group].rm_so == -1) + return NULL; + + matchLoc *mloc = xMalloc (1, matchLoc); + if (window->patbuf->regptype == REG_PARSER_SINGLE_LINE) + { + mloc->delta = 0; + mloc->line = getInputLineNumber (); + mloc->pos = getInputFilePosition (); + } + else + { + 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->pos = getInputFilePositionForLine (mloc->line); + } + return mloc; +} + +static EsObject* lrop_set_scope (OptVM *vm, EsObject *name) +{ + EsObject *corkIndex = opt_vm_ostack_top (vm); + if (!es_integer_p (corkIndex)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (corkIndex); + if (n < 0) + return OPT_ERR_RANGECHECK; + + if (n >= countEntryInCorkQueue()) + return OPT_ERR_RANGECHECK; + + struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + lcb->currentScope = n; + + opt_vm_ostack_pop (vm); + + return es_false; +} + +static EsObject* lrop_pop_scope (OptVM *vm, EsObject *name) +{ + struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + if (lcb->currentScope != CORK_NIL) + { + tagEntryInfo *e = getEntryInCorkQueue (lcb->currentScope); + if (e) + lcb->currentScope = e->extensionFields.scopeIndex; + } + return es_false; +} + +static EsObject* lrop_clear_scope (OptVM *vm, EsObject *name) +{ + struct lregexControlBlock *lcb = opt_vm_get_app_data (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); + + if (lcb->currentScope == 0) + { + opt_vm_ostack_push (vm, es_false); + return es_false; + } + + EsObject *q = es_integer_new (lcb->currentScope); + + if (es_error_p (q)) + return q; + + opt_vm_ostack_push (vm, q); + es_object_unref (q); + opt_vm_ostack_push (vm, es_true); + return es_false; +} + +static EsObject* lrop_refN_scope (OptVM *vm, EsObject *name) +{ + EsObject *nobj = opt_vm_ostack_top (vm); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get(nobj); + + struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + int scope = lcb->currentScope; + + while (n--) + { + if (scope == CORK_NIL) + break; + tagEntryInfo *e = getEntryInCorkQueue (scope); + if (e == NULL) + break; + + scope = e->extensionFields.scopeIndex; + } + + EsObject *q = es_integer_new (scope); + if (es_error_p(q)) + return q; + + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, q); + es_object_unref (q); + + return es_false; +} + +static EsObject* lrop_get_scope_depth (OptVM *vm, EsObject *name) +{ + int n = 0; + + struct lregexControlBlock *lcb = opt_vm_get_app_data (vm); + int scope = lcb->currentScope; + + while (scope != CORK_NIL) + { + tagEntryInfo *e = getEntryInCorkQueue (scope); + if (!e) + break; + + scope = e->extensionFields.scopeIndex; + n++; + } + + EsObject *q = es_integer_new (scope); + if (es_error_p(q)) + return q; + + opt_vm_ostack_push (vm, q); + es_object_unref (q); + return es_false; +} + +static EsObject* lrop_repl (OptVM *vm, EsObject *name) +{ + char *old_prompt = opt_vm_set_prompt (vm, "\n% type \"quit\" for exiting from repl\nOPT"); + + opt_vm_print_prompt (vm); + opt_vm_set_prompt (vm, "OPT"); + + while (true) + { + EsObject *o = opt_vm_read (vm, NULL); + if (es_object_equal (o, ES_READER_EOF)) + { + es_object_unref (o); + break; + } + EsObject *e = opt_vm_eval (vm, o); + es_object_unref (o); + + if (es_error_p (e)) + { + if (!es_object_equal (e, OPT_ERR_QUIT)) + opt_vm_report_error (vm, e, NULL); + break; + } + } + + opt_vm_set_prompt (vm, old_prompt); + return es_false; +} + +static EsObject *OPTSCRIPT_ERR_UNKNOWNTABLE; +static EsObject *OPTSCRIPT_ERR_NOTMTABLEPTRN; + +static struct regexTable *getRegexTableForOptscriptName (struct lregexControlBlock *lcb, + EsObject *tableName) +{ + EsObject *table_sym = es_pointer_get (tableName); + const char *table_str = es_symbol_get (table_sym); + int n = getTableIndexForName (lcb, table_str); + if (n < 0) + return NULL; + return ptrArrayItem (lcb->tables, n); +} + +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) + { + error (WARNING, "Use table related operators only with mtable regular expression"); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; + } + + EsObject *table = opt_vm_ostack_top (vm); + if (es_object_get_type (table) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + struct regexTable *t = getRegexTableForOptscriptName (lcb, table); + if (t == NULL) + return OPTSCRIPT_ERR_UNKNOWNTABLE; + + lcb->window->taction = (struct mTableActionSpec){ + .action = action, + .table = t, + .continuation_table = NULL, + }; + + opt_vm_ostack_pop (vm); + return es_false; +} + +static EsObject* lrop_tenter (OptVM *vm, EsObject *name) +{ + return lrop_tenter_common (vm, name, TACTION_ENTER); +} + +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) + { + error (WARNING, "Use table related operators only with mtable regular expression"); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; + } + + EsObject *cont = opt_vm_ostack_top (vm); + EsObject *table = opt_vm_ostack_peek (vm, 1); + + if (es_object_get_type (table) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + if (es_object_get_type (cont) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + struct regexTable *t = getRegexTableForOptscriptName (lcb, table); + if (t == NULL) + return OPTSCRIPT_ERR_UNKNOWNTABLE; + struct regexTable *c = getRegexTableForOptscriptName (lcb, cont); + if (c == NULL) + return OPTSCRIPT_ERR_UNKNOWNTABLE; + + lcb->window->taction = (struct mTableActionSpec){ + .action = TACTION_ENTER, + .table = t, + .continuation_table = c, + }; + + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + return es_false; +} + +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) + { + error (WARNING, "Use table related operators only with mtable regular expression"); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; + } + + lcb->window->taction.action = TACTION_LEAVE; + return es_false; +} + +static EsObject* lrop_tjump (OptVM *vm, EsObject *name) +{ + return lrop_tenter_common (vm, name, TACTION_JUMP); +} + +static EsObject* lrop_treset (OptVM *vm, EsObject *name) +{ + return lrop_tenter_common (vm, name, TACTION_RESET); +} + +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) + { + error (WARNING, "Use table related operators only with mtable regular expression"); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; + } + + lcb->window->taction.action = TACTION_QUIT; + return es_false; +} + +static EsObject* lrop_traced (OptVM *vm, EsObject *name) +{ +#ifdef DO_TRACING + langType lang = getInputLanguage (); + if (isLanguageTraced (lang)) + opt_vm_ostack_push (vm, es_true); + else + opt_vm_ostack_push (vm, es_false); +#else + opt_vm_ostack_push (vm, es_false); +#endif + return false; +} + +EsObject *OPTSCRIPT_ERR_UNKNOWNEXTRA; +static EsObject* lrop_extraenabled (OptVM *vm, EsObject *name) +{ + EsObject *extra = opt_vm_ostack_top (vm); + if (es_object_get_type (extra) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + xtagType xt = optscriptGetXtagType (extra); + if (xt == XTAG_UNKNOWN) + return OPTSCRIPT_ERR_UNKNOWNEXTRA; + + EsObject *r = isXtagEnabled (xt)? es_true: es_false; + opt_vm_ostack_pop (vm); + opt_vm_ostack_push (vm, r); + return es_false; +} + +static EsObject *lrop_markextra (OptVM *vm, EsObject *name) +{ + EsObject *tag = opt_vm_ostack_peek (vm, 1); + tagEntryInfo *e; + if (es_integer_p (tag)) + { + int n = es_integer_get (tag); + 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 + return OPT_ERR_TYPECHECK; + + if (e == NULL) + return OPTSCRIPT_ERR_NOTAGENTRY; + + EsObject *extra = opt_vm_ostack_top (vm); + if (es_object_get_type (extra) != OPT_TYPE_NAME) + return OPT_ERR_TYPECHECK; + + xtagType xt = optscriptGetXtagType (extra); + if (xt == XTAG_UNKNOWN) + return OPTSCRIPT_ERR_UNKNOWNEXTRA; + + langType lang = getXtagOwner (xt); + if (lang != LANG_IGNORE && e->langType != lang) + { + error (WARNING, + "mismatch in the language of the tag (%s) and the language of field (%s)", + getLanguageName (e->langType), getLanguageName (lang)); + return OPTSCRIPT_ERR_UNKNOWNEXTRA; + } + + markTagExtraBit (e, xt); + + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + + return es_false; +} + +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) + { + error (WARNING, "don't use `%s' operator in --regex- option", + es_symbol_get (name)); + return OPTSCRIPT_ERR_NOTMTABLEPTRN; /* TODO */ + } + + EsObject *mlocobj = opt_vm_ostack_top (vm); + if (es_object_get_type (mlocobj) != OPT_TYPE_MATCHLOC) + return OPT_ERR_TYPECHECK; + + matchLoc *loc = es_pointer_get (mlocobj); + lcb->window->advanceto = true; + lcb->window->advanceto_delta = loc->delta; + + return es_true; +} + +static EsObject *lrop_markplaceholder (OptVM *vm, EsObject *name) +{ + EsObject *tag = opt_vm_ostack_top (vm); + + if (!es_integer_p (tag)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (tag); + if (! (CORK_NIL < n && n < countEntryInCorkQueue())) + return OPT_ERR_RANGECHECK; + + tagEntryInfo *e = getEntryInCorkQueue (n); + if (e == NULL) + return OPTSCRIPT_ERR_NOTAGENTRY; + + markTagPlaceholder (e, true); + + opt_vm_ostack_pop (vm); + return es_false; +} + +static struct optscriptOperatorRegistration lropOperators [] = { + { + .name = "_matchstr", + .fn = lrop_get_match_string_gorup_on_stack, + .arity = 1, + .help_str = "group:int _MATCHSTR string true%" + "group:int _MATCHSTR false", + }, + { + .name = "_matchloc", + .fn = lrop_get_match_loc, + .arity = -1, + .help_str = "group:int /start|/end _MATCHLOC matchloc%" + "group:int _MATCHLOC matchloc", + }, + { + .name = "_tagloc", + .fn = lrop_get_tag_loc, + .arity = 1, + .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, + .arity = -1, + .help_str = "name:str kind:name role:name matchloc _REFTAG tag%" + "name:str kind:name role:name _REFTAG tag%", + }, + { + .name = "_commit", + .fn = lrop_commit_tag, + .arity = 1, + .help_str = "tag _COMMIT int", + }, + { + .name = "_scopeset", + .fn = lrop_set_scope, + .arity = 1, + .help_str = "int _SCOPESET -", + }, + { + .name = "_scopepop", + .fn = lrop_pop_scope, + .arity = 0, + .help_str = "- _SCOPEPOP -", + }, + { + .name = "_scopeclear", + .fn = lrop_clear_scope, + .arity = 0, + .help_str = "- _SCOPECLEAR -", + }, + { + .name = "_scopetop", + .fn = lrop_ref0_scope, + .arity = 0, + .help_str = "- _SCOPETOP int true%" + "- _SCOPETOP false", + }, + { + .name = "_scopeNth", + .fn = lrop_refN_scope, + .arity = 1, + .help_str = "index:int _SCOPENTH int", + }, + { + .name = "_scopedepth", + .fn = lrop_get_scope_depth, + .arity = 0, + .help_str = "- _SCOPEDEPTH int", + }, + { + .name = "_repl", + .fn = lrop_repl, + .arity = 0, + .help_str = "- _repl -", + }, + { + .name = "_tenter", + .fn = lrop_tenter, + .arity = 1, + .help_str = "table:name _TENTER -", + }, + { + .name = "_tentercont", + .fn = lrop_tenter_with_continuation, + .arity = 2, + .help_str = "table:name cont:name _TENTERCONT -", + }, + { + .name = "_tleave", + .fn = lrop_tleave, + .arity = 0, + .help_str = "- _TLEAVE -", + }, + { + .name = "_tjump", + .fn = lrop_tjump, + .arity = 1, + .help_str = "table:name _TJUMP -", + }, + { + .name = "_treset", + .fn = lrop_treset, + .arity = 1, + .help_str = "table:name _TRESET -", + }, + { + .name = "_tquit", + .fn = lrop_tquit, + .arity = 0, + .help_str = "- _TQUIT -", + }, + { + .name = "_extraenabled", + .fn = lrop_extraenabled, + .arity = 1, + .help_str = "extra:name _extraenabled bool%" + "language.extra _extraenabled bool", + }, + { + .name = "_markextra", + .fn = lrop_markextra, + .arity = 2, + .help_str = "tag:int|tag:tag extra:name _MARKEXTRA -%" + "tag:int|tag:tag lang.extra:name _MARKEXTRA -", + }, + { + .name = "_advanceto", + .fn = lrop_advanceto, + .arity = 1, + .help_str = "matchloc _ADVANCETO -%" + }, + { + .name = "_traced", + .fn = lrop_traced, + .arity = 0, + .help_str = "- _TRACED true|false", + }, + { + .name = "_markplaceholder", + .fn = lrop_markplaceholder, + .arity = 1, + .help_str = "tag:int _MARKPLACEHOLDER -", + } +}; + +extern void initRegexOptscript (void) +{ + if (!regexAvailable) + return; + + if (optvm) + return; + + optvm = optscriptInit (); + lregex_dict = opt_dict_new (17); + + OPTSCRIPT_ERR_UNKNOWNTABLE = es_error_intern ("unknowntable"); + OPTSCRIPT_ERR_NOTMTABLEPTRN = es_error_intern ("notmtableptrn"); + OPTSCRIPT_ERR_UNKNOWNEXTRA = es_error_intern ("unknownextra"); + OPTSCRIPT_ERR_UNKNOWNLANGUAGE = es_error_intern ("unknownlanguage"); + OPTSCRIPT_ERR_UNKNOWNKIND = es_error_intern ("unknownkind"); + OPTSCRIPT_ERR_UNKNOWNROLE = es_error_intern ("unknownrole"); + + optscriptInstallProcs (lregex_dict, lrop_get_match_string_named_group); + + optscriptRegisterOperators (lregex_dict, + lropOperators, ARRAY_SIZE(lropOperators)); + + extern const char ctagsCommonPrelude[]; + opt_vm_dstack_push (optvm, lregex_dict); + MIO *mio = mio_new_memory ((unsigned char*)ctagsCommonPrelude, strlen (ctagsCommonPrelude), NULL, NULL); + EsObject *e = optscriptLoad (optvm, mio); + if (es_error_p (e)) + error (FATAL, "failed in loading built-in procedures"); + mio_unref (mio); + opt_vm_dstack_pop (optvm); +} + +extern void listRegexOpscriptOperators (FILE *fp) +{ + EsObject *procdocs; + if (!opt_dict_known_and_get_cstr (lregex_dict, + "__procdocs", + &procdocs)) + procdocs = NULL; + + opt_vm_dstack_push (optvm, lregex_dict); + optscriptHelp (optvm, fp, procdocs); + opt_vm_dstack_pop (optvm); +} diff --git a/ctags/main/lregex_p.h b/ctags/main/lregex_p.h index cd017c7bbb..663f71ba29 100644 --- a/ctags/main/lregex_p.h +++ b/ctags/main/lregex_p.h @@ -19,8 +19,18 @@ * INCLUDE FILES */ #include "general.h" +#include "flags_p.h" #include "kind_p.h" #include "lregex.h" +#include "parse.h" + +#include + +/* +* MACRO DEFINITIONS +*/ +/* Back-references \0 through \9 */ +#define BACK_REFERENCE_COUNT 10 /* * DATA DECLARATIONS @@ -33,6 +43,31 @@ enum regexParserType { struct lregexControlBlock; +typedef struct sRegexCompiledCode { + struct regexBackend *backend; + void * code; +} regexCompiledCode; + +struct regexBackend { + flagDefinition *fdefs; + unsigned int fdef_count; + + void (* set_icase_flag) (int *); + regexCompiledCode (* compile) (struct regexBackend *, + const char* const, + int); + int (* match) (struct regexBackend *, + void *, const char *, size_t, + regmatch_t[BACK_REFERENCE_COUNT]); + void (* delete_code) (void *); +}; + +struct flagDefsDescriptor { + struct regexBackend *backend; + int flags; + enum regexParserType regptype; +}; + /* * FUNCTION PROTOTYPES */ @@ -55,7 +90,7 @@ extern void addTagMultiTableRegex(struct lregexControlBlock *lcb, bool *disabled); extern bool matchRegex (struct lregexControlBlock *lcb, const vString* const line); -extern bool hasScopeActionInRegex (struct lregexControlBlock *lcb); +extern bool doesExpectCorkInRegex (struct lregexControlBlock *lcb); extern void addCallbackRegex (struct lregexControlBlock *lcb, const char* const regex, const char* const flags, @@ -72,6 +107,20 @@ extern void notifyRegexInputEnd (struct lregexControlBlock *lcb); extern void addRegexTable (struct lregexControlBlock *lcb, const char *name); extern void extendRegexTable (struct lregexControlBlock *lcb, const char *src, const char *dist); +extern void initRegexOptscript (void); +extern void listRegexOpscriptOperators (FILE *fp); + +extern void addOptscriptToHook (struct lregexControlBlock *lcb, enum scriptHook hook, const char *code); + extern void printMultitableStatistics (struct lregexControlBlock *lcb); +extern void basic_regex_flag_short (char c, void* data); +extern void basic_regex_flag_long (const char* const s, const char* const unused, void* data); +extern void extend_regex_flag_short (char c, void* data); +extern void extend_regex_flag_long (const char* const s, const char* const unused, void* data); +#ifdef HAVE_PCRE2 +extern void pcre2_regex_flag_short (char c, void* data); +extern void pcre2_regex_flag_long (const char* const s, const char* const unused, void* data); +#endif + #endif /* CTAGS_MAIN_LREGEX_PRIVATEH */ diff --git a/ctags/main/main.c b/ctags/main/main.c index a9f66f2b1c..6536a21616 100644 --- a/ctags/main/main.c +++ b/ctags/main/main.c @@ -57,6 +57,7 @@ #include "keyword_p.h" #include "main_p.h" #include "options_p.h" +#include "optscript.h" #include "parse_p.h" #include "read_p.h" #include "routines_p.h" @@ -120,8 +121,9 @@ static bool recurseUsingOpendir (const char *const dirName) } return resize; } +#endif -#elif defined (HAVE__FINDFIRST) +#ifdef HAVE__FINDFIRST static bool createTagsForWildcardEntry ( const char *const pattern, const size_t dirLength, @@ -144,9 +146,8 @@ static bool createTagsForWildcardUsingFindfirst (const char *const pattern) { bool resize = false; const size_t dirLength = baseFilename (pattern) - pattern; -#if defined (HAVE__FINDFIRST) struct _finddata_t fileInfo; - findfirst_t hFile = _findfirst (pattern, &fileInfo); + intptr_t hFile = _findfirst (pattern, &fileInfo); if (hFile != -1L) { do @@ -156,7 +157,6 @@ static bool createTagsForWildcardUsingFindfirst (const char *const pattern) } while (_findnext (hFile, &fileInfo) == 0); _findclose (hFile); } -#endif return resize; } @@ -577,6 +577,7 @@ extern int ctags_cli_main (int argc CTAGS_ATTR_UNUSED, char **argv) initializeParsing (); testEtagsInvocation (); initOptions (); + initRegexOptscript (); readOptionConfiguration (); verbose ("Reading initial options from command line\n"); parseCmdlineOptions (args); diff --git a/ctags/main/mio.c b/ctags/main/mio.c index 0268e68759..59a3030f13 100644 --- a/ctags/main/mio.c +++ b/ctags/main/mio.c @@ -43,6 +43,11 @@ #include #include +#ifndef _MSC_VER +#define MAY_HAVE_FTRUNCATE +#include +#endif + #ifdef READTAGS_DSL #define xMalloc(n,Type) (Type *)eMalloc((size_t)(n) * sizeof (Type)) #define xRealloc(p,n,Type) (Type *)eRealloc((p), (n) * sizeof (Type)) @@ -649,9 +654,29 @@ static int mem_try_resize (MIO *mio, size_t new_size) return success; } +static int file_try_resize (MIO *mio, size_t new_size) +{ + mio_flush (mio); + + FILE *fp = mio_file_get_fp (mio); + +#ifdef MAY_HAVE_FTRUNCATE + if (ftruncate(fileno(fp), (off_t)new_size) < 0) + return false; +#else + /* See https://stackoverflow.com/questions/873454/how-to-truncate-a-file-in-c/874704#874704 */ + if ((errno = _chsize_s(_fileno(fp), (__int64)new_size)) != 0) + return false; +#endif + return true; +} + int mio_try_resize (MIO *mio, size_t new_size) { - return mem_try_resize (mio, new_size); + if (mio->type == MIO_TYPE_MEMORY) + return mem_try_resize (mio, new_size); + else + return file_try_resize (mio, new_size); } /* diff --git a/ctags/main/options.c b/ctags/main/options.c index 94732a3615..c84909104d 100644 --- a/ctags/main/options.c +++ b/ctags/main/options.c @@ -80,7 +80,6 @@ verbose ("Entering configuration stage: loading %s\n", StageDescription[Stage]); \ } \ } while (0) -#define ACCEPT(STAGE) (1UL << OptionLoadingStage##STAGE) /* * Data declarations @@ -93,6 +92,7 @@ enum eOptionLimits { typedef struct sOptionDescription { int usedByEtags; + int experimentalOption; const char *description; } optionDescription; @@ -173,6 +173,7 @@ optionValues Option = { .putFieldPrefix = false, .maxRecursionDepth = 0xffffffff, .interactive = false, + .fieldsReset = false, #ifdef WIN32 .useSlashAsFilenameSeparator = FILENAME_SEP_UNSET, #endif @@ -189,6 +190,16 @@ struct localOptionValues { .withListHeader = true, }; +typedef enum eOptionLoadingStage { + OptionLoadingStageNone, + OptionLoadingStageCustom, + OptionLoadingStageXdg, + OptionLoadingStageHomeRecursive, + OptionLoadingStageCurrentRecursive, + OptionLoadingStageEnvVar, + OptionLoadingStageCmdline, +} OptionLoadingStage; + static OptionLoadingStage Stage = OptionLoadingStageNone; #define STAGE_ANY ~0UL @@ -197,307 +208,321 @@ static OptionLoadingStage Stage = OptionLoadingStageNone; */ static optionDescription LongOptionDescription [] = { - {1," -? Print this option summary."}, - {1," -a Append the tags to an existing tag file."}, -#ifdef DEBUG - {1," -b "}, - {1," Set break line."}, -#endif - {0," -B Use backward searching patterns (?...?)."}, -#ifdef DEBUG - {1," -d "}, - {1," Set debug level."}, -#endif - {1," -D ="}, - {1," (CPreProcessor) Give definition for macro."}, - {0," -e Output tag file for use with Emacs."}, - {1," -f "}, - {1," Write tags to specified file. Value of \"-\" writes tags to stdout"}, - {1," [\"tags\"; or \"TAGS\" when -e supplied]."}, - {0," -F Use forward searching patterns (/.../; default)."}, - {1," -G Equivalent to --guess-language-eagerly."}, - {1," -h "}, - {1," Specify list of file extensions to be treated as include files"}, - {1," [\".h.H.hh.hpp.hxx.h++.inc.def\"]."}, - {1," -I "}, - {1," A list of tokens to be specially handled is read from either the"}, - {1," command line or the specified file."}, - {1," -L "}, - {1," A list of input file names is read from the specified file."}, - {1," If specified as \"-\", then standard input is read."}, - {0," -n Equivalent to --excmd=number."}, - {0," -N Equivalent to --excmd=pattern."}, - {1," -o Alternative for -f."}, + {1,0,"Input/Output Options"}, + {1,0," --exclude="}, + {1,0," Exclude files and directories matching ."}, + {1,0," See also --exclude-exception option."}, + {1,0," --exclude-exception="}, + {1,0," Don't exclude files and directories matching even if"}, + {1,0," they match the pattern specified with --exclude option."}, + {1,0," --filter[=(yes|no)]"}, + {1,0," Behave as a filter, reading file names from standard input and"}, + {1,0," writing tags to standard output [no]."}, + {1,0," --filter-terminator="}, + {1,0," Specify to print to stdout following the tags for each file"}, + {1,0," parsed when --filter is enabled."}, + {1,0," --links[=(yes|no)]"}, + {1,0," Indicate whether symbolic links should be followed [yes]."}, + {1,0," --maxdepth="}, #ifdef RECURSE_SUPPORTED - {1," -R Equivalent to --recurse."}, + {1,0," Specify maximum recursion depth."}, #else - {1," -R Not supported on this platform."}, -#endif - {0," -u Equivalent to --sort=no."}, - {1," -V Equivalent to --verbose."}, - {1," -x Print a tabular cross reference file to standard output."}, - {1," --alias-=[+|-]aliasPattern"}, - {1," Add a pattern detecting a name, can be used as an alternative name"}, - {1," for LANG."}, - {1," --append=[yes|no]"}, - {1," Should tags should be appended to existing tag file [no]?"}, - {1," --etags-include=file"}, - {1," Include reference to 'file' in Emacs-style tag file (requires -e)."}, - {1," --exclude=pattern"}, - {1," Exclude files and directories matching 'pattern'."}, - {1," See also --exclude-exception option."}, - {1," --exclude-exception=pattern"}, - {1," Don't exclude files and directories matching 'pattern' even if"}, - {1," they match the pattern specified with --exclude option."}, - {0," --excmd=number|pattern|mix|combine"}, -#ifdef MACROS_USE_PATTERNS - {0," Uses the specified type of EX command to locate tags [pattern]."}, + {1,0," Not supported on this platform."}, +#endif + {1,0," --recurse[=(yes|no)]"}, +#ifdef RECURSE_SUPPORTED + {1,0," Recurse into directories supplied on command line [no]."}, + {1,0," -R Equivalent to --recurse."}, #else - {0," Uses the specified type of EX command to locate tags [mix]."}, -#endif - {1," --extras=[+|-]flags"}, - {1," Include extra tag entries for selected information (flags: \"fFgpqrs\") [F]."}, - {1," --extras-=[+|-]flags"}, - {1," Include own extra tag entries for selected information"}, - {1," (flags: see the output of --list-extras= option)."}, - {1," --fields=[+|-]flags"}, - {1," Include selected extension fields (flags: \"aCeEfFikKlmnNpPrRsStxzZ\") [fks]."}, - {1," --fields-=[+|-]flags"}, - {1," Include selected own extension fields"}, - {1," (flags: see the output of --list-fields= option)."}, - {1," --filter=[yes|no]"}, - {1," Behave as a filter, reading file names from standard input and"}, - {1," writing tags to standard output [no]."}, - {1," --filter-terminator=string"}, - {1," Specify string to print to stdout following the tags for each file"}, - {1," parsed when --filter is enabled."}, - {0," --format=level"}, + {1,0," Not supported on this platform."}, + {1,0," -R Not supported on this platform."}, +#endif + {1,0," -L "}, + {1,0," A list of input file names is read from the specified ."}, + {1,0," If specified as \"-\", then standard input is read."}, + {1,0," --append[=(yes|no)]"}, + {1,0," Should tags should be appended to existing tag file [no]?"}, + {1,0," -a Append the tags to an existing tag file."}, + {1,0," -f "}, + {1,0," Write tags to specified . Value of \"-\" writes tags to stdout"}, + {1,0," [\"tags\"; or \"TAGS\" when -e supplied]."}, + {1,0," -o Alternative for -f."}, + {1,0,""}, + {1,0,"Output Format Options"}, + {0,0," --format=(1|2)"}, #if DEFAULT_FILE_FORMAT == 1 - {0," Force output of specified tag file format [1]."}, + {0,0," Force output of specified tag file format [1]."}, #else - {0," Force output of specified tag file format [2]."}, -#endif - {1," --guess-language-eagerly"}, - {1," Guess the language of input file more eagerly"}, - {1," (but taking longer time for guessing):"}, - {1," o shebang, even if the input file is not executable,"}, - {1," o emacs mode specification at the beginning and end of input file, and"}, - {1," o vim syntax specification at the end of input file."}, - {1," --help"}, - {1," Print this option summary."}, - {1," --help-full"}, - {1," Print this option summary including experimental features."}, - {1," --if0=[yes|no]"}, - {1," Should code within #if 0 conditional branches be parsed [no]?"}, -#ifdef HAVE_ICONV - {1," --input-encoding=encoding"}, - {1," Specify encoding of all input files."}, - {1," --input-encoding-=encoding"}, - {1," Specify encoding of the LANG input files."}, -#endif - {1," --kinddef-=letter,name,desc"}, - {1," Define new kind for ."}, - {1," --kinds-=[+|-]kinds, or"}, - {1," Enable/disable tag kinds for language ."}, - {1," --langdef=name"}, - {1," Define a new language to be parsed with regular expressions."}, - {1," --langmap=map(s)"}, - {1," Override default mapping of language to input file extension."}, - {1," --language-force=language"}, - {1," Force all files to be interpreted using specified language."}, - {1," --languages=[+|-]list"}, - {1," Restrict files scanned for tags to those mapped to languages"}, - {1," specified in the comma-separated 'list'. The list can contain any"}, - {1," built-in or user-defined language [all]."}, - {1," --license"}, - {1," Print details of software license."}, - {0," --line-directives=[yes|no]"}, - {0," Should #line directives be processed [no]?"}, - {1," --links=[yes|no]"}, - {1," Indicate whether symbolic links should be followed [yes]."}, - {1," --list-aliases=[language|all]"}, - {1," Output list of alias patterns."}, - {1," --list-excludes"}, - {1," Output list of exclude patterns for excluding files/directories."}, - {1," --list-extras=[language|all]"}, - {1," Output list of extra tag flags."}, - {1," --list-features"}, - {1," Output list of compiled features."}, - {1," --list-fields=[language|all]"}, - {1," Output list of fields."}, - {1," --list-kinds=[language|all]"}, - {1," Output a list of all tag kinds for specified language or all."}, - {1," --list-kinds-full=[language|all]"}, - {1," List the details of all tag kinds for specified language or all"}, - {1," For each line, associated language name is printed when \"all\" is"}, - {1," specified as language."}, - {1," --list-languages"}, - {1," Output list of supported languages."}, - {1," --list-map-extensions=[language|all]"}, - {1," Output list of language extensions in mapping."}, - {1," --list-map-patterns=[language|all]"}, - {1," Output list of language patterns in mapping."}, - {1," --list-maps=[language|all]"}, - {1," Output list of language mappings(both extensions and patterns)."}, - {1," --list-mline-regex-flags"}, - {1," Output list of flags which can be used in a multiline regex parser definition."}, - {1," --list-params=[language|all]"}, - {1," Output list of language parameters. This works with --machinable."}, - {0," --list-pseudo-tags"}, - {0," Output list of pseudo tags."}, - {1," --list-regex-flags"}, - {1," Output list of flags which can be used in a regex parser definition."}, - {1," --list-roles=[[language|all].[kindspecs|*]]"}, - {1," Output list of all roles of tag kind(s) specified for language(s)."}, - {1," Both letters and names can be used in kindspecs."}, - {1," e.g. --list-roles=C.{header}d"}, - {1," --list-subparsers=[baselang|all]"}, - {1," Output list of subparsers for the base language."}, - {1," --machinable=[yes|no]"}, - {1," Use tab separated representation in --list- option output. [no]"}, - {1," --list-{aliases,extras,features,fields,kind-full,langdef-flags,params," }, - {1," pseudo-tags,regex-flags,roles,subparsers} support this option."}, - {1," Suitable for scripting. Specify before --list-* option."}, - {1," --map-=[+|-]extension|pattern"}, - {1," Set, add(+) or remove(-) the map for ."}, - {1," Unlike --langmap, this doesn't take a list; only one file name pattern"}, - {1," or one file extension can be specified at once."}, - {1," Unlike --langmap the change with this option affects mapping of only."}, - {1," --maxdepth=N"}, -#ifdef RECURSE_SUPPORTED - {1," Specify maximum recursion depth."}, -#else - {1," Not supported on this platform."}, -#endif - {1," --mline-regex-=/line_pattern/name_pattern/[flags]"}, - {1," Define multiline regular expression for locating tags in specific language."}, - {1," --options=path"}, - {1," Specify file(or directory) from which command line options should be read."}, - {1," --options-maybe=path"}, - {1," Do the same as --options but this doesn't make an error for non-existing file."}, - {1," --optlib-dir=[+]DIR"}, - {1," Add or set DIR to optlib search path."}, -#ifdef HAVE_ICONV - {1," --output-encoding=encoding"}, - {1," The encoding to write the tag file in. Defaults to UTF-8 if --input-encoding"}, - {1," is specified, otherwise no conversion is performed."}, + {0,0," Force output of specified tag file format [2]."}, #endif - {0," --output-format=u-ctags|e-ctags|etags|xref" #ifdef HAVE_JANSSON - "|json" -#endif - }, - {0," Specify the output format. [u-ctags]"}, - {1," --param-:name=argument"}, - {1," Set specific parameter. Available parameters can be listed with --list-params."}, - {0," --pattern-length-limit=N"}, - {0," Cutoff patterns of tag entries after N characters. Disable by setting to 0. [96]"}, - {0," --print-language"}, - {0," Don't make tags file but just print the guessed language name for"}, - {0," input file."}, - {0," --pseudo-tags=[+|-]ptag"}, - {0," --pseudo-tags=*"}, - {0," Enable/disable emitting pseudo tag named ptag."}, - {0," if * is given, enable emitting all pseudo tags."}, - {0," --put-field-prefix"}, - {0," Put \"" CTAGS_FIELD_PREFIX "\" as prefix for the name of fields newly introduced in"}, - {0," universal-ctags."}, - {1," --quiet=[yes|no]"}, - {0," Don't print NOTICE class messages [no]."}, - {1," --recurse=[yes|no]"}, -#ifdef RECURSE_SUPPORTED - {1," Recurse into directories supplied on command line [no]."}, + {0,0," --output-format=(u-ctags|e-ctags|etags|xref|json)"}, +#else + {0,0," --output-format=(u-ctags|e-ctags|etags|xref)"}, +#endif + {0,0," Specify the output format. [u-ctags]"}, + {0,0," -e Output tag file for use with Emacs."}, + {1,0," -x Print a tabular cross reference file to standard output."}, + {0,0," --sort=(yes|no|foldcase)"}, + {0,0," Should tags be sorted (optionally ignoring case) [yes]?"}, + {0,0," -u Equivalent to --sort=no."}, + {1,0," --etags-include="}, + {1,0," Include reference to in Emacs-style tag file (requires -e)."}, +#ifdef HAVE_ICONV + {1,0," --input-encoding="}, + {1,0," Specify of all input files."}, + {1,0," --input-encoding-="}, + {1,0," Specify of the input files."}, + {1,0," --output-encoding="}, + {1,0," The to write the tag file in. Defaults to UTF-8 if --input-encoding"}, + {1,0," is specified, otherwise no conversion is performed."}, +#endif + {1,1," --_xformat="}, + {1,1," Specify custom format for tabular cross reference (-x)."}, + {1,1," Fields can be specified with letter listed in --list-fields."}, + {1,1," e.g. --_xformat=%10N %10l:%K @ %-20F:%-20n"}, + {1,0,""}, + {1,0,"Language Selection and Mapping Options"}, + {1,0," --language-force=(|auto)"}, + {1,0," Force all files to be interpreted using specified ."}, + {1,0," --languages=[+|-](|all)"}, + {1,0," Restrict files scanned for tags to those mapped to languages"}, + {1,0," specified in the comma-separated . The list can contain any"}, + {1,0," built-in or user-defined language [all]."}, + {1,0," --alias-=[+|-](|default)"}, + {1,0," Add a detecting a name, can be used as an alternative name"}, + {1,0," for ."}, + {1,0," --guess-language-eagerly"}, + {1,0," Guess the language of input file more eagerly"}, + {1,0," (but taking longer time for guessing):"}, + {1,0," o shebang, even if the input file is not executable,"}, + {1,0," o emacs mode specification at the beginning and end of input file, and"}, + {1,0," o vim syntax specification at the end of input file."}, + {1,0," -G Equivalent to --guess-language-eagerly."}, + {1,0," --langmap=[,[...]]"}, + {1,0," Override default mapping of language to input file extension."}, + {1,0," e.g. --langmap=c:.c.x,java:+.j,make:([Mm]akefile).mak"}, + {1,0," --map-=[+|-]|"}, + {1,0," Set, add(+) or remove(-) the map for ."}, + {1,0," Unlike --langmap, this doesn't take a list; only one file name "}, + {1,0," or one file can be specified at once."}, + {1,0," Unlike --langmap the change with this option affects mapping of only."}, + {1,0,""}, + {1,0,"Tags File Contents Options"}, + {0,0," --excmd=(number|pattern|mix|combine)"}, +#ifdef MACROS_USE_PATTERNS + {0,0," Uses the specified type of EX command to locate tags [pattern]."}, #else - {1," Not supported on this platform."}, -#endif - {1," --regex-=/line_pattern/name_pattern/[flags]"}, - {1," Define regular expression for locating tags in specific language."}, - {1," --roles-.kind=[+|-]role, or"}, - {1," Enable/disable tag roles for kinds of language ."}, - {0," --sort=[yes|no|foldcase]"}, - {0," Should tags be sorted (optionally ignoring case) [yes]?"}, - {0," --tag-relative=[yes|no|always|never]"}, - {0," Should paths be relative to location of tag file [no; yes when -e]?"}, - {0," always: be relative even if input files are passed in with absolute paths" }, - {0," never: be absolute even if input files are passed in with relative paths" }, - {1," --totals=[yes|no|extra]"}, - {1," Print statistics about input and tag files [no]."}, + {0,0," Uses the specified type of EX command to locate tags [mix]."}, +#endif + {0,0," -n Equivalent to --excmd=number."}, + {0,0," -N Equivalent to --excmd=pattern."}, + {1,0," --extras=[+|-][|*]"}, + {1,0," Include extra tag entries for selected information (: \"fFgpqrs\") [F]."}, + {1,0," --extras-(|all)=[+|-][|*]"}, + {1,0," Include own extra tag entries for selected information"}, + {1,0," (: see the output of --list-extras= option)."}, + {1,0," --fields=[+|-][|*]"}, + {1,0," Include selected extension fields (: \"aCeEfFikKlmnNpPrRsStxzZ\") [fks]."}, + {1,0," --fields-(|all)=[+|-][|*]"}, + {1,0," Include selected own extension fields"}, + {1,0," (: see the output of --list-fields= option)."}, + {1,0," --kinds-(|all)=[+|-](|*)"}, + {1,0," Enable/disable tag for language ."}, + {0,0," --pattern-length-limit="}, + {0,0," Cutoff patterns of tag entries after characters. Disable by setting to 0. [96]"}, + {0,0," --pseudo-tags=[+|-](|*)"}, + {0,0," Enable/disable emitting pseudo tag named ."}, + {0,0," if '*' is given, enable emitting all pseudo tags."}, + {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," 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 - {1," --use-slash-as-filename-separator=[yes|no]"}, - {1," Use slash as filename separator [yes] for u-ctags output format."}, -#endif - {1," --verbose=[yes|no]"}, - {1," Enable verbose messages describing actions on each input file."}, - {1," --version"}, - {1," Print version identifier to standard output."}, - {1," --with-list-header=[yes|no]"}, - {1," Prepend the column descriptions in --list- output. [yes]"}, - {1," --list-{aliases,extras,features,fields,kind-full,langdef-flags,params," }, - {1," pseudo-tags,regex-flags,roles,subparsers} support this option."}, - {1," Specify before --list-* option."}, - {1, NULL} -}; - -static optionDescription ExperimentalLongOptionDescription [] = { - {1," --_anonhash=fname"}, - {1," Used in u-ctags test harness"}, - {1," --_dump-keywords"}, - {1," Dump keywords of initialized parser(s)."}, - {1," --_dump-options"}, - {1," Dump options."}, - {1," --_echo=msg"}, - {1," Echo MSG to standard error. Useful to debug the chain"}, - {1," of loading option files."}, - {1," --_extradef-=name,desc"}, - {1," Define new extra for . \"--extras-=+{name}\" enables it."}, - {1," --_fatal-warnings"}, - {1," Make all warnings fatal."}, - {1," --_fielddef-=name,description"}, - {1," EXPERIMENTAL, Define new field for ."}, - {1," --_force-initializing"}, - {1," Initialize all parsers in early stage"}, - {1," --_force-quit=[num]"}, - {1," Quit when the option is processed. Useful to debug the chain"}, - {1," of loading option files."}, + {1,0," --use-slash-as-filename-separator[=(yes|no)]"}, + {1,0," Use slash as filename separator [yes] for u-ctags output format."}, +#endif + {0,0," -B Use backward searching patterns (?...?)."}, + {0,0," -F Use forward searching patterns (/.../; default)."}, + {1,0,""}, + {1,0,"Option File Options"}, + {1,0," --options="}, + {1,0," Specify file (or directory) from which command line options should be read."}, + {1,0," --options-maybe="}, + {1,0," Do the same as --options but this doesn't make an error for non-existing file."}, + {1,0," --optlib-dir=[+]"}, + {1,0," Add or set to optlib search path."}, + {1,1," --_echo="}, + {1,1," Echo to standard error. Useful to debug the chain"}, + {1,1," of loading option files."}, + {1,1," --_force-quit[=]"}, + {1,1," Quit when loading the option files is processed."}, + {1,1," Useful to debug the chain of loading option files."}, + {1,0,""}, + {1,0,"optlib Options"}, + {1,0," --kinddef-=,,"}, + {1,0," Define new kind for ."}, + {1,0," --langdef="}, + {1,0," Define a new language to be parsed with regular expressions."}, + {1,0," --mline-regex-=////[]"}, + {1,0," Define multiline regular expression for locating tags in specific language."}, + {1,0," --regex-=////[]"}, + {1,0," Define single-line regular expression for locating tags in specific language."}, + {1,1," --_extradef-=,"}, + {1,1," Define new extra for . --extras-=+{name} enables it."}, + {1,1," --_fielddef-=,"}, + {1,1," Define new field for ."}, + {1,1," --_mtable-extend-=disttable+srctable."}, + {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," --_prelude-={{ optscript-code }}"}, + {1,1," Specify code run before parsing with parser."}, + {1,1," --_pretend-="}, + {1,1," Make NEWLANG parser pretend OLDLANG parser in lang: field."}, + {1,1," --_roledef-.=,"}, + {1,1," Define new role for the kind in ."}, + {1,1," --_scopesep-=[|*]/(|*):"}, + {1,1," Specify scope separator between and ."}, + {1,1," --_sequel-={{ optscript-code }}"}, + {1,1," Specify code run after parsing with parser."}, + {1,1," --_tabledef-="}, + {1,1," Define new regex table for ."}, + {1,0,""}, + {1,0,"Language Specific Options"}, + {1,0," --if0[=(yes|no)]"}, + {1,0," Should code within #if 0 conditional branches be parsed [no]?"}, + {0,0," --line-directives[=(yes|no)]"}, + {0,0," Should '#line' directives be processed [no]?"}, + {1,0," -D ="}, + {1,0," (CPreProcessor) Give for ."}, + {1,0," -h (|default)"}, + {1,0," Specify a of file extensions to be treated as include files"}, + {1,0," [\".h.H.hh.hpp.hxx.h++.inc.def\"]."}, + {1,0," -I [+|-]|@"}, + {1,0," A of tokens to be specially handled is read from either the"}, + {1,0," command line or the specified ."}, + {1,0," --param-.="}, + {1,0," Set specific parameter. Available parameters can be listed with --list-params."}, + {1,0,""}, + {1,0,"Listing Options"}, + {1,0," --list-aliases[=(|all)]"}, + {1,0," Output list of alias patterns."}, + {1,0," --list-excludes"}, + {1,0," Output list of exclude patterns for excluding files/directories."}, + {1,0," --list-extras[=(|all)]"}, + {1,0," Output list of extra tag flags."}, + {1,0," --list-features"}, + {1,0," Output list of compiled features."}, + {1,0," --list-fields[=(|all)]"}, + {1,0," Output list of fields."}, + {1,0," --list-kinds[=(|all)]"}, + {1,0," Output a list of all tag kinds for specified or all."}, + {1,0," --list-kinds-full[=(|all)]"}, + {1,0," List the details of all tag kinds for specified or all"}, + {1,0," For each line, associated language name is printed when \"all\" is"}, + {1,0," specified as language."}, + {1,0," --list-languages"}, + {1,0," Output list of supported languages."}, + {1,0," --list-map-extensions[=(|all)]"}, + {1,0," Output list of language extensions in mapping."}, + {1,0," --list-map-patterns[=(|all)]"}, + {1,0," Output list of language patterns in mapping."}, + {1,0," --list-maps[=(|all)]"}, + {1,0," Output list of language mappings (both extensions and patterns)."}, + {1,0," --list-mline-regex-flags"}, + {1,0," Output list of flags which can be used in a multiline regex parser definition."}, + {1,0," --list-params[=(|all)]"}, + {1,0," Output list of language parameters. This works with --machinable."}, + {0,0," --list-pseudo-tags"}, + {0,0," Output list of pseudo tags."}, + {1,0," --list-regex-flags"}, + {1,0," Output list of flags which can be used in a regex parser definition."}, + {1,0," --list-roles[=(|all)[.(|*)]]"}, + {1,0," Output list of all roles of tag kind(s) specified for ."}, + {1,0," Both letters and names can be used in ."}, + {1,0," e.g. --list-roles=C.{header}d"}, + {1,0," --list-subparsers[=(|all)]"}, + {1,0," Output list of subparsers for the base language."}, + {1,0," --machinable[=(yes|no)]"}, + {1,0," Use tab separated representation in --list-* option output. [no]"}, + {1,0," --list-{aliases,extras,features,fields,kind-full,langdef-flags,params," }, + {1,0," pseudo-tags,regex-flags,roles,subparsers} support this option."}, + {1,0," Suitable for scripting. Specify before --list-* option."}, + {1,0," --with-list-header[=(yes|no)]"}, + {1,0," Prepend the column descriptions in --list- output. [yes]"}, + {1,0," --list-{aliases,extras,features,fields,kind-full,langdef-flags,params," }, + {1,0," pseudo-tags,regex-flags,roles,subparsers} support this option."}, + {1,0," Specify before --list-* option."}, + {1,1," --_list-kinddef-flags"}, + {1,1," Output list of flags which can be used with --kinddef option."}, + {1,1," --_list-langdef-flags"}, + {1,1," Output list of flags which can be used with --langdef option."}, + {1,1," --_list-mtable-regex-flags"}, + {1,1," Output list of flags which can be used in a multitable regex parser definition."}, + {1,1," --_list-operators"}, + {1,1," Output list of optscript operators."}, + {1,0,""}, + {1,0,"Miscellaneous Options"}, + {1,0," --help"}, + {1,0," Print this option summary."}, + {1,0," -? Print this option summary."}, + {1,0," --help-full"}, + {1,0," Print this option summary including experimental features."}, + {1,0," --license"}, + {1,0," Print details of software license."}, + {0,0," --print-language"}, + {0,0," Don't make tags file but just print the guessed language name for"}, + {0,0," input file."}, + {1,0," --quiet[=(yes|no)]"}, + {0,0," Don't print NOTICE class messages [no]."}, + {1,0," --totals[=(yes|no|extra)]"}, + {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," -V Equivalent to --verbose."}, +#ifdef DEBUG + {1,0," -b "}, + {1,0," Set break line. (for DEBUG)"}, + {1,0," -d "}, + {1,0," Set debug level. (for DEBUG)"}, +#endif + + {1,1," --_anonhash="}, + {1,1," Used in u-ctags test harness"}, + {1,1," --_dump-keywords"}, + {1,1," Dump keywords of initialized parser(s)."}, + {1,1," --_dump-options"}, + {1,1," Dump options."}, + {1,1," --_dump-prelude"}, + {1,1," Dump contents of optscript prelude."}, + {1,1," --_fatal-warnings"}, + {1,1," Make all warnings fatal."}, + {1,1," --_force-initializing"}, + {1,1," Initialize all parsers in early stage"}, #ifdef HAVE_JANSSON - {0," --_interactive" + {0,1," --_interactive" #ifdef HAVE_SECCOMP - "=[default|sandbox]" + "[=(default|sandbox)]" #endif }, - {0," Enter interactive mode (json over stdio)."}, + {0,1," Enter interactive mode (JSON over stdio)."}, #ifdef HAVE_SECCOMP - {0," Enter file I/O limited interactive mode if sandbox is specified. [default]"}, -#endif -#endif - {1," --_list-kinddef-flags"}, - {1," Output list of flags which can be used with --kinddef option."}, - {1," --_list-langdef-flags"}, - {1," Output list of flags which can be used with --langdef option."}, - {1," --_list-mtable-regex-flags"}, - {1," Output list of flags which can be used in a multitable regex parser definition."}, - {1," --_mtable-extend-=disttable+srctable."}, - {1," Copy patterns of a regex table to another regex table."}, - {1," --_mtable-regex-=table/line_pattern/name_pattern/[flags]"}, - {1," Define multitable regular expression for locating tags in specific language."}, - {1," --_pretend-="}, - {1," Make NEWLANG parser pretend OLDLANG parser in lang: field."}, - {1," --_roledef-.kind=role_name,role_desc"}, - {1," Define new role for the kind in ."}, - {1," --_scopesep-=[parent_kind_letter]/child_kind_letter:separator"}, - {1," Specify scope separator between and ."}, - {1," * as a kind letter matches any kind."}, - {1," --_tabledef-=name"}, - {1," Define new regex table for ."}, + {0,1," Enter file I/O limited interactive mode if sandbox is specified. [default]"}, +#endif +#endif #ifdef DO_TRACING - {1," --_trace=list"}, - {1," Trace parsers for the languages."}, -#endif - {1," --_xformat=field_format"}, - {1," Specify custom format for tabular cross reference (-x)."}, - {1," Fields can be specified with letter listed in --list-fields."}, - {1," e.g. --_xformat=%10N %10l:%K @ %-20F:%-20n"}, - {1, NULL} + {1,1," --_trace="}, + {1,1," Trace parsers for the languages."}, +#endif + {1,1, NULL} }; static const char* const License1 = @@ -529,7 +554,13 @@ static struct Feature { /* Following two features are always available on universal ctags */ {"wildcards", "can use glob matching"}, {"regex", "can use regular expression based pattern matching"}, - +#ifdef USE_GNULIB_FNMATCH + {"gnulib_fnmatch", "linked with the Gnulib fnmatch library"}, +#endif +/* https://lists.gnu.org/archive/html/bug-gnulib/2011-07/msg00435.html */ +#ifdef _REGEX_INCLUDE_LIMITS_H + {"gnulib_regex", "linked with the Gnulib regular expression library"}, +#endif #ifndef EXTERNAL_SORT {"internal-sort", "uses internal sort routine instead of invoking sort command"}, #endif @@ -570,6 +601,10 @@ static struct Feature { #ifdef HAVE_PACKCC /* The test harnesses use this as hints for skipping test cases */ {"packcc", "has peg based parser(s)"}, +#endif + {"optscript", "can use the interpreter"}, +#ifdef HAVE_PCRE2 + {"pcre2", "has pcre2 regex engine"}, #endif {NULL,} }; @@ -577,14 +612,9 @@ static struct Feature { static const char *const StageDescription [] = { [OptionLoadingStageNone] = "not initialized", [OptionLoadingStageCustom] = "custom file", - [OptionLoadingStageDosCnf] = "DOS .cnf file", - [OptionLoadingStageEtc] = "file under /etc (e.g. ctags.conf)", - [OptionLoadingStageLocalEtc] = "file under /usr/local/etc (e.g. ctags.conf)", [OptionLoadingStageXdg] = "file(s) under $XDG_CONFIG_HOME and $HOME/.config", [OptionLoadingStageHomeRecursive] = "file(s) under $HOME", [OptionLoadingStageCurrentRecursive] = "file(s) under the current directory", - [OptionLoadingStagePreload] = "optlib preload files", - [OptionLoadingStageEnvVar] = "environment variable", [OptionLoadingStageCmdline] = "command line", }; @@ -663,20 +693,6 @@ extern void verbose (const char *const format, ...) } } -extern void notice (const char *const format, ...) -{ - if (!Option.quiet) - { - va_list ap; - fprintf (stderr, "%s: Notice: ", getExecutableName ()); - va_start (ap, format); - vfprintf (stderr, format, ap); - va_end (ap); - fputs ("\n", stderr); - } -} - - static char *stringCopy (const char *const string) { char* result = NULL; @@ -756,7 +772,7 @@ extern void checkOptions (void) if (Option.tagFileName != NULL) error (WARNING, "%s ignores output tag file name", notice); } - writerCheckOptions (); + writerCheckOptions (Option.fieldsReset); } extern langType getLanguageComponentInOptionFull (const char *const option, @@ -783,12 +799,18 @@ extern langType getLanguageComponentInOptionFull (const char *const option, } /* Extract from - * --param-:=..., and + * --param-.=..., and * --_roledef-.=... */ + + /* `:' is only for keeping self compatibility. */ sep = strpbrk (lang, ":."); if (sep) + { + if (*sep == ':') + error (WARNING, "using `:' as a separator is obsolete; use `.' instead: --%s", option); lang_len = sep - lang; - language = getNamedLanguageFull (lang, lang_len, noPretending); + } + language = getNamedLanguageFull (lang, lang_len, noPretending, false); if (language == LANG_IGNORE) { const char *langName = (lang_len == 0)? lang: eStrndup (lang, lang_len); @@ -1325,7 +1347,10 @@ static void resetFieldsOption (langType lang, bool mode) for (i = 0; i < countFields (); ++i) if ((lang == LANG_AUTO) || (lang == getFieldOwner (i))) - enableField (i, mode, false); + enableField (i, mode); + + if ((lang == LANG_AUTO || lang == LANG_IGNORE)&& !mode) + Option.fieldsReset = true; } static void processFieldsOption ( @@ -1341,6 +1366,8 @@ static void processFieldsOption ( longName = vStringNewOrClearWithAutoRelease (longName); + Option.fieldsReset = false; + if (*p == '*') { resetFieldsOption (LANG_IGNORE, true); @@ -1384,7 +1411,7 @@ static void processFieldsOption ( if (t == FIELD_UNKNOWN) error(FATAL, "no such field: \'%s\'", vStringValue (longName)); - enableField (t, mode, true); + enableField (t, mode); inLongName = false; vStringClear (longName); @@ -1399,7 +1426,7 @@ static void processFieldsOption ( error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c, option); else - enableField (t, mode, true); + enableField (t, mode); } break; } @@ -1453,17 +1480,6 @@ static void printInvocationDescription (void) printf (INVOCATION, getExecutableName ()); } -static void printOptionDescriptions (const optionDescription *const optDesc) -{ - int i; - for (i = 0 ; optDesc [i].description != NULL ; ++i) - { - if (! Option.etags || optDesc [i].usedByEtags) - puts (optDesc [i].description); - } -} - - static int excludesCompare (struct colprintLine *a, struct colprintLine *b) { return strcmp (colprintLineGetColumn (a, 0), colprintLineGetColumn (b, 0)); @@ -1550,6 +1566,9 @@ static void processListFeaturesOption(const char *const option CTAGS_ATTR_UNUSED static void processListFieldsOption(const char *const option CTAGS_ATTR_UNUSED, const char *const parameter) { + /* Before listing, adjust states of enabled/disabled for fixed fields. */ + writerCheckOptions (Option.fieldsReset); + struct colprintTable * table = fieldColprintTableNew (); if (parameter [0] == '\0' || strcasecmp (parameter, RSV_LANG_ALL) == 0) @@ -1607,9 +1626,14 @@ static void processHelpOptionCommon ( putchar ('\n'); printInvocationDescription (); putchar ('\n'); - printOptionDescriptions (LongOptionDescription); - if (includingExperimentalOptions) - printOptionDescriptions (ExperimentalLongOptionDescription); + + int i; + for (i = 0 ; LongOptionDescription [i].description != NULL ; ++i) + { + if ((! Option.etags || LongOptionDescription [i].usedByEtags) + && (! LongOptionDescription [i].experimentalOption || includingExperimentalOptions)) + puts (LongOptionDescription [i].description); + } } static void processHelpOption ( @@ -2003,12 +2027,13 @@ extern bool processParamOption ( return false; sep = option + strlen ("param-") + strlen (getLanguageName (language)); - if (*sep != ':') - error (FATAL, "no separator(:) is given for %s=%s", option, value); + /* `:' 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 parameter is given for %s", option); + error (FATAL, "no value is given for %s", option); applyParameter (language, name, value); @@ -2174,25 +2199,25 @@ static void processListPseudoTagsOptions ( static void processListRegexFlagsOptions ( const char *const option CTAGS_ATTR_UNUSED, - const char *const parameter CTAGS_ATTR_UNUSED) + const char *const parameter) { - printRegexFlags (localOption.withListHeader, localOption.machinable, stdout); + printRegexFlags (localOption.withListHeader, localOption.machinable, parameter, stdout); exit (0); } static void processListMultilineRegexFlagsOptions ( const char *const option CTAGS_ATTR_UNUSED, - const char *const parameter CTAGS_ATTR_UNUSED) + const char *const parameter) { - printMultilineRegexFlags (localOption.withListHeader, localOption.machinable, stdout); + printMultilineRegexFlags (localOption.withListHeader, localOption.machinable, parameter, stdout); exit (0); } static void processListMultitableRegexFlagsOptions ( const char *const option CTAGS_ATTR_UNUSED, - const char *const parameter CTAGS_ATTR_UNUSED) + const char *const parameter) { - printMultitableRegexFlags (localOption.withListHeader, localOption.machinable, stdout); + printMultitableRegexFlags (localOption.withListHeader, localOption.machinable, parameter, stdout); exit (0); } @@ -2286,6 +2311,13 @@ static void processListSubparsersOptions (const char *const option CTAGS_ATTR_UN exit (0); } +static void processListOperators (const char *const option CTAGS_ATTR_UNUSED, + const char *const parameter) +{ + listRegexOpscriptOperators (stdout); + exit (0); +} + static void freeSearchPathList (searchPathList** pathList) { stringListClear (*pathList); @@ -2799,6 +2831,7 @@ static void setBooleanToXtagWithWarning(booleanOption *const option, bool value) */ static void processDumpOptionsOption (const char *const option, const char *const parameter); +static void processDumpPreludeOption (const char *const option, const char *const parameter); static parametricOption ParametricOptions [] = { { "etags-include", processEtagsInclude, false, STAGE_ANY }, @@ -2855,15 +2888,17 @@ static parametricOption ParametricOptions [] = { { "_anonhash", processAnonHashOption, false, STAGE_ANY }, { "_dump-keywords", processDumpKeywordsOption, false, STAGE_ANY }, { "_dump-options", processDumpOptionsOption, false, STAGE_ANY }, + { "_dump-prelude", processDumpPreludeOption, false, STAGE_ANY }, { "_echo", processEchoOption, false, STAGE_ANY }, - { "_force-initializing", processForceInitOption, false, STAGE_ANY }, + { "_force-initializing", processForceInitOption, false, STAGE_ANY }, { "_force-quit", processForceQuitOption, false, STAGE_ANY }, #ifdef HAVE_JANSSON { "_interactive", processInteractiveOption, true, STAGE_ANY }, #endif - { "_list-kinddef-flags", processListKinddefFlagsOptions, true, STAGE_ANY }, - { "_list-langdef-flags", processListLangdefFlagsOptions, true, STAGE_ANY }, + { "_list-kinddef-flags", processListKinddefFlagsOptions, true, STAGE_ANY }, + { "_list-langdef-flags", processListLangdefFlagsOptions, true, STAGE_ANY }, { "_list-mtable-regex-flags", processListMultitableRegexFlagsOptions, true, STAGE_ANY }, + { "_list-operators", processListOperators, true, STAGE_ANY }, #ifdef DO_TRACING { "_trace", processTraceOption, false, STAGE_ANY }, #endif @@ -2976,13 +3011,13 @@ static void enableLanguageField (langType language, const char *field, bool mode t = getFieldTypeForNameAndLanguage (field, language); if (t == FIELD_UNKNOWN) error(FATAL, "no such field: \'%s\'", field); - enableField (t, mode, (language != LANG_AUTO)); + enableField (t, mode); if (language == LANG_AUTO) { fieldType ftype_next = t; while ((ftype_next = nextSiblingField (ftype_next)) != FIELD_UNKNOWN) - enableField (ftype_next, mode, (language != LANG_AUTO)); + enableField (ftype_next, mode); } } @@ -3314,10 +3349,18 @@ static void processLongOption ( ; else if (processScopesepOption (option, parameter)) ; + else if (processPreludeOption (option, parameter)) + ; + else if (processSequelOption (option, parameter)) + ; else if (processPretendOption (option, parameter)) ; else if (processRolesOption (option, parameter)) ; + else if (option[0] == '_' && option[1] == '_') + /* ctags ignores an argument started from --__. + * optlib2c may handle the argument. */ + ; else error (FATAL, "Unknown option: --%s", option); } @@ -3761,40 +3804,10 @@ static void parseConfigurationFileOptions (void) preload (preload_path_list); } -static void parseEnvironmentOptions (void) -{ - const char *envOptions = NULL; - const char* var = NULL; - - ENTER(EnvVar); - if (Option.etags) - { - var = ETAGS_ENVIRONMENT; - envOptions = getenv (var); - } - if (envOptions == NULL) - { - var = CTAGS_ENVIRONMENT; - envOptions = getenv (var); - } - if (envOptions != NULL && envOptions [0] != '\0') - { - cookedArgs* const args = cArgNewFromString (envOptions); - verbose ("Reading options from $CTAGS\n"); - parseOptions (args); - cArgDelete (args); - if (NonOptionEncountered) - error (WARNING, "Ignoring non-option in %s variable", var); - } -} - extern void readOptionConfiguration (void) { if (! SkipConfiguration) - { parseConfigurationFileOptions (); - parseEnvironmentOptions (); - } } /* @@ -3905,6 +3918,13 @@ static void processDumpOptionsOption (const char *const option CTAGS_ATTR_UNUSED fprintf(stdout, "%s\n", BooleanOptions[i].name); } +static void processDumpPreludeOption (const char *const option CTAGS_ATTR_UNUSED, const char *const parameter CTAGS_ATTR_UNUSED) +{ + extern const char ctagsCommonPrelude[]; + fprintf(stdout, "%s\n", ctagsCommonPrelude); + exit (0); +} + extern bool inSandbox (void) { return (Option.interactive == INTERACTIVE_SANDBOX); diff --git a/ctags/main/options.h b/ctags/main/options.h index 850f7c1d4f..4726de2b75 100644 --- a/ctags/main/options.h +++ b/ctags/main/options.h @@ -25,7 +25,6 @@ /* * FUNCTION PROTOTYPES */ -extern void notice (const char *const format, ...) CTAGS_ATTR_PRINTF (1, 2); extern void verbose (const char *const format, ...) CTAGS_ATTR_PRINTF (1, 2); #define BEGIN_VERBOSE(VFP) do { if (ctags_verbose) { \ diff --git a/ctags/main/options_p.h b/ctags/main/options_p.h index bbb5b8def7..4fe8a77b90 100644 --- a/ctags/main/options_p.h +++ b/ctags/main/options_p.h @@ -70,20 +70,6 @@ typedef enum eTagRelative { TREL_NEVER, } tagRelative; -typedef enum eOptionLoadingStage { - OptionLoadingStageNone, - OptionLoadingStageCustom, - OptionLoadingStageDosCnf, - OptionLoadingStageEtc, - OptionLoadingStageLocalEtc, - OptionLoadingStageXdg, - OptionLoadingStageHomeRecursive, - OptionLoadingStageCurrentRecursive, - OptionLoadingStagePreload, - OptionLoadingStageEnvVar, - OptionLoadingStageCmdline, -} OptionLoadingStage; - /* This stores the command line options. */ typedef struct sOptionValues { @@ -118,6 +104,7 @@ typedef struct sOptionValues { unsigned int patternLengthLimit; /* --pattern-length-limit=N */ bool putFieldPrefix; /* --put-field-prefix */ unsigned int maxRecursionDepth; /* --maxdepth= */ + bool fieldsReset; /* --fields=[^+-] */ enum interactiveMode { INTERACTIVE_NONE = 0, INTERACTIVE_DEFAULT, INTERACTIVE_SANDBOX, } interactive; /* --interactive */ @@ -187,6 +174,8 @@ extern bool processLanguageEncodingOption (const char *const option, const char #endif extern bool processRoledefOption (const char *const option, const char *const parameter); extern bool processScopesepOption (const char *const option, const char *const parameter); +extern bool processPreludeOption (const char *const option, const char *const parameter); +extern bool processSequelOption (const char *const option, const char *const parameter); extern bool processPretendOption (const char *const option, const char *const parameter); extern bool processRolesOption (const char *const option, const char *const parameter); diff --git a/ctags/main/parse.c b/ctags/main/parse.c index f9a51f7e99..4b41d3544b 100644 --- a/ctags/main/parse.c +++ b/ctags/main/parse.c @@ -210,17 +210,8 @@ extern int makeSimplePlaceholder(const vString* const name) extern bool isLanguageEnabled (const langType language) { - const parserDefinition* const lang = LanguageTable [language].def; - - if (!lang->enabled) - return false; - - if ((lang->kindTable == NULL) && - (!(lang->method & METHOD_REGEX)) && - (!(lang->method & METHOD_XPATH))) - return false; - else - return true; + const parserDefinition* const def = LanguageTable [language].def; + return def->enabled; } extern bool isLanguageVisible (const langType language) @@ -370,7 +361,8 @@ extern roleDefinition* getLanguageRoleForName (const langType language, int kind return getRoleForName (LanguageTable [language].kindControlBlock, kindIndex, roleName); } -extern langType getNamedLanguageFull (const char *const name, size_t len, bool noPretending) +extern langType getNamedLanguageFull (const char *const name, size_t len, bool noPretending, + bool include_aliases) { langType result = LANG_IGNORE; unsigned int i; @@ -392,6 +384,12 @@ extern langType getNamedLanguageFull (const char *const name, size_t len, bool n if (strcasecmp (vStringValue (vstr), lang->name) == 0) result = i; + else if (include_aliases) + { + stringList* const aliases = LanguageTable [i].currentAliases; + if (aliases && stringListCaseMatched (aliases, vStringValue (vstr))) + result = i; + } vStringDelete (vstr); } @@ -405,7 +403,12 @@ extern langType getNamedLanguageFull (const char *const name, size_t len, bool n extern langType getNamedLanguage (const char *const name, size_t len) { - return getNamedLanguageFull (name, len, false); + return getNamedLanguageFull (name, len, false, false); +} + +extern langType getNamedLanguageOrAlias (const char *const name, size_t len) +{ + return getNamedLanguageFull (name, len, false, true); } static langType getNameOrAliasesLanguageAndSpec (const char *const key, langType start_index, @@ -422,16 +425,13 @@ static langType getNameOrAliasesLanguageAndSpec (const char *const key, langType for (i = start_index ; i < LanguageCount && result == LANG_IGNORE ; ++i) { + if (! isLanguageEnabled (i)) + continue; + const parserObject* const parser = LanguageTable + i; stringList* const aliases = parser->currentAliases; vString* tmp; - /* isLanguageEnabled is not used here. - It calls initializeParser which takes - cost. */ - if (! parser->def->enabled) - continue; - if (parser->def->name != NULL && strcasecmp (key, parser->def->name) == 0) { result = i; @@ -473,16 +473,13 @@ static langType getPatternLanguageAndSpec (const char *const baseName, langType *spec = NULL; for (i = start_index ; i < LanguageCount && result == LANG_IGNORE ; ++i) { + if (! isLanguageEnabled (i)) + continue; + parserObject *parser = LanguageTable + i; stringList* const ptrns = parser->currentPatterns; vString* tmp; - /* isLanguageEnabled is not used here. - It calls initializeParser which takes - cost. */ - if (! parser->def->enabled) - continue; - if (ptrns != NULL && (tmp = stringListFileFinds (ptrns, baseName))) { result = i; @@ -494,16 +491,13 @@ static langType getPatternLanguageAndSpec (const char *const baseName, langType for (i = start_index ; i < LanguageCount && result == LANG_IGNORE ; ++i) { + if (! isLanguageEnabled (i)) + continue; + parserObject *parser = LanguageTable + i; stringList* const exts = parser->currentExtensions; vString* tmp; - /* isLanguageEnabled is not used here. - It calls initializeParser which takes - cost. */ - if (! parser->def->enabled) - continue; - if (exts != NULL && (tmp = stringListExtensionFinds (exts, fileExtension (baseName)))) { @@ -1369,6 +1363,7 @@ struct GetLanguageRequest { enum { GLR_OPEN, GLR_DISCARD, GLR_REUSE, } type; const char *const fileName; MIO *mio; + time_t mtime; }; static langType @@ -1466,7 +1461,13 @@ getFileLanguageForRequestInternal (struct GetLanguageRequest *req) cleanup: if (req->type == GLR_OPEN && glc.input) + { req->mio = mio_ref (glc.input); + if (!fstatus) + fstatus = eStat (fileName); + if (fstatus) + req->mtime = fstatus->mtime; + } GLC_FCLOSE(&glc); if (fstatus) eStatFree (fstatus); @@ -1482,9 +1483,8 @@ getFileLanguageForRequestInternal (struct GetLanguageRequest *req) verbose (" fallback[hint = %d]: %s\n", i, getLanguageName (language)); } - /* We cannot use isLanguageEnabled() here. */ if (language == LANG_IGNORE - && LanguageTable[LANG_FALLBACK].def->enabled) + && isLanguageEnabled (LANG_FALLBACK)) { language = LANG_FALLBACK; verbose (" last resort: using \"%s\" parser\n", @@ -1516,6 +1516,7 @@ extern langType getLanguageForFilenameAndContents (const char *const fileName) struct GetLanguageRequest req = { .type = GLR_DISCARD, .fileName = fileName, + .mtime = (time_t)0, }; return getFileLanguageForRequest (&req); @@ -2055,6 +2056,15 @@ static void pre_lang_def_flag_base_long (const char* const optflag, const char* } + langType cpreproc = getNamedLanguage ("CPreProcessor", 0); + if (base == cpreproc) + { + error (WARNING, + "Because of an internal limitation, Making a sub parser based on the CPreProcessor parser is not allowed: %s", + param); + return; + } + flag_data->base = eStrdup(param); } @@ -2846,10 +2856,10 @@ extern bool processKindsOption ( * How --param should be change to align --roles- notation * --------------------------------------------------------------------- * - * --_param-.name=argument + * --param-.name=argument * - * * The notation was - * --_param-:name=argument + * The notation was + * --param-:name=argument * * * How --paramdef should be to align --roles- notation @@ -3541,8 +3551,7 @@ static void printLanguage (const langType language, parserDefinition** ltable) if (lang->invisible) return; - if (lang->kindTable != NULL || (lang->method & METHOD_REGEX)) - printf ("%s%s\n", lang->name, isLanguageEnabled (lang->id) ? "" : " [disabled]"); + printf ("%s%s\n", lang->name, isLanguageEnabled (lang->id) ? "" : " [disabled]"); } extern void printLanguageList (void) @@ -3678,6 +3687,11 @@ static bool processLangDefineField (const langType language, fdef->name = eStrndup(parameter, name_end - parameter); fdef->description = desc; fdef->isValueAvailable = NULL; + fdef->getValueObject = NULL; + fdef->getterValueType = NULL; + fdef->setValueObject = NULL; + fdef->setterValueType = NULL; + fdef->checkValueForSetter = NULL; fdef->dataType = FIELDTYPE_STRING; /* TODO */ fdef->ftype = FIELD_UNKNOWN; DEFAULT_TRASH_BOX(fdef, fieldDefinitionDestroy); @@ -3715,7 +3729,6 @@ static rescanReason createTagsForFile (const langType language, Assert (lang->parser || lang->parser2); - notifyLanguageRegexInputStart (language); notifyInputStart (); if (lang->parser != NULL) @@ -3724,7 +3737,6 @@ static rescanReason createTagsForFile (const langType language, rescan = lang->parser2 (passCount); notifyInputEnd (); - notifyLanguageRegexInputEnd (language); return rescan; } @@ -3746,7 +3758,7 @@ static unsigned int parserCorkFlags (parserDefinition *parser) r |= parser->useCork; - if (hasLanguageScopeActionInRegex (parser->id) + if (doesLanguageExpectCorkInRegex (parser->id) || parser->requestAutomaticFQTag) r |= CORK_QUEUE; @@ -3834,7 +3846,8 @@ static bool createTagsWithFallback1 (const langType language, if (useCork) corkTagFile(corkFlags); - addParserPseudoTags (language); + if (isXtagEnabled (XTAG_PSEUDO_TAGS)) + addParserPseudoTags (language); initializeParserStats (parser); tagFilePosition (&tagfpos); @@ -3855,7 +3868,7 @@ static bool createTagsWithFallback1 (const langType language, { /* Restore prior state of tag file. */ - setTagFilePosition (&tagfpos); + setTagFilePosition (&tagfpos, true); setNumTagsAdded (numTags); writerRescanFailed (numTags); tagFileResized = true; @@ -3917,14 +3930,14 @@ extern bool runParserInNarrowedInputStream (const langType language, static bool createTagsWithFallback ( const char *const fileName, const langType language, - MIO *mio, bool *failureInOpenning) + MIO *mio, time_t mtime, bool *failureInOpenning) { langType exclusive_subparser = LANG_IGNORE; bool tagFileResized = false; Assert (0 <= language && language < (int) LanguageCount); - if (!openInputFile (fileName, language, mio)) + if (!openInputFile (fileName, language, mio, mtime)) { *failureInOpenning = true; return false; @@ -4081,7 +4094,7 @@ extern bool parseFile (const char *const fileName) return bRet; } -static bool parseMio (const char *const fileName, langType language, MIO* mio, bool useSourceFileTagPath, +static bool parseMio (const char *const fileName, langType language, MIO* mio, time_t mtime, bool useSourceFileTagPath, void *clientData) { bool tagFileResized = false; @@ -4093,7 +4106,7 @@ static bool parseMio (const char *const fileName, langType language, MIO* mio, b initParserTrashBox (); - tagFileResized = createTagsWithFallback (fileName, language, mio, &failureInOpenning); + tagFileResized = createTagsWithFallback (fileName, language, mio, mtime, &failureInOpenning); finiParserTrashBox (); @@ -4115,6 +4128,7 @@ extern bool parseFileWithMio (const char *const fileName, MIO *mio, .fileName = fileName, .mio = mio, }; + memset (&req.mtime, 0, sizeof (req.mtime)); language = getFileLanguageForRequest (&req); Assert (language != LANG_AUTO); @@ -4139,7 +4153,7 @@ extern bool parseFileWithMio (const char *const fileName, MIO *mio, /* TODO: checkUTF8BOM can be used to update the encodings. */ openConverter (getLanguageEncoding (language), Option.outputEncoding); #endif - tagFileResized = parseMio (fileName, language, req.mio, true, clientData); + tagFileResized = parseMio (fileName, language, req.mio, req.mtime, true, clientData); if (Option.filter && ! Option.interactive) closeTagFile (tagFileResized); addTotals (1, 0L, 0L); @@ -4164,7 +4178,7 @@ extern bool parseRawBuffer(const char *fileName, unsigned char *buffer, if (buffer) mio = mio_new_memory (buffer, bufferSize, NULL, NULL); - r = parseMio (fileName, language, mio, false, clientData); + r = parseMio (fileName, language, mio, (time_t)0, false, clientData); if (buffer) mio_unref (mio); @@ -4258,12 +4272,12 @@ extern void addLanguageCallbackRegex (const langType language, const char *const addCallbackRegex ((LanguageTable +language)->lregexControlBlock, regex, flags, callback, disabled, userData); } -extern bool hasLanguageScopeActionInRegex (const langType language) +extern bool doesLanguageExpectCorkInRegex (const langType language) { bool hasScopeAction; pushLanguage (language); - hasScopeAction = lregexQueryParserAndSubparsers (language, hasScopeActionInRegex); + hasScopeAction = lregexQueryParserAndSubparsers (language, doesExpectCorkInRegex); popLanguage (); return hasScopeAction; @@ -4339,6 +4353,12 @@ static void installTagRegexTable (const langType language) if (lang->tagRegexTable != NULL) { + /* ctags_cli_main() calls initRegexOptscript (). + * However, mini-geany deasn't call ctags_cli_main(). + * So we call initRegexOptscript () here. + */ + initRegexOptscript (); + for (i = 0; i < lang->tagRegexCount; ++i) { if (lang->tagRegexTable [i].mline) @@ -4541,6 +4561,30 @@ static bool makeKindDescriptionPseudoTag (kindDefinition *kind, return false; } +static bool makeRoleDescriptionPseudoTag (kindDefinition *kind, + roleDefinition *role, + void *user_data) +{ + struct makeKindDescriptionPseudoTagData *data = user_data; + + vString *parser_and_kind_name = vStringNewInit (data->langName); + 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), + vStringValue (parser_and_kind_name)); + + vStringDelete (description); + vStringDelete (parser_and_kind_name); + + return false; +} + extern bool makeKindDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc) { @@ -4650,6 +4694,47 @@ extern bool makeExtraDescriptionsPseudoTags (const langType language, return written; } +extern bool makeRoleDescriptionsPseudoTags (const langType language, + const ptagDesc *pdesc) +{ + parserObject *parser; + struct kindControlBlock *kcb; + parserDefinition* lang; + kindDefinition *kind; + struct makeKindDescriptionPseudoTagData data; + + Assert (0 <= language && language < (int) LanguageCount); + parser = LanguageTable + language; + kcb = parser->kindControlBlock; + lang = parser->def; + + unsigned int kindCount = countKinds(kcb); + + data.langName = lang->name; + data.pdesc = pdesc; + data.written = false; + + for (unsigned int i = 0; i < kindCount; ++i) + { + if (!isLanguageKindEnabled (language, i)) + continue; + + kind = getKind (kcb, i); + + unsigned int roleCount = countRoles (kcb, i); + for (unsigned int j = 0; j < roleCount; ++j) + { + if (isRoleEnabled (kcb, i, j)) + { + roleDefinition *role = getRole (kcb, i, j); + makeRoleDescriptionPseudoTag (kind, role, &data); + } + } + } + + return data.written; +} + /* * Copyright (c) 2016, Szymon Tomasz Stefanek * @@ -4950,6 +5035,39 @@ extern void addLanguageTagMultiTableRegex(const langType language, name, kinds, flags, disabled); } +extern void addLanguageOptscriptToHook (langType language, enum scriptHook hook, const char *const src) +{ + addOptscriptToHook (LanguageTable [language].lregexControlBlock, hook, src); +} + +static bool processHookOption (const char *const option, const char *const parameter, const char *prefix, + enum scriptHook hook) +{ + langType language = getLanguageComponentInOption (option, prefix); + if (language == LANG_IGNORE) + return false; + + if (parameter == NULL || parameter[0] == '\0') + error (FATAL, "A parameter is needed after \"%s\" option", option); + + const char * code = flagsEval (parameter, NULL, 0, NULL); + if (code == NULL) + error (FATAL, "Cannot recognized a code block surrounded by `{{' and `}}' after \"%s\" option", option); + addLanguageOptscriptToHook (language, hook, code); + + return true; +} + +extern bool processPreludeOption (const char *const option, const char *const parameter) +{ + return processHookOption (option, parameter, "_prelude-", SCRIPT_HOOK_PRELUDE); +} + +extern bool processSequelOption (const char *const option, const char *const parameter) +{ + return processHookOption (option, parameter, "_sequel-", SCRIPT_HOOK_SEQUEL); +} + extern bool processPretendOption (const char *const option, const char *const parameter) { langType new_language, old_language; @@ -4962,7 +5080,7 @@ extern bool processPretendOption (const char *const option, const char *const pa if (parameter == NULL || parameter[0] == '\0') error (FATAL, "A parameter is needed after \"%s\" option", option); - old_language = getNamedLanguageFull (parameter, 0, true); + old_language = getNamedLanguageFull (parameter, 0, true, false); if (old_language == LANG_IGNORE) error (FATAL, "Unknown language \"%s\" in option \"--%s=%s\"", parameter, option, parameter); @@ -4998,6 +5116,12 @@ extern bool processPretendOption (const char *const option, const char *const pa return true; } +extern unsigned int getLanguageCorkUsage (langType lang) +{ + parserObject* const parser = LanguageTable + lang; + return parserCorkFlags (parser->def); +} + /* * The universal fallback parser. * If any parser doesn't handle the input, this parser is @@ -5074,6 +5198,7 @@ typedef enum { K_ROLES, K_ROLES_DISABLED, K_FIELD_TESTING, + K_TRIGGER_NOTICE, KIND_COUNT } CTST_Kind; @@ -5153,6 +5278,7 @@ static kindDefinition CTST_Kinds[KIND_COUNT] = { {false, 'R', "rolesDisabled", "emit a tag with multi roles(disabled by default)", .referenceOnly = true, ATTACH_ROLES (CTST_RolesDisabledKindRoles)}, {true, 'f', "fieldMaker", "tag for testing field:" }, + {true, 'n', "triggerNotice", "trigger notice output"}, }; typedef enum { @@ -5334,6 +5460,9 @@ static void createCTSTTags (void) break; } + case K_TRIGGER_NOTICE: + notice ("notice output for testing: %s", CTST_Kinds [i].name); + break; } } } diff --git a/ctags/main/parse.h b/ctags/main/parse.h index 93a15c5ef4..b13089a045 100644 --- a/ctags/main/parse.h +++ b/ctags/main/parse.h @@ -70,6 +70,13 @@ typedef enum { CORK_SYMTAB = (1 << 1), } corkUsage; +/* optlib2c requires the declaration here. */ +enum scriptHook { + SCRIPT_HOOK_PRELUDE, + SCRIPT_HOOK_SEQUEL, + SCRIPT_HOOK_MAX, +}; + struct sParserDefinition { /* defined by parser */ char* name; /* name of language */ @@ -84,7 +91,7 @@ struct sParserDefinition { rescanParser parser2; /* rescanning parser (unusual case) */ selectLanguage* selectLanguage; /* may be used to resolve conflicts */ unsigned int method; /* See METHOD_ definitions above */ - unsigned int useCork; /* bit or of corkUsage */ + unsigned int useCork; /* bit fields of corkUsage */ bool useMemoryStreamInput; bool allowNullTag; bool requestAutomaticFQTag; @@ -139,6 +146,7 @@ extern const char *getLanguageName (const langType language); extern const char *getLanguageKindName (const langType language, const int kindIndex); extern langType getNamedLanguage (const char *const name, size_t len); +extern langType getNamedLanguageOrAlias (const char *const name, size_t len); extern langType getLanguageForFilenameAndContents (const char *const fileName); extern langType getLanguageForCommand (const char *const command, langType startFrom); extern langType getLanguageForFilename (const char *const filename, langType startFrom); @@ -149,6 +157,7 @@ extern bool isLanguageRoleEnabled (const langType language, int kindIndex, int r extern kindDefinition* getLanguageKindForLetter (const langType language, char kindLetter); extern void initializeParser (langType language); +extern unsigned int getLanguageCorkUsage (langType language); #ifdef HAVE_ICONV extern const char *getLanguageEncoding (const langType language); @@ -169,6 +178,8 @@ extern void addLanguageTagMultiTableRegex(const langType language, const char* const name, const char* const kinds, const char* const flags, bool *disabled); +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 anonHashString (const char *filename, char buf[9]); diff --git a/ctags/main/parse_p.h b/ctags/main/parse_p.h index 43eb3819ac..089908aadd 100644 --- a/ctags/main/parse_p.h +++ b/ctags/main/parse_p.h @@ -63,7 +63,7 @@ extern parserDefinitionFunc PEG_PARSER_LIST; extern bool doesLanguageAllowNullTag (const langType language); extern bool doesLanguageRequestAutomaticFQTag (const langType language); -extern langType getNamedLanguageFull (const char *const name, size_t len, bool noPretending); +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); @@ -145,10 +145,10 @@ extern void matchLanguageRegex (const langType language, const vString* const li extern void freeRegexResources (void); extern bool checkRegex (void); extern void useRegexMethod (const langType language); -extern void printRegexFlags (bool withListHeader, bool machinable, FILE *fp); -extern void printMultilineRegexFlags (bool withListHeader, bool machinable, FILE *fp); -extern void printMultitableRegexFlags (bool withListHeader, bool machinable, FILE *fp); -extern bool hasLanguageScopeActionInRegex (const langType language); +extern void printRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp); +extern void printMultilineRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp); +extern void printMultitableRegexFlags (bool withListHeader, bool machinable, const char *flags, FILE *fp); +extern bool doesLanguageExpectCorkInRegex (const langType language); /* Multiline Regex Interface */ extern bool hasLanguageMultilineRegexPatterns (const langType language); @@ -170,6 +170,8 @@ extern bool makeFieldDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc); extern bool makeExtraDescriptionsPseudoTags (const langType language, const ptagDesc *pdesc); +extern bool makeRoleDescriptionsPseudoTags (const langType language, + const ptagDesc *pdesc); extern void printLanguageMultitableStatistics (langType language); extern void printParserStatisticsIfUsed (langType lang); diff --git a/ctags/main/parsers_p.h b/ctags/main/parsers_p.h index 7392fdbe3c..fc2e5f12c4 100644 --- a/ctags/main/parsers_p.h +++ b/ctags/main/parsers_p.h @@ -36,13 +36,17 @@ #ifdef HAVE_PACKCC #define PEG_PARSER_LIST \ - VarlinkParser + VarlinkParser, \ + KotlinParser, \ + ThriftParser #else #define PEG_PARSER_LIST #endif /* Add the name of any new parser definition function here */ #define PARSER_LIST \ + AbaqusParser, \ + AbcParser, \ AdaParser, \ AntParser, \ AsciidocParser, \ @@ -64,6 +68,8 @@ CsharpParser, \ CtagsParser, \ CobolParser, \ + CobolFreeParser, \ + CobolVariableParser, \ CUDAParser, \ DParser, \ DiffParser, \ @@ -78,26 +84,35 @@ FalconParser, \ FlexParser, \ FortranParser, \ + FunctionParametersParser, \ FyppParser, \ GdbinitParser, \ GoParser, \ + HaskellParser, \ + HaxeParser, \ HtmlParser, \ IniconfParser, \ InkoParser, \ + IPythonCellParser, \ ITclParser, \ JavaParser, \ JavaPropertiesParser, \ JavaScriptParser, \ JsonParser, \ + JuliaParser, \ KconfigParser, \ LdScriptParser, \ + LEXParser, \ LispParser, \ + LiterateHaskellParser, \ LuaParser, \ M4Parser, \ ManParser, \ MakefileParser, \ MarkdownParser, \ MatLabParser, \ + MesonParser, \ + MesonOptionsParser, \ MooseParser, \ MyrddinParser, \ NsisParser, \ @@ -123,6 +138,7 @@ RSpecParser, \ RexxParser, \ RobotParser, \ + RpmMacrosParser, \ RpmSpecParser, \ RstParser, \ RubyParser, \ @@ -141,6 +157,7 @@ TexParser, \ TexBeamerParser, \ TTCNParser, \ + Txt2tagsParser, \ TypeScriptParser, \ VeraParser, \ VerilogParser, \ @@ -148,7 +165,7 @@ VhdlParser, \ VimParser, \ WindResParser, \ - YaccParser, \ + YACCParser, \ YumRepoParser, \ ZephirParser diff --git a/ctags/main/promise.c b/ctags/main/promise.c index 509a0341a3..0e8e35f968 100644 --- a/ctags/main/promise.c +++ b/ctags/main/promise.c @@ -21,6 +21,7 @@ #include "xtag.h" #include "numarray.h" #include "routines.h" +#include "options.h" #include @@ -68,7 +69,11 @@ int makePromise (const char *parser, { struct promise *p; int r; - langType lang; + langType lang = LANG_IGNORE; + + verbose("makePromise: %s start(line: %lu, offset: %lu, srcline: %lu), end(line: %lu, offset: %lu)\n", + parser? parser: "*", startLine, startCharOffset, sourceLineOffset, + endLine, endCharOffset); if ((!isThinStreamSpec(startLine, startCharOffset, @@ -78,9 +83,12 @@ int makePromise (const char *parser, && ( !isXtagEnabled (XTAG_GUEST))) return -1; - lang = getNamedLanguage (parser, 0); - if (lang == LANG_IGNORE) - return -1; + if (parser) + { + lang = getNamedLanguage (parser, 0); + if (lang == LANG_IGNORE) + return -1; + } if ( promise_count == promise_allocated) { @@ -164,15 +172,17 @@ bool forcePromises (void) { current_promise = i; struct promise *p = promises + i; - tagFileResized = runParserInNarrowedInputStream (p->lang, - p->startLine, - p->startCharOffset, - p->endLine, - p->endCharOffset, - p->sourceLineOffset, - i) - ? true - : tagFileResized; + + if (p->lang != LANG_IGNORE && isLanguageEnabled (p->lang)) + tagFileResized = runParserInNarrowedInputStream (p->lang, + p->startLine, + p->startCharOffset, + p->endLine, + p->endCharOffset, + p->sourceLineOffset, + i) + ? true + : tagFileResized; } freeModifiers (0); @@ -306,3 +316,12 @@ void runModifiers (int promise, } ptrArrayDelete (modifiers); } + +void promiseUpdateLanguage (int promise, langType lang) +{ + Assert (promise >= 0); + + struct promise *p = promises + promise; + + p->lang = lang; +} diff --git a/ctags/main/promise.h b/ctags/main/promise.h index 65b2734739..3f1fd18214 100644 --- a/ctags/main/promise.h +++ b/ctags/main/promise.h @@ -17,6 +17,8 @@ #include "parse.h" #include "numarray.h" +/* parser can be NULL; give a name with promiseUpdateLanguage() + * when the name can be determined. */ int makePromise (const char *parser, unsigned long startLine, long startCharOffset, unsigned long endLine, long endCharOffset, @@ -26,4 +28,6 @@ int makePromise (const char *parser, The callee takes the ownership of lines. */ void promiseAttachLineFiller (int promise, ulongArray *lines); +void promiseUpdateLanguage (int promise, langType lang); + #endif /* CTAGS_MAIN_PROMISE_H */ diff --git a/ctags/main/ptag.c b/ctags/main/ptag.c index e8c844cf24..eef71a74d1 100644 --- a/ctags/main/ptag.c +++ b/ctags/main/ptag.c @@ -116,6 +116,12 @@ static bool ptagMakeExtraDescriptions (ptagDesc *desc, langType language, return makeExtraDescriptionsPseudoTags (language, desc); } +static bool ptagMakeRoleDescriptions (ptagDesc *desc, langType language, + const void *data CTAGS_ATTR_UNUSED) +{ + return makeRoleDescriptionsPseudoTags (language, desc); +} + static bool ptagMakeProcCwd (ptagDesc *desc, langType language, const void *data CTAGS_ATTR_UNUSED) { @@ -176,6 +182,10 @@ static ptagDesc ptagDescs [] = { "the names and descriptions of enabled extras", ptagMakeExtraDescriptions, PTAGF_COMMON|PTAGF_PARSER }, + { false, "TAG_ROLE_DESCRIPTION", + "the names and descriptions of enabled roles", + ptagMakeRoleDescriptions, + PTAGF_PARSER }, { true, "TAG_OUTPUT_MODE", "the output mode: u-ctags or e-ctags", ptagMakeCtagsOutputMode, diff --git a/ctags/main/ptag_p.h b/ctags/main/ptag_p.h index 5409b974a3..1bfbad632f 100644 --- a/ctags/main/ptag_p.h +++ b/ctags/main/ptag_p.h @@ -9,8 +9,8 @@ * GNU General Public License version 2 or (at your option) any later version. * */ -#ifndef CTAGS_MAIN_PTAG_H -#define CTAGS_MAIN_PTAG_H +#ifndef CTAGS_MAIN_PTAG_PRIVATE_H +#define CTAGS_MAIN_PTAG_PRIVATE_H #include "general.h" #include "types.h" @@ -37,6 +37,7 @@ typedef enum ePtagType { /* pseudo tag content control */ PTAG_KIND_DESCRIPTION, PTAG_FIELD_DESCRIPTION, PTAG_EXTRA_DESCRIPTION, + PTAG_ROLE_DESCRIPTION, PTAG_OUTPUT_MODE, PTAG_OUTPUT_FILESEP, PTAG_PATTERN_TRUNCATION, @@ -79,4 +80,4 @@ extern bool isPtagCommonInParsers (ptagType type); extern bool isPtagParserSpecific (ptagType type); extern bool enablePtag (ptagType type, bool state); -#endif /* CTAGS_MAIN_FIELD_H */ +#endif /* CTAGS_MAIN_PTAG_PRIVATE_H */ diff --git a/ctags/main/ptrarray.c b/ctags/main/ptrarray.c index c0b9f62eb7..299ae810a0 100644 --- a/ctags/main/ptrarray.c +++ b/ctags/main/ptrarray.c @@ -56,6 +56,28 @@ extern unsigned int ptrArrayAdd (ptrArray *const current, void *ptr) return current->count++; } +extern bool ptrArrayUpdate (ptrArray *const current, + unsigned int indx, void *ptr, void *padding) +{ + Assert (current != NULL); + if (current->count > indx) + { + void *r = current->array [indx]; + if (current->deleteFunc) + current->deleteFunc (r); + current->array [indx] = ptr; + return true; + } + else + { + unsigned int c = indx - current->count; + for (unsigned int i = 0; i < c; i++) + ptrArrayAdd (current, padding); + ptrArrayAdd (current, ptr); + return false; + } + +} extern void *ptrArrayRemoveLast (ptrArray *const current) { Assert (current != NULL); @@ -65,6 +87,20 @@ extern void *ptrArrayRemoveLast (ptrArray *const current) return r; } +extern void ptrArrayDeleteLastInBatch (ptrArray *const current, unsigned int count) +{ + Assert (current != NULL); + Assert (current->count >= count); + while (count > 0) + { + void *r = ptrArrayLast (current); + if (current->deleteFunc) + current->deleteFunc (r); + --current->count; + --count; + } +} + /* Combine array `from' into `current', deleting `from' */ extern void ptrArrayCombine (ptrArray *const current, ptrArray *const from) { @@ -86,14 +122,15 @@ extern unsigned int ptrArrayCount (const ptrArray *const current) extern void* ptrArrayItem (const ptrArray *const current, const unsigned int indx) { Assert (current != NULL); + Assert (current->count > indx); return current->array [indx]; } -extern void* ptrArrayLast (const ptrArray *const current) +extern void* ptrArrayItemFromLast (const ptrArray *const current, const unsigned int indx) { Assert (current != NULL); - Assert (current->count > 0); - return current->array [current->count - 1]; + Assert (current->count > indx); + return current->array [current->count - 1 - indx]; } extern void ptrArrayClear (ptrArray *const current) @@ -166,6 +203,32 @@ extern void ptrArrayDeleteItem (ptrArray* const current, unsigned int indx) --current->count; } +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; + + return ptr; +} + +extern void ptrArrayInsertItem (ptrArray* const current, unsigned int indx, void *ptr) +{ + Assert (current != NULL); + if (current->count == current->max) + { + current->max *= 2; + current->array = xRealloc (current->array, current->max, void*); + } + + memmove (current->array + indx + 1, current->array + indx, + (current->count - indx) * sizeof (*current->array)); + current->array[indx] = ptr; + ++current->count; +} + static int (*ptrArraySortCompareVar)(const void *, const void *); static int ptrArraySortCompare(const void *a0, const void *b0) diff --git a/ctags/main/ptrarray.h b/ctags/main/ptrarray.h index 9fc7fc3884..88476a3b8d 100644 --- a/ctags/main/ptrarray.h +++ b/ctags/main/ptrarray.h @@ -30,12 +30,17 @@ typedef void (*ptrArrayDeleteFunc) (void *data); extern ptrArray *ptrArrayNew (ptrArrayDeleteFunc deleteFunc); extern unsigned int ptrArrayAdd (ptrArray *const current, void *ptr); +extern bool ptrArrayUpdate (ptrArray *const current, unsigned int indx, void *ptr, void *padding); extern void *ptrArrayRemoveLast (ptrArray *const current); +#define ptrArrayDeleteLast(A) ptrArrayDeleteLastInBatch(A, 1) +extern void ptrArrayDeleteLastInBatch (ptrArray *const current, unsigned int count); extern void ptrArrayCombine (ptrArray *const current, ptrArray *const from); extern void ptrArrayClear (ptrArray *const current); extern unsigned int ptrArrayCount (const ptrArray *const current); +#define ptrArrayIsEmpty(A) (ptrArrayCount(A) == 0) extern void* ptrArrayItem (const ptrArray *const current, const unsigned int indx); -extern void* ptrArrayLast (const ptrArray *const current); +extern void* ptrArrayItemFromLast (const ptrArray *const current, const unsigned int indx); +#define ptrArrayLast(A) ptrArrayItemFromLast(A, 0) extern void ptrArrayDelete (ptrArray *const current); extern bool ptrArrayHasTest (const ptrArray *const current, bool (*test)(const void *ptr, void *userData), @@ -43,6 +48,8 @@ extern bool ptrArrayHasTest (const ptrArray *const current, extern bool ptrArrayHas (const ptrArray *const current, void *ptr); extern void ptrArrayReverse (const ptrArray *const current); extern void ptrArrayDeleteItem (ptrArray* const current, unsigned int indx); +extern void*ptrArrayRemoveItem (ptrArray* const current, unsigned int indx); +extern void ptrArrayInsertItem (ptrArray* const current, unsigned int indx, void *ptr); extern void ptrArraySort (ptrArray *const current, int (*compare)(const void *, const void *)); diff --git a/ctags/main/read.c b/ctags/main/read.c index e19f156974..9f0a35f80a 100644 --- a/ctags/main/read.c +++ b/ctags/main/read.c @@ -112,6 +112,7 @@ typedef struct sInputFile { inputLineFposMap lineFposMap; vString *allLines; int thinDepth; + time_t mtime; } inputFile; static inputLangInfo inputLang; @@ -190,7 +191,7 @@ extern MIOPos getInputFilePositionForLine (unsigned int line) extern long getInputFileOffsetForLine (unsigned int line) { compoundPos *cpos = getInputFileCompoundPosForLine (line); - return cpos->offset; + return cpos->offset - (File.bomFound? 3: 0); } extern langType getInputLanguage (void) @@ -361,6 +362,9 @@ extern unsigned long getInputLineNumberForFileOffset(long offset) { compoundPos *p; + if (File.bomFound) + offset += 3; + p = bsearch (&offset, File.lineFposMap.pos, File.lineFposMap.count, sizeof (compoundPos), compoundPosForOffset); if (p == NULL) @@ -604,8 +608,8 @@ static bool parseLineDirective (char *s) #define MAX_IN_MEMORY_FILE_SIZE (1024*1024) #endif -extern MIO *getMio (const char *const fileName, const char *const openMode, - bool memStreamRequired) +static MIO *getMioFull (const char *const fileName, const char *const openMode, + bool memStreamRequired, time_t *mtime) { FILE *src; fileStatus *st; @@ -614,6 +618,8 @@ extern MIO *getMio (const char *const fileName, const char *const openMode, st = eStat (fileName); size = st->size; + if (mtime) + *mtime = st->mtime; eStatFree (st); if ((!memStreamRequired) && (size > MAX_IN_MEMORY_FILE_SIZE || size == 0)) @@ -637,6 +643,12 @@ extern MIO *getMio (const char *const fileName, const char *const openMode, return mio_new_memory (data, size, eRealloc, eFreeNoNullCheck); } +extern MIO *getMio (const char *const fileName, const char *const openMode, + bool memStreamRequired) +{ + return getMioFull (fileName, openMode, memStreamRequired, NULL); +} + /* Return true if utf8 BOM is found */ static bool checkUTF8BOM (MIO *mio, bool skipIfFound) { @@ -671,7 +683,7 @@ static void rewindInputFile (inputFile *f) * fails, it will display an error message and leave the File.mio set to NULL. */ extern bool openInputFile (const char *const fileName, const langType language, - MIO *mio) + MIO *mio, time_t mtime) { const char *const openMode = "rb"; bool opened = false; @@ -708,7 +720,7 @@ extern bool openInputFile (const char *const fileName, const langType language, mio_rewind (mio); } - File.mio = mio? mio_ref (mio): getMio (fileName, openMode, memStreamRequired); + File.mio = mio? mio_ref (mio): getMioFull (fileName, openMode, memStreamRequired, &File.mtime); if (File.mio == NULL) error (WARNING | PERROR, "cannot open \"%s\"", fileName); @@ -716,6 +728,8 @@ extern bool openInputFile (const char *const fileName, const langType language, { opened = true; + if (File.mio == mio) + File.mtime = mtime; File.bomFound = checkUTF8BOM (File.mio, true); @@ -748,6 +762,11 @@ extern bool openInputFile (const char *const fileName, const langType language, return opened; } +extern time_t getInputFileMtime (void) +{ + return File.mtime; +} + extern void resetInputFile (const langType language) { Assert (File.mio); @@ -875,7 +894,7 @@ static eolType readLine (vString *const vLine, MIO *const mio) return r; } -static vString *iFileGetLine (void) +static vString *iFileGetLine (bool chop_newline) { eolType eol; langType lang = getInputLanguage(); @@ -895,11 +914,17 @@ static vString *iFileGetLine (void) if (Option.lineDirectives && vStringChar (File.line, 0) == '#') parseLineDirective (vStringValue (File.line) + 1); - matchLanguageRegex (lang, File.line); if (File.allLines) vStringCat (File.allLines, File.line); + bool chopped = vStringStripNewline (File.line); + + matchLanguageRegex (lang, File.line); + + if (chopped && !chop_newline) + vStringPutNewlinAgainUnsafe (File.line); + return File.line; } else @@ -943,7 +968,7 @@ extern int getcFromInputFile (void) } else { - vString* const line = iFileGetLine (); + vString* const line = iFileGetLine (false); if (line != NULL) File.currentLine = (unsigned char*) vStringValue (line); if (File.currentLine == NULL) @@ -999,12 +1024,11 @@ extern int skipToCharacterInInputFile2 (int c0, int c1) */ extern const unsigned char *readLineFromInputFile (void) { - vString* const line = iFileGetLine (); + vString* const line = iFileGetLine (true); const unsigned char* result = NULL; if (line != NULL) { result = (const unsigned char*) vStringValue (line); - vStringStripNewline (line); DebugStatement ( debugPrintf (DEBUG_READ, "%s\n", result); ) } return result; diff --git a/ctags/main/read_p.h b/ctags/main/read_p.h index ac2761a007..95dd11187f 100644 --- a/ctags/main/read_p.h +++ b/ctags/main/read_p.h @@ -52,7 +52,7 @@ extern void freeInputFileResources (void); argument. If the 3rd argument is NULL, openInputFile calls getMio internally. The 3rd argument is introduced for reusing mio object created in parser guessing stage. */ -extern bool openInputFile (const char *const fileName, const langType language, MIO *mio); +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); @@ -64,6 +64,8 @@ extern unsigned int getNestedInputBoundaryInfo (unsigned long lineNumber); extern const char *getSourceFileTagPath (void); extern langType getSourceLanguage (void); +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 ( diff --git a/ctags/main/repoinfo.h b/ctags/main/repoinfo.h index 15312d43da..cd5f7a8117 100644 --- a/ctags/main/repoinfo.h +++ b/ctags/main/repoinfo.h @@ -1 +1 @@ -#define CTAGS_REPOINFO "2fc21493" +#define CTAGS_REPOINFO "01342f01" diff --git a/ctags/main/routines.c b/ctags/main/routines.c index b9e5fc66d2..6c5a592966 100644 --- a/ctags/main/routines.c +++ b/ctags/main/routines.c @@ -477,6 +477,7 @@ extern fileStatus *eStat (const char *const fileName) file.isSetuid = (bool) ((status.st_mode & S_ISUID) != 0); file.isSetgid = (bool) ((status.st_mode & S_ISGID) != 0); file.size = status.st_size; + file.mtime = status.st_mtime; } } } diff --git a/ctags/main/routines.h b/ctags/main/routines.h index 1d43f2bb98..475cf76e90 100644 --- a/ctags/main/routines.h +++ b/ctags/main/routines.h @@ -34,12 +34,13 @@ * DATA DECLARATIONS */ typedef int errorSelection; -enum eErrorTypes { FATAL = 1, WARNING = 2, PERROR = 4 }; +enum eErrorTypes { FATAL = 1, WARNING = 2, NOTICE = 4, PERROR = 8, }; /* * FUNCTION PROTOTYPES */ extern void error (const errorSelection selection, const char *const format, ...) CTAGS_ATTR_PRINTF (2, 3); +#define notice(...) error (NOTICE, __VA_ARGS__) /* Memory allocation functions */ #ifdef NEED_PROTO_MALLOC diff --git a/ctags/main/routines_p.h b/ctags/main/routines_p.h index 2ff746777f..61401f053f 100644 --- a/ctags/main/routines_p.h +++ b/ctags/main/routines_p.h @@ -68,6 +68,9 @@ typedef struct { /* Size of file (pointed to) */ unsigned long size; + + /* The last modified time */ + time_t mtime; } fileStatus; /* diff --git a/ctags/main/script.c b/ctags/main/script.c new file mode 100644 index 0000000000..1d7bd0d4b6 --- /dev/null +++ b/ctags/main/script.c @@ -0,0 +1,527 @@ +/* +* Copyright (c) 2020, Masatake YAMATO +* Copyright (c) 2020, 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 ctags specific optscript objects +*/ + +#include "general.h" /* must always come first */ + +#include "debug.h" +#include "entry.h" +#include "field_p.h" +#include "htable.h" /* For HT_PTR_TO_INT */ +#include "optscript.h" +#include "parse.h" +#include "routines.h" +#include "script_p.h" +#include "xtag_p.h" + +#include +#include + +EsObject *OPTSCRIPT_ERR_NOTAGENTRY; +EsObject *OPTSCRIPT_ERR_UNKNOWNLANGUAGE; + +int OPT_TYPE_MATCHLOC; +static int locEqual (const void *a, const void *b); +static void locPrint (const void *a, MIO *out); + +int OPT_TYPE_TAG; +static void tagFree (void *a); +static int tagEqual (const void *a, const void *b); +static void tagPrint (const void *a, MIO *out); + +static void vStringCatToupperS (vString *str, const char *s) +{ + for (const char *tmp = s; *tmp != '\0'; tmp++) + { + int c = toupper (*tmp); + vStringPut (str, c); + } +} + +extern OptVM *optscriptInit (void) +{ + opt_init (); + MIO *in = mio_new_fp (stdin, NULL); + + /* stdout is for emitting tags. + * The interpreter should not touch it; use only stderr. */ + MIO *out = mio_new_fp (stderr, NULL); + MIO *err = mio_new_fp (stderr, NULL); + + OptVM *optvm = opt_vm_new (in, out, err); + + mio_unref (err); + mio_unref (out); + mio_unref (in); + + OPTSCRIPT_ERR_NOTAGENTRY = es_error_intern ("notagentry"); + + OPT_TYPE_MATCHLOC = es_type_define_pointer ("matchloc", + eFreeNoNullCheck, + locEqual, + locPrint); + OPT_TYPE_TAG = es_type_define_pointer ("tagEntryInfo", + tagFree, + tagEqual, + tagPrint); + return optvm; +} + +static EsObject* lrop_get_field_value (OptVM *vm, EsObject *name) +{ + EsObject *nobj = opt_vm_ostack_top (vm); + if (!es_integer_p (nobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (nobj); + tagEntryInfo *e = getEntryInCorkQueue (n); + if (e == NULL) + return OPTSCRIPT_ERR_NOTAGENTRY;; + + void * data = es_symbol_get_data (name); + fieldType ftype = HT_PTR_TO_INT (data); + EsObject *val = getFieldValue (ftype, e); + if (es_error_p (val)) + return val; + + opt_vm_ostack_pop (vm); + + if (isFieldValueAvailableAlways (ftype)) + { + opt_vm_ostack_push (vm, val); + es_object_unref (val); + } + else if (es_null (val)) + { + opt_vm_ostack_push (vm, es_false); + } + else + { + opt_vm_ostack_push (vm, val); + opt_vm_ostack_push (vm, es_true); + es_object_unref (val); + } + return es_false; +} + +static EsObject* lrop_set_field_value (OptVM *vm, EsObject *name) +{ + EsObject *indexobj = opt_vm_ostack_peek (vm, 1); + if (!es_integer_p (indexobj)) + return OPT_ERR_TYPECHECK; + + int n = es_integer_get (indexobj); + tagEntryInfo *e = getEntryInCorkQueue (n); + if (e == NULL) + return OPTSCRIPT_ERR_NOTAGENTRY;; + + void * data = es_symbol_get_data (name); + fieldType ftype = HT_PTR_TO_INT (data); + unsigned int fdata_type = getFieldDataType (ftype); + + EsObject *valobj = opt_vm_ostack_top (vm); + int valtype = es_object_get_type (valobj); + + if (hasFieldValueCheckerForSetter (ftype)) + { + EsObject *e = checkFieldValueForSetter (ftype, valobj); + if (!es_object_equal (e, es_false)) + return e; + } + else + { + if (! (((fdata_type & FIELDTYPE_STRING) && (valtype == OPT_TYPE_STRING)) + || ((fdata_type & FIELDTYPE_BOOL) && (valtype == ES_TYPE_BOOLEAN)) + || ((fdata_type & FIELDTYPE_INTEGER) && (valtype == ES_TYPE_INTEGER)))) + return OPT_ERR_TYPECHECK; + } + + EsObject *r = setFieldValue (ftype, e, valobj); + if (es_error_p (r)) + return r; + + opt_vm_ostack_pop (vm); + opt_vm_ostack_pop (vm); + + return es_false; +} + +static void optscriptInstallFieldGetter (EsObject *dict, fieldType ftype, + vString *op_name, vString *op_desc) +{ + const char *fname = getFieldName (ftype); + vStringPut (op_name, ':'); + vStringCatS (op_name, fname); + EsObject *op_sym = es_symbol_intern (vStringValue (op_name)); + es_symbol_set_data (op_sym, HT_INT_TO_PTR (ftype)); + + const char *vtype = getFieldGetterValueType (ftype); + unsigned int fdata_type = getFieldDataType (ftype); + + vStringCatS (op_desc, "int :"); + vStringCatToupperS (op_desc, fname); + vStringPut (op_desc, ' '); + + if (vtype) + vStringCatS (op_desc, vtype); + else + { + Assert (fdata_type); + if (fdata_type & FIELDTYPE_STRING) + vStringCatS (op_desc, "string|"); + if (fdata_type & FIELDTYPE_INTEGER) + vStringCatS (op_desc, "int|"); + if (fdata_type & FIELDTYPE_BOOL) + vStringCatS (op_desc, "bool|"); + vStringChop (op_desc); + } + + if (!isFieldValueAvailableAlways (ftype)) + { + vStringPut (op_desc, ' '); + vStringCatS (op_desc, "true%"); + vStringCatS (op_desc, "int :"); + vStringCatToupperS (op_desc, fname); + vStringCatS (op_desc, " false"); + } + + EsObject *op = opt_operator_new (lrop_get_field_value, + vStringValue (op_name), + 1, vStringValue (op_desc)); + opt_dict_def (dict, op_sym, op); + es_object_unref (op); +} + +static void optscriptInstallFieldSetter (EsObject *dict, fieldType ftype, + vString *op_name, vString *op_desc) +{ + const char *fname = getFieldName (ftype); + vStringCatS (op_name, fname); + vStringPut (op_name, ':'); + + EsObject *op_sym = es_symbol_intern (vStringValue (op_name)); + es_symbol_set_data (op_sym, HT_INT_TO_PTR (ftype)); + + const char *vtype = getFieldSetterValueType (ftype); + unsigned int fdata_type = getFieldDataType (ftype); + vStringCatS (op_desc, "int "); + + if (vtype) + vStringCatS (op_desc, vtype); + else + { + Assert (fdata_type); + if (fdata_type & FIELDTYPE_STRING) + vStringCatS (op_desc, "string|"); + if (fdata_type & FIELDTYPE_INTEGER) + vStringCatS (op_desc, "int|"); + if (fdata_type & FIELDTYPE_BOOL) + vStringCatS (op_desc, "bool|"); + vStringChop (op_desc); + } + + vStringPut (op_desc, ' '); + vStringCatToupperS (op_desc, fname); + vStringCatS (op_desc, ": -"); + + EsObject *op = opt_operator_new (lrop_set_field_value, + vStringValue (op_name), + 2, vStringValue (op_desc)); + opt_dict_def (dict, op_sym, op); + es_object_unref (op); +} + +static void optscriptInstallFieldAccessors (EsObject *dict) +{ + vString *op_name = vStringNew (); + vString *op_desc = vStringNew (); + + for (fieldType ftype = 0; ftype <= FIELD_BUILTIN_LAST; ftype++) + { + if (hasFieldGetter (ftype)) + { + optscriptInstallFieldGetter (dict, ftype, op_name, op_desc); + vStringClear (op_name); + vStringClear (op_desc); + } + if (hasFieldSetter (ftype)) + { + optscriptInstallFieldSetter (dict, ftype, op_name, op_desc); + vStringClear (op_name); + vStringClear (op_desc); + } + } + + vStringDelete (op_name); + vStringDelete (op_desc); +} + +/* Define \1, \2,... \9 */ +static void optscriptInstallMatchResultProcs (EsObject *dict, + OptOperatorFn fun) +{ + char name [] = { [0] = '\\', [2] = '\0' }; + char help [] = "- \\_ string|false"; + char *p = strchr (help, '_'); + for (int i = 1; i <= 9; i++) + { + name [1] = '0' + i; + *p = name [1]; + EsObject *op_sym = es_symbol_intern (name); + es_symbol_set_data (op_sym, HT_INT_TO_PTR (i)); + EsObject *op = opt_operator_new (fun, name, 0, help); + opt_dict_def (dict, op_sym, op); + es_object_unref (op); + } +} + +extern void optscriptInstallProcs (EsObject *dict, + OptOperatorFn matchResultAccessor) +{ + optscriptInstallFieldAccessors (dict); + optscriptInstallMatchResultProcs (dict, matchResultAccessor); +} + +static EsObject *optscript_CorkIndex_sym = es_nil; +extern void optscriptSetup (OptVM *vm, EsObject *dict, int corkIndex) +{ + if (corkIndex != CORK_NIL) + { + static EsObject *corkIndex_sym = es_nil; + if (es_null (corkIndex_sym)) + corkIndex_sym = es_symbol_intern ("."); + EsObject *corkIndex_val = es_integer_new (corkIndex); + opt_dict_def (dict, corkIndex_sym, corkIndex_val); + es_object_unref (corkIndex_val); + optscript_CorkIndex_sym = corkIndex_sym; + } +} + +extern void optscriptTeardown (OptVM *vm, EsObject *dict) +{ + if (!es_null (optscript_CorkIndex_sym)) + { + opt_dict_undef (dict, optscript_CorkIndex_sym); + optscript_CorkIndex_sym = es_nil; + } +} + +extern EsObject *optscriptRead (OptVM *vm, const char *src, size_t len) +{ + if (len == 0) + len = strlen (src); + + MIO *mio = mio_new_memory ((unsigned char *)src, len, NULL, NULL); + EsObject *obj = opt_vm_read (vm, mio); + if (es_error_p (obj)) + opt_vm_report_error (vm, obj, NULL); + mio_unref (mio); + return obj; +} + +extern EsObject* optscriptEval (OptVM *vm, EsObject *code) +{ + static EsObject *exec = es_nil; + + if (es_null (exec)) + { + MIO *mio = mio_new_memory ((unsigned char*)"//exec", 6, NULL, NULL); + exec = opt_vm_read (vm, mio); + if (es_error_p (exec)) + { + opt_vm_report_error (vm, exec, NULL); + error (FATAL, "failed in converting //exec to an optscript object"); + } + mio_unref (mio); + } + + EsObject *o = opt_vm_eval (vm, code); + if (es_error_p (o)) + { + opt_vm_report_error (vm, o, NULL); + error (FATAL, "failed to push the proc representing the script"); + } + es_object_unref (o); + + EsObject *r = opt_vm_eval (vm, exec);; + if (es_error_p (r)) + opt_vm_report_error (vm, r, NULL); + return r; +} + +extern EsObject* optscriptDefine (EsObject *dict, + const char *name, EsObject *obj) +{ + EsObject *sym = es_symbol_intern (name); + opt_dict_def (dict, sym, obj); + return sym; +} + + +extern EsObject *optscriptLoad (OptVM *vm, MIO *mio) +{ + while (true) + { + EsObject *o = opt_vm_read (vm, mio); + if (es_object_equal (o, ES_READER_EOF)) + { + es_object_unref (o); + return es_false; + } + else if (es_error_p (o)) + { + opt_vm_report_error (vm, o, NULL); + return o; + } + + EsObject *e = opt_vm_eval (vm, o); + if (es_error_p (e)) + { + opt_vm_report_error (vm, e, NULL); + es_object_unref (o); + return e; + } + + es_object_unref (o); + } +} + +extern EsObject *optscriptReadAndEval (OptVM *vm, const char *src, size_t len) +{ + EsObject *obj = optscriptRead (vm, src, len); + if (es_error_p (obj)) + return obj; + + EsObject *r = optscriptEval (vm, obj); + es_object_unref (obj); + return r; +} + +extern EsObject *optscriptReadAndDefine (OptVM *vm, EsObject *dict, const char *name, + const char *src, size_t len) +{ + EsObject *obj = optscriptRead (vm, src, len); + if (es_error_p (obj)) + return obj; + return optscriptDefine (dict, name, obj); +} + +static bool procdocs_add_key_val (EsObject *proc, EsObject *help_str, void *data) +{ + ptrArray *a = data; + + if (es_object_get_type (help_str) == OPT_TYPE_STRING) + ptrArrayAdd (a, proc); + + return true; +} + +static const char* procdocs_get_help_str (EsObject *proc, void *data) +{ + EsObject *dict = data; + const char *name = opt_name_get_cstr (proc); + EsObject *help_str = NULL; + + if (opt_dict_known_and_get_cstr (dict, name, &help_str)) + return opt_string_get_cstr(help_str); + return NULL; +} + +static void procdocs_add (ptrArray *a, void *data) +{ + EsObject *dict = data; + opt_dict_foreach (dict, procdocs_add_key_val, a); +} + +static struct OptHelpExtender procdocs_help_extender = { + .add = procdocs_add, + .get_help_str = procdocs_get_help_str, +}; + +extern void optscriptHelp (OptVM *vm, FILE *fp, EsObject *procdocs) +{ + MIO *out = mio_new_fp (fp, NULL); + opt_vm_help (vm, out, procdocs? &procdocs_help_extender: NULL, procdocs); + mio_unref (out); +} + +static int locEqual (const void *a, const void *b) +{ + if (a == b) + return 1; + + const matchLoc *al = a; + const matchLoc *bl = b; + + if (al->line == bl->line + && memcmp (&al->pos, &bl->pos, sizeof (al->pos)) == 0) + return 1; + return 0; +} + +static void locPrint (const void *a, MIO *out) +{ + const matchLoc *al = a; + mio_printf (out, "#", a, al->line); +} + +static void tagFree (void *a) +{ + tagEntryInfo *e = a; + eFree ((void *)e->name); /* TODO */ + eFree (e); +} + +static int tagEqual (const void *a, const void *b) +{ + if (a == b) + return 1; + return 0; +} + +static void tagPrint (const void *a, MIO *out) +{ + const tagEntryInfo *tag = a; + mio_printf (out, "#", + tag, tag->name, tag->lineNumber); +} + +extern void optscriptRegisterOperators(EsObject * dict, + struct optscriptOperatorRegistration regs[], size_t count) +{ + EsObject *op; + EsObject *sym; + + for (size_t i = 0; i < count; i++) + { + sym = es_symbol_intern (regs[i].name); + op = opt_operator_new (regs[i].fn, es_symbol_get (sym), regs[i].arity, + regs[i].help_str); + opt_dict_def (dict, sym, op); + es_object_unref (op); + } +} + +extern xtagType optscriptGetXtagType (const EsObject *extra) +{ + EsObject *extra_sym = es_pointer_get (extra); + const char *extra_str = es_symbol_get (extra_sym); + + const char *sep = strchr (extra_str, '.'); + if (sep) + { + langType lang = getNamedLanguage (extra_str, sep - extra_str); + if (lang == LANG_IGNORE) + return XTAG_UNKNOWN; + + return getXtagTypeForNameAndLanguage (sep + 1, lang); + } + else + return getXtagTypeForNameAndLanguage (extra_str, LANG_IGNORE); +} diff --git a/ctags/main/script_p.h b/ctags/main/script_p.h new file mode 100644 index 0000000000..5122322598 --- /dev/null +++ b/ctags/main/script_p.h @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2020, Masatake YAMATO +* Copyright (c) 2020, 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 ctags specific optscript objects +*/ + +#ifndef CTAGS_MAIN_SCRIPT_PRIVATE_H +#define CTAGS_MAIN_SCRIPT_PRIVATE_H + +#include "general.h" /* must always come first */ + +#include "optscript.h" +#include "mio.h" + +struct optscriptOperatorRegistration { + const char *name; + OptOperatorFn fn; + int arity; + const char *help_str; +}; +extern void optscriptRegisterOperators(EsObject * dict, + struct optscriptOperatorRegistration regs[], size_t count); + +extern EsObject *OPTSCRIPT_ERR_NOTAGENTRY; +extern EsObject *OPTSCRIPT_ERR_UNKNOWNLANGUAGE; +extern EsObject *OPTSCRIPT_ERR_UNKNOWNEXTRA; + +extern OptVM *optscriptInit (void); + +extern void optscriptInstallProcs (EsObject *dict, OptOperatorFn matchResultAccessor); + +extern void optscriptSetup (OptVM *vm, EsObject *dict, int corkIndex); +extern void optscriptTeardown (OptVM *vm, EsObject *dict); + +/* If len is 0, strlen (src) is used instead of 0. */ +extern EsObject *optscriptRead (OptVM *vm, const char *src, size_t len); +extern EsObject *optscriptEval (OptVM *vm, EsObject *code); +extern EsObject *optscriptDefine (EsObject *dict, const char *name, EsObject *obj); + +extern EsObject *optscriptReadAndEval (OptVM *vm, const char *src, size_t len); +extern EsObject *optscriptReadAndDefine (OptVM *vm, EsObject *dict, const char *name, + const char *src, size_t len); + +extern EsObject *optscriptLoad (OptVM *vm, MIO *mio); + +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 line; + MIOPos pos; +} matchLoc; +extern int OPT_TYPE_MATCHLOC; + +extern int OPT_TYPE_TAG; +#endif /* CTAGS_MAIN_SCRIPT_PRIVATE_H */ diff --git a/ctags/main/seccomp.c b/ctags/main/seccomp.c index 12e6098219..f87b353513 100644 --- a/ctags/main/seccomp.c +++ b/ctags/main/seccomp.c @@ -43,6 +43,13 @@ int installSyscallFilter (void) // The bowels of stdio want to know the size of a file, even for stdout. seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (fstat), 0); 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); +#endif +#ifdef __SNR_statx + // armhf fallback + seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (statx), 0); +#endif // seems unnecessary, but this comes from // main/parse.c:2764 : tagFilePosition (&tagfpos); diff --git a/ctags/main/selectors.c b/ctags/main/selectors.c index ee0662957e..8a05edb834 100644 --- a/ctags/main/selectors.c +++ b/ctags/main/selectors.c @@ -34,6 +34,9 @@ static const char *TR_ASM = "Asm"; static const char *TR_REXX = "REXX"; static const char *TR_DOSBATCH = "DosBatch"; +static const char *TR_LISP = "Lisp"; +static const char *TR_LEX = "LEX"; + #define startsWith(line,prefix) \ (strncmp(line, prefix, strlen(prefix)) == 0? true: false) @@ -301,6 +304,24 @@ selectByRexxCommentAndDosbatchLabelPrefix (MIO *input, NULL, &in_rexx_comment); } +static const char * +tasteLispOrLEXLines (const char *line, void *data CTAGS_ATTR_UNUSED) +{ + if (strcmp(line, "%{\n") == 0 + || strcmp(line, "%top{\n") == 0 + || strcmp(line, "%%\n") == 0) + return TR_LEX; + return TR_UNKNOWN; +} + +const char * +selectLispOrLEXByLEXMarker (MIO *input, + langType *candidates CTAGS_ATTR_UNUSED, + unsigned int nCandidates CTAGS_ATTR_UNUSED) +{ + return selectByLines (input, tasteLispOrLEXLines, TR_LISP, NULL); +} + #ifdef HAVE_LIBXML #include diff --git a/ctags/main/selectors.h b/ctags/main/selectors.h index 6b2eb7f7d9..e90bf26903 100644 --- a/ctags/main/selectors.h +++ b/ctags/main/selectors.h @@ -27,6 +27,9 @@ selectByArrowOfR (MIO *, langType *, unsigned int); const char * selectByRexxCommentAndDosbatchLabelPrefix (MIO *, langType *, unsigned int); +const char * +selectLispOrLEXByLEXMarker (MIO *, langType *, unsigned int); + const char * selectByXpathFileSpec (MIO *input, langType *candidates, unsigned int nCandidates); diff --git a/ctags/main/strlist.c b/ctags/main/strlist.c index 7decc23d81..75e250dfc0 100644 --- a/ctags/main/strlist.c +++ b/ctags/main/strlist.c @@ -212,6 +212,16 @@ extern vString* stringListExtensionFinds ( #endif } +extern bool stringListCaseMatched (const stringList* const list, const char* const str) +{ + return stringListCaseFinds(list, str)? true: false; +} + +extern vString* stringListCaseFinds (const stringList* const list, const char* const str) +{ + return stringListFinds (list, str, compareStringInsensitive); +} + static bool fileNameMatched ( const vString* const vpattern, const char* const fileName) { diff --git a/ctags/main/strlist.h b/ctags/main/strlist.h index e227a58090..1bbbcb2987 100644 --- a/ctags/main/strlist.h +++ b/ctags/main/strlist.h @@ -45,10 +45,26 @@ extern bool stringListHasTest (const stringList *const current, bool (*test)(const char *s, void *userData), void *userData); extern bool stringListDeleteItemExtension (stringList* const current, const char* const extension); + +/* + * stringListExtension{Matched,Finds} + * They compares strcmp or strcasecmp. + * The choice of case-sensitive or case-insensitive is platform-dependent. + * + * stringListFile{Matched,Finds}: + * They do glob-matching with fnmatch(). + * The choice of case-sensitive or case-insensitive is platform-dependent. + * + * stringListCase{Matched,Finds}: + * They always work case-insensitive way. + */ extern bool stringListExtensionMatched (const stringList* const list, const char* const extension); extern vString* stringListExtensionFinds (const stringList* const list, const char* const extension); extern bool stringListFileMatched (const stringList* const list, const char* const str); extern vString* stringListFileFinds (const stringList* const list, const char* const str); +extern bool stringListCaseMatched (const stringList* const list, const char* const str); +extern vString* stringListCaseFinds (const stringList* const list, const char* const str); + extern void stringListPrint (const stringList *const current, FILE *fp); extern void stringListReverse (const stringList *const current); diff --git a/ctags/main/subparser.h b/ctags/main/subparser.h index 836fd7cc68..3ba6f7b25c 100644 --- a/ctags/main/subparser.h +++ b/ctags/main/subparser.h @@ -61,6 +61,8 @@ extern void leaveSubparser(void); extern subparser* getSubparserRunningBaseparser (void); extern void chooseExclusiveSubparser (subparser *s, void *data); +extern subparser *getLanguageSubparser (langType sublang, bool including_none_crafted_parser); + /* Interface for Subparsers */ #define RUN_DEFAULT_SUBPARSERS -1 extern void scheduleRunningBaseparser (int dependencyIndex); diff --git a/ctags/main/tokeninfo.c b/ctags/main/tokeninfo.c index 5dea1b884c..0a0e01d5de 100644 --- a/ctags/main/tokeninfo.c +++ b/ctags/main/tokeninfo.c @@ -108,7 +108,11 @@ void tokenReadFull (tokenInfo *token, void *data) tokenDelete (backlog); } else + { token->klass->read (token, data); + if (!tokenIsEOF (token)) + token->klass->read_counter++; + } } void tokenRead (tokenInfo *token) diff --git a/ctags/main/tokeninfo.h b/ctags/main/tokeninfo.h index 4f68a9218b..51360106b7 100644 --- a/ctags/main/tokeninfo.h +++ b/ctags/main/tokeninfo.h @@ -53,6 +53,14 @@ struct tokenInfoClass { void (*copy) (tokenInfo *dest, tokenInfo *src, void *data); objPool *pool; ptrArray *backlog; + + /* read_counter is incremented every time when reading a + * new token from the input stream unless the new token is EOF. + * + * When filling a tokenInfo from an entry in the backlog, we don't + * regard it as "reading a new token". + */ + int read_counter; }; void *newToken (struct tokenInfoClass *klass); diff --git a/ctags/main/trace.c b/ctags/main/trace.c index 45d4aa16f2..a44ff03036 100644 --- a/ctags/main/trace.c +++ b/ctags/main/trace.c @@ -117,4 +117,20 @@ bool isMainTraced(void) return tracingMain; } +#else +bool isTraced (void) { return false; } +void traceLanguage (langType language) {} +bool isLanguageTraced (langType language) { return false; } + +void traceEnter(const char * szFunction,const char * szFormat,...) {} +void traceLeave(const char * szFunction,const char * szFormat,...) {} +void tracePrint(const char * szFunction,const char * szFormat,...) {} + +void tracePrintPrefix(const char * szFunction) {} +void tracePrintFmt(const char * szFormat,...) {} +void tracePrintNewline(void) {} + +void traceMain(void); +bool isMainTraced(void) { return false; } + #endif // DO_TRACING diff --git a/ctags/main/trace.h b/ctags/main/trace.h index f5f055c165..b4912bf189 100644 --- a/ctags/main/trace.h +++ b/ctags/main/trace.h @@ -25,19 +25,22 @@ #define DO_TRACING #endif -#ifdef DO_TRACING +bool isTraced (void); +void traceLanguage (langType language); +bool isLanguageTraced (langType language); + +void traceEnter(const char * szFunction,const char * szFormat,...); +void traceLeave(const char * szFunction,const char * szFormat,...); +void tracePrint(const char * szFunction,const char * szFormat,...); - bool isTraced (void); - void traceLanguage (langType language); - bool isLanguageTraced (langType language); +void tracePrintPrefix(const char * szFunction); +void tracePrintFmt(const char * szFormat,...); +void tracePrintNewline(void); - void traceEnter(const char * szFunction,const char * szFormat,...); - void traceLeave(const char * szFunction,const char * szFormat,...); - void tracePrint(const char * szFunction,const char * szFormat,...); +void traceMain(void); +bool isMainTraced(void); - void tracePrintPrefix(const char * szFunction); - void tracePrintFmt(const char * szFormat,...); - void tracePrintNewline(void); +#ifdef DO_TRACING #define TRACE_ENTER() traceEnter(__func__,"") #define TRACE_LEAVE() traceLeave(__func__,"") @@ -75,9 +78,6 @@ } \ } while(0) - void traceMain(void); - bool isMainTraced(void); - #else //!DO_TRACING #define TRACE_ENTER() do { } while(0) diff --git a/ctags/main/vstring.c b/ctags/main/vstring.c index 17c9bd92cb..1b5f684d09 100644 --- a/ctags/main/vstring.c +++ b/ctags/main/vstring.c @@ -152,18 +152,21 @@ extern void vStringCatS (vString *const string, const char *const s) /* Strip trailing newline from string. */ -extern void vStringStripNewline (vString *const string) +extern bool vStringStripNewline (vString *const string) { const size_t final = string->length - 1; if (string->length == 0) - return; + return false; if (string->buffer [final] == '\n') { string->buffer [final] = '\0'; string->length--; + return true; } + + return false; } /* Strip leading white space from string. diff --git a/ctags/main/vstring.h b/ctags/main/vstring.h index 6e3840e469..b423d46a9f 100644 --- a/ctags/main/vstring.h +++ b/ctags/main/vstring.h @@ -56,7 +56,7 @@ typedef struct sVString { extern void vStringResize (vString *const string, const size_t newSize); extern vString *vStringNew (void); extern void vStringDelete (vString *const string); -extern void vStringStripNewline (vString *const string); +extern bool vStringStripNewline (vString *const string); extern void vStringStripLeading (vString *const string); extern void vStringChop (vString *const string); extern void vStringStripTrailing (vString *const string); @@ -97,6 +97,10 @@ extern void vStringCatSWithEscapingAsPattern (vString *output, const char* input /* * INLINE FUNCTIONS */ +CTAGS_INLINE void vStringPutNewlinAgainUnsafe (vString *const string) +{ + string->buffer [string->length++] = '\n'; +} CTAGS_INLINE void vStringPut (vString *const string, const int c) { diff --git a/ctags/main/writer-ctags.c b/ctags/main/writer-ctags.c index 276ef04721..541ba5c046 100644 --- a/ctags/main/writer-ctags.c +++ b/ctags/main/writer-ctags.c @@ -35,7 +35,7 @@ static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, const char *const parserName, void *clientData); static bool treatFieldAsFixed (int fieldType); -static void checkCtagsOptions (tagWriter *writer); +static void checkCtagsOptions (tagWriter *writer, bool fieldsWereReset); #ifdef WIN32 static enum filenameSepOp overrideFilenameSeparator (enum filenameSepOp currentSetting); @@ -305,14 +305,10 @@ static int addExtensionFields (tagWriter *writer, MIO *mio, const tagEntryInfo * sep [0] = '\0'; } - length += renderExtensionFieldMaybe (writer, FIELD_INHERITANCE, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_ACCESS, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_IMPLEMENTATION, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_SIGNATURE, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_ROLES, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_EXTRAS, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_XPATH, tag, sep, mio); - length += renderExtensionFieldMaybe (writer, FIELD_END_LINE, tag, sep, mio); + for (int k = FIELD_ECTAGS_LOOP_START; k <= FIELD_ECTAGS_LOOP_LAST; k++) + length += renderExtensionFieldMaybe (writer, k, tag, sep, mio); + for (int k = FIELD_UCTAGS_LOOP_START; k <= FIELD_BUILTIN_LAST; k++) + length += renderExtensionFieldMaybe (writer, k, tag, sep, mio); return length; } @@ -345,7 +341,7 @@ static int writeCtagsEntry (tagWriter *writer, else { if (Option.locate == EX_COMBINE) - length += mio_printf(mio, "%lu;", tag->lineNumber + (Option.backward? 1: -1)); + length += mio_printf(mio, "%lu;", tag->lineNumber); length += mio_puts(mio, escapeFieldValue(writer, tag, FIELD_PATTERN)); } @@ -388,20 +384,22 @@ static int writeCtagsPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED, #undef OPT } +static fieldType fixedFields [] = { + FIELD_NAME, + FIELD_INPUT_FILE, + FIELD_PATTERN, +}; + static bool treatFieldAsFixed (int fieldType) { - switch (fieldType) - { - case FIELD_NAME: - case FIELD_INPUT_FILE: - case FIELD_PATTERN: - return true; - default: - return false; - } + for (int i = 0; i < ARRAY_SIZE(fixedFields); i++) + if (fixedFields [i] == fieldType) + return true; + return false; } -static void checkCtagsOptions (tagWriter *writer CTAGS_ATTR_UNUSED) +static void checkCtagsOptions (tagWriter *writer CTAGS_ATTR_UNUSED, + bool fieldsWereReset) { if (isFieldEnabled (FIELD_KIND_KEY) && (!(isFieldEnabled (FIELD_KIND_LONG) || @@ -416,7 +414,7 @@ static void checkCtagsOptions (tagWriter *writer CTAGS_ATTR_UNUSED) getFieldLetter (FIELD_KIND_LONG), getFieldLetter (FIELD_KIND_KEY), getFieldName (FIELD_KIND_KEY)); - enableField (FIELD_KIND_LONG, true, true); + enableField (FIELD_KIND_LONG, true); } if (isFieldEnabled (FIELD_SCOPE_KEY) && !isFieldEnabled (FIELD_SCOPE)) @@ -429,6 +427,30 @@ static void checkCtagsOptions (tagWriter *writer CTAGS_ATTR_UNUSED) getFieldLetter (FIELD_SCOPE), getFieldLetter (FIELD_SCOPE_KEY), getFieldName (FIELD_SCOPE_KEY)); - enableField (FIELD_SCOPE, true, true); + enableField (FIELD_SCOPE, true); + } + + for (int i = 0; i < ARRAY_SIZE (fixedFields); i++) + { + if (!isFieldEnabled (fixedFields [i])) + { + enableField (fixedFields [i], true); + + if (fieldsWereReset) + continue; + + const char *name = getFieldName (fixedFields [i]); + unsigned char letter = getFieldLetter (fixedFields [i]); + + if (name && letter != NUL_FIELD_LETTER) + error(WARNING, "Cannot disable fixed field: '%c'{%s} in ctags output mode", + letter, name); + else if (name) + error(WARNING, "Cannot disable fixed field: {%s} in ctags output mode", + name); + else if (letter != NUL_FIELD_LETTER) + error(WARNING, "Cannot disable fixed field: '%c' in ctags output mode", + letter); + } } } diff --git a/ctags/main/writer-json.c b/ctags/main/writer-json.c index 4cb4194d0f..3434c1754e 100644 --- a/ctags/main/writer-json.c +++ b/ctags/main/writer-json.c @@ -172,18 +172,18 @@ static void addExtensionFields (json_t *response, const tagEntryInfo *const tag) That cannot be changed to keep the compatibility of tags file format. Use FIELD_KIND_KEY instead */ if (isFieldEnabled (FIELD_KIND) || isFieldEnabled (FIELD_KIND_LONG)) - enableField (FIELD_KIND_KEY, true, false); + enableField (FIELD_KIND_KEY, true); /* FIELD_SCOPE has no name; getFieldName (FIELD_KIND_KEY) returns NULL. That cannot be changed to keep the compatibility of tags file format. Use FIELD_SCOPE_KEY and FIELD_SCOPE_KIND_LONG instead. */ if (isFieldEnabled (FIELD_SCOPE)) { - enableField (FIELD_SCOPE_KEY, true, false); - enableField (FIELD_SCOPE_KIND_LONG, true, false); + enableField (FIELD_SCOPE_KEY, true); + enableField (FIELD_SCOPE_KIND_LONG, true); } - for (k = FIELD_EXTENSION_START; k <= FIELD_BUILTIN_LAST; k++) + for (k = FIELD_JSON_LOOP_START; k <= FIELD_BUILTIN_LAST; k++) renderExtensionFieldMaybe (k, tag, response); } diff --git a/ctags/main/writer.c b/ctags/main/writer.c index 56d404c0dd..e8ba67a604 100644 --- a/ctags/main/writer.c +++ b/ctags/main/writer.c @@ -165,7 +165,7 @@ extern bool ptagMakeCtagsOutputExcmd (ptagDesc *desc, excmd = "pattern"; break; case EX_COMBINE: - excmd = "combine"; + excmd = "combineV2"; break; default: AssertNotReached (); @@ -173,14 +173,14 @@ extern bool ptagMakeCtagsOutputExcmd (ptagDesc *desc, break; } return writePseudoTag (desc, excmd, - "number, pattern, mixed, or combine", + "number, pattern, mixed, or combineV2", NULL); } -extern void writerCheckOptions (void) +extern void writerCheckOptions (bool fieldsWereReset) { if (writer->checkOptions) - writer->checkOptions (writer); + writer->checkOptions (writer, fieldsWereReset); } extern bool writerPrintPtagByDefault (void) diff --git a/ctags/main/writer_p.h b/ctags/main/writer_p.h index 16edc5f04d..2367748218 100644 --- a/ctags/main/writer_p.h +++ b/ctags/main/writer_p.h @@ -52,7 +52,7 @@ struct sTagWriter { void *clientData); bool (* treatFieldAsFixed) (int fieldType); - void (* checkOptions) (tagWriter *writer); + void (* checkOptions) (tagWriter *writer, bool fieldsWereReset); #ifdef WIN32 enum filenameSepOp (* overrideFilenameSeparator) (enum filenameSepOp currentSetting); @@ -97,7 +97,7 @@ extern bool ptagMakeCtagsOutputExcmd (ptagDesc *desc, langType language CTAGS_AT extern bool writerCanPrintPtag (void); extern bool writerDoesTreatFieldAsFixed (int fieldType); -extern void writerCheckOptions (void); +extern void writerCheckOptions (bool fieldsWereReset); extern bool writerPrintPtagByDefault (void); #ifdef WIN32 diff --git a/ctags/main/xtag.c b/ctags/main/xtag.c index 61fb97a4c4..4e3e60cc80 100644 --- a/ctags/main/xtag.c +++ b/ctags/main/xtag.c @@ -282,12 +282,12 @@ extern bool isCommonXtag (xtagType type) return (type < XTAG_COUNT)? true: false; } -extern int getXtagOwner (xtagType type) +extern langType getXtagOwner (xtagType type) { return getXtagObject (type)->language; } -const char* getXtagName (xtagType type) +extern const char* getXtagName (xtagType type) { xtagDefinition* def = getXtagDefinition (type); if (def) @@ -296,7 +296,7 @@ const char* getXtagName (xtagType type) return NULL; } -const char* getXtagDescription (xtagType type) +extern const char* getXtagDescription (xtagType type) { xtagDefinition* def = getXtagDefinition (type); if (def) diff --git a/ctags/main/xtag_p.h b/ctags/main/xtag_p.h index 7326e98003..2b9968475e 100644 --- a/ctags/main/xtag_p.h +++ b/ctags/main/xtag_p.h @@ -18,8 +18,12 @@ #include "general.h" +#include "types.h" +#include "xtag.h" #include "colprint_p.h" +#include + /* * FUNCTION PROTOTYPES */ @@ -31,10 +35,12 @@ extern xtagType getXtagTypeForNameAndLanguage (const char *name, langType langu extern bool enableXtag (xtagType type, bool state); extern bool isXtagFixed (xtagType type); extern bool isCommonXtag (xtagType type); -extern int getXtagOwner (xtagType type); -const char* getXtagName (xtagType type); -const char* getXtagDescription (xtagType type); +/* Return LANG_IGNORE for common fields. */ +extern langType getXtagOwner (xtagType type); + +extern const char* getXtagName (xtagType type); +extern const char* getXtagDescription (xtagType type); extern void initXtagObjects (void); extern int countXtags (void); diff --git a/scripts/update-ctags.py b/scripts/update-ctags.py index 44d0075575..d9f27dfdec 100755 --- a/scripts/update-ctags.py +++ b/scripts/update-ctags.py @@ -7,6 +7,7 @@ if len(sys.argv) != 3: print('Usage: update-ctags.py ') + sys.exit(1) srcdir = os.path.abspath(sys.argv[1]) dstdir = os.path.abspath(sys.argv[2]) @@ -19,6 +20,10 @@ for f in parser_dst_files: shutil.copy(f, dstdir + '/parsers') +print('Copying dsl files...') +for f in ['dsl/es.c', 'dsl/es.h', 'dsl/optscript.c', 'dsl/optscript.h']: + shutil.copy(srcdir + '/' + f, dstdir + '/' + f) + os.chdir(srcdir) main_src_files = glob.glob('main/*.c') + glob.glob('main/*.h') os.chdir(dstdir) diff --git a/src/tagmanager/Makefile.am b/src/tagmanager/Makefile.am index 066192b952..319befa58f 100644 --- a/src/tagmanager/Makefile.am +++ b/src/tagmanager/Makefile.am @@ -1,8 +1,14 @@ AM_CPPFLAGS = \ -I$(srcdir) \ -I$(top_srcdir)/ctags/main \ + -I$(top_srcdir)/ctags/dsl \ -DGEANY_PRIVATE \ -DG_LOG_DOMAIN=\"Tagmanager\" + +if USE_BUNDLED_REGEX +AM_CPPFLAGS += -I$(top_srcdir)/ctags/gnu_regex +endif + AM_CFLAGS = \ $(GTK_CFLAGS) \ @LIBGEANY_CFLAGS@ diff --git a/src/tagmanager/tm_ctags.c b/src/tagmanager/tm_ctags.c index 015bca5694..b2e2e0a1f4 100644 --- a/src/tagmanager/tm_ctags.c +++ b/src/tagmanager/tm_ctags.c @@ -207,6 +207,7 @@ void tm_ctags_init(void) initializeParsing(); initOptions(); + initRegexOptscript(); /* make sure all parsers are initialized */ initializeParser(LANG_AUTO);