branch: externals/xeft commit babe67496a753a214265aa9ec7740297acca7cd1 Author: Yuan Fu <caso...@gmail.com> Commit: Yuan Fu <caso...@gmail.com>
Add xapian-lite source This way the user don't need to clone the git repo of xapian-lite. * Makefile: * emacs-module-prelude.h: * emacs-module.h: * xapian-lite.cc: Copied straight from xapian-lite. --- Makefile | 19 +- emacs-module-prelude.h | 163 +++++++++++ emacs-module.h | 763 +++++++++++++++++++++++++++++++++++++++++++++++++ xapian-lite.cc | 505 ++++++++++++++++++++++++++++++++ 4 files changed, 1438 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 8a623b43ba..45d2c036b1 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ .POSIX: -# Even if this is unnecessary, it doesn’t hurt. -PREFIX=/usr/local -CXX=g++ -CXXFLAGS=-fPIC -I$(PREFIX)/include -std=c++11 -LDFLAGS=-L$(PREFIX)/lib -LDLIBS=-lxapian +PREFIX ?= /usr/local +CXX ?= g++ +CXXFLAGS = -fPIC -I$(PREFIX)/include -std=c++11 +LDFLAGS = -L$(PREFIX)/lib +LDLIBS = -lxapian # Dylib extensions. ifeq ($(OS),Windows_NT) @@ -15,12 +14,8 @@ else SOEXT = so endif -xapian-lite.$(SOEXT): module/xapian-lite.cc +xapian-lite.$(SOEXT): xapian-lite.cc $(CXX) $< -o $@ -shared $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) -module/xapian-lite.cc: - git clone https://github.com/casouri/xapian-lite module --depth=1 - clean: - rm -f *.so *.o - rm -rf module + rm -f *.so *.o *.dylib *.dll diff --git a/emacs-module-prelude.h b/emacs-module-prelude.h new file mode 100644 index 0000000000..a4587652be --- /dev/null +++ b/emacs-module-prelude.h @@ -0,0 +1,163 @@ +#include "emacs-module.h" +#include <stdbool.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#ifndef EMACS_MODULE_PRELUDE_H +#define EMACS_MODULE_PRELUDE_H + +#define EMP_MAJOR_VERSION 1 +#define EMP_MINOR_VERSION 0 +#define EMP_PATCH_VERSION 0 + + +/* + Copy a Lisp string VALUE into BUFFER, and store the string size in + SIZE. A user doesn’t need to allocate BUFFER, but it is the user’s + responsibility to free it. + */ +bool +emp_copy_string_contents +(emacs_env *env, emacs_value value, char **buffer, size_t *size) +/* Copied from Pillipp’s document. I commented out assertions. */ +{ + ptrdiff_t buffer_size; + if (!env->copy_string_contents (env, value, NULL, &buffer_size)) + return false; + /* assert (env->non_local_exit_check (env) == emacs_funcall_exit_return); */ + /* assert (buffer_size > 0); */ + *buffer = (char*) malloc ((size_t) buffer_size); + if (*buffer == NULL) + { + env->non_local_exit_signal (env, env->intern (env, "memory-full"), + env->intern (env, "nil")); + return false; + } + ptrdiff_t old_buffer_size = buffer_size; + if (!env->copy_string_contents (env, value, *buffer, &buffer_size)) + { + free (*buffer); + *buffer = NULL; + return false; + } + /* assert (env->non_local_exit_check (env) == emacs_funcall_exit_return); */ + /* assert (buffer_size == old_buffer_size); */ + *size = (size_t) (buffer_size - 1); + return true; +} + +/* + Return a Lisp string. This is basically env->make_string except that + it calls strlen for you. + */ +emacs_value +emp_build_string (emacs_env *env, const char *string) +{ + return env->make_string (env, string, strlen (string)); +} + +/* + Intern NAME to a symbol. NAME has to be all-ASCII. + */ +emacs_value +emp_intern (emacs_env *env, const char *name) +{ + return env->intern (env, name); +} + +/* + Call a function named FN which takes NARGS number of arguments. + Example: funcall (env, "cons", 2, car, cdr); + */ +emacs_value +emp_funcall (emacs_env *env, const char* fn, ptrdiff_t nargs, ...) +{ + va_list argv; + va_start (argv, nargs); + emacs_value *args = (emacs_value *) malloc(nargs * sizeof(emacs_value)); + for (int idx = 0; idx < nargs; idx++) + { + args[idx] = va_arg (argv, emacs_value); + } + va_end (argv); + emacs_value val = env->funcall (env, emp_intern (env, fn), nargs, args); + free (args); + return val; +} + +/* + Provide FEATURE like ‘provide’ in Lisp. +*/ +void +emp_provide (emacs_env *env, const char *feature) +{ + emp_funcall (env, "provide", 1, emp_intern (env, feature)); +} + +/* + Raise a signal where NAME is the signal name and MESSAGE is the + error message. + */ +void +emp_signal_message1 +(emacs_env *env, const char *name, const char *message) +{ + env->non_local_exit_signal + (env, env->intern (env, name), + emp_funcall (env, "cons", 2, + env->make_string (env, message, strlen (message)), + emp_intern (env, "nil"))); +} + +/* + Define an error like ‘define-error’. + */ +void +emp_define_error +(emacs_env *env, const char *name, + const char *description, const char *parent) +{ + emp_funcall (env, "define-error", 3, + emp_intern (env, name), + env->make_string (env, description, strlen (description)), + emp_intern (env, parent)); +} + +/* + Return true if VAL is symbol nil. + */ +bool +emp_nilp (emacs_env *env, emacs_value val) +{ + return !env->is_not_nil (env, val); +} + +/* + Define a function NAME. The number of arguments that the function + takes is between MIN_ARITY and MAX_ARITY. FUNCTION is a function + with signature + + static emacs_value + function + (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) + EMACS_NOEXCEPT + + DOCUMENTATION is the docstring for FUNCTION. + */ +void +emp_define_function +(emacs_env *env, const char *name, ptrdiff_t min_arity, + ptrdiff_t max_arity, + emacs_value (*function) (emacs_env *env, + ptrdiff_t nargs, + emacs_value* args, + void *data) EMACS_NOEXCEPT, + const char *documentation) +{ + emacs_value fn = env->make_function + (env, min_arity, max_arity, function, documentation, NULL); + emp_funcall (env, "fset", 2, emp_intern (env, name), fn); +} + +#endif /* EMACS_MODULE_PRELUDE_H */ diff --git a/emacs-module.h b/emacs-module.h new file mode 100644 index 0000000000..1185c06f45 --- /dev/null +++ b/emacs-module.h @@ -0,0 +1,763 @@ +/* emacs-module.h - GNU Emacs module API. + +Copyright (C) 2015-2021 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ + +/* +This file defines the Emacs module API. Please see the chapter +`Dynamic Modules' in the GNU Emacs Lisp Reference Manual for +information how to write modules and use this header file. +*/ + +#ifndef EMACS_MODULE_H +#define EMACS_MODULE_H + +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#define EMACS_MAJOR_VERSION 28 + +#if defined __cplusplus && __cplusplus >= 201103L +# define EMACS_NOEXCEPT noexcept +#else +# define EMACS_NOEXCEPT +#endif + +#if defined __cplusplus && __cplusplus >= 201703L +# define EMACS_NOEXCEPT_TYPEDEF noexcept +#else +# define EMACS_NOEXCEPT_TYPEDEF +#endif + +#if 3 < __GNUC__ + (3 <= __GNUC_MINOR__) +# define EMACS_ATTRIBUTE_NONNULL(...) \ + __attribute__ ((__nonnull__ (__VA_ARGS__))) +#elif (defined __has_attribute \ + && (!defined __clang_minor__ \ + || 3 < __clang_major__ + (5 <= __clang_minor__))) +# if __has_attribute (__nonnull__) +# define EMACS_ATTRIBUTE_NONNULL(...) \ + __attribute__ ((__nonnull__ (__VA_ARGS__))) +# endif +#endif +#ifndef EMACS_ATTRIBUTE_NONNULL +# define EMACS_ATTRIBUTE_NONNULL(...) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Current environment. */ +typedef struct emacs_env_28 emacs_env; + +/* Opaque pointer representing an Emacs Lisp value. + BEWARE: Do not assume NULL is a valid value! */ +typedef struct emacs_value_tag *emacs_value; + +enum { emacs_variadic_function = -2 }; + +/* Struct passed to a module init function (emacs_module_init). */ +struct emacs_runtime +{ + /* Structure size (for version checking). */ + ptrdiff_t size; + + /* Private data; users should not touch this. */ + struct emacs_runtime_private *private_members; + + /* Return an environment pointer. */ + emacs_env *(*get_environment) (struct emacs_runtime *runtime) + EMACS_ATTRIBUTE_NONNULL (1); +}; + +/* Type aliases for function pointer types used in the module API. + Note that we don't use these aliases directly in the API to be able + to mark the function arguments as 'noexcept' before C++20. + However, users can use them if they want. */ + +/* Function prototype for the module Lisp functions. These must not + throw C++ exceptions. */ +typedef emacs_value (*emacs_function) (emacs_env *env, ptrdiff_t nargs, + emacs_value *args, + void *data) + EMACS_NOEXCEPT_TYPEDEF EMACS_ATTRIBUTE_NONNULL (1); + +/* Function prototype for module user-pointer and function finalizers. + These must not throw C++ exceptions. */ +typedef void (*emacs_finalizer) (void *data) EMACS_NOEXCEPT_TYPEDEF; + +/* Possible Emacs function call outcomes. */ +enum emacs_funcall_exit +{ + /* Function has returned normally. */ + emacs_funcall_exit_return = 0, + + /* Function has signaled an error using `signal'. */ + emacs_funcall_exit_signal = 1, + + /* Function has exit using `throw'. */ + emacs_funcall_exit_throw = 2 +}; + +/* Possible return values for emacs_env.process_input. */ +enum emacs_process_input_result +{ + /* Module code may continue */ + emacs_process_input_continue = 0, + + /* Module code should return control to Emacs as soon as possible. */ + emacs_process_input_quit = 1 +}; + +/* Define emacs_limb_t so that it is likely to match GMP's mp_limb_t. + This micro-optimization can help modules that use mpz_export and + mpz_import, which operate more efficiently on mp_limb_t. It's OK + (if perhaps a bit slower) if the two types do not match, and + modules shouldn't rely on the two types matching. */ +typedef size_t emacs_limb_t; +#define EMACS_LIMB_MAX SIZE_MAX + +struct emacs_env_25 +{ + /* Structure size (for version checking). */ + ptrdiff_t size; + + /* Private data; users should not touch this. */ + struct emacs_env_private *private_members; + + /* Memory management. */ + + emacs_value (*make_global_ref) (emacs_env *env, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*free_global_ref) (emacs_env *env, emacs_value global_value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Non-local exit handling. */ + + enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_clear) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + enum emacs_funcall_exit (*non_local_exit_get) + (emacs_env *env, emacs_value *symbol, emacs_value *data) + EMACS_ATTRIBUTE_NONNULL(1, 2, 3); + + void (*non_local_exit_signal) (emacs_env *env, + emacs_value symbol, emacs_value data) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_throw) (emacs_env *env, + emacs_value tag, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Function registration. */ + + emacs_value (*make_function) (emacs_env *env, + ptrdiff_t min_arity, + ptrdiff_t max_arity, + emacs_value (*func) (emacs_env *env, + ptrdiff_t nargs, + emacs_value* args, + void *data) + EMACS_NOEXCEPT + EMACS_ATTRIBUTE_NONNULL(1), + const char *docstring, + void *data) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + emacs_value (*funcall) (emacs_env *env, + emacs_value func, + ptrdiff_t nargs, + emacs_value* args) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*intern) (emacs_env *env, const char *name) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Type conversion. */ + + emacs_value (*type_of) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*is_not_nil) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*eq) (emacs_env *env, emacs_value a, emacs_value b) + EMACS_ATTRIBUTE_NONNULL(1); + + intmax_t (*extract_integer) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_integer) (emacs_env *env, intmax_t n) + EMACS_ATTRIBUTE_NONNULL(1); + + double (*extract_float) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_float) (emacs_env *env, double d) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 + null-terminated string. + + SIZE must point to the total size of the buffer. If BUFFER is + NULL or if SIZE is not big enough, write the required buffer size + to SIZE and return true. + + Note that SIZE must include the last null byte (e.g. "abc" needs + a buffer of size 4). + + Return true if the string was successfully copied. */ + + bool (*copy_string_contents) (emacs_env *env, + emacs_value value, + char *buf, + ptrdiff_t *len) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + /* Create a Lisp string from a utf8 encoded string. */ + emacs_value (*make_string) (emacs_env *env, + const char *str, ptrdiff_t len) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Embedded pointer type. */ + emacs_value (*make_user_ptr) (emacs_env *env, + void (*fin) (void *) EMACS_NOEXCEPT, + void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void *(*get_user_ptr) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr)) + (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_finalizer) (emacs_env *env, emacs_value arg, + void (*fin) (void *) EMACS_NOEXCEPT) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Vector functions. */ + emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index, + emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector) + EMACS_ATTRIBUTE_NONNULL(1); +}; + +struct emacs_env_26 +{ + /* Structure size (for version checking). */ + ptrdiff_t size; + + /* Private data; users should not touch this. */ + struct emacs_env_private *private_members; + + /* Memory management. */ + + emacs_value (*make_global_ref) (emacs_env *env, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*free_global_ref) (emacs_env *env, emacs_value global_value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Non-local exit handling. */ + + enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_clear) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + enum emacs_funcall_exit (*non_local_exit_get) + (emacs_env *env, emacs_value *symbol, emacs_value *data) + EMACS_ATTRIBUTE_NONNULL(1, 2, 3); + + void (*non_local_exit_signal) (emacs_env *env, + emacs_value symbol, emacs_value data) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_throw) (emacs_env *env, + emacs_value tag, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Function registration. */ + + emacs_value (*make_function) (emacs_env *env, + ptrdiff_t min_arity, + ptrdiff_t max_arity, + emacs_value (*func) (emacs_env *env, + ptrdiff_t nargs, + emacs_value* args, + void *data) + EMACS_NOEXCEPT + EMACS_ATTRIBUTE_NONNULL(1), + const char *docstring, + void *data) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + emacs_value (*funcall) (emacs_env *env, + emacs_value func, + ptrdiff_t nargs, + emacs_value* args) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*intern) (emacs_env *env, const char *name) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Type conversion. */ + + emacs_value (*type_of) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*is_not_nil) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*eq) (emacs_env *env, emacs_value a, emacs_value b) + EMACS_ATTRIBUTE_NONNULL(1); + + intmax_t (*extract_integer) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_integer) (emacs_env *env, intmax_t n) + EMACS_ATTRIBUTE_NONNULL(1); + + double (*extract_float) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_float) (emacs_env *env, double d) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 + null-terminated string. + + SIZE must point to the total size of the buffer. If BUFFER is + NULL or if SIZE is not big enough, write the required buffer size + to SIZE and return true. + + Note that SIZE must include the last null byte (e.g. "abc" needs + a buffer of size 4). + + Return true if the string was successfully copied. */ + + bool (*copy_string_contents) (emacs_env *env, + emacs_value value, + char *buf, + ptrdiff_t *len) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + /* Create a Lisp string from a utf8 encoded string. */ + emacs_value (*make_string) (emacs_env *env, + const char *str, ptrdiff_t len) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Embedded pointer type. */ + emacs_value (*make_user_ptr) (emacs_env *env, + void (*fin) (void *) EMACS_NOEXCEPT, + void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void *(*get_user_ptr) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr)) + (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_finalizer) (emacs_env *env, emacs_value arg, + void (*fin) (void *) EMACS_NOEXCEPT) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Vector functions. */ + emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index, + emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Returns whether a quit is pending. */ + bool (*should_quit) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); +}; + +struct emacs_env_27 +{ + /* Structure size (for version checking). */ + ptrdiff_t size; + + /* Private data; users should not touch this. */ + struct emacs_env_private *private_members; + + /* Memory management. */ + + emacs_value (*make_global_ref) (emacs_env *env, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*free_global_ref) (emacs_env *env, emacs_value global_value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Non-local exit handling. */ + + enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_clear) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + enum emacs_funcall_exit (*non_local_exit_get) + (emacs_env *env, emacs_value *symbol, emacs_value *data) + EMACS_ATTRIBUTE_NONNULL(1, 2, 3); + + void (*non_local_exit_signal) (emacs_env *env, + emacs_value symbol, emacs_value data) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_throw) (emacs_env *env, + emacs_value tag, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Function registration. */ + + emacs_value (*make_function) (emacs_env *env, + ptrdiff_t min_arity, + ptrdiff_t max_arity, + emacs_value (*func) (emacs_env *env, + ptrdiff_t nargs, + emacs_value* args, + void *data) + EMACS_NOEXCEPT + EMACS_ATTRIBUTE_NONNULL(1), + const char *docstring, + void *data) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + emacs_value (*funcall) (emacs_env *env, + emacs_value func, + ptrdiff_t nargs, + emacs_value* args) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*intern) (emacs_env *env, const char *name) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Type conversion. */ + + emacs_value (*type_of) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*is_not_nil) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*eq) (emacs_env *env, emacs_value a, emacs_value b) + EMACS_ATTRIBUTE_NONNULL(1); + + intmax_t (*extract_integer) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_integer) (emacs_env *env, intmax_t n) + EMACS_ATTRIBUTE_NONNULL(1); + + double (*extract_float) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_float) (emacs_env *env, double d) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 + null-terminated string. + + SIZE must point to the total size of the buffer. If BUFFER is + NULL or if SIZE is not big enough, write the required buffer size + to SIZE and return true. + + Note that SIZE must include the last null byte (e.g. "abc" needs + a buffer of size 4). + + Return true if the string was successfully copied. */ + + bool (*copy_string_contents) (emacs_env *env, + emacs_value value, + char *buf, + ptrdiff_t *len) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + /* Create a Lisp string from a utf8 encoded string. */ + emacs_value (*make_string) (emacs_env *env, + const char *str, ptrdiff_t len) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Embedded pointer type. */ + emacs_value (*make_user_ptr) (emacs_env *env, + void (*fin) (void *) EMACS_NOEXCEPT, + void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void *(*get_user_ptr) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr)) + (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_finalizer) (emacs_env *env, emacs_value arg, + void (*fin) (void *) EMACS_NOEXCEPT) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Vector functions. */ + emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index, + emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Returns whether a quit is pending. */ + bool (*should_quit) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Processes pending input events and returns whether the module + function should quit. */ + enum emacs_process_input_result (*process_input) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL (1); + + struct timespec (*extract_time) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL (1); + + emacs_value (*make_time) (emacs_env *env, struct timespec time) + EMACS_ATTRIBUTE_NONNULL (1); + + bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign, + ptrdiff_t *count, emacs_limb_t *magnitude) + EMACS_ATTRIBUTE_NONNULL (1); + + emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count, + const emacs_limb_t *magnitude) + EMACS_ATTRIBUTE_NONNULL (1); +}; + +struct emacs_env_28 +{ + /* Structure size (for version checking). */ + ptrdiff_t size; + + /* Private data; users should not touch this. */ + struct emacs_env_private *private_members; + + /* Memory management. */ + + emacs_value (*make_global_ref) (emacs_env *env, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*free_global_ref) (emacs_env *env, emacs_value global_value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Non-local exit handling. */ + + enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_clear) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + enum emacs_funcall_exit (*non_local_exit_get) + (emacs_env *env, emacs_value *symbol, emacs_value *data) + EMACS_ATTRIBUTE_NONNULL(1, 2, 3); + + void (*non_local_exit_signal) (emacs_env *env, + emacs_value symbol, emacs_value data) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*non_local_exit_throw) (emacs_env *env, + emacs_value tag, emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Function registration. */ + + emacs_value (*make_function) (emacs_env *env, + ptrdiff_t min_arity, + ptrdiff_t max_arity, + emacs_value (*func) (emacs_env *env, + ptrdiff_t nargs, + emacs_value* args, + void *data) + EMACS_NOEXCEPT + EMACS_ATTRIBUTE_NONNULL(1), + const char *docstring, + void *data) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + emacs_value (*funcall) (emacs_env *env, + emacs_value func, + ptrdiff_t nargs, + emacs_value* args) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*intern) (emacs_env *env, const char *name) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Type conversion. */ + + emacs_value (*type_of) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*is_not_nil) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + bool (*eq) (emacs_env *env, emacs_value a, emacs_value b) + EMACS_ATTRIBUTE_NONNULL(1); + + intmax_t (*extract_integer) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_integer) (emacs_env *env, intmax_t n) + EMACS_ATTRIBUTE_NONNULL(1); + + double (*extract_float) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + + emacs_value (*make_float) (emacs_env *env, double d) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Copy the content of the Lisp string VALUE to BUFFER as an utf8 + null-terminated string. + + SIZE must point to the total size of the buffer. If BUFFER is + NULL or if SIZE is not big enough, write the required buffer size + to SIZE and return true. + + Note that SIZE must include the last null byte (e.g. "abc" needs + a buffer of size 4). + + Return true if the string was successfully copied. */ + + bool (*copy_string_contents) (emacs_env *env, + emacs_value value, + char *buf, + ptrdiff_t *len) + EMACS_ATTRIBUTE_NONNULL(1, 4); + + /* Create a Lisp string from a utf8 encoded string. */ + emacs_value (*make_string) (emacs_env *env, + const char *str, ptrdiff_t len) + EMACS_ATTRIBUTE_NONNULL(1, 2); + + /* Embedded pointer type. */ + emacs_value (*make_user_ptr) (emacs_env *env, + void (*fin) (void *) EMACS_NOEXCEPT, + void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void *(*get_user_ptr) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr)) + (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1); + void (*set_user_finalizer) (emacs_env *env, emacs_value arg, + void (*fin) (void *) EMACS_NOEXCEPT) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Vector functions. */ + emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index) + EMACS_ATTRIBUTE_NONNULL(1); + + void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index, + emacs_value value) + EMACS_ATTRIBUTE_NONNULL(1); + + ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Returns whether a quit is pending. */ + bool (*should_quit) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL(1); + + /* Processes pending input events and returns whether the module + function should quit. */ + enum emacs_process_input_result (*process_input) (emacs_env *env) + EMACS_ATTRIBUTE_NONNULL (1); + + struct timespec (*extract_time) (emacs_env *env, emacs_value arg) + EMACS_ATTRIBUTE_NONNULL (1); + + emacs_value (*make_time) (emacs_env *env, struct timespec time) + EMACS_ATTRIBUTE_NONNULL (1); + + bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign, + ptrdiff_t *count, emacs_limb_t *magnitude) + EMACS_ATTRIBUTE_NONNULL (1); + + emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count, + const emacs_limb_t *magnitude) + EMACS_ATTRIBUTE_NONNULL (1); + + /* Add module environment functions newly added in Emacs 28 here. + Before Emacs 28 is released, remove this comment and start + module-env-29.h on the master branch. */ + + void (*(*EMACS_ATTRIBUTE_NONNULL (1) + get_function_finalizer) (emacs_env *env, + emacs_value arg)) (void *) EMACS_NOEXCEPT; + + void (*set_function_finalizer) (emacs_env *env, emacs_value arg, + void (*fin) (void *) EMACS_NOEXCEPT) + EMACS_ATTRIBUTE_NONNULL (1); + + int (*open_channel) (emacs_env *env, emacs_value pipe_process) + EMACS_ATTRIBUTE_NONNULL (1); + + void (*make_interactive) (emacs_env *env, emacs_value function, + emacs_value spec) + EMACS_ATTRIBUTE_NONNULL (1); + + /* Create a unibyte Lisp string from a string. */ + emacs_value (*make_unibyte_string) (emacs_env *env, + const char *str, ptrdiff_t len) + EMACS_ATTRIBUTE_NONNULL(1, 2); +}; + +/* Every module should define a function as follows. */ +extern int emacs_module_init (struct emacs_runtime *runtime) + EMACS_NOEXCEPT + EMACS_ATTRIBUTE_NONNULL (1); + +#ifdef __cplusplus +} +#endif + +#endif /* EMACS_MODULE_H */ diff --git a/xapian-lite.cc b/xapian-lite.cc new file mode 100644 index 0000000000..539c15046c --- /dev/null +++ b/xapian-lite.cc @@ -0,0 +1,505 @@ +/* Xapian-lite. + +Copyright (C) 2021-2023 Free Software Foundation, Inc. + +Maintainer: Yuan Fu <caso...@gmail.com> + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ + +#include <string> +#include <cstring> +#include <iostream> +#include <fstream> +#include <vector> +#include <exception> +#include <iterator> +#include <cstdarg> + +#include <stdlib.h> +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <xapian.h> + +#include "emacs-module.h" +#include "emacs-module-prelude.h" + +using namespace std; + +int plugin_is_GPL_compatible; + +#if defined __cplusplus && __cplusplus >= 201103L +# define EMACS_NOEXCEPT noexcept +#else +# define EMACS_NOEXCEPT +#endif + +#define CHECK_EXIT(env) \ + if (env->non_local_exit_check (env) \ + != emacs_funcall_exit_return) \ + { return NULL; } + +/* A few notes: The database we use, WritableDatabase, will not throw + DatabaseModifiedError, so we don’t need to handle that. For query, + we first try to parse it with special syntax enabled, i.e., with + AND, OR, +/-, etc. If that doesn’t parse, we’ll just parse it as + plain text. + + REF: https://lists.xapian.org/pipermail/xapian-discuss/2021-August/009906.html + + To find a Xapian document by a path, we assign each document a + special term QV<path>. But since Xapian has a limit on the length of + a term, we hash long paths, and the actual term becomes QH<hash>. + */ + +/*** Xapian stuff */ + +// The index of the document value that stores the mtime. +static const Xapian::valueno DOC_MTIME = 0; +// The index of the document value that store the file path. +static const Xapian::valueno DOC_FILEPATH = 1; + +static Xapian::WritableDatabase database; +static string cached_dbpath = ""; + +class xapian_lite_cannot_open_file: public exception {}; + +// Return the hash of KEY. +static uint64_t +fingerprint (string key) +{ + // Polynomial rolling hash. + // http://web.cs.unlv.edu/larmore/Courses/CSC477/F14/Assignments/horners.pdf + // https://begriffs.com/posts/2014-03-28-magic-numbers-in-polynomial-hash.html + const uint64_t prime = 31; + uint64_t hash = 0; + for (int idx = 0; idx < key.length(); idx++) + { + hash = key[idx] + hash * prime; // mod 2^64. + } + return hash; +} + +// Return the hash string of PATH. The length limit of a term is ~245 +// bytes, but we don’t have to use that as the threshold. +static string +hash_path (string path) +{ + if (path.length() < 50) + { + return "QV" + path; + } + else + { + return "QH" + to_string (fingerprint (path)); + } +} + +// Reindex the file at PATH, using database at DBPATH. Throws +// cannot_open_file. Both path must be absolute. Normally only reindex +// if file has change since last index, if FORCE is true, always +// reindex. Return true if re-indexed, return false if didn’t. +// LANG is the language used by the stemmer. +// Possible langauges: +// https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html +static bool +reindex_file +(string path, string dbpath, string lang = "en", bool force = false) +{ + // Check for mtime. + struct stat st; + time_t file_mtime; + off_t file_size; + if (stat (path.c_str(), &st) == 0) + { + file_mtime = st.st_mtime; + file_size = st.st_size; + } + else + { + throw xapian_lite_cannot_open_file(); + } + + // Even though the document says that database object only carries a + // pointer to the actual object, it is still not cheap enough. By + // using this cache, we get much better performance when reindexing + // hundreds of files, which most are no-op because they hasn’t been + // modified. + if (dbpath != cached_dbpath) + { + database = Xapian::WritableDatabase + (dbpath, Xapian::DB_CREATE_OR_OPEN); + cached_dbpath = dbpath; + } + // Track doc with file path as "id". See + // https://getting-started-with-xapian.readthedocs.io/en/latest/practical_example/indexing/updating_the_database.html + // Also https://trac.xapian.org/wiki/FAQ/UniqueIds + string termID = hash_path (path); + Xapian::PostingIterator it_begin = database.postlist_begin (termID); + Xapian::PostingIterator it_end = database.postlist_end (termID); + bool has_doc = it_begin != it_end; + time_t db_mtime; + if (has_doc) + { + // sortable_serialise is for double and we can’t really use it. + Xapian::Document db_doc = database.get_document(*it_begin); + db_mtime = (time_t) stoi (db_doc.get_value (DOC_MTIME)); + } + + // Need re-index. + if (!has_doc || (has_doc && db_mtime < file_mtime) || force) + { + // Get the file content. + // REF: https://stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c + ifstream infile (path); + string content ((istreambuf_iterator<char>(infile)), + (istreambuf_iterator<char>())); + // Create the indexer. + Xapian::TermGenerator indexer; + Xapian::Stem stemmer (lang); + indexer.set_stemmer (stemmer); + indexer.set_stemming_strategy + (Xapian::TermGenerator::STEM_SOME); + // Support CJK. + indexer.set_flags (Xapian::TermGenerator::FLAG_CJK_NGRAM); + // Index file content. + Xapian::Document new_doc; + indexer.set_document (new_doc); + indexer.index_text (content); + // Set doc info. + new_doc.add_boolean_term (termID); + // We store the path in value, no need to use set_data. + new_doc.add_value (DOC_FILEPATH, path); + new_doc.add_value (DOC_MTIME, (string) to_string (file_mtime)); + database.replace_document (termID, new_doc); + return true; + } + else + { + return false; + } +} + +// Query TERM in the databse at DBPATH. OFFSET and PAGE_SIZE is for +// paging, see the docstring for the lisp function. If a file in the +// result doesn’t exist anymore, it is removed from the database. +// LANG is the language used by the stemmer. +// Possible langauges: +// https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html +static vector<string> +query_term +(string term, string dbpath, int offset, int page_size, + string lang = "en") +{ + // See reindex_file for the reason for caching the database object. + if (dbpath != cached_dbpath) + { + database = Xapian::WritableDatabase + (dbpath, Xapian::DB_CREATE_OR_OPEN); + cached_dbpath = dbpath; + } + + Xapian::QueryParser parser; + Xapian::Stem stemmer (lang); + parser.set_stemmer (stemmer); + parser.set_stemming_strategy (Xapian::QueryParser::STEM_SOME); + // Partial match (FLAG_PARTIAL) needs the database to expand + // wildcards. + parser.set_database(database); + + Xapian::Query query; + try + { + query = parser.parse_query + // CJK_NGRAM is the flag for CJK support. PARTIAL makes + // interactive search more stable. DEFAULT enables AND OR and + // +/-. + (term, Xapian::QueryParser::FLAG_CJK_NGRAM + | Xapian::QueryParser::FLAG_PARTIAL + | Xapian::QueryParser::FLAG_DEFAULT); + } + // If the syntax is syntactically wrong, Xapian throws this error. + // Try again without enabling any special syntax. + catch (Xapian::QueryParserError &e) + { + query = parser.parse_query + (term, Xapian::QueryParser::FLAG_CJK_NGRAM + | Xapian::QueryParser::FLAG_PARTIAL); + } + + Xapian::Enquire enquire (database); + enquire.set_query (query); + + Xapian::MSet mset = enquire.get_mset (offset, page_size); + vector<string> result (0); + for (Xapian::MSetIterator it = mset.begin(); it != mset.end(); it++) + { + Xapian::Document doc = it.get_document(); + string path = doc.get_value(DOC_FILEPATH); + // If the file doesn’t exists anymore, remove it. + struct stat st; + if (stat (path.c_str(), &st) == 0) + { + result.push_back (doc.get_value (DOC_FILEPATH)); + } + else + { + database.delete_document (doc.get_docid()); + } + } + return result; +} + +/*** Module definition */ + +static string +copy_string (emacs_env *env, emacs_value value) +{ + char* char_buffer; + size_t size; + if (emp_copy_string_contents (env, value, &char_buffer, &size)) + { + string str = (string) char_buffer; + free (char_buffer); + return str; + } + else + { + emp_signal_message1 (env, "xapian-lite-error", + "Error turning lisp string to C++ string"); + return ""; + } +} + +static bool +NILP (emacs_env *env, emacs_value val) +{ + return !env->is_not_nil (env, val); +} + +static const char* xapian_lite_reindex_file_doc = + "Refindex file at PATH with database at DBPATH\n" + "Both paths has to be absolute. Normally, this function only\n" + "reindex a file if it has been modified since last indexed,\n" + "but if FORCE is non-nil, this function will always reindex.\n" + "Return non-nil if actually reindexed the file, return nil if not.\n" + "\n" + "LANG is the language used by the indexer, it tells Xapian how to\n" + "reduce words to word stems, e.g., apples <-> apple.\n" + "A full list of possible languages can be found at\n" + "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html.\n" + "By default, LANG is \"en\".\n" + "\n" + "(fn PATH DBPATH &optional LANG FORCE)"; + +static emacs_value +Fxapian_lite_reindex_file +(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) + EMACS_NOEXCEPT +{ + + // Decode arguments. + emacs_value lisp_path = args[0]; + emacs_value lisp_dbpath = args[1]; + + if (NILP (env, emp_funcall (env, "file-name-absolute-p", 1, lisp_path))) + { + emp_signal_message1 (env, "xapian-lite-file-error", + "PATH is not a absolute path"); + return NULL; + } + if (NILP (env, + emp_funcall (env, "file-name-absolute-p", 1, lisp_dbpath))) + { + emp_signal_message1 (env, "xapian-lite-file-error", + "DBPATH is not a absolute path"); + return NULL; + } + + // Expand "~" in the filename. + emacs_value lisp_args[] = {lisp_path}; + lisp_path = emp_funcall (env, "expand-file-name", 1, lisp_path); + lisp_dbpath = emp_funcall (env, "expand-file-name", 1, lisp_dbpath); + + emacs_value lisp_lang = nargs < 3 ? emp_intern (env, "nil") : args[2]; + emacs_value lisp_force = nargs < 4 ? emp_intern (env, "nil") : args[3]; + + string path = copy_string (env, lisp_path); + string dbpath = copy_string (env, lisp_dbpath); + bool force = !NILP (env, lisp_force); + CHECK_EXIT (env); + string lang = NILP (env, lisp_lang) ? + "en" : copy_string (env, lisp_lang); + CHECK_EXIT (env); + + // Do the work. + bool indexed; + try + { + indexed = reindex_file (path, dbpath, lang, force); + return indexed ? emp_intern (env, "t") : emp_intern (env, "nil"); + } + catch (xapian_lite_cannot_open_file &e) + { + emp_signal_message1 (env, "xapian-lite-file-error", + "Cannot open the file"); + return NULL; + } + catch (Xapian::DatabaseCorruptError &e) + { + emp_signal_message1 (env, "xapian-lite-database-corrupt-error", + e.get_description().c_str()); + return NULL; + } + catch (Xapian::DatabaseLockError &e) + { + emp_signal_message1 (env, "xapian-lite-database-lock-error", + e.get_description().c_str()); + return NULL; + } + catch (Xapian::Error &e) + { + emp_signal_message1 (env, "xapian-lite-lib-error", + e.get_description().c_str()); + return NULL; + } + catch (exception &e) + { + emp_signal_message1 (env, "xapian-lite-error", + "Something went wrong"); + return NULL; + } +} + +static const char *xapian_lite_query_term_doc = + "Query for TERM in database at DBPATH.\n" + "Paging is supported by OFFSET and PAGE-SIZE. OFFSET specifies page\n" + "start, and PAGE-SIZE the size. For example, if a page is 10 entries,\n" + "OFFSET and PAGE-SIZE would be first 0 and 10, then 10 and 10, and\n" + "so on.\n" + "\n" + "If a file in the result doesn't exist anymore, it is removed from\n" + "the database, and is not included in the return value.\n" + "\n" + "LANG is the language used by the indexer, it tells Xapian how to\n" + "reduce words to word stems, e.g., apples <-> apple.\n" + "A full list of possible languages can be found at\n" + "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html.\n" + "By default, LANG is \"en\".\n" + "\n" + "TERM can use common Xapian syntax like AND, OR, and +/-.\n" + "Specifically, this function supports:\n" + "\n" + " Boolean operators: AND, OR, XOR, NOT\n" + " Parenthesized expression: ()\n" + " Love/hate terms: +/-\n" + " Exact match: \"\"\n" + "\n" + "If TERM contains syntactic errors, like \"a AND AND b\",\n" + "it is treated as a plain term.\n" + "\n" + "(fn TERM DBPATH OFFSET PAGE-SIZE &optional LANG)"; + +static emacs_value +Fxapian_lite_query_term +(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) + EMACS_NOEXCEPT +{ + // Decode arguments. + emacs_value lisp_term = args[0]; + emacs_value lisp_dbpath = args[1]; + emacs_value lisp_offset = args[2]; + emacs_value lisp_page_size = args[3]; + + if (NILP (env, + emp_funcall (env, "file-name-absolute-p", 1, lisp_dbpath))) + { + emp_signal_message1 (env, "xapian-lite-file-error", + "DBPATH is not a absolute path"); + return NULL; + } + + lisp_dbpath = emp_funcall (env, "expand-file-name", 1, lisp_dbpath); + + string term = copy_string (env, lisp_term); + string dbpath = copy_string (env, lisp_dbpath); + int offset = env->extract_integer (env, lisp_offset); + int page_size = env->extract_integer (env, lisp_page_size); + CHECK_EXIT (env); + + vector<string> result; + try + { + result = query_term (term, dbpath, offset, page_size); + } + catch (Xapian::Error &e) + { + emp_signal_message1 (env, "xapian-lite-lib-error", + e.get_description().c_str()); + return NULL; + } + catch (exception &e) + { + emp_signal_message1 (env, "xapian-lite-error", + "Something went wrong"); + return NULL; + } + + vector<string>::iterator it; + emacs_value ret = emp_intern (env, "nil"); + for (it = result.begin(); it != result.end(); it++) { + ret = emp_funcall (env, "cons", 2, + env->make_string + (env, it->c_str(), strlen(it->c_str())), + ret); + CHECK_EXIT (env); + } + return emp_funcall (env, "reverse", 1, ret); +} + +int +emacs_module_init (struct emacs_runtime *ert) EMACS_NOEXCEPT +{ + emacs_env *env = ert->get_environment (ert); + + emp_define_error (env, "xapian-lite-error", + "Generic xapian-lite error", "error"); + emp_define_error (env, "xapian-lite-lib-error", + "Xapian library error", "xapian-lite-error"); + emp_define_error (env, "xapian-lite-database-corrupt-error", + "Xapian library error", "xapian-lite-lib-error"); + emp_define_error (env, "xapian-lite-database-lock-error", + "Xapian library error", "xapian-lite-lib-error"); + emp_define_error (env, "xapian-lite-file-error", + "Cannot open file", "xapian-lite-error"); + + emp_define_function(env, "xapian-lite-reindex-file", 2, 3, + &Fxapian_lite_reindex_file, + xapian_lite_reindex_file_doc); + emp_define_function(env, "xapian-lite-query-term", 4, 4, + &Fxapian_lite_query_term, + xapian_lite_query_term_doc); + + emp_provide (env, "xapian-lite"); + + /* Return 0 to indicate module loaded successfully. */ + return 0; +}