]> git.eshelyaron.com Git - emacs.git/commitdiff
Add conversions to and from struct timespec to module interface.
authorPhilipp Stephani <phst@google.com>
Thu, 18 Apr 2019 15:42:45 +0000 (17:42 +0200)
committerPhilipp Stephani <phst@google.com>
Wed, 24 Apr 2019 09:33:52 +0000 (11:33 +0200)
Time values are a fundamental data type, and such conversions are hard
to implement within modules because of the various forms of time
values in Emacs Lisp.  Adding dedicated conversion functions can
significantly simplify module code dealing with times.

This approach uses nanosecond precision.  While Emacs in theory has
support for higher-precision time values, in practice most languages
and standards, such as POSIX, C, Java, and Go, have settled on
nanosecond-precision integers to represent time.

* src/emacs-module.h.in: Add header for struct timespec.

* src/module-env-27.h: Add module functions for time conversion.

* src/emacs-module.c (module_extract_time, module_make_time): New
functions.
(initialize_environment): Use them.

* test/data/emacs-module/mod-test.c (Fmod_test_add_nanosecond): New
test function.
(emacs_module_init): Define it.

* test/src/emacs-module-tests.el (mod-test-add-nanosecond/valid)
(mod-test-add-nanosecond/nil, mod-test-add-nanosecond/invalid): New
unit tests.

* doc/lispref/internals.texi (Module Values): Document time
conversion functions.

doc/lispref/internals.texi
etc/NEWS
src/emacs-module.c
src/emacs-module.h.in
src/module-env-27.h
test/data/emacs-module/mod-test.c
test/src/emacs-module-tests.el

index 25892d4b57c69f5cb6ec347e8ea1ae1f09396d14..0e7a1339e7625681ef875b3502314ba32f28851d 100644 (file)
@@ -1387,6 +1387,38 @@ This function returns the value of a Lisp float specified by
 @var{arg}, as a C @code{double} value.
 @end deftypefn
 
+@deftypefn Function struct timespec extract_time (emacs_env *@var{env}, emacs_value @var{time})
+This function, which is available since Emacs 27, interprets
+@var{time} as an Emacs Lisp time value and returns the corresponding
+@code{struct timespec}.  @xref{Time of Day}.  @code{struct timespec}
+represents a timestamp with nanosecond precision.  It has the
+following members:
+
+@table @code
+@item time_t tv_sec
+Whole number of seconds.
+@item long tv_nsec
+Fractional seconds as number of nanoseconds, always less than one
+billion.
+@end table
+
+@noindent
+@xref{Elapsed Time,,,libc}.
+
+If @var{time} has higher precision than nanoseconds, then this
+function truncates it to nanosecond precision.  This function signals
+an error if @var{time} (truncated to nanoseconds) cannot be
+represented by @code{struct timespec}.  For example, if @code{time_t}
+is a 32-bit integral type, then a @var{time} value of ten billion
+seconds would signal an error, but a @var{time} value of 600
+picoseconds would get truncated to zero.
+
+If you need to deal with time values that are not representable by
+@code{struct timespec}, or if you want higher precision, call the Lisp
+function @code{encode-time} and work with its return value.
+@xref{Time Conversion}.
+@end deftypefn
+
 @deftypefn Function bool copy_string_contents (emacs_env *@var{env}, emacs_value @var{arg}, char *@var{buf}, ptrdiff_t *@var{len})
 This function stores the UTF-8 encoded text of a Lisp string specified
 by @var{arg} in the array of @code{char} pointed by @var{buf}, which
@@ -1452,6 +1484,18 @@ This function takes a @code{double} argument @var{d} and returns the
 corresponding Emacs floating-point value.
 @end deftypefn
 
+@deftypefn Function emacs_value make_time (emacs_env *@var{env}, struct timespec @var{time})
+This function, which is available since Emacs 27, takes a @code{struct
+timespec} argument @var{time} and returns the corresponding Emacs
+timestamp as a pair @code{(@var{ticks} . @var{hz})}.  @xref{Time of
+Day}.  The return value represents exactly the same timestamp as
+@var{time}: all input values are representable, and there is never a
+loss of precision.  @code{@var{time}.tv_sec} and
+@code{@var{time}.tv_nsec} can be arbitrary values.  In particular,
+there's no requirement that @var{time} be normalized.  This means that
+@code{@var{time}.tv_nsec} can be negative or larger than 999,999,999.
+@end deftypefn
+
 @deftypefn Function emacs_value make_string (emacs_env *@var{env}, const char *@var{str}, ptrdiff_t @var{strlen})
 This function creates an Emacs string from C text string pointed by
 @var{str} whose length in bytes, not including the terminating null
index b13ab47768b55441809a89b73d3dbb2583fc9a24..fc9b828baa1866733e5fc2bdaa46ed0837d64fd3 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1910,6 +1910,9 @@ returns a regexp that never matches anything, which is an identity for
 this operation.  Previously, the empty string was returned in this
 case.
 
+** New module environment functions 'make_time' and 'extract_time' to
+convert between timespec structures and Emacs Lisp time values.
+
 \f
 * Changes in Emacs 27.1 on Non-Free Operating Systems
 
