aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEli Zaretskii2018-10-13 10:13:10 +0300
committerEli Zaretskii2018-10-13 10:13:10 +0300
commit95f69e7db235ca450a17c5a24680b742dfdf9aae (patch)
treec186902d218c8fca88d3379f84f26738cdfc9fc5
parent6cf4dfe472650b3396d2f2592726621a43896de3 (diff)
downloademacs-95f69e7db235ca450a17c5a24680b742dfdf9aae.tar.gz
emacs-95f69e7db235ca450a17c5a24680b742dfdf9aae.zip
Improve 'json-insert' so it doesn't cons a string from JSON
* src/json.c (struct json_buffer_and_size): New member inserted_bytes. (json_insert): Instead of creating a string and inserting it into the current buffer, copy the unibyte text into the gap. (struct json_insert_data): New member inserted_bytes. (json_insert_callback): Update commentary. Pass the inserted_bytes value to json_insert and on its return copy the updated value back into DATA. (Fjson_insert): Decode the unibyte text inserted into the gap. Call before-change-functions and after-change-functions only once, before and after processing the insertion of the entire JSON representation. * test/src/json-tests.el (json-insert/throw): Adapt to the modified implementation of json-insert: it no longer calls the modification hooks once for each inserted chunk of JSON representation.
-rw-r--r--src/json.c102
-rw-r--r--test/src/json-tests.el9
2 files changed, 94 insertions, 17 deletions
diff --git a/src/json.c b/src/json.c
index 17cc0965b12..e5c0dc22179 100644
--- a/src/json.c
+++ b/src/json.c
@@ -624,42 +624,54 @@ struct json_buffer_and_size
624{ 624{
625 const char *buffer; 625 const char *buffer;
626 ptrdiff_t size; 626 ptrdiff_t size;
627 /* This tracks how many bytes were inserted by the callback since
628 json_dump_callback was called. */
629 ptrdiff_t inserted_bytes;
627}; 630};
628 631
629static Lisp_Object 632static Lisp_Object
630json_insert (void *data) 633json_insert (void *data)
631{ 634{
632 struct json_buffer_and_size *buffer_and_size = data; 635 struct json_buffer_and_size *buffer_and_size = data;
633 /* FIXME: This should be possible without creating an intermediate 636 ptrdiff_t len = buffer_and_size->size;
634 string object. */ 637 ptrdiff_t inserted_bytes = buffer_and_size->inserted_bytes;
635 Lisp_Object string 638 ptrdiff_t gap_size = GAP_SIZE - inserted_bytes;
636 = json_make_string (buffer_and_size->buffer, buffer_and_size->size); 639
637 insert1 (string); 640 /* Enlarge the gap if necessary. */
641 if (gap_size < len)
642 make_gap (len - gap_size);
643
644 /* Copy this chunk of data into the gap. */
645 memcpy ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE + inserted_bytes,
646 buffer_and_size->buffer, len);
647 buffer_and_size->inserted_bytes += len;
638 return Qnil; 648 return Qnil;
639} 649}
640 650
641struct json_insert_data 651struct json_insert_data
642{ 652{
653 /* This tracks how many bytes were inserted by the callback since
654 json_dump_callback was called. */
655 ptrdiff_t inserted_bytes;
643 /* nil if json_insert succeeded, otherwise the symbol 656 /* nil if json_insert succeeded, otherwise the symbol
644 Qcatch_all_memory_full or a cons (ERROR-SYMBOL . ERROR-DATA). */ 657 Qcatch_all_memory_full or a cons (ERROR-SYMBOL . ERROR-DATA). */
645 Lisp_Object error; 658 Lisp_Object error;
646}; 659};
647 660
648/* Callback for json_dump_callback that inserts the UTF-8 string in 661/* Callback for json_dump_callback that inserts a JSON representation
649 [BUFFER, BUFFER + SIZE) into the current buffer. 662 as a unibyte string into the gap. DATA must point to a structure
650 If [BUFFER, BUFFER + SIZE) does not contain a valid UTF-8 string, 663 of type json_insert_data. This function may not exit nonlocally.
651 an unspecified string is inserted into the buffer. DATA must point 664 It catches all nonlocal exits and stores them in data->error for
652 to a structure of type json_insert_data. This function may not 665 reraising. */
653 exit nonlocally. It catches all nonlocal exits and stores them in
654 data->error for reraising. */
655 666
656static int 667static int
657json_insert_callback (const char *buffer, size_t size, void *data) 668json_insert_callback (const char *buffer, size_t size, void *data)
658{ 669{
659 struct json_insert_data *d = data; 670 struct json_insert_data *d = data;
660 struct json_buffer_and_size buffer_and_size 671 struct json_buffer_and_size buffer_and_size
661 = {.buffer = buffer, .size = size}; 672 = {.buffer = buffer, .size = size, .inserted_bytes = d->inserted_bytes};
662 d->error = internal_catch_all (json_insert, &buffer_and_size, Fidentity); 673 d->error = internal_catch_all (json_insert, &buffer_and_size, Fidentity);
674 d->inserted_bytes = buffer_and_size.inserted_bytes;
663 return NILP (d->error) ? 0 : -1; 675 return NILP (d->error) ? 0 : -1;
664} 676}
665 677
@@ -695,10 +707,15 @@ usage: (json-insert OBJECT &rest ARGS) */)
695 json_t *json = lisp_to_json (args[0], &conf); 707 json_t *json = lisp_to_json (args[0], &conf);
696 record_unwind_protect_ptr (json_release_object, json); 708 record_unwind_protect_ptr (json_release_object, json);
697 709
710 prepare_to_modify_buffer (PT, PT, NULL);
711 move_gap_both (PT, PT_BYTE);
698 struct json_insert_data data; 712 struct json_insert_data data;
713 data.inserted_bytes = 0;
699 /* If desired, we might want to add the following flags: 714 /* If desired, we might want to add the following flags:
700 JSON_DECODE_ANY, JSON_ALLOW_NUL. */ 715 JSON_DECODE_ANY, JSON_ALLOW_NUL. */
701 int status 716 int status
717 /* Could have used json_dumpb, but that became available only in
718 Jansson 2.10, whereas we want to support 2.7 and upward. */
702 = json_dump_callback (json, json_insert_callback, &data, JSON_COMPACT); 719 = json_dump_callback (json, json_insert_callback, &data, JSON_COMPACT);
703 if (status == -1) 720 if (status == -1)
704 { 721 {
@@ -708,6 +725,65 @@ usage: (json-insert OBJECT &rest ARGS) */)
708 json_out_of_memory (); 725 json_out_of_memory ();
709 } 726 }
710 727
728 ptrdiff_t inserted = 0;
729 ptrdiff_t inserted_bytes = data.inserted_bytes;
730 if (inserted_bytes > 0)
731 {
732 /* Make the inserted text part of the buffer, as unibyte text. */
733 GAP_SIZE -= inserted_bytes;
734 GPT += inserted_bytes;
735 GPT_BYTE += inserted_bytes;
736 ZV += inserted_bytes;
737 ZV_BYTE += inserted_bytes;
738 Z += inserted_bytes;
739 Z_BYTE += inserted_bytes;
740
741 if (GAP_SIZE > 0)
742 /* Put an anchor to ensure multi-byte form ends at gap. */
743 *GPT_ADDR = 0;
744
745 /* If required, decode the stuff we've read into the gap. */
746 struct coding_system coding;
747 /* JSON strings are UTF-8 encoded strings. If for some reason
748 the text returned by the Jansson library includes invalid
749 byte sequences, they will be represented by raw bytes in the
750 buffer text. */
751 setup_coding_system (Qutf_8_unix, &coding);
752 coding.dst_multibyte =
753 !NILP (BVAR (current_buffer, enable_multibyte_characters));
754 if (CODING_MAY_REQUIRE_DECODING (&coding))
755 {
756 move_gap_both (PT, PT_BYTE);
757 GAP_SIZE += inserted_bytes;
758 ZV_BYTE -= inserted_bytes;
759 Z_BYTE -= inserted_bytes;
760 ZV -= inserted_bytes;
761 Z -= inserted_bytes;
762 decode_coding_gap (&coding, inserted_bytes, inserted_bytes);
763 inserted = coding.produced_char;
764 }
765 else
766 {
767 /* The target buffer is unibyte, so we don't need to decode. */
768 invalidate_buffer_caches (current_buffer,
769 PT, PT + inserted_bytes);
770 adjust_after_insert (PT, PT_BYTE,
771 PT + inserted_bytes,
772 PT_BYTE + inserted_bytes,
773 inserted_bytes);
774 inserted = inserted_bytes;
775 }
776 }
777
778 /* Call after-change hooks. */
779 signal_after_change (PT, 0, inserted);
780 if (inserted > 0)
781 {
782 update_compositions (PT, PT, CHECK_BORDER);
783 /* Move point to after the inserted text. */
784 SET_PT_BOTH (PT + inserted, PT_BYTE + inserted_bytes);
785 }
786
711 return unbind_to (count, Qnil); 787 return unbind_to (count, Qnil);
712} 788}
713 789
diff --git a/test/src/json-tests.el b/test/src/json-tests.el
index 911bc49730d..bffee8f39d3 100644
--- a/test/src/json-tests.el
+++ b/test/src/json-tests.el
@@ -272,10 +272,11 @@ Test with both unibyte and multibyte strings."
272 (cl-incf calls) 272 (cl-incf calls)
273 (throw 'test-tag 'throw-value)) 273 (throw 'test-tag 'throw-value))
274 :local) 274 :local)
275 (should-error 275 (should
276 (catch 'test-tag 276 (equal
277 (json-insert '((a . "b") (c . 123) (d . [1 2 t :false])))) 277 (catch 'test-tag
278 :type 'no-catch) 278 (json-insert '((a . "b") (c . 123) (d . [1 2 t :false]))))
279 'throw-value))
279 (should (equal calls 1))))) 280 (should (equal calls 1)))))
280 281
281(ert-deftest json-serialize/bignum () 282(ert-deftest json-serialize/bignum ()