summaryrefslogtreecommitdiff
path: root/greader-dict.el
blob: da12820ab2a248305052fcdfad4170bbe0cc9216 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
;;; greader-dict.el --- dictionary module for greader. -*- lexical-binding: t; -*-
;; 
;; Filename: greader-dict.el
;; Description:
;; Author: Michelangelo Rodriguez
;; Maintainer:
;; Created: Lun Gen  8 09:52:58 2024 (+0100)
;; Version:
;; Last-Updated:
;;           By:
;;     Update #: 0
;; URL:
;; Doc URL:
;; Compatibility:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Commentary:
;; dictionary module for greader.
;; This module gives greader the ability to define different wais Of
;; to pronounce a given sequence of characters.
;;
;;
;; There are two types of items that you can add to
;; the dictionary:
;; `word'
;; This is a substitution where the word to be replaced
;; constitutes a word as a whole.
;; For example, the word "dog" will be replaced only if it is
;; surrounded by characters that do not constitute a word.  (what is a
;; word may change depending on the major-mode
;; currently in use).
;; `match'
;; This is a "literal" substitution, that is, in which it is sufficient
;; that the word to be replaced is a substring of a word.
;; For example, if you add the match "dog", it will be replaced
;; also in the words "dogs", "doggy", ETC.
;; To add a word of type `word' simply execute the
;; command `C-r d a" (greader-dict-add-entry).
;; (when you launch the command, default values ​​will be proposed which
;; you can consult with the arrow keys.
;; The `greader-dict-add-entry' command includes  in its defaults
;; already existing definitions.
;; In this case, choosing one of these values ​​will change the
;; replacement to be applied.
;; To add a word like `match' run the command
;; `greader-dict-add-entry' with the prefix.
;; In this case, the proposed alternatives will only be those that
;; in the dictionary they are classified, precisely, as match.
;; The command is also useful when selecting text: in this
;; case, it will be proposed to add a match that includes only the
;; selected characters.
;; Matches can also be regular expressions, through
;; which you can create more refined filters than you can
;; just do with simple strings.
;;
;; visibility of dictionary.
;;
;; In general, you can choose between three dictionary visibilities:
;; `global'
;; The default dictionary visibility.
;; `mode'
;; This visibility is shared by all buffers in which a particular mode is
;; in effect.
;; For example, if you are visiting the buffer "foo.txt" in text-mode,
;; and you choose the visibility `mode', all the buffers in which
;; `text-mode' is active and in which 'mode' visibility is set, those
;; buffers all will refer to mode dictionary.
;; `buffer'
;; The most local visibility, in which the dictionary is valid only in
;; the current buffer.
;; You can set the dictionary visibility for each buffer.
;; Use `C-r d c" (greader-dict-change-dictionary) to change the
;; dictionary visibility in the current buffer.
;; If you instead wish to set a particular visibility in a particular
;; buffer or mode as default, you can add the following code snippet
;; in your .emacs file:
;; Suppose that you want to set dictionary `mode' visibility for
;; `info-mode':
;;Just copy the following snippet in your init file without quotes:
;; "(add-hook 'info-mode-hook
;; (lambda ()
;; (greader-dict-mode 1)
;; (greader-dict--set-file 'mode)))
;;
;; filters
;; Filters are an alternative way of implementing your pronunciation
;; rules.
;; Word and match abstractions exist to make simpler daily tasks, or,
;; put in other words, matches and words are regexp presets.
;; Matches in particular have the limitation that you must define a
;; rule in terms of human language entities, matches must have
;; necessarily an alphabetic part.
;; You cannot use matches to define rules for character substitution.
;; Filters instead allow you to use whatever you want in terms of
;; regexps, and to substitute them with all the constructs that the
;; regexp matcher of Emacs allow.
;; filters however can become fastly inefficent, because the algoritm
;; used is crude when applying filters: take every filter and cycle
;; over the buffer until you have applied all the filters.
;; the dictionary feature, instead, can handle thousands of
;; definitions with a small decrease of performance.
;; I suggest that you should use word definitions when possible, even
;; if those are similar.
;; Use matches when you individuate a pattern that can work with a
;; common set of characters, using `shy groups'.
;; See the Emacs manual for more information about regexp syntax and
;; related.
;; Use `greader-dict-filter-add' to add a new filter to the database,
;; `greader-dict-filter-remove' to remove an existing filter from the
;; database,
;; `greader-dict-filter-modify' to modify a key preserving its old
;; value.
;; to enable filters, use the command `greader-dict-filters-mode',
;; it is also a customizable variable.
;; This module offers a command
;; `greader-dict-pronounce-in-other-language' that can be used for
;; earing how the tts pronounces a word in another language.
;;
;; Dictionary merging
;;
;; You can merge one or more auxiliary dictionaries into the current one
;; using `greader-dict-merge-dictionary' (C-r d M).
;; Merged entries are loaded into memory and applied during reading, but
;; are never written back to the main dictionary file.  This means that
;; the original dictionaries remain independent: any change to the main
;; dictionary will not affect the auxiliary ones, and vice versa.
;;
;; Merge configurations can be made persistent across Emacs sessions.
;; The behaviour is controlled by the customizable variable
;; `greader-dict-merge-save':
;;   t    - always save merge configurations automatically.
;;   ask  - ask whether to save after each merge (the default).
;;   nil  - never save; merges are lost when Emacs exits.
;;
;; Saved merge configurations are stored in the file pointed to by
;; `greader-dict-merge-file' (default: `.merges' in the dictionary
;; directory).  When `greader-dict-mode' is enabled, any previously
;; saved merge configurations are loaded and applied automatically.
;;
;; You can merge additional dictionaries at any time by calling
;; `greader-dict-merge-dictionary' again.  Calling it without an
;; argument re-applies all auxiliary dictionaries currently recorded for
;; the active dictionary.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Change Log:
;; 
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;; Copyright (C) 2023, 2024  Free Software Foundation, Inc.

