comparison configmix/_speedups.c @ 554:36d7aa000435

Implement a C-version of Configuration.interpolate_variables
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 03 Jan 2022 00:11:41 +0100
parents 9d2bd411f5c5
children b7434a34a1f4
comparison
equal deleted inserted replaced
553:9d2bd411f5c5 554:36d7aa000435
21 struct speedups_state { 21 struct speedups_state {
22 PyObject *DOT; 22 PyObject *DOT;
23 PyObject *QUOTE; 23 PyObject *QUOTE;
24 PyObject *NS_SEPARATOR; 24 PyObject *NS_SEPARATOR;
25 PyObject *FILTER_SEPARATOR; 25 PyObject *FILTER_SEPARATOR;
26 PyObject *EMPTY_FILTER;
27 PyObject *NONE_FILTER;
26 PyObject *EMPTY_STR; 28 PyObject *EMPTY_STR;
27 PyObject *QUOTE_MAP; 29 PyObject *QUOTE_MAP;
30 PyObject *MISSING;
31 PyObject *STARTTOK;
32 PyObject *ENDTOK;
28 }; 33 };
29 34
30 35
31 static 36 static
32 int 37 int
682 PyTuple_SetItem(res, 1, o1); /* steals */ 687 PyTuple_SetItem(res, 1, o1); /* steals */
683 return res; 688 return res;
684 } 689 }
685 690
686 691
692 static
693 PyObject *
694 fast_interpolate_variables(PyObject *self, PyObject *args)
695 {
696 PyObject *config;
697 PyObject *s;
698 PyObject *cache;
699
700 Py_ssize_t s_len;
701 Py_ssize_t idx;
702 Py_ssize_t i, j;
703 PyObject *parts = NULL;
704 Py_ssize_t parts_len;
705 PyObject *res_parts = NULL;
706 PyObject *res = NULL;
707 PyObject *tmp;
708 PyObject *tmp2;
709 PyObject *pb;
710 Py_ssize_t pb_len;
711 PyObject *varname = NULL;
712 PyObject *varvalue = NULL;
713 PyObject *filters = NULL;
714 int cacheable;
715 int use_cache = 1;
716 int first_part_is_empty;
717 PyObject *err_type;
718 PyObject *err_value;
719 PyObject *err_tb;
720 struct speedups_state *sstate;
721
722 if (!PyArg_UnpackTuple(args, "s", 3, 3, &config, &s, &cache)) {
723 return NULL;
724 }
725 s_len = PyUnicode_GetLength(s); /* also an implicit type check */
726 if (s_len < 0) {
727 return NULL;
728 }
729 if (s_len < 4) {
730 PyErr_Clear();
731 Py_INCREF(s);
732 return s;
733 }
734 sstate = PyModule_GetState(self);
735 if (sstate == NULL) {
736 PyErr_SetString(PyExc_RuntimeError, "no module state available");
737 return NULL;
738 }
739
740 idx = PyUnicode_Find(s, sstate->STARTTOK, 0, s_len, 1);
741 if (idx < 0) {
742 PyErr_Clear();
743 Py_INCREF(s);
744 return s;
745 }
746
747 res = PyDict_GetItem(cache, s); /* borrowed */
748 if (res != NULL) {
749 if (res == sstate->MISSING) {
750 return PyErr_Format(
751 PyExc_KeyError,
752 "Cannot interpolate variables in string %R (cached)",
753 s);
754 }
755 else {
756 Py_INCREF(res);
757 return res;
758 }
759 }
760
761 parts = PyUnicode_Split(s, sstate->STARTTOK, -1);
762 if (parts == NULL) {
763 goto error;
764 }
765 parts_len = PyList_Size(parts);
766 if (parts_len < 0) {
767 goto error;
768 }
769 res_parts = PyList_New(1);
770 if (res_parts == NULL) {
771 goto error;
772 }
773
774 tmp = PyList_GetItem(parts, 0); /* borrowed */
775 if (tmp == NULL) {
776 goto error;
777 }
778 /*
779 * The first item may be also the empty string if `s' starts with
780 * an interpolation token.
781 */
782 first_part_is_empty = PyObject_Not(tmp);
783 Py_INCREF(tmp); /* because PyList_SetItem steals -- and o is borrowed */
784 PyList_SetItem(res_parts, 0, tmp); /* steals -- cannot fail */
785 tmp = NULL;
786
787 for (i=1; i<parts_len; i++) {
788 pb = PyList_GetItem(parts, i); /* borrowed */
789 pb_len = PyUnicode_GetLength(pb);
790 if (pb_len < 0) {
791 goto error;
792 }
793 idx = PyUnicode_Find(pb, sstate->ENDTOK, 0, pb_len, 1);
794 if (idx < 0) {
795 /*
796 * Behave similar to the pure-Python version: copy the complete
797 * rest as-is. Also include the start tokens!
798 */
799 if (PyList_Append(res_parts, sstate->STARTTOK) < 0) {
800 goto error;
801 }
802 if (PyList_Append(res_parts, pb) < 0) {
803 goto error;
804 }
805 for (j=i+1; j<parts_len; j++) {
806 if (PyList_Append(res_parts, sstate->STARTTOK) < 0) {
807 goto error;
808 }
809 pb = PyList_GetItem(parts, j); /* borrowed */
810 if (PyList_Append(res_parts, pb) < 0) {
811 goto error;
812 }
813 }
814 break; /* the for-loop */
815 }
816
817 varname = PyUnicode_Substring(pb, 0, idx);
818 if (varname == NULL) {
819 goto error;
820 }
821
822 tmp = _fast_split_filters(varname, NULL, sstate);
823 if (tmp == NULL) {
824 goto error;
825 }
826 if (PyTuple_Size(tmp) != 2) {
827 PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
828 Py_DECREF(tmp);
829 goto error;
830 }
831 /* Unpack the result tuple */
832 tmp2 = PyTuple_GetItem(tmp, 0); /* borrowed -- cannot fail */
833 Py_DECREF(varname);
834 Py_INCREF(tmp2);
835 varname = tmp2;
836 tmp2 = PyTuple_GetItem(tmp, 1); /* borrowed -- cannot fail */
837 Py_INCREF(tmp2);
838 filters = tmp2;
839 Py_DECREF(tmp);
840 tmp = tmp2 = NULL;
841
842 tmp = PyObject_CallMethod(
843 config, "_getvar_s_with_cache_info", "O", varname);
844 if (tmp == NULL) {
845 if (PyErr_ExceptionMatches(PyExc_KeyError)) {
846 /* XXX TBD handle KeyError (None and Empty-filter) */
847 cacheable = 1;
848 if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
849 PyErr_Clear();
850 Py_INCREF(Py_None);
851 varvalue = Py_None;
852 }
853 else {
854 if (PySequence_Contains(filters, sstate->EMPTY_FILTER) == 1) {
855 PyErr_Clear();
856 Py_INCREF(sstate->EMPTY_STR);
857 varvalue = sstate->EMPTY_STR;
858 }
859 else {
860 PyErr_Fetch(&err_type, &err_value, &err_tb);
861 /* this does NOT steal */
862 PyDict_SetItem(cache, s, sstate->MISSING);
863 PyErr_Restore(err_type, err_value, err_tb);
864 goto error;
865 }
866 }
867 }
868 else {
869 /* other exception/error than KeyError */
870 goto error;
871 }
872 }
873 else {
874 if (PyTuple_Size(tmp) != 2) {
875 Py_DECREF(tmp);
876 PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
877 goto error;
878 }
879 /* unpack the result */
880 varvalue = PyTuple_GetItem(tmp, 0); /* borrowed -- but want own */
881 Py_INCREF(varvalue);
882 cacheable = PyObject_IsTrue(PyTuple_GetItem(tmp, 1));
883 Py_DECREF(tmp); tmp = NULL;
884 }
885
886 if (!cacheable) {
887 use_cache = 0;
888 }
889
890 Py_DECREF(varname); varname = NULL;
891
892 tmp = PyObject_CallMethod(
893 config, "_apply_filters", "OO", filters, varvalue);
894 if (tmp == NULL) {
895 goto error;
896 }
897 Py_DECREF(varvalue);
898 varvalue = tmp;
899 tmp = NULL;
900
901 /*
902 * Dont apply and type conversions to the variable value if
903 * the whole `s` is just one expansion
904 */
905 if (first_part_is_empty && (i == 1) && (pb_len == s_len - 2) && (idx == pb_len - 2)) {
906 res = varvalue; varvalue = NULL;
907 goto success; /* break out early */
908 }
909 if (varvalue != Py_None) {
910 tmp = PyObject_Str(varvalue);
911 if (tmp == NULL) {
912 goto error;
913 }
914 if (PyList_Append(res_parts, tmp) < 0) {
915 Py_DECREF(tmp);
916 goto error;
917 }
918 Py_DECREF(tmp);
919 }
920 Py_DECREF(varvalue); varvalue = NULL;
921 /* append the rest of the string */
922 tmp = PyUnicode_Substring(pb, idx+2, pb_len);
923 if (tmp == NULL) {
924 goto error;
925 }
926 if (PyList_Append(res_parts, tmp) < 0) {
927 Py_DECREF(tmp);
928 goto error;
929 }
930 Py_DECREF(tmp); tmp = NULL;
931 }
932
933 res = PyUnicode_Join(sstate->EMPTY_STR, res_parts);
934 if (res == NULL) {
935 goto error;
936 }
937
938 success:
939 Py_DECREF(parts);
940 Py_DECREF(res_parts);
941 Py_XDECREF(filters);
942
943 if (use_cache) {
944 PyDict_SetItem(cache, s, res);
945 PyErr_Clear(); /* clear any possible cache-related error */
946 }
947 return res;
948
949 error:
950 Py_XDECREF(varname);
951 Py_XDECREF(varvalue);
952 Py_XDECREF(parts);
953 Py_XDECREF(res_parts);
954 Py_XDECREF(res);
955 Py_XDECREF(filters);
956 return NULL;
957 }
958
959
960 static
961 PyObject *
962 sync_MISSING(PyObject *self, PyObject *missing)
963 {
964 struct speedups_state *sstate;
965
966 sstate = PyModule_GetState(self);
967 if (sstate == NULL) {
968 PyErr_SetString(PyExc_RuntimeError, "no module state available");
969 return NULL;
970 }
971 if (sstate->MISSING != NULL) {
972 PyErr_SetString(PyExc_RuntimeError, "_MISSING already set");
973 return NULL;
974 }
975 Py_INCREF(missing);
976 sstate->MISSING = missing;
977 Py_RETURN_NONE;
978 }
979
980
687 static struct PyMethodDef speedups_methods[] = { 981 static struct PyMethodDef speedups_methods[] = {
688 {"fast_unquote", fast_unquote, METH_O, PyDoc_STR("C-implementation of configmix.unquote")}, 982 {"fast_unquote", fast_unquote, METH_O, PyDoc_STR("C-implementation of configmix.unquote")},
689 {"fast_quote", fast_quote, METH_O, PyDoc_STR("C-implementation of configmix.quote")}, 983 {"fast_quote", fast_quote, METH_O, PyDoc_STR("C-implementation of configmix.quote")},
690 {"fast_pathstr2path", fast_pathstr2path, METH_O, PyDoc_STR("C-implementation of configmix.pathstr2path")}, 984 {"fast_pathstr2path", fast_pathstr2path, METH_O, PyDoc_STR("C-implementation of configmix.pathstr2path")},
691 {"_fast_split_filters", fast_split_filters, METH_O, PyDoc_STR("C-implementation of configmix.config._split_filters")}, 985 {"_fast_split_filters", fast_split_filters, METH_O, PyDoc_STR("C-implementation of configmix.config._split_filters")},
692 {"_fast_split_ns", fast_split_ns, METH_O, PyDoc_STR("C-implementation of configmix.config._split_ns")}, 986 {"_fast_split_ns", fast_split_ns, METH_O, PyDoc_STR("C-implementation of configmix.config._split_ns")},
987 {"_fast_interpolate_variables", fast_interpolate_variables, METH_VARARGS, PyDoc_STR("C-implementation of configmix.config.Configuration.interpolate_variables")},
988 {"_sync_MISSING", sync_MISSING, METH_O, PyDoc_STR("Internal function to easily sync the _MISSING object with configmix.config")},
989
693 {NULL, NULL, 0, NULL} 990 {NULL, NULL, 0, NULL}
694 }; 991 };
695 992
696 #define STRINGIFY(s) #s 993 #define STRINGIFY(s) #s
697 #define XSTRINGIFY(s) STRINGIFY(s) 994 #define XSTRINGIFY(s) STRINGIFY(s)
736 sstate->FILTER_SEPARATOR = PyUnicode_FromStringAndSize("|", 1); 1033 sstate->FILTER_SEPARATOR = PyUnicode_FromStringAndSize("|", 1);
737 if (sstate->FILTER_SEPARATOR == NULL) { 1034 if (sstate->FILTER_SEPARATOR == NULL) {
738 return -1; 1035 return -1;
739 } 1036 }
740 PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR)); 1037 PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR));
1038
1039 sstate->EMPTY_FILTER = PyUnicode_FromStringAndSize("Empty", 5);
1040 if (sstate->EMPTY_FILTER == NULL) {
1041 return -1;
1042 }
1043 PyUnicode_InternInPlace(&(sstate->EMPTY_FILTER));
1044
1045 sstate->NONE_FILTER = PyUnicode_FromStringAndSize("None", 4);
1046 if (sstate->NONE_FILTER == NULL) {
1047 return -1;
1048 }
1049 PyUnicode_InternInPlace(&(sstate->NONE_FILTER));
741 1050
742 sstate->EMPTY_STR = PyUnicode_FromStringAndSize("", 0); 1051 sstate->EMPTY_STR = PyUnicode_FromStringAndSize("", 0);
743 if (sstate->EMPTY_STR == NULL) { 1052 if (sstate->EMPTY_STR == NULL) {
744 return -1; 1053 return -1;
745 } 1054 }
760 0x5d, "%x5d"); 1069 0x5d, "%x5d");
761 if (sstate->QUOTE_MAP == NULL) { 1070 if (sstate->QUOTE_MAP == NULL) {
762 return -1; 1071 return -1;
763 } 1072 }
764 1073
1074 sstate->STARTTOK = PyUnicode_FromStringAndSize("{{", 2);
1075 if (sstate->STARTTOK == NULL) {
1076 return -1;
1077 }
1078 PyUnicode_InternInPlace(&(sstate->STARTTOK));
1079
1080 sstate->ENDTOK = PyUnicode_FromStringAndSize("}}", 2);
1081 if (sstate->ENDTOK == NULL) {
1082 return -1;
1083 }
1084 PyUnicode_InternInPlace(&(sstate->ENDTOK));
1085
765 return 0; 1086 return 0;
766 } 1087 }
767 1088
768 1089
769 static 1090 static
775 if (sstate != NULL) { 1096 if (sstate != NULL) {
776 Py_VISIT(sstate->DOT); 1097 Py_VISIT(sstate->DOT);
777 Py_VISIT(sstate->QUOTE); 1098 Py_VISIT(sstate->QUOTE);
778 Py_VISIT(sstate->NS_SEPARATOR); 1099 Py_VISIT(sstate->NS_SEPARATOR);
779 Py_VISIT(sstate->FILTER_SEPARATOR); 1100 Py_VISIT(sstate->FILTER_SEPARATOR);
1101 Py_VISIT(sstate->EMPTY_FILTER);
1102 Py_VISIT(sstate->NONE_FILTER);
780 Py_VISIT(sstate->EMPTY_STR); 1103 Py_VISIT(sstate->EMPTY_STR);
781 Py_VISIT(sstate->QUOTE_MAP); 1104 Py_VISIT(sstate->QUOTE_MAP);
1105 Py_VISIT(sstate->MISSING);
1106 Py_VISIT(sstate->STARTTOK);
1107 Py_VISIT(sstate->ENDTOK);
782 } 1108 }
783 return 0; 1109 return 0;
784 } 1110 }
785 1111
786 1112
793 if (sstate != NULL) { 1119 if (sstate != NULL) {
794 Py_CLEAR(sstate->DOT); 1120 Py_CLEAR(sstate->DOT);
795 Py_CLEAR(sstate->QUOTE); 1121 Py_CLEAR(sstate->QUOTE);
796 Py_CLEAR(sstate->NS_SEPARATOR); 1122 Py_CLEAR(sstate->NS_SEPARATOR);
797 Py_CLEAR(sstate->FILTER_SEPARATOR); 1123 Py_CLEAR(sstate->FILTER_SEPARATOR);
1124 Py_CLEAR(sstate->EMPTY_FILTER);
1125 Py_CLEAR(sstate->NONE_FILTER);
798 Py_CLEAR(sstate->EMPTY_STR); 1126 Py_CLEAR(sstate->EMPTY_STR);
799 Py_CLEAR(sstate->QUOTE_MAP); 1127 Py_CLEAR(sstate->QUOTE_MAP);
1128 Py_CLEAR(sstate->MISSING);
1129 Py_CLEAR(sstate->STARTTOK);
1130 Py_CLEAR(sstate->ENDTOK);
800 } 1131 }
801 return 0; 1132 return 0;
802 } 1133 }
803 1134
804 1135