index b812fdc2df4930328a7755a6b49575e59b805b14..e46af30ce8449708c94e2e228a46d4301c14bb14 100644 (file)
@@ -77,6 +77,7 @@ To add a new module function, proceed as follows:
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <time.h>
 
 #include "lisp.h"
 #include "dynlib.h"
@@ -737,6 +738,20 @@ module_process_input (emacs_env *env)
   return emacs_process_input_continue;
 }
 
+static struct timespec
+module_extract_time (emacs_env *env, emacs_value value)
+{
+  MODULE_FUNCTION_BEGIN ((struct timespec) {0});
+  return lisp_time_argument (value_to_lisp (value));
+}
+
+static emacs_value
+module_make_time (emacs_env *env, struct timespec time)
+{
+  MODULE_FUNCTION_BEGIN (NULL);
+  return lisp_to_value (env, make_lisp_time (time));
+}
+
 \f
 /* Subroutines.  */
 
@@ -1140,6 +1155,8 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv)
   env->vec_size = module_vec_size;
   env->should_quit = module_should_quit;
   env->process_input = module_process_input;
+  env->extract_time = module_extract_time;
+  env->make_time = module_make_time;
   Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments);
   return env;
 }
index 009d1583fefe34db6a4d205350ae24ec200715e6..bfbe226dd90f633c27ba16e0f549936bbaa6623b 100644 (file)
@@ -22,6 +22,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <stdint.h>
 #include <stddef.h>
+#include <time.h>
 
 #ifndef __cplusplus
 #include <stdbool.h>
index b491b60fbbca0f74e7803d277d55928871cff5f3..e63843f8d6308fe2d5ed05aca333ee3572415685 100644 (file)
@@ -2,3 +2,9 @@
      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 value)
+    EMACS_ATTRIBUTE_NONNULL (1);
+
+  emacs_value (*make_time) (emacs_env *env, struct timespec time)
+    EMACS_ATTRIBUTE_NONNULL (1);
index a39e41afee68f1f1e919d73a69a0df97b571d77c..dbdbfecfe6a2331e8ca20e0ebeb546b3e64bfecf 100644 (file)
@@ -366,6 +366,18 @@ Fmod_test_sleep_until (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
   return env->intern (env, "finished");
 }
 
+static emacs_value
+Fmod_test_add_nanosecond (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
+                          void *data)
+{
+  assert (nargs == 1);
+  struct timespec time = env->extract_time (env, args[0]);
+  assert (time.tv_nsec >= 0);
+  assert (time.tv_nsec < 2000000000);  /* possible leap second */
+  time.tv_nsec++;
+  return env->make_time (env, time);
+}
+
 /* Lisp utilities for easier readability (simple wrappers).  */
 
 /* Provide FEATURE to Emacs.  */
@@ -434,6 +446,7 @@ emacs_module_init (struct emacs_runtime *ert)
   DEFUN ("mod-test-invalid-finalizer", Fmod_test_invalid_finalizer, 0, 0,
          NULL, NULL);
   DEFUN ("mod-test-sleep-until", Fmod_test_sleep_until, 2, 2, NULL, NULL);
+  DEFUN ("mod-test-add-nanosecond", Fmod_test_add_nanosecond, 1, 1, NULL, NULL);
 
 #undef DEFUN
 
index 35aaaa64b658beeaf794aad4a95d57f07a55a272..eea4c611655e34801797fbf8de5767d9b45c031a 100644 (file)
@@ -310,4 +310,32 @@ Interactively, you can try hitting \\[keyboard-quit] to quit."
                       'finished))
         (quit)))))
 
+(ert-deftest mod-test-add-nanosecond/valid ()
+  (dolist (input (list
+                  ;; Some realistic examples.
+                  (current-time) (time-to-seconds)
+                  (encode-time 12 34 5 6 7 2019 t)
+                  ;; Various legacy timestamp forms.
+                  '(123 456) '(123 456 789) '(123 456 789 6000)
+                  ;; Corner case: this will result in a nanosecond
+                  ;; value of 1000000000 after addition.  The module
+                  ;; code should handle this correctly.
+                  '(123 65535 999999 999000)
+                  ;; Seconds since the epoch.
+                  123 123.45
+                  ;; New (TICKS . HZ) format.
+                  '(123456789 . 1000000000)))
+    (ert-info ((format "input: %s" input))
+      (should (time-equal-p (mod-test-add-nanosecond input)
+                            (time-add input '(0 0 0 1000)))))))
+
+(ert-deftest mod-test-add-nanosecond/nil ()
+  (should (<= (float-time (mod-test-add-nanosecond nil))
+              (+ (float-time) 1e-9))))
+
+(ert-deftest mod-test-add-nanosecond/invalid ()
+  (dolist (input '(1.0e+INF 1.0e-INF 0.0e+NaN (123) (123.45 6 7) "foo" [1 2]))
+    (ert-info ((format "input: %s" input))
+      (should-error (mod-test-add-nanosecond input)))))
+
 ;;; emacs-module-tests.el ends here