diff options
| author | Eli Zaretskii | 2018-10-13 10:13:10 +0300 |
|---|---|---|
| committer | Eli Zaretskii | 2018-10-13 10:13:10 +0300 |
| commit | 95f69e7db235ca450a17c5a24680b742dfdf9aae (patch) | |
| tree | c186902d218c8fca88d3379f84f26738cdfc9fc5 | |
| parent | 6cf4dfe472650b3396d2f2592726621a43896de3 (diff) | |
| download | emacs-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.c | 102 | ||||
| -rw-r--r-- | test/src/json-tests.el | 9 |
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 | ||
| 629 | static Lisp_Object | 632 | static Lisp_Object |
| 630 | json_insert (void *data) | 633 | json_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 | ||
| 641 | struct json_insert_data | 651 | struct 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 | ||
| 656 | static int | 667 | static int |
| 657 | json_insert_callback (const char *buffer, size_t size, void *data) | 668 | json_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 () |