;; This program 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.

;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Code:

(require 'greader)

(defgroup greader-dict nil
  "String substitution module for greader."
  :group 'greader)

;; variable definitions
(defvar greader-dict-match-indicator "%*"
  "Regexp that will be used for match delimiter.")

(defvar-local  greader-dictionary nil)

(defvar greader-dict--timer nil)
(defvar greader-dict--item-type-alist '((match
					 . "%*")
					(filter
					 . "%f")
					(word . ""))
  "Item types and relative prefixes.")

;; This function saves the contents of the hash table.
(defvar-local greader-dict-directory (concat user-emacs-directory
					     ".greader-dict/"
					     (greader-get-language)
					     "/")
  "The directory containing greader-dict files.")

(defvar-local greader-dict-local-language (greader-get-language))

(defvar-local greader-dict-filename "greader-dict.global"
  "File name where dictionary definitions are stored.")

(defvar greader-dict--current-reading-buffer (current-buffer))
;; We use this variable to know if greader-dictionary is saved after
;; the last modification.
(defvar-local greader-dict--saved-flag t)
(defvar greader-dict-filters-prefix-map
  (let ((map (make-sparse-keymap)))
    (define-key map "a" #'greader-dict-filter-add)
    (define-key map "m" #'greader-dict-filter-modify)
    (define-key map "k" #'greader-dict-filter-remove)
    map)
  "Keymap for `greader-dict' filters commands.")

(defvar greader-dict-prefix-map
  (let ((map (make-sparse-keymap)))
    (define-key map "i" #'greader-dict-info)
    (define-key map "a" #'greader-dict-add-entry)
    (define-key map "k" #'greader-dict-remove-entry)
    (define-key map "c" #'greader-dict-change-dictionary)
    (define-key map "l" #'greader-dict-pronounce-in-other-language)
    (define-key map "m" #'greader-dict-modify-key)
    (define-key map "s" #'greader-dict-save)
    (define-key map "M" #'greader-dict-merge-dictionary)
    (define-key map "f" greader-dict-filters-prefix-map)
    map)
  "Keymap for `greader-dict' commands.")

(define-key greader-prefix-keymap "d" greader-dict-prefix-map)

(defvar greader-dict--type-file-alternatives '(buffer mode global))

(defvar greader-dict-lang-history nil)

(defvar-local greader-filters nil
  "Hash table containing our filters.")

(defvar greader-dict-filter-indicator "%f")

(defvar greader-dict-filters-mode)

;; This macro calls `with-temp-buffer', setting all the necessary
;; local variables to useful values. This means that all sensible
;; variables will be bound to `greader-dict-current-buffer' local
;; values.
(defmacro with-greader-dict-temp-buffer (&rest body)
  "Optimized `with-temp-buffer' for greader-dict.
Execute BODY in a temporary bufer as if we where in the reading
buffer."
  (declare (indent defun))
  `(with-temp-buffer
     (setq greader-dict--current-reading-buffer (buffer-local-value
						 'greader-dict--current-reading-buffer
						 (or
						  greader--current-buffer
						  (current-buffer))))
     (setq greader-dictionary (buffer-local-value 'greader-dictionary
						  (or
						   greader--current-buffer
						   greader-dict--current-reading-buffer)))
     (setq greader-dict-filename (buffer-local-value
				  'greader-dict-filename
				  (or greader--current-buffer greader-dict--current-reading-buffer)))
     (setq greader-dict-local-language (buffer-local-value
					'greader-dict-local-language
					(or greader--current-buffer greader-dict--current-reading-buffer)))
     (setq greader-filters (buffer-local-value 'greader-filters
					       (or
						greader--current-buffer
						greader-dict--current-reading-buffer)))
     (setq greader-dict-filters-mode (buffer-local-value
					'greader-dict-filters-mode
					(or greader--current-buffer greader-dict--current-reading-buffer)))
     ,@body))

;; merging dictionaries.

(defun greader-dict--merged-p (key)
  "Return t if KEY is merged, nil otherwise."
  (get-text-property 0 'greader-dict-merged key))

(defun greader-dict--merge (key)
  "Return KEY with the `greader-dict-merged' text property set."
  (if (greader-dict--merged-p key)
      key
    (propertize key 'greader-dict-merged t)))

;; merge customization
(defvar greader-dict-merge-dictionaries-alist ()
  "Alist of merged dictionaries.
Each entry has the form (MAIN-DICT AUX1 AUX2 ...) where MAIN-DICT is
the absolute path of the main dictionary and each AUXn is the path of
an auxiliary dictionary merged into it.
Auxiliary files should reside in the same directory as the main
dictionary; this prevents accidentally mixing dictionaries for different
languages.
Use `greader-dict-merge-dictionary' to add entries to this list.")

(defcustom greader-dict-merge-save 'ask
  "Controls persistence of merge configurations across Emacs sessions.
If t, merge configurations are saved automatically after each merge.
If `ask', you are prompted whether to save after each merge.
If nil, merge configurations are never saved."
  :type '(choice (const :tag "Always save"   t)
		 (const :tag "Ask every time" ask)
		 (const :tag "Do not save at all" nil)))

(defcustom greader-dict-merge-file (file-name-concat
				    greader-dict-directory ".merges")
  "File used to persist merge configurations.
The file stores `greader-dict-merge-dictionaries-alist' so that merges
survive Emacs restarts.  See also `greader-dict-merge-save'."
  :type '(file))

(defun greader-dict-merge-save-to-file (&optional filename)
  "Save `greader-dict-merge-dictionaries-alist' to FILENAME.
If FILENAME is nil, use `greader-dict-merge-file'."
  (unless filename
    (setq filename greader-dict-merge-file))
  (with-greader-dict-temp-buffer
    (print greader-dict-merge-dictionaries-alist (current-buffer))
    (write-region (point-min) (point-max) filename)))

(defun greader-dict-merge-get ()
  "Return merged dictionaries for this dictionary.
Return nil if this dictionary does not contain merged dictionaries."
  (if-let* ((dictionaries (assoc (greader-dict--get-file-name) greader-dict-merge-dictionaries-alist)))
      dictionaries
    nil))

(defun greader-dict-load-merges (&optional filename)
  "Populate `greader-dict-merge-dictionaries-alist' with contents of
  FILENAME.
Return FILENAME contents, or nil if FILENAME is empty or does not
exist or some other system errors."
  (unless filename
    (setq filename greader-dict-merge-file))
  (with-greader-dict-temp-buffer
    (if (file-exists-p filename)
	(progn
	  (insert-file-contents filename)
	  (if-let* ((result (read (current-buffer))))
	      (setq greader-dict-merge-dictionaries-alist result)
	    nil))
      nil)))

(defun greader-dict-merge--check-candidate (filename)
  "Predicate for `read-file-name'.
Return t for every file except the current dictionary itself."
  (not (equal (expand-file-name filename)
	      (expand-file-name (greader-dict--get-file-name)))))

(defun greader-dict-merge--dictionaries ()
  "Merge auxiliary dictionaries listed for the current dictionary.
Reads all files in the cdr of the matching entry in
`greader-dict-merge-dictionaries-alist', marking every loaded entry
as merged so it is never written back to the main dictionary file."
  (when-let* ((dictionaries (greader-dict-merge-get)))
    (dolist (dictionary (cdr dictionaries))
      (greader-dict-read-from-dict-file t dictionary t))))

(defun greader-dict-merge-dictionary (&optional filename)
  "Merge contents of FILENAME into `greader-dictionary'.
FILENAME entries are marked as merged and are never saved to the main
dictionary file.
If FILENAME is nil, re-merge all auxiliaries already recorded in
`greader-dict-merge-dictionaries-alist' for the current dictionary."
  (interactive
   (list
    (read-file-name "Dictionary file to merge: " greader-dict-directory nil t nil
		    #'greader-dict-merge--check-candidate)))
  (if (null filename)
      (greader-dict-merge--dictionaries)
    (let ((main (greader-dict--get-file-name)))
      (if-let* ((entry (assoc main greader-dict-merge-dictionaries-alist)))
	  (unless (member filename (cdr entry))
	    (setcdr entry (append (cdr entry) (list filename))))
	(push (list main filename) greader-dict-merge-dictionaries-alist)))
    (greader-dict-read-from-dict-file t filename t)
    (cond
     ((eq greader-dict-merge-save t)
      (greader-dict-merge-save-to-file))
     ((eq greader-dict-merge-save 'ask)
      (when (y-or-n-p "Save merge configuration? ")
	(greader-dict-merge-save-to-file))))))

(defvar-keymap greader-dict-filter-map
  :doc "key bindings for greader-dict filter feature."
  "C-r d f a" #'greader-dict-filter-add
  "C-r d f m" #'greader-dict-filter-modify
  "C-r r" #'isearch-backward
  "C-r d f k" #'greader-dict-filter-remove)


;; filters.
;; filters allow users to define arbitrary regexps to be replaced
;; either with empty strings or by another string.
;; It is necessary to conceptually abstract filters from other types of
;; match because the filters allow the use of any character and in any case, being applied as they are,
;; the user can better exploit the expressive power of regexps.
;; so filters are a separate feature, which we can consider an
;; "advanced" use case of greader-dict.
;;;###autoload
(define-minor-mode greader-dict-filters-mode
  "Enable or disable filters.
Filters allow you to replace every regexp you wish with something
else you wish.
While matches and words are conceived as facilities that are
designated to be user-friendly interfaces to regexps, with filters
you can unleash all
your expressiveness!
Filters and dictionary are considered independent features for now, so
you can enable filters without the extra payload given by
`greader-dict-mode'.
To use a filter you must first enable this mode, and, eventually, add
a filter.
So use `greader-dict-filter-add' to do that.
When you are prompted for the filter, you should insert the regexp
that must match to have the associated replacement.
You can use the usual `\\\\' expressions, shy groups and all the power
of regexps.
If you are interested in how to write a regexp please consult the info
node `(emacs) Regexps'."
  :lighter " gr-filters"
  (when greader-dict-filters-mode
    (setq greader-filters (make-hash-table :test 'ignore-case))
    (setq greader-dict--current-reading-buffer (current-buffer))
    (unless greader-dictionary
      (greader-dict-mode 1)
      (greader-dict-mode -1))
    (greader-dict--filter-init)))

;;;###autoload
(define-obsolete-function-alias 'greader-dict-toggle-filters
  #'greader-dict-filters-mode "0.14")

(defcustom greader-dict-include-sentences-in-defaults nil
  "Includi le parole della frase come alternative.
When active, the constituent words of the sentence currently in
reading will be added to the list of defaults (where it makes sense
to do it).
In this way anyone who wishes can search for the word to manipulate
using a menu instead of navigating the buffer."
  :type 'boolean)

;;;###autoload
(define-minor-mode greader-dict-mode
  "Dictionary module for greader.
With this mode it is possible to instruct greader to pronounce in an
alternative way the words that the tts mispronounces in a given language.
There are two types of definitions understood by greader-dict-mode:
\"word definitions\" are those that must be surrounded by
Non-constituent word characters;
\"Match definitions\" are those that can be replaced regardless of
surrounding characters.
The definition type is determined when you add a new definition:
If you use the region to mark a word, you can select a partial word or
the entire word, and greader-dict-mode will understand that you want
to add a match definition.
If instead you add simply the word under the point, it will be added
as a word definition."
  :lighter " gr-dictionary"
  (cond
   (greader-dict-mode
    (setq greader-dictionary (make-hash-table :test 'ignore-case))
    (setq greader-dict--current-reading-buffer (current-buffer))
    (greader-dict-read-from-dict-file)
    (greader-dict-load-merges)
    (greader-dict-merge--dictionaries)
    (add-hook 'greader-after-get-sentence-functions
	      #'greader-dict--replace-wrapper 1)
    (add-hook 'buffer-list-update-hook #'greader-dict--update)
    (add-hook 'greader-after-change-language-hook
	      (lambda ()
		(when greader-dict-mode
		  (setq greader-dict-local-language
			(greader-get-language))
		  (greader-dict--update)))))))

;; THanks to the loved and alwais useful elisp reference.
(defun greader-dict--string-hash-ignore-case (a)
  (sxhash-equal (upcase a)))

(define-hash-table-test 'ignore-case
			'string-equal-ignore-case
			'greader-dict--string-hash-ignore-case)
(declare-function string-remove-suffix nil)
;; The following two functions deal, respectively, with
;; replace a dictionary item with the value specified in
;; `greader-dictionari' and its possible variants.
;; The `greader-dict-substitute-match' function takes care of substitution
;; an item even within a word, a sort of partial substitution.
;; The `greader-dict-substitute-word' function takes care of that instead
;; replace a dictionary item only if the sequence matches
;; replace is surrounded by one or more blank class characters.
;; This will allow the user to specify whether a given rule
;; pronunciation in the dictionary should be applied more literally,
;; (for example, a pronunciation rule can be defined such that if a
;; word contains the sequence "ez" it is replaced with the
;; sequence "es", for which, for example, "Rodriguez" would be replaced
;; with "Rodrigues").
(defun greader-dict-substitute-match (match)
  "Replace MATCH with the matching value in `greader-dictionary."
  (let ((normalized-match (string-remove-suffix
			   greader-dict-match-indicator match)))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward normalized-match nil t)
	(replace-match (gethash match greader-dictionary))))))

(defun greader-dict-substitute-word (match)
  "Substitute MATCH only if it constitutes an entire word."

  (save-excursion
    (goto-char (point-min))
    (let ((word (concat "\\(\\W?\\)" "\\(" match "\\)" "\\(\\W+\\)"))
	  (alternative-word
	   (concat "\\(^\\)" "\\(" match "\\)" "\\(\\W+\\)"))
	  (end-word
	   (concat "\\(\\W+\\)" "\\(" match "\\)" "\\(\\W*$\\)")))
      (while (or (re-search-forward word nil t) (re-search-forward
						 alternative-word nil
						 t)
		 (re-search-forward end-word nil t))
	(let ((replacement
	       (concat (match-string 1)
		       (gethash match greader-dictionary)
		       (match-string 3))))
	  (replace-match replacement nil t))))))

;; This function adds to the `greader-dictionary' variable the
;; key/value pair that you pass as arguments.
(defcustom greader-dict-save-after-time 30
  "Amount of idleness to wait before saving dictionary data.
A value of 0 indicates saving immediately."
  :type 'number)
(defun greader-dict-add (word replacement &optional merge)
  "Add the WORD REPLACEMent pair to `greader-dictionary'.
If you want to add a partial replacement, you should
add `\*'to the end of the WORD string parameter.
If MERGE is non-nil, then add the pair as merged."
  ;; We prevent an infinite loop if disallowing that key and values
  ;; are the same.
  (unless replacement
    (setq replacement ""))
  (when (string-equal-ignore-case word replacement)
    (user-error "key and value are the same, aborting"))
  (when merge
    (setq word (greader-dict--merge word)))
  (puthash word replacement greader-dictionary)
  (setq greader-dict--saved-flag nil)
  (cond
   ((> greader-dict-save-after-time 0)
    (when (timerp greader-dict--timer)
      (cancel-timer greader-dict--timer))
    (run-with-idle-timer greader-dict-save-after-time nil
			 #'greader-dict-write-file))
   ((= greader-dict-save-after-time 0)
    (unless greader-dict--saved-flag
      (greader-dict-write-file)))
   (t
    (setq greader-dict--saved-flag t)
    nil)))

;; This function removes the association indicated by the key argument.
(defun greader-dict-remove (key)
  "Remove the association specified by KEY from the variable
`greader-dictionary'."
  (remhash key greader-dictionary)
  (setq greader-dict--saved-flag nil)
  (cond
   ((> greader-dict-save-after-time 0)
    (when (timerp greader-dict--timer)
      (cancel-timer greader-dict--timer))
    (run-with-idle-timer greader-dict-save-after-time nil
			 #'greader-dict-write-file))
   ((= greader-dict-save-after-time 0)
    (unless greader-dict--saved-flag
      (greader-dict-write-file)))
   (t
    (setq greader-dict--saved-flag t)
    nil)))

(defun greader-dict-item-type (key)
  "Return the type of KEY.
Possible return values are:
`word' for a wole word,
`match' for partial matches,
`filter' for filters.
There may be more in the future.
Return nil if KEY is not present in `greader-dictionary'."
  (when key
    (let (result)
      (catch 'key-found
	(dolist (type greader-dict--item-type-alist)
	  (if
	      (gethash
	       (concat key
		       (unless (string-suffix-p (cdr type) key)
			 (cdr type)))
	       greader-dictionary)
	      (progn
		(setq result (car type))
		(throw 'key-found result))
	    nil))))))

(defcustom greader-dict-transform-match-to-word nil
  "If enabled, every match in the text will be added as a word.
This option could improve slightly the performance of this framework,
by adding every match found in the text as a word."
  :type 'boolean)

(defun greader-dict--get-key-from-word (word &optional matches)
  "Return key related to WORD, nil otherwise.
MATCHES, if provided, is a pre-computed list of match-type keys as
returned by `greader-dict--get-matches'; when nil the list is
computed on demand.  Callers that invoke this function repeatedly
should pre-compute the list once and pass it here to avoid
rebuilding it on every call."
  (unless word
    (setq word ""))
  (setq word (string-trim word))
  (cond
   ((gethash word greader-dictionary)
    word)
   (t
    (catch 'key-matched
      (dolist (k (or matches (greader-dict--get-matches 'match)))
	(let ((normalized (string-remove-suffix
			   greader-dict-match-indicator k)))
	  (when (string-match normalized word)
	    (when greader-dict-transform-match-to-word
	      (greader-dict--add-match-as-word
	       normalized word (gethash k greader-dictionary)))
	    (throw 'key-matched k))))
      nil))))

;; This function checks that, in the string you pass to it, there are
;; effectively words to be replaced. If so, use apis
;; previously defined to adequately replace the words that
;; could need it.
;; This is the function to add to
;; `greader-after-get-sentence-functions'.
;;;###autoload
(defun greader-dict-check-and-replace (text)
  "Return TEXT modified by applying dictionary to TEXT."
  (with-greader-dict-temp-buffer
    (insert text)
    (goto-char (point-min))
    (when greader-dict-filters-mode
      (greader-dict-filters-apply))
    (if
	(buffer-local-value 'greader-dict-mode
			    (or greader--current-buffer greader-dict--current-reading-buffer))
	(progn
	  ;; We check if text is actually just one word, and in that case
	  ;; insert a new line at end of temp buffer.
	  (when (= (count-words (point-min) (point-max)) 1)
	    (save-excursion (goto-char (point-max)) (ignore-errors (newline))))
	  (let ((inhibit-read-only t)
		(matches (greader-dict--get-matches 'match)))
	    (re-search-forward "\\w" nil t)
	    (while (not (eobp))
	      (let*
		  ((key (greader-dict--get-key-from-word
			  (thing-at-point 'word) matches)))
		(cond
		 ((equal (greader-dict-item-type key) 'word)
		  (greader-dict-substitute-word (string-remove-suffix
						 greader-dict-match-indicator
						 key)))
		 ((equal (greader-dict-item-type key) 'match)
		  (greader-dict-substitute-match key))
		 ((not (greader-dict-item-type key))
		  nil)))
	      (re-search-forward "\\W*\\w" nil 1))
	    (buffer-string)))
      (buffer-string))))

(defun greader-dict-write-file ()
  "Save `greader-dictionary' stored in `greader-dict-filename'."
  (unless (file-exists-p greader-dict-directory)
    (make-directory greader-dict-directory t))
  (with-greader-dict-temp-buffer
    (maphash
     (lambda (k v)
       (unless (greader-dict--merged-p k)
	 (insert "\"" k "\"" "=" v "\n")))
     greader-dictionary)
    (write-region (point-min) (point-max)
		  (greader-dict--get-file-name)))
  (setq greader-dict--saved-flag t))

(defun greader-dict-read-from-dict-file
    (&optional force filename merge)
  "populate `greader-dictionary' with the contents of
`greader-dict-filename'.
If FORCE is non-nil, reading happens even if there are definitions not
yet saved.
If FORCE is nil \(the default\) then this function generates an
user-error and aborts the reading process.
If filename is specified, then use that file for reading instead of
the standard.
If merge is non-nil, then merge only the definitions."
  ;; This code is to provide a variable
  ;; `greader-dictionary' by default usable in the buffer
  ;; temporary where the replacements defined in `greader-after-get-sentence-functions' occur.
  (when (and (not greader-dict--saved-flag) (not force))
    (user-error "Dictionary has been modified and not yet saved"))
  (let ((filename (or filename (greader-dict--get-file-name))))
    (when (file-exists-p filename)
      (with-greader-dict-temp-buffer
	(insert-file-contents filename)
	(when-let* ((lines (string-lines (buffer-string) t)))
	  (dolist (line lines)
	    (setq line (split-string line "=" ))
	    (setf (car line) (car (split-string (car line) "\"" t)))
	    (let ((greader-dict-save-after-time -1))
	      (greader-dict-add (car line) (car (cdr line)) merge)))
	  (greader-dict--filter-init)
	  (setq greader-dict--saved-flag t)))))
  (add-hook 'buffer-list-update-hook #'greader-dict--update))

;; Command for saving interactively dictionary data.
(defun greader-dict-save ()
  "Save dictionary data.
You should use this command when you want to save your dictionary and
`greader-dict-save-after-time' is set to a negative number.
Otherwise, data saving is done automatically when you add a definition
to the dictionary."
  (interactive)
  (let ((greader-dict--saved-flag nil))
    (greader-dict-write-file)))

(declare-function greader-get-sentence nil)
;; This command Adds a definition to `greader-dictionary'.
;; If the region is active and it does not constitute more than one word,
;; the command will propose the selected word as the original word to
;; substitute.
;; The selected word will be added to `greader-dictionary' as
;; "match", then the definition thus obtained can be applied to
;; any character sequence that includes it.
;; However, if the region is not active, this function will try to
;; determine the word to add through the function
;; `thing-at-point'. In case this function returns a word,
;; it will be used to propose it as the original word to be replaced.
;; In this last case, the word will be added to
;; `greader-dictionary' as "word", so it must constitute itself
;; a word to be replaced.
(defun greader-dict-add-entry (arg)
  "Add an entry to the dictionary.
If called interactively and point is on a word, this function proposes
to add that word as
default.
In this case, you can also use history commands to modify key already
present in the dictionary.
The word will be added as a word, but you can choice to add it as a
match using history commands when in the minibuffer.
If the region is active, and you have selected a word or a partial
word, it will be added as a match.
If neither the region is active nor point is on a word, simply asks
for definition and substitution, without defaults.
If called with prefix argument, ask for a match.
In this case you can type a partial word or a regular expression.
You can use regular expressions to, for example, craft filters instead
of pronunciation rules.
If the customizable variable
`greader-dict-include-sentences-in-defaults' is enabled, when adding
an entry the defaults will include also the set of words that makes up
the current sentence."
  (interactive "P")
  (let (key value)
    (cond
     (arg
      (setq key (read-regexp "match to add or modify: "
			     (greader-dict--get-matches 'match)))
      (unless key
	(user-error "Input is empty: aborting"))
      (setq key (concat key greader-dict-match-indicator))
      (setq value (read-string (concat "substitute match "
				       (string-remove-suffix
					greader-dict-match-indicator
					key)
				       " with:
")
			       nil nil(gethash key
					       greader-dictionary)))
      (greader-dict-add key value))
     ((region-active-p)
      (when (= (count-words(region-beginning) (region-end)) 1)
	(setq key (concat (read-string "Original word to substitute:"
				       nil nil
				       (buffer-substring
					(region-beginning)
					(region-end)))
			  greader-dict-match-indicator))
	(setq value (read-string (concat "substitute match " key
					 "with: ")
				 nil nil
				 (gethash key greader-dictionary)))
	(greader-dict-add key value)))
     ((not (region-active-p))
      (if-let* ((default-word (thing-at-point 'word)))
	  (progn
	    (setq key (read-string "Original word to substitute or
modify: "
				   nil
				   nil
				   (append (list
					    (substring-no-properties
					     default-word))
					   (when
					       greader-dict-include-sentences-in-defaults
					     (greader-dict--get-word-alternatives
					      (greader-get-sentence)))
					   (greader-dict--get-matches
					    'word))))
	    (setq value (read-string (concat "substitute word " key
					     " with: ")
				     (gethash key
					      greader-dictionary)))
	    (when (string-match "[[:punct:]]" value)
	      (user-error "Replacement cannot contain punctuation signs.
If you want to listen a sign, please include it literally \(wrote by
letters\) in the replacement"))
	    (greader-dict-add key value))
	(setq key (read-string "Word to add or modify: " nil nil
			       (greader-dict--get-matches 'word)))
	(setq value (read-string (concat "substitute " key " with: ")
				 nil nil
				 (gethash key greader-dictionary)))
	(when (string-match "[[:punct:]]" value)
	  (user-error "Replacement cannot contain punctuation signs.
If you want to listen a sign, please include it literally \(wrote by
letters\) in the replacement"))
	(greader-dict-add key value)))))
  (deactivate-mark t))

(declare-function hash-table-keys nil)
(defun greader-dict-remove-entry (key)
  "Remove KEY from the dictionary.
If KEY is not present, signal an user-error."
  (interactive
   (list
    (read-string "key to remove: "nil nil
		 (sort (hash-table-keys greader-dictionary)
		       (lambda (s1 s2)
			 (string-greaterp s2 s1))))))
  (unless (greader-dict-remove key)
    (user-error "Key not found")))

(defun greader-dict-clear ()
  "Clean the definition table.
It does'nt save cleaned table in the definitions file automatically,
  instead you should save it manually if you want.
Please use `greader-dict-save' for that purpose."
  (interactive)
  (clrhash greader-dictionary)
  (setq greader-dict--saved-flag nil)
  (message "Cleaned."))
(defun greader-dict--replace-wrapper (text)
  "Function to add to `greader-after-get-sentence-functions'.
It simply calls `greader-dict-check-and-replace' with TEXT as its
argument, only if `greader-dict-mode' is enabled."
  (if (or greader-dict-mode greader-dict-filters-mode)
      (greader-dict-check-and-replace text)
    text))
(declare-function greader-get-language nil)
(defun greader-dict--get-file-name ()
  "Return the absolute path of current dictionary file."
  (let ((language-part (car (last (split-string greader-dict-directory
						"/" t)))))
    (unless (equal language-part (buffer-local-value
				  'greader-dict-local-language
				  (or greader--current-buffer greader-dict--current-reading-buffer)))
      (setq greader-dict-directory (string-remove-suffix (concat
							  language-part
							  "/")
							 greader-dict-directory))
      (setq greader-dict-directory
	    (concat greader-dict-directory (buffer-local-value
					    'greader-dict-local-language
					    (or
					     greader--current-buffer greader-dict--current-reading-buffer))
		    "/"))))
  (concat greader-dict-directory (buffer-local-value
				  'greader-dict-filename
				  (or greader--current-buffer greader-dict--current-reading-buffer))))

(defun greader-dict--set-file (type)
  "Set `greader-dict-filename' according to TYPE.
TYPE Must be a symbol, and accepted symbols are:
`buffer', `mode', and `global'.
See also the documentation of `greader-dict--file-type' For
technicalities."
  (cond
   ((not greader-dict--saved-flag)
    (greader-dict-write-file)))
  (cond
   ;; We use `setq-local' only for clarity.
   ((equal type 'buffer)
    (setq-local greader-dict-filename (concat (buffer-name) ".dict")))
   ((equal type 'mode)
    (setq-local greader-dict-filename
		(concat (symbol-name major-mode) ".dict")))
   ((equal type 'global)
    (setq-local greader-dict-filename "greader-dict.global"))
   (t
    (error (concat "type " (symbol-name type) " not valid as "
		   (symbol-name (type-of type)))))))


(defcustom greader-dict-ask-before-change t
  "Ask before changing the dictionary in current buffer.
If toggled on, when you attempt to change the dictionary and current
  dictionary table as data not yet saved,
  `greader-dict-change-dictionary will ask you if you want to save
  the current dictionary first.
If you answer no, you will loose that data.
If you answer yes, instead, data will be saved in the current
  dictionary before setting the dictionary at newone."
  :type 'boolean)

(defun greader-dict--file-type ()
  "Return the file type of dictionary for the current buffer.
The `file type' refers to the scope in a given context:
`buffer'
Means that it exists a file named `(concat buffer-file-name \".dict\")
in `greader-dict-directory'.
`mode'
Means it exists a file called `(concat major-mode \".dict\")' in
`greader-dict-directory'.
`global'
Means it exists a file called \"greader-dict.global\" in
`greader-dict-directory'."
  (let ((default-directory (concat greader-dict-directory
				   (greader-get-language) "/")))
    (cond
     ((string-equal (concat (buffer-name) ".dict")
		    greader-dict-filename)
      'buffer)
     ((string-equal (concat (symbol-name major-mode) ".dict")
		    greader-dict-filename)
      'mode)
     ((string-equal "greader-dict.global" greader-dict-filename)
      'global)
     (t 'global))))

(defun greader-dict--type-alternatives ()
  "Return the list of currently valid alternatives for dictionary."
  (let ((alternatives nil))
    (dolist (alternative greader-dict--type-file-alternatives)
      (unless (equal alternative (greader-dict--file-type))
	(push (symbol-name alternative) alternatives)))
    alternatives))

(defun greader-dict-change-dictionary (new-dict)
  "Change the current dictionary.
You can choose between the alternatives by using the arrow keys when
asked."
  (interactive
   (list
    (read-string (concat "Change dictionary from "
			 (symbol-name
			  (greader-dict--file-type))
			 " to: ")
		 nil nil
		 (greader-dict--type-alternatives))))
  (unless greader-dict-mode
    (user-error "Please enable `greader-dict-mode' first"))
  (let ((old-dict (greader-dict--file-type))
	(response nil))
    (unless (equal new-dict old-dict)
      (cond
       ((and greader-dict-ask-before-change (not
					     greader-dict--saved-flag))
	(setq response (yes-or-no-p "There are definitions not yet
  saved; Do you want to save them before changing?"))
	(if response (greader-dict-write-file)
	  (setq
	   greader-dict--saved-flag
	   t))))
      (clrhash greader-dictionary)
      (clrhash greader-filters)
      (greader-dict--set-file (intern new-dict))
      (unless (file-exists-p (greader-dict--get-file-name))
	(shell-command-to-string
	 (concat "touch " (greader-dict--get-file-name))))
      (greader-dict--update))))

;; (remove-hook 'buffer-list-update-hook #'greader-dict--update)))))

(defvar greader-reading-mode)
(defun greader-dict--update ()
  (when greader-dict-filters-mode
    (setq greader-dict--current-reading-buffer (or
						greader--current-buffer
						(current-buffer)))
    (unless greader-reading-mode
      (let ((dict-mode-state greader-dict-mode))
	(greader-dict-mode 1)
	(greader-dict-read-from-dict-file t)
	(unless dict-mode-state
	  (greader-dict-mode -1)))))
  (when greader-dict-mode
    (setq greader-dict--current-reading-buffer (or
						greader--current-buffer
						(current-buffer)))
    (unless greader-dict--saved-flag
      (greader-dict-write-file))
    ;; I decided to keep the following code for historical reasons and
    ;; memento.
    ;;   Indeed it is superfluous as it is, because "buffer-locality", so
    ;; the following conditional is not necessary.
    (unless greader-reading-mode
      (clrhash
       (buffer-local-value 'greader-dictionary
			   greader-dict--current-reading-buffer))
      (greader-dict-read-from-dict-file t))))

;; Questa funzione è solo di utilità e potrebbe essere rimossa o
;; modificata in qualsiasi momento.
(defun greader-dict-beep ()
  (beep))

(defun greader-dict-info ()
  "Print some information about current dictionary."
  (interactive)
  (let ((message
	 (concat "Current dictionary is "
		 (symbol-name (greader-dict--file-type))
		 " in file " greader-dict-filename " it has "
		 (number-to-string (hash-table-count
				    greader-dictionary))
		 " entries" (if greader-dict-filters-mode (progn
							      (concat
							       ", and "
							       (number-to-string
								(hash-table-count
								 greader-filters))
							       "
filters."))
			      ".")
		 " Active language is \""
		 greader-dict-local-language "\".")))
    (message "%s" message)))

(defun greader-dict--get-matches (type &optional decorate)
  "Return a list with keys classified as TYPE.
If TYPE is `all', all items in the current dictionary will be included."
  (let ((matches nil))
    (maphash
     (lambda (k _v)
       (cond
	((equal (greader-dict-item-type k) type)
	 (let ((match (and (string-remove-suffix
			    greader-dict-match-indicator k)
			   (string-remove-suffix
			    greader-dict-filter-indicator
			    k))))
	   (when decorate
	     (setq match
		   (concat match " \(" (gethash k greader-dictionary)
			   "\)")))
	   (push match matches)))
	((equal type 'all)
	 (let
	     ((match
	       (string-remove-suffix greader-dict-match-indicator k)))
	   (when decorate
	     (setq match
		   (concat match " \(" (gethash k greader-dictionary)
			   "\)")))
	   (push match matches)))))
     greader-dictionary)
    (sort
     matches
     (lambda (s1 s2)
       (string-greaterp s2 s1)))))
(defun greader-dict-modify-key (arg)
  "Modify a key \(_NOT_ a value associated with it!\).
While `greader-dict-add-entry can modify either keys or associated
values, `greader-dict-modify-key' allows you to modify the key
itself, without modifying the associated value.
if prefix ARG is non-nil, then this command proposes only keys that
are classified as matches.
When called without a prefix, it modifies only keys that are
classified as words."
  (interactive "P")
  (let ((key (read-string "key to modify: " nil nil (if arg
							(greader-dict--get-matches
							 'match)
						      (greader-dict--get-matches
						       'word))))
	(new-key nil))
    (unless key
      (user-error "Key not valid"))
    (if-let* ((backup-value (gethash key greader-dictionary)))
	(progn
	  (setq new-key (read-string (concat "substitute key " key "
  with:")
				     nil nil key))
	  (unless new-key
	    (user-error "Invalid replacement"))
	  (greader-dict-remove key)
	  (greader-dict-add new-key backup-value))
      (user-error "Key not found"))))

(defun greader-dict--get-word-alternatives (text)
  "Return a list with a set of words in TEXT."
  (if-let* ((alternatives text))
      (progn
	(setq alternatives nil)
	(dolist
	    (word
	     (split-string (substring-no-properties text) "\\W" t))
	  (unless (member word alternatives)
	    (push word alternatives)))
	(reverse alternatives))
    (user-error "No text")))
(declare-function greader-set-language nil)
(declare-function greader-read-asynchronous nil)
;;;###autoload
(defun greader-dict-pronounce-in-other-language (word new-lang)
  "Pronounce WORD in the language specified by NEW-LANG.
The currently configured backend will be used for the voice.
NEW-LANG should be an ISO code, compatible with the back-end you are
using.
If the variable `greader-dict-include-sentences-in-defaults is enabled, when
asked about the word to pronounce, the defaults will be a set of words
in the current sentence."

  (interactive
   (list
    (read-string "Word to pronounce: " nil t
		 (when greader-dict-include-sentences-in-defaults
		   (greader-dict--get-word-alternatives
		    (greader-get-sentence))))
    (read-string (concat "language in which you wish to listen:") nil
		 'greader-dict-lang-history)))
  (unless new-lang (user-error "No language specified"))
  (let ((old-lang (greader-get-language)))
    (greader-set-language new-lang)
    (greader-read-asynchronous word)
    (greader-set-language old-lang)))

(defun greader-dict--is-filter-p (key)
  "Return t if KEY is a filter based on
`greader-dict-filter-indicator'."
  (string-suffix-p greader-dict-filter-indicator key))

(defun greader-dict-filter-add (key value)
  "Add KEY as a filter with associated VALUE.
KEY should be a regexp, and value can contain \"shi-groups\".
You should hobey using punctuation signs in your replacement, apart
from those that constitute the pattern.
Adding a punctuation sign in the replacement part of a filter is
considered undefined behavior."
  (interactive
   (let*
       ((key (read-string "filter (regexp) to add or modify: " nil nil
			  (greader-dict--get-matches 'filter)))
	(value (read-string
		(concat "substitute regexp " key " with: ") nil nil
		(gethash (concat key greader-dict-filter-indicator)
			 greader-dictionary))))
     (list key value)))
  (while (string-match "\n" key)
    (setq key (replace-match "[[:space:]]" nil nil key)))
  (greader-dict-add (concat key greader-dict-filter-indicator) value)
  (greader-dict--filter-init))

(defun greader-dict-filter-remove (key)
  "Remove KEY from the hash-table."
  (interactive
   (let ((key (read-string "filter to remove: " nil nil
			   (greader-dict--get-matches 'filter))))
     (list key)))
  (when (gethash (concat key greader-dict-filter-indicator)
		 greader-dictionary)
    (greader-dict-remove (concat key
				 greader-dict-filter-indicator))
    (greader-dict--filter-init)))

(defun greader-dict-filter-modify (key new-key)
  "Modify _Only_ KEY.
KEY must be an existing filter key, this command provides a list of
them accessible using the history commands.
NEW-KEY must not be empty, if empty, this command will signal an error."

  (interactive
   (let* ((key (read-string "key to modify: " nil nil
			    (greader-dict--get-matches 'filter)))
	  (new-key (read-string "new value: " nil nil key)))
     (unless
	 (gethash (concat key greader-dict-filter-indicator)
		  greader-dictionary)
       (user-error "Invalid key"))
     (when (string-empty-p new-key)
       (user-error "Invalid replacement, string is empty"))
     (list key new-key)))
  (let ((old-value (gethash (concat key greader-dict-filter-indicator)
			    greader-dictionary)))
    (greader-dict-filter-remove key)
    (greader-dict-filter-add new-key old-value))
  (greader-dict--filter-init))

(defun greader-dict--filter-init ()
  "Initialize filters hash table.
It works by subtracting from `greader-dictionary' the entries that are
classified as filters and, eventually, adding them to the filters
hash table."
  (when greader-filters (clrhash greader-filters))
  (maphash
   (lambda (k v)
     (when (and greader-dict-filters-mode (string-suffix-p
					     greader-dict-filter-indicator
					     k))
       (puthash k v greader-filters)))
   greader-dictionary))

(defun greader-dict-filters-apply ()
  "Apply filters defined in sequence to the current buffer."
  (maphash
   (lambda (k v)
     (setq k (string-remove-suffix greader-dict-filter-indicator k))
     (setq k (string-trim-right k))
     (save-excursion
       (goto-char (point-min))
       (while (re-search-forward k nil t)
	 (replace-match v))))
   greader-filters))

(defun greader-dict--add-match-as-word (key word replacement &optional
					    original-word)
  "Add WORD and REPLACEMENT to the current dictionary.
This function is used internally, please use the normal entry-points
to add your own items to the dictionary.
This function works only when REPLACEMENT contains only word
constituents."

  (unless (string-match "\\W" replacement nil t)
    (let (value start end)
      (string-match key word)
      (setq start (match-beginning 0))
      (setq end (match-end 0))
      (when (> start 0)
	(setq value (concat (substring word 0 start))))
      (setq value (concat value replacement))
      (when (> (length word) (length value))
	(setq value (concat value (substring word end))))
      (if (string-match key value)
	  (progn
	    (setq original-word word)
	    (greader-dict--add-match-as-word key value replacement
					     original-word))
	(setq key (or original-word word))
	(greader-dict-add key value)))))

(provide 'greader-dict)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; greader-dict.el ends here