aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile251
-rw-r--r--README38
-rw-r--r--common.h632
-rw-r--r--config.mk19
-rw-r--r--libnormalform.794
-rw-r--r--libnormalform.h4213
-rw-r--r--libnormalform_all.c452
-rw-r--r--libnormalform_and.c578
-rw-r--r--libnormalform_and2.c82
-rw-r--r--libnormalform_and2__.c119
-rw-r--r--libnormalform_and_checked.c14
-rw-r--r--libnormalform_andl.c14
-rw-r--r--libnormalform_andl_checked.c14
-rw-r--r--libnormalform_andl_macro_test.c5
-rw-r--r--libnormalform_any.c451
-rw-r--r--libnormalform_clone.c558
-rw-r--r--libnormalform_empty.c69
-rw-r--r--libnormalform_evaluate.c44
-rw-r--r--libnormalform_existentially.c235
-rw-r--r--libnormalform_exists.c243
-rw-r--r--libnormalform_express.c849
-rw-r--r--libnormalform_false.c155
-rw-r--r--libnormalform_free.c123
-rw-r--r--libnormalform_from_string.c918
-rw-r--r--libnormalform_function.c287
-rw-r--r--libnormalform_if.c249
-rw-r--r--libnormalform_if2.c14
-rw-r--r--libnormalform_if_checked.c14
-rw-r--r--libnormalform_ifl.c14
-rw-r--r--libnormalform_ifl_checked.c14
-rw-r--r--libnormalform_ifl_macro_test.c5
-rw-r--r--libnormalform_imply.c166
-rw-r--r--libnormalform_imply2.c14
-rw-r--r--libnormalform_imply_checked.c14
-rw-r--r--libnormalform_implyl.c14
-rw-r--r--libnormalform_implyl_checked.c14
-rw-r--r--libnormalform_implyl_macro_test.c5
-rw-r--r--libnormalform_nand.c235
-rw-r--r--libnormalform_nand2.c14
-rw-r--r--libnormalform_nand_checked.c14
-rw-r--r--libnormalform_nandl.c14
-rw-r--r--libnormalform_nandl_checked.c14
-rw-r--r--libnormalform_nandl_macro_test.c5
-rw-r--r--libnormalform_nexists.c243
-rw-r--r--libnormalform_nif.c228
-rw-r--r--libnormalform_nif2.c14
-rw-r--r--libnormalform_nif_checked.c14
-rw-r--r--libnormalform_nifl.c14
-rw-r--r--libnormalform_nifl_checked.c14
-rw-r--r--libnormalform_nifl_macro_test.c5
-rw-r--r--libnormalform_nimply.c172
-rw-r--r--libnormalform_nimply2.c14
-rw-r--r--libnormalform_nimply_checked.c14
-rw-r--r--libnormalform_nimplyl.c14
-rw-r--r--libnormalform_nimplyl_checked.c14
-rw-r--r--libnormalform_nimplyl_macro_test.c5
-rw-r--r--libnormalform_nonempty.c69
-rw-r--r--libnormalform_nor.c235
-rw-r--r--libnormalform_nor2.c14
-rw-r--r--libnormalform_nor_checked.c14
-rw-r--r--libnormalform_norl.c14
-rw-r--r--libnormalform_norl_checked.c14
-rw-r--r--libnormalform_norl_macro_test.c5
-rw-r--r--libnormalform_not.c115
-rw-r--r--libnormalform_one.c449
-rw-r--r--libnormalform_or.c562
-rw-r--r--libnormalform_or2.c82
-rw-r--r--libnormalform_or2__.c119
-rw-r--r--libnormalform_or_checked.c14
-rw-r--r--libnormalform_orl.c14
-rw-r--r--libnormalform_orl_checked.c14
-rw-r--r--libnormalform_orl_macro_test.c5
-rw-r--r--libnormalform_ref.c57
-rw-r--r--libnormalform_reset_indices_and_counts__.c44
-rw-r--r--libnormalform_set_indices_and_counts__.c56
-rw-r--r--libnormalform_singleton.c71
-rw-r--r--libnormalform_to_string.c519
-rw-r--r--libnormalform_transformation.c419
-rw-r--r--libnormalform_true.c156
-rw-r--r--libnormalform_unique.c234
-rw-r--r--libnormalform_uniquely.c224
-rw-r--r--libnormalform_universally.c233
-rw-r--r--libnormalform_vand.c14
-rw-r--r--libnormalform_vand_checked.c14
-rw-r--r--libnormalform_variable.c153
-rw-r--r--libnormalform_vif.c14
-rw-r--r--libnormalform_vif_checked.c14
-rw-r--r--libnormalform_vimply.c14
-rw-r--r--libnormalform_vimply_checked.c14
-rw-r--r--libnormalform_vnand.c14
-rw-r--r--libnormalform_vnand_checked.c14
-rw-r--r--libnormalform_vnif.c14
-rw-r--r--libnormalform_vnif_checked.c14
-rw-r--r--libnormalform_vnimply.c14
-rw-r--r--libnormalform_vnimply_checked.c14
-rw-r--r--libnormalform_vnor.c14
-rw-r--r--libnormalform_vnor_checked.c14
-rw-r--r--libnormalform_vor.c14
-rw-r--r--libnormalform_vor_checked.c14
-rw-r--r--libnormalform_vxnor.c14
-rw-r--r--libnormalform_vxnor_checked.c14
-rw-r--r--libnormalform_vxor.c14
-rw-r--r--libnormalform_vxor_checked.c14
-rw-r--r--libnormalform_xnor.c437
-rw-r--r--libnormalform_xnor2.c14
-rw-r--r--libnormalform_xnor_checked.c14
-rw-r--r--libnormalform_xnorl.c14
-rw-r--r--libnormalform_xnorl_checked.c14
-rw-r--r--libnormalform_xnorl_macro_test.c5
-rw-r--r--libnormalform_xor.c710
-rw-r--r--libnormalform_xor2.c63
-rw-r--r--libnormalform_xor2__.c174
-rw-r--r--libnormalform_xor_checked.c14
-rw-r--r--libnormalform_xorl.c14
-rw-r--r--libnormalform_xorl_checked.c14
-rw-r--r--libnormalform_xorl_macro_test.c5
l---------man3/LIBNORMALFORM_AND.31
l---------man3/LIBNORMALFORM_IF.31
l---------man3/LIBNORMALFORM_IMPLY.31
l---------man3/LIBNORMALFORM_NAND.31
l---------man3/LIBNORMALFORM_NIF.31
l---------man3/LIBNORMALFORM_NIMPLY.31
l---------man3/LIBNORMALFORM_NOR.31
l---------man3/LIBNORMALFORM_OR.31
-rw-r--r--man3/LIBNORMALFORM_SENTENCE.361
l---------man3/LIBNORMALFORM_XNOR.31
l---------man3/LIBNORMALFORM_XOR.31
l---------man3/enum_libnormalform_builtin_transformer.31
l---------man3/enum_libnormalform_value.31
-rw-r--r--man3/libnormalform_all.3202
-rw-r--r--man3/libnormalform_and.3233
l---------man3/libnormalform_and2.31
l---------man3/libnormalform_and_checked.31
l---------man3/libnormalform_andl.31
l---------man3/libnormalform_andl_checked.31
-rw-r--r--man3/libnormalform_any.3294
l---------man3/libnormalform_builtin_transformer.31
-rw-r--r--man3/libnormalform_clone.3104
l---------man3/libnormalform_empty.31
-rw-r--r--man3/libnormalform_evaluate.378
l---------man3/libnormalform_existentially.31
l---------man3/libnormalform_exists.31
-rw-r--r--man3/libnormalform_false.370
-rw-r--r--man3/libnormalform_free.373
-rw-r--r--man3/libnormalform_from_string.3265
-rw-r--r--man3/libnormalform_function.3152
-rw-r--r--man3/libnormalform_if.3233
l---------man3/libnormalform_if2.31
l---------man3/libnormalform_if_checked.31
l---------man3/libnormalform_ifl.31
l---------man3/libnormalform_ifl_checked.31
-rw-r--r--man3/libnormalform_imply.3236
l---------man3/libnormalform_imply2.31
l---------man3/libnormalform_imply_checked.31
l---------man3/libnormalform_implyl.31
l---------man3/libnormalform_implyl_checked.31
l---------man3/libnormalform_map.31
l---------man3/libnormalform_mapping.31
-rw-r--r--man3/libnormalform_nand.3250
l---------man3/libnormalform_nand2.31
l---------man3/libnormalform_nand_checked.31
l---------man3/libnormalform_nandl.31
l---------man3/libnormalform_nandl_checked.31
l---------man3/libnormalform_nexists.31
-rw-r--r--man3/libnormalform_nif.3239
l---------man3/libnormalform_nif2.31
l---------man3/libnormalform_nif_checked.31
l---------man3/libnormalform_nifl.31
l---------man3/libnormalform_nifl_checked.31
-rw-r--r--man3/libnormalform_nimply.3241
l---------man3/libnormalform_nimply2.31
l---------man3/libnormalform_nimply_checked.31
l---------man3/libnormalform_nimplyl.31
l---------man3/libnormalform_nimplyl_checked.31
l---------man3/libnormalform_nonempty.31
-rw-r--r--man3/libnormalform_nor.3245
l---------man3/libnormalform_nor2.31
l---------man3/libnormalform_nor_checked.31
l---------man3/libnormalform_norl.31
l---------man3/libnormalform_norl_checked.31
-rw-r--r--man3/libnormalform_not.392
-rw-r--r--man3/libnormalform_one.3253
-rw-r--r--man3/libnormalform_or.3233
l---------man3/libnormalform_or2.31
l---------man3/libnormalform_or_checked.31
l---------man3/libnormalform_orl.31
l---------man3/libnormalform_orl_checked.31
-rw-r--r--man3/libnormalform_ref.3111
l---------man3/libnormalform_representation_spec.31
l---------man3/libnormalform_sentence.31
l---------man3/libnormalform_singleton.31
-rw-r--r--man3/libnormalform_to_string.3105
-rw-r--r--man3/libnormalform_transformation.3245
l---------man3/libnormalform_transformer.31
-rw-r--r--man3/libnormalform_true.370
l---------man3/libnormalform_unique.31
l---------man3/libnormalform_uniquely.31
l---------man3/libnormalform_universally.31
l---------man3/libnormalform_value.31
l---------man3/libnormalform_vand.31
l---------man3/libnormalform_vand_checked.31
-rw-r--r--man3/libnormalform_variable.3127
l---------man3/libnormalform_vif.31
l---------man3/libnormalform_vif_checked.31
l---------man3/libnormalform_vimply.31
l---------man3/libnormalform_vimply_checked.31
l---------man3/libnormalform_vnand.31
l---------man3/libnormalform_vnand_checked.31
l---------man3/libnormalform_vnif.31
l---------man3/libnormalform_vnif_checked.31
l---------man3/libnormalform_vnimply.31
l---------man3/libnormalform_vnimply_checked.31
l---------man3/libnormalform_vnor.31
l---------man3/libnormalform_vnor_checked.31
l---------man3/libnormalform_vor.31
l---------man3/libnormalform_vor_checked.31
l---------man3/libnormalform_vxnor.31
l---------man3/libnormalform_vxnor_checked.31
l---------man3/libnormalform_vxor.31
l---------man3/libnormalform_vxor_checked.31
-rw-r--r--man3/libnormalform_xnor.3245
l---------man3/libnormalform_xnor2.31
l---------man3/libnormalform_xnor_checked.31
l---------man3/libnormalform_xnorl.31
l---------man3/libnormalform_xnorl_checked.31
-rw-r--r--man3/libnormalform_xor.3242
l---------man3/libnormalform_xor2.31
l---------man3/libnormalform_xor_checked.31
l---------man3/libnormalform_xorl.31
l---------man3/libnormalform_xorl_checked.31
l---------man3/struct_libnormalform_function.31
l---------man3/struct_libnormalform_map.31
l---------man3/struct_libnormalform_mapping.31
l---------man3/struct_libnormalform_representation_spec.31
l---------man3/struct_libnormalform_sentence.31
l---------man3/struct_libnormalform_transformer.31
l---------man3/struct_libnormalform_variable.31
-rw-r--r--memcheck.c638
-rw-r--r--memcheck.h11
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
244 files changed, 23224 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58ba5bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+*.t
+*.to
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fccd785
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2024 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..30e8344
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,251 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = normalform
+
+
+OBJ_PUBLIC =\
+ libnormalform_ref.o\
+ libnormalform_free.o\
+ libnormalform_clone.o\
+ libnormalform_evaluate.o\
+ libnormalform_express.o\
+ libnormalform_to_string.o\
+ libnormalform_from_string.o\
+ libnormalform_variable.o\
+ libnormalform_function.o\
+ libnormalform_transformation.o\
+ libnormalform_true.o\
+ libnormalform_false.o\
+ libnormalform_not.o\
+ libnormalform_all.o\
+ libnormalform_any.o\
+ libnormalform_one.o\
+ libnormalform_and.o\
+ libnormalform_or.o\
+ libnormalform_xor.o\
+ libnormalform_if.o\
+ libnormalform_imply.o\
+ libnormalform_nand.o\
+ libnormalform_nor.o\
+ libnormalform_xnor.o\
+ libnormalform_nif.o\
+ libnormalform_nimply.o\
+ libnormalform_exists.o\
+ libnormalform_nexists.o\
+ libnormalform_unique.o\
+ libnormalform_existentially.o\
+ libnormalform_universally.o\
+ libnormalform_uniquely.o\
+ libnormalform_empty.o\
+ libnormalform_nonempty.o\
+ libnormalform_singleton.o\
+ libnormalform_and_checked.o\
+ libnormalform_or_checked.o\
+ libnormalform_xor_checked.o\
+ libnormalform_if_checked.o\
+ libnormalform_imply_checked.o\
+ libnormalform_nand_checked.o\
+ libnormalform_nor_checked.o\
+ libnormalform_xnor_checked.o\
+ libnormalform_nif_checked.o\
+ libnormalform_nimply_checked.o\
+ libnormalform_vand.o\
+ libnormalform_vor.o\
+ libnormalform_vxor.o\
+ libnormalform_vif.o\
+ libnormalform_vimply.o\
+ libnormalform_vnand.o\
+ libnormalform_vnor.o\
+ libnormalform_vxnor.o\
+ libnormalform_vnif.o\
+ libnormalform_vnimply.o\
+ libnormalform_andl.o\
+ libnormalform_orl.o\
+ libnormalform_xorl.o\
+ libnormalform_ifl.o\
+ libnormalform_implyl.o\
+ libnormalform_nandl.o\
+ libnormalform_norl.o\
+ libnormalform_xnorl.o\
+ libnormalform_nifl.o\
+ libnormalform_nimplyl.o\
+ libnormalform_vand_checked.o\
+ libnormalform_vor_checked.o\
+ libnormalform_vxor_checked.o\
+ libnormalform_vif_checked.o\
+ libnormalform_vimply_checked.o\
+ libnormalform_vnand_checked.o\
+ libnormalform_vnor_checked.o\
+ libnormalform_vxnor_checked.o\
+ libnormalform_vnif_checked.o\
+ libnormalform_vnimply_checked.o\
+ libnormalform_andl_checked.o\
+ libnormalform_orl_checked.o\
+ libnormalform_xorl_checked.o\
+ libnormalform_ifl_checked.o\
+ libnormalform_implyl_checked.o\
+ libnormalform_nandl_checked.o\
+ libnormalform_norl_checked.o\
+ libnormalform_xnorl_checked.o\
+ libnormalform_nifl_checked.o\
+ libnormalform_nimplyl_checked.o\
+ libnormalform_and2.o\
+ libnormalform_or2.o\
+ libnormalform_xor2.o\
+ libnormalform_if2.o\
+ libnormalform_imply2.o\
+ libnormalform_nand2.o\
+ libnormalform_nor2.o\
+ libnormalform_xnor2.o\
+ libnormalform_nif2.o\
+ libnormalform_nimply2.o\
+
+OBJ =\
+ $(OBJ_PUBLIC)\
+ libnormalform_and2__.o\
+ libnormalform_or2__.o\
+ libnormalform_xor2__.o\
+ libnormalform_set_indices_and_counts__.o\
+ libnormalform_reset_indices_and_counts__.o
+
+TOBJ = $(OBJ:.o=.to)\
+ libnormalform_andl_macro_test.to\
+ libnormalform_orl_macro_test.to\
+ libnormalform_xorl_macro_test.to\
+ libnormalform_ifl_macro_test.to\
+ libnormalform_implyl_macro_test.to\
+ libnormalform_nandl_macro_test.to\
+ libnormalform_norl_macro_test.to\
+ libnormalform_xnorl_macro_test.to\
+ libnormalform_nifl_macro_test.to\
+ libnormalform_nimplyl_macro_test.to
+
+HDR =\
+ libnormalform.h
+
+MAN3 =\
+ $(OBJ_PUBLIC:.o=.3)\
+ LIBNORMALFORM_AND.3\
+ LIBNORMALFORM_OR.3\
+ LIBNORMALFORM_XOR.3\
+ LIBNORMALFORM_IF.3\
+ LIBNORMALFORM_IMPLY.3\
+ LIBNORMALFORM_NAND.3\
+ LIBNORMALFORM_NOR.3\
+ LIBNORMALFORM_XNOR.3\
+ LIBNORMALFORM_NIF.3\
+ LIBNORMALFORM_NIMPLY.3\
+ struct_libnormalform_map.3\
+ libnormalform_map.3\
+ struct_libnormalform_mapping.3\
+ libnormalform_mapping.3\
+ struct_libnormalform_representation_spec.3\
+ libnormalform_representation_spec.3\
+ struct_libnormalform_transformer.3\
+ libnormalform_transformer.3\
+ enum_libnormalform_builtin_transformer.3\
+ libnormalform_builtin_transformer.3\
+ struct_libnormalform_function.3\
+ struct_libnormalform_variable.3\
+ enum_libnormalform_value.3\
+ libnormalform_value.3\
+ LIBNORMALFORM_SENTENCE.3\
+ struct_libnormalform_sentence.3\
+ libnormalform_sentence.3
+
+LOBJ = $(OBJ:.o=.lo)
+TEST = $(TOBJ:.to=.t)
+
+
+all: libnormalform.a libnormalform.$(LIBEXT) $(TEST)
+$(OBJ): $(HDR) common.h
+$(LOBJ): $(HDR) common.h
+$(TOBJ): $(HDR) common.h
+$(TEST): libnormalform.a $(MEMCHECK)
+memcheck.to: memcheck.h
+
+L = libnormalform
+C = checked
+
+$L_and2.to $L_andl.to $L_vand.to $L_and_$C.to $L_andl_$C.to $L_vand_$C.to $L_andl_macro_test.to : $L_and.c
+$L_or2.to $L_orl.to $L_vor.to $L_or_$C.to $L_orl_$C.to $L_vor_$C.to $L_orl_macro_test.to : $L_or.c
+$L_xor2.to $L_xorl.to $L_vxor.to $L_xor_$C.to $L_xorl_$C.to $L_vxor_$C.to $L_xorl_macro_test.to : $L_xor.c
+$L_if2.to $L_ifl.to $L_vif.to $L_if_$C.to $L_ifl_$C.to $L_vif_$C.to $L_ifl_macro_test.to : $L_if.c
+$L_imply2.to $L_implyl.to $L_vimply.to $L_imply_$C.to $L_implyl_$C.to $L_vimply_$C.to $L_implyl_macro_test.to : $L_imply.c
+$L_nand2.to $L_nandl.to $L_vnand.to $L_nand_$C.to $L_nandl_$C.to $L_vnand_$C.to $L_nandl_macro_test.to : $L_nand.c
+$L_nor2.to $L_norl.to $L_vnor.to $L_nor_$C.to $L_norl_$C.to $L_vnor_$C.to $L_norl_macro_test.to : $L_nor.c
+$L_xnor2.to $L_xnorl.to $L_vxnor.to $L_xnor_$C.to $L_xnorl_$C.to $L_vxnor_$C.to $L_xnorl_macro_test.to : $L_xnor.c
+$L_nif2.to $L_nifl.to $L_vnif.to $L_nif_$C.to $L_nifl_$C.to $L_vnif_$C.to $L_nifl_macro_test.to : $L_nif.c
+$L_nimply2.to $L_nimplyl.to $L_vnimply.to $L_nimply_$C.to $L_nimplyl_$C.to $L_vnimply_$C.to $L_nimplyl_macro_test.to : $L_nimply.c
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.to:
+ $(CC) -DTEST -DTEST_TIMEOUT_SECONDS=$(TEST_TIMEOUT_SECONDS) -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS)
+
+.to.t:
+ $(CC) -o $@ $< libnormalform.a $(MEMCHECK_OBJ) $(TEST_LDFLAGS)
+
+libnormalform.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+libnormalform.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: $(TEST)
+ set -e;\
+ for t in $(TEST:.t=); do\
+ printf 'Testing %s\n' "$$t" >&2;\
+ $(CHECK_PREFIX) ./$$t.t;\
+ done
+
+install: libnormalform.a libnormalform.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libnormalform.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libnormalform.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBMINOREXT)"
+ ln -sf -- libnormalform.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBMAJOREXT)"
+ ln -sf -- libnormalform.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBEXT)"
+ cp -- libnormalform.h "$(DESTDIR)$(PREFIX)/include/"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7"
+ cp -- libnormalform.7 "$(DESTDIR)$(MANPREFIX)/man7/"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3"
+ cp -P -- $$(printf 'man3/%s\n' $(MAN3)) "$(DESTDIR)$(MANPREFIX)/man3/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libnormalform.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libnormalform.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libnormalform.h"
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man7/libnormalform.7"
+ -(cd -- "$(DESTDIR)$(MANPREFIX)/man3/" && rm -f -- $(MAN3))
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) *.t *.to
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .t .to
+
+.PHONY: all check install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..37019cc
--- /dev/null
+++ b/README
@@ -0,0 +1,38 @@
+NAME
+ libnormalform - First-order logic sentence canonicalisation library
+
+SYNOPSIS
+ #include <libnormalform.h>
+
+ Link with -lnormalform.
+
+DESCRIPTION
+ The libnormalform library provides a mechanism for expressing
+ first-order logic sentence and normalises them on the fly to
+ negation normal form, and then lets the user choose disjunctive
+ normal form (DNF) or conjunctive normal form (CNF).
+
+ libnormalform support all Boolean connectives: AND, OR, XOR,
+ IMPLY, IF, NAND, NOR, XNOR, NIMPLY, NIF, and NOT, as well as the
+ boolean constants TRUE and FALSE. Additionally, libnormalform
+ supports three standard qualifiers: the universal qualifier
+ (FOR ALL), the existential qualifier (THERE EXISTS), and the
+ unique existential (uniqueness) qualifier (THERE EXISTS ONE).
+ For all binary connectives, libnormalform support simply
+ chaining (e.g. AND(a, b, c) instead of AND(AND(a, b), c)).
+ For all qualifiers, libnormalform supports qualification over
+ an array or an associative array.
+
+ libnormalform also provides application managed boolean
+ variables and functions. These as well as the domains for
+ qualifiers can be set dynamically and the value of the
+ sentence can be evaluate at any time.
+
+ When libnormalform is asked to output the sentences in DNF
+ or CNF, it can relax parts of sentence in case the application
+ wants to express the sentence in a particular form but cannot
+ express all parts of, and therefore needs alternative that is
+ at least as true. While relaxing the sentence, it can eliminiate
+ parts of the sentence that become redundant; to be able to do
+ this, it queries the application for how the application
+ defined variables, functions and domains depend on each other.
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..68df99c
--- /dev/null
+++ b/common.h
@@ -0,0 +1,632 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBNORMALFORM_ALLOW_BAD_WARNINGS
+# define LIBNORMALFORM_ALLOW_BAD_WARNINGS
+#endif
+#include "libnormalform.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+
+#if defined(__GNUC__)
+# define PURE __attribute__((__pure__))
+# define CONST __attribute__((__const__))
+# define HIDDEN __attribute__((__visibility__("hidden")))
+# define USE_RESULT __attribute__((__warn_unused_result__))
+# define NONNULL_INPUT __attribute__((__nonnull__))
+# define SOME_NONNULL_INPUT(...) __attribute__((__nonnull__(__VA_ARGS__)))
+#else
+# define PURE
+# define CONST
+# define HIDDEN
+# define USE_RESULT
+# define NONNULL_INPUT
+# define SOME_NONNULL_INPUT(...)
+#endif
+
+
+/**
+ * Hash used for tautologies and contradictions
+ */
+#define TRUE_FALSE_HASH 1U
+
+/**
+ * Returns the has for a user provided object
+ *
+ * @param A The user provided object
+ */
+#define USER_HASH__(P) ((uintmax_t)(uintptr_t)(P))
+
+/**
+ * Hash generator for literals
+ *
+ * @param A The atomic sentence of the literal
+ */
+#define LITERAL_HASH(A)\
+ _Generic((A),\
+ struct libnormalform_variable *: USER_HASH__((A)),\
+ struct libnormalform_function *: USER_HASH__((A)))
+
+/**
+ * Hash generator for transformations
+ *
+ * @param F:struct libnormalform_transformer * The transformation function
+ * @param S:LIBNORMALFORM_SENTENCE * The sentence
+ */
+#define TRANS_HASH(F, S)\
+ _Generic((F), struct libnormalform_transformer *:\
+ (USER_HASH__(F) ^ (S)->hash))
+
+/**
+ * Hash generator for conjunctions and disjunctions
+ *
+ * @param L:LIBNORMALFORM_SENTENCE * The left-hand term
+ * @param R:LIBNORMALFORM_SENTENCE * The right-hand term
+ */
+#define AND_OR_HASH(L, R) ((L)->hash + (R)->hash)
+
+/**
+ * Hash generator for exclusive disjunctions
+ *
+ * The reason this is twice of conjunction or disjunction
+ * is because a ⊕ b = (a ∨ b) ∧ ¬(a ∧ b) and
+ * (a ∨ b) and ¬(a ∧ b) have the same hashes, and if
+ * if u = (a ∨ b) and v = ¬(a ∧ b), the hash of
+ * u ∧ v is the sum of the hashes of u and v which
+ * are both the sum of the hashes of a and b, thus
+ * twice the hash of a and b.
+ *
+ * @param L:LIBNORMALFORM_SENTENCE * The left-hand term
+ * @param R:LIBNORMALFORM_SENTENCE * The right-hand term
+ */
+#define XOR_HASH(L, R) (AND_OR_HASH(L, R) * 2)
+
+/**
+ * Hash generator for universial and existential qualifiers
+ *
+ * @param D:struct libnormalform_map * The domain
+ * @param K:LIBNORMALFORM_SENTENCE * The antecedent (left-hand term)
+ * @param V:LIBNORMALFORM_SENTENCE * The predicate (right-hand term)
+ */
+#define ANY_ALL_HASH(D, K, V)\
+ _Generic((D), struct libnormalform_map *:\
+ (USER_HASH__(D) * 5 + AND_OR_HASH(K, V) * 3))
+
+/**
+ * Hash generator for uniqueness qualifiers
+ *
+ * @param D:struct libnormalform_map * The domain
+ * @param K:LIBNORMALFORM_SENTENCE * The antecedent (left-hand term)
+ * @param V:LIBNORMALFORM_SENTENCE * The predicate (right-hand term)
+ */
+#define ONE_HASH(D, K, V)\
+ _Generic((D), struct libnormalform_map *:\
+ (USER_HASH__(D) * 5 + AND_OR_HASH(K, V) * 7))
+
+
+/**
+ * Offset for values in `enum type`,
+ * used by `libnormalform_free` to distinguish
+ * `struct libnormalform_sentence *` for
+ * `struct libnormalform_term *`
+*/
+#define SENTENCE_TYPE_OFFSET 1024
+
+
+/**
+ * Sentence classification
+ */
+enum type {
+ TYPE_TRUE = SENTENCE_TYPE_OFFSET, /**< Tautology */
+ TYPE_FALSE, /**< Contradiction */
+ TYPE_VARIABLE, /**< Variable literal */
+ TYPE_FUNCTION, /**< Function literal */
+ TYPE_TRANS, /**< Input transformation */
+ /* Insert new non-branches (leafs and lines) above */
+ TYPE_AND, /**< Conjuction */
+ TYPE_OR, /**< Disjunction */
+ TYPE_XOR, /**< Exclusive disjunction */
+ TYPE_ALL, /**< Univerial qualification */
+ TYPE_ANY, /**< Existential qualification */
+ TYPE_ONE, /**< Uniqueness qualification */
+ TYPE_NOT_ONE /**< Negated uniqueness qualification */
+ /* Insert new binary branches here */
+};
+
+
+struct atom { /* TODO doc */
+ /**
+ * The number of references to the object
+ */
+ size_t refcount;
+};
+
+
+/**
+ * See `LIBNORMALFORM_SENTENCE`
+ */
+struct libnormalform_sentence {
+ /**
+ * Sentence classification
+ */
+ enum type type;
+
+ /**
+ * The number of references to the object
+ */
+ size_t refcount;
+
+ /**
+ * Hash of the sentence
+ *
+ * For any two sentences it is preferably that
+ * they have the same hash if and only if they
+ * can be determined to be equivalent or each
+ * other's inverse
+ */
+ uintmax_t hash;
+
+ struct atom *atom; /* TODO doc */
+
+ /**
+ * Used by some function for sentence analysis,
+ * in particular to index duplicated subsentence
+ */
+ size_t travel_index;
+
+ /**
+ * Used by some function for sentence analysis,
+ * in particular to count duplications
+ */
+ size_t travel_count;
+
+ /**
+ * Used by some function recurse over the sentence
+ */
+ LIBNORMALFORM_SENTENCE *travel_head;
+
+ /**
+ * Create the inverse of the sentence
+ *
+ * @param this The sentence
+ * @return The inverse of the sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Unlike `libnormalform_not`, this function
+ * will not take ownership if `this`, so
+ * its reference count will not be modified
+ */
+ LIBNORMALFORM_SENTENCE *(*inverse)(LIBNORMALFORM_SENTENCE *this);
+
+ /**
+ * Check the equivalence between the sentence and another sentence
+ *
+ * @param this The sentence
+ * @param other The the other sentence
+ * @param inv_out Will be set to 0 if the sentences are equivalent,
+ * and set to 1 if the sentences are each other's inverse
+ * @return 1 if the functions are equal or each other's inverse,
+ * 0 otherwise
+ */
+ int (*equals)(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out);
+
+ /**
+ * Evaluate the truthness of the sentence
+ *
+ * @param this The sentence
+ * @param input See `libnormalform_evaluate`
+ * @return See `libnormalform_evaluate`
+ */
+ int (*evaluate)(LIBNORMALFORM_SENTENCE *this, void *input);
+
+ /**
+ * Type specification information
+ *
+ * Unused if `.type` is `TYPE_TRUE` or `TYPE_FALSE`
+ */
+ union {
+ /**
+ * Used for literals (that is,
+ * if `.type` is `TYPE_VARIABLE` or `TYPE_FUNCTION`)
+ */
+ struct {
+ /**
+ * The literal's atomic sentence
+ */
+ union {
+ struct libnormalform_variable *variable; /**< Use if `.type == TYPE_VARIABLE` */
+ struct libnormalform_function *function; /**< Use if `.type == TYPE_FUNCTION` */
+ } atom;
+
+ /**
+ * Whether the literal is the inverse of its atomic sentence
+ */
+ int inverted;
+ } literal;
+
+ /**
+ * Used for input transformations (that is,
+ * if `.type` is `TYPE_TRANS`)
+ */
+ struct {
+ LIBNORMALFORM_SENTENCE *input; /**< Input */
+ struct libnormalform_transformer *function; /**< Transformation function */
+ } trans;
+
+ /**
+ * Used for binary connectives (that is,
+ * if `.type` is `TYPE_AND`, `TYPE_OR`, or `TYPE_XOR`)
+ *
+ * Traversal code can also use this instead of `.data.qualifer`
+ */
+ struct {
+ LIBNORMALFORM_SENTENCE *l; /**< Left-hand term */
+ LIBNORMALFORM_SENTENCE *r; /**< Right-hand term */
+ } binary;
+
+ /**
+ * Used for qualifiers (that is,
+ * if `.type` is `TYPE_ALL`, `TYPE_ANY`, `TYPE_ONE`, `TYPE_NOT_ONE`)
+ */
+ struct {
+ LIBNORMALFORM_SENTENCE *antecedent; /**< Antecedent */
+ LIBNORMALFORM_SENTENCE *predicate; /**< Predicate */
+ struct libnormalform_map *domain; /**< Domain of interest */
+ } qualifier;
+ } data;
+};
+
+/**
+ * Intial values for `struct libnormalform_sentence`
+ * that should always be used
+ */
+#define PROTOTYPE_COMMON\
+ .refcount = 1U,\
+ .atom = NULL,\
+ .travel_index = 0U,\
+ .travel_count = 0U
+
+
+/**
+ * Helper macro to make macros type self, for macros that take sentences
+ */
+#define REQ_SENTENCE(OBJ, ASTERISKS, EXP) _Generic((OBJ), LIBNORMALFORM_SENTENCE ASTERISKS: (EXP))
+
+/**
+ * Transversal helper macro that return 1
+ * if a sentence has two subsentences
+ *
+ * @param S:LIBNORMALFORM_SENTENCE * The sentence
+ * @return :int 1 if the sentence has two subsentences, 0 otherwise
+ */
+#define IS_BRANCH(S) REQ_SENTENCE((S), *, (S)->type >= TYPE_AND)
+
+/**
+ * Transversal helper macro that returns
+ * the left-hand subsentence (antecedent
+ * for qualifiers)
+ *
+ * @param S:LIBNORMALFORM_SENTENCE * The sentence
+ * @return :LIBNORMALFORM_SENTENCE * The left subsentence
+ */
+#define LEFT(S) REQ_SENTENCE((S), *, (S)->data.binary.l)
+
+/**
+ * Transversal helper macro that returns
+ * the right-hand subsentence (predicate
+ * for qualifiers)
+ *
+ * @param S:LIBNORMALFORM_SENTENCE * The sentence
+ * @return :LIBNORMALFORM_SENTENCE * The right subsentence
+ */
+#define RIGHT(S) REQ_SENTENCE((S), *, (S)->data.binary.r)
+
+/**
+ * Transversal helper macro that pushes
+ * a sentence onto a stack
+ *
+ * @param UNTO:LIBNORMALFORM_SENTENCE ** Reference to the head of the stack
+ * @param S:LIBNORMALFORM_SENTENCE * The sentence
+ */
+#define PUSH(UNTO, S)\
+ REQ_SENTENCE((UNTO), **,\
+ REQ_SENTENCE((S), *,\
+ ((S)->travel_head = *(UNTO), *(UNTO) = (S), (void) 0)\
+ ))
+
+/**
+ * Transversal helper macro that pops
+ * a sentence stack
+ *
+ * @param FROM:LIBNORMALFORM_SENTENCE ** Reference to the head of the stack
+ * @param INTO:LIBNORMALFORM_SENTENCE ** Output parameter for the sentence popped from the stack
+ * @return :int 1 if a sentences was popped,
+ * 0 if the stack was empty (*(INTO) will be set to `NULL`)
+ */
+#define POP(FROM, INTO)\
+ REQ_SENTENCE((FROM), **,\
+ REQ_SENTENCE((INTO), **,\
+ ((*(INTO) = *(FROM)) ? (*(FROM) = (*(INTO))->travel_head, 1) : 0)\
+ ))
+
+
+
+
+
+/**
+ * Create a non-reduced AND expression with two terms
+ *
+ * @param l The left-hand term; ownership is transfered to the function
+ * @param r The right-hand term; ownership is transfered to the function
+ * @return The expression (l ∧ r) (may commuted), `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+HIDDEN USE_RESULT NONNULL_INPUT
+LIBNORMALFORM_SENTENCE *(libnormalform_and2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+/**
+ * Create a non-reduced OR expression with two terms
+ *
+ * @param l The left-hand term; ownership is transfered to the function
+ * @param r The right-hand term; ownership is transfered to the function
+ * @return The expression (l ∨ r) (may commuted), `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+HIDDEN USE_RESULT NONNULL_INPUT
+LIBNORMALFORM_SENTENCE *(libnormalform_or2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+/**
+ * Create a non-reduced XOR expression with two terms
+ *
+ * @param l The left-hand term; ownership is transfered to the function
+ * @param r The right-hand term; ownership is transfered to the function
+ * @return The expression (l ⊕ r) (may commuted), `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+HIDDEN USE_RESULT NONNULL_INPUT
+LIBNORMALFORM_SENTENCE *(libnormalform_xor2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+/**
+ * Annotate every sentence that appear multiple times
+ * with a unique index (counted from 0 up)
+ *
+ * @param this The sentence that shall be inspected
+ * recursively for annotation
+ * @return The number of annotated sentences
+ */
+HIDDEN USE_RESULT NONNULL_INPUT
+size_t (libnormalform_set_indices_and_counts__)(LIBNORMALFORM_SENTENCE *this);
+
+/**
+ * Undo `libnormalform_set_indices_and_counts__`
+ *
+ * This is needed because `libnormalform_set_indices_and_counts__`
+ * requires some otherwise unused memory to be properly initialised
+ *
+ * @param this The sentence that shall be recursively restored
+ */
+HIDDEN NONNULL_INPUT
+void (libnormalform_reset_indices_and_counts__)(LIBNORMALFORM_SENTENCE *this);
+
+
+#ifdef DEBUG
+/* libnormalform_to_string.c */
+void libnormalform__debug_print__(LIBNORMALFORM_SENTENCE *this, size_t depth_limit);
+#endif
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Winline"
+#endif
+
+
+
+
+
+#ifdef TEST
+
+#include <sys/resource.h>
+#include <unistd.h>
+
+#ifdef CHECK_MEMLEAK
+# include "memcheck.h"
+# define IF_MEMCHECK(...) __VA_ARGS__
+#else
+# define IF_MEMCHECK(...)
+#endif
+
+#define MEMLIMIT (512UL * 1024UL * 1024UL) /* valgrind requires quite a bit (but not this much) */
+
+#define SET_LIMIT(WHAT, HOW_MUCH)\
+ do {\
+ struct rlimit lim;\
+ if (getrlimit(WHAT, &lim)) {\
+ perror("getrlimit "#WHAT);\
+ break;\
+ }\
+ if ((HOW_MUCH) < lim.rlim_cur)\
+ lim.rlim_cur = (HOW_MUCH);\
+ lim.rlim_max = lim.rlim_cur;\
+ if (setrlimit(WHAT, &lim)) {\
+ perror("setrlimit "#WHAT);\
+ break;\
+ }\
+ } while (0)
+
+#define TEST_BEGIN\
+ SET_LIMIT(RLIMIT_CORE, 0);\
+ SET_LIMIT(RLIMIT_AS, MEMLIMIT);\
+ SET_LIMIT(RLIMIT_DATA, MEMLIMIT);\
+ SET_LIMIT(RLIMIT_RSS, MEMLIMIT);\
+ SET_LIMIT(RLIMIT_STACK, MEMLIMIT);\
+ SET_LIMIT(RLIMIT_CPU, TEST_TIMEOUT_SECONDS);\
+ alarm(TEST_TIMEOUT_SECONDS);\
+ IF_MEMCHECK(memcheck_begin();)\
+ do {\
+ int exit_value_just_to_remove_warnings_about_this_following_variable_definitions__ = 0
+
+#define TEST_END\
+ IF_MEMCHECK(if (!memcheck_check_memleaks()) exit(1);)\
+ exit(exit_value_just_to_remove_warnings_about_this_following_variable_definitions__); \
+ } while (0)
+
+
+#define ASSUME(X)\
+ do {\
+ int assumption_saved_errno__ = errno;\
+ errno = 0;\
+ if (!(X)) {\
+ fprintf(stderr, "Assumption `%s` failed at %s:%i, errno: %s\n", #X, __FILE__, __LINE__, strerror(errno));\
+ exit(1);\
+ }\
+ errno = assumption_saved_errno__;\
+ } while (0)
+
+#define ASSERT(X)\
+ do {\
+ if (!(X)) {\
+ fprintf(stderr, "Assertion `%s` failed at %s:%i\n", #X, __FILE__, __LINE__);\
+ exit(1);\
+ }\
+ } while (0)
+
+#define ASSERT_EQUAL(A, B)\
+ do {\
+ int inv__;\
+ LIBNORMALFORM_SENTENCE *a__ = (A);\
+ LIBNORMALFORM_SENTENCE *b__ = (B);\
+ ASSERT(a__->equals(a__, b__, &inv__) == 1);\
+ ASSERT(inv__ == 0);\
+ } while (0)
+
+#define ASSERT_INVEQUAL(A, B)\
+ do {\
+ int inv__;\
+ LIBNORMALFORM_SENTENCE *a__ = (A);\
+ LIBNORMALFORM_SENTENCE *b__ = (B);\
+ ASSERT(a__->equals(a__, b__, &inv__) == 1);\
+ ASSERT(inv__ == 1);\
+ } while (0)
+
+#define ASSERT_NOTEQUAL(A, B)\
+ do {\
+ int inv__;\
+ LIBNORMALFORM_SENTENCE *a__ = (A);\
+ LIBNORMALFORM_SENTENCE *b__ = (B);\
+ ASSERT(a__->equals(a__, b__, &inv__) == 0);\
+ } while (0)
+
+#define REF libnormalform_ref
+
+#define VALIST(...) __VA_OPT__(__VA_ARGS__,) NULL
+#define LIST(...) ((LIBNORMALFORM_SENTENCE *[]){VALIST(__VA_ARGS__)})
+#define COUNT(...) (sizeof(LIST(__VA_ARGS__)) / sizeof(LIBNORMALFORM_SENTENCE *) - 1U)
+
+#if defined(USE_TWO)
+# define USE_CHECKED_VERSION
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, A, B)\
+ libnormalform_##CONNECTIVE##2(A, B)
+
+#elif defined(USE_VARARGS) || defined(USE_VARARGS_MACRO)
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (libnormalform_##CONNECTIVE##l(VALIST(__VA_ARGS__)))
+
+#elif defined(USE_CHECKED_VARARGS)
+# define USE_CHECKED_VERSION
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (libnormalform_##CONNECTIVE##l_checked(COUNT(__VA_ARGS__), VALIST(__VA_ARGS__)))
+
+#elif defined(USE_VALIST)
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (vatrampoline(&libnormalform_v##CONNECTIVE, VALIST(__VA_ARGS__)))
+USE_RESULT static LIBNORMALFORM_SENTENCE *
+vatrampoline(LIBNORMALFORM_SENTENCE *(*f)(LIBNORMALFORM_SENTENCE *, va_list),
+ LIBNORMALFORM_SENTENCE *a, ...)
+{
+ LIBNORMALFORM_SENTENCE *ret;
+ va_list args;
+ va_start(args, a);
+ ret = (*f)(a, args);
+ va_end(args);
+ return ret;
+}
+
+#elif defined(USE_CHECKED_VALIST)
+# define USE_CHECKED_VERSION
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (vatrampoline(&libnormalform_v##CONNECTIVE##_checked, COUNT(__VA_ARGS__), VALIST(__VA_ARGS__)))
+USE_RESULT static LIBNORMALFORM_SENTENCE *
+vatrampoline(LIBNORMALFORM_SENTENCE *(*f)(size_t, LIBNORMALFORM_SENTENCE *, va_list),
+ size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{
+ LIBNORMALFORM_SENTENCE *ret;
+ va_list args;
+ va_start(args, a);
+ ret = (*f)(n, a, args);
+ va_end(args);
+ return ret;
+}
+
+#elif defined(USE_CHECKED)
+# define USE_CHECKED_VERSION
+
+#else
+# undef LIBNORMALFORM_MACRO__
+# define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (libnormalform_##CONNECTIVE(LIST(__VA_ARGS__)))
+#endif
+
+#if !defined(USE_VARARGS_MACRO)
+# undef libnormalform_andl
+# undef libnormalform_orl
+# undef libnormalform_xorl
+# undef libnormalform_ifl
+# undef libnormalform_implyl
+# undef libnormalform_nandl
+# undef libnormalform_norl
+# undef libnormalform_xnorl
+# undef libnormalform_nifl
+# undef libnormalform_nimplyl
+#endif
+
+#define TO\
+DO_TEST /* no indent before, breaking the word TO|DO to hide from grep */ CONST int main(void) {return 0;}
+
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..a9ae3eb
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,19 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc -std=c23
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
+
+#MEMCHECK_OBJ = memcheck.to
+#MEMCHECK = $(MEMCHECK_OBJ) memcheck.h
+#MEMCHECK_CPPFLAGS = -DCHECK_MEMLEAK -DPRINT_BACKTRACES
+#MEMCHECK_LDFLAGS = -lunwind -ldw
+
+TEST_CPPFLAGS = $(CPPFLAGS) $(MEMCHECK_CPPFLAGS)
+TEST_CFLAGS = $(CFLAGS)
+TEST_LDFLAGS = $(LDFLAGS) $(MEMCHECK_LDFLAGS)
+
+TEST_TIMEOUT_SECONDS = 5
diff --git a/libnormalform.7 b/libnormalform.7
new file mode 100644
index 0000000..1607b34
--- /dev/null
+++ b/libnormalform.7
@@ -0,0 +1,94 @@
+.TH LIBNORMALFORM 7 LIBNORMALFORM
+.SH NAME
+libnormalform \- First-order logic sentence canonicalisation library
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_true(void);
+LIBNORMALFORM_SENTENCE *libnormalform_false(void);
+LIBNORMALFORM_SENTENCE *libnormalform_variable(struct libnormalform_variable *);
+LIBNORMALFORM_SENTENCE *libnormalform_function(struct libnormalform_function *);
+LIBNORMALFORM_SENTENCE *libnormalform_transformation(struct libnormalform_transformer *, LIBNORMALFORM_SENTENCE *);
+
+LIBNORMALFORM_SENTENCE *libnormalform_not(LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_and(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_or(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_xor(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_imply(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_if(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_nand(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_nor(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_xnor(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_nimply(LIBNORMALFORM_SENTENCE **);
+LIBNORMALFORM_SENTENCE *libnormalform_nif(LIBNORMALFORM_SENTENCE **);
+
+LIBNORMALFORM_SENTENCE *libnormalform_all(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_any(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_one(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_exists(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_nexists(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_unique(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_existentially(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_universally(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_uniquely(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_empty(struct libnormalform_map *);
+LIBNORMALFORM_SENTENCE *libnormalform_nonempty(struct libnormalform_map *);
+LIBNORMALFORM_SENTENCE *libnormalform_singleton(struct libnormalform_map *);
+
+LIBNORMALFORM_SENTENCE *libnormalform_ref(LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_clone(LIBNORMALFORM_SENTENCE *);
+void libnormalform_free(LIBNORMALFORM_SENTENCE *);
+
+char *libnormalform_to_string(LIBNORMALFORM_SENTENCE *);
+LIBNORMALFORM_SENTENCE *libnormalform_from_string(char *, char **, const struct libnormalform_representation_spec *);
+
+int libnormalform_evaluate(LIBNORMALFORM_SENTENCE *);
+struct libnormalform_term *libnormalform_dnf(LIBNORMALFORM_SENTENCE *, uint64_t);
+struct libnormalform_term *libnormalform_cnf(LIBNORMALFORM_SENTENCE *, uint64_t);
+struct libnormalform_term *libnormalform_express(LIBNORMALFORM_SENTENCE *, uint64_t);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.B libnormalform
+library provides a mechanism for expressing
+first-order logic sentence and normalises them on the fly to
+negation normal form, and then lets the user choose disjunctive
+normal form (DNF) or conjunctive normal form (CNF).
+.PP
+.B libnormalform
+support all Boolean connectives: AND, OR, XOR,
+IMPLY, IF, NAND, NOR, XNOR, NIMPLY, NIF, and NOT, as well as the
+boolean constants TRUE and FALSE. Additionally,
+.B libnormalform
+supports three standard qualifiers: the universal qualifier
+(FOR ALL), the existential qualifier (THERE EXISTS), and the
+unique existential (uniqueness) qualifier (THERE EXISTS ONE).
+For all binary connectives, libnormalform support simply
+chaining (e.g. AND(a, b, c) instead of AND(AND(a, b), c)).
+For all qualifiers, libnormalform supports qualification over
+an array or an associative array.
+.P
+.B libnormalform
+also provides application managed boolean
+variables and functions. These as well as the domains for
+qualifiers can be set dynamically and the value of the
+sentence can be evaluate at any time.
+.PP
+When
+.B libnormalform
+is asked to output the sentences in DNF
+or CNF, it can relax parts of sentence in case the application
+wants to express the sentence in a particular form but cannot
+express all parts of, and therefore needs alternative that is
+at least as true. While relaxing the sentence, it can eliminiate
+parts of the sentence that become redundant; to be able to do
+this, it queries the application for how the application
+defined variables, functions and domains depend on each other.
+.SH SEE ALSO
+None.
diff --git a/libnormalform.h b/libnormalform.h
new file mode 100644
index 0000000..ed3b256
--- /dev/null
+++ b/libnormalform.h
@@ -0,0 +1,4213 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBNORMALFORM_H
+#define LIBNORMALFORM_H
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Winline"
+#endif
+
+
+/* For internal use { */
+
+#if defined(__GNUC__)
+# define LIBNORMALFORM_USE_RESULT__ __attribute__((__warn_unused_result__))
+# define LIBNORMALFORM_FREE_WITH__(DESTRUCTOR) __attribute__((__malloc__(DESTRUCTOR)))
+# define LIBNORMALFORM_USE_FREE__ __attribute__((__malloc__))
+# define LIBNORMALFORM_NONNULL_INPUT__ __attribute__((__nonnull__))
+# define LIBNORMALFORM_ONE_NONNULL_INPUT__(INDEX) __attribute__((__nonnull__(INDEX)))
+# if defined(LIBNORMALFORM_ALLOW_BAD_WARNINGS)
+# define LIBNORMALFORM_NULL_LAST__ __attribute__((__sentinel__))
+# else
+# define LIBNORMALFORM_NULL_LAST__
+# endif
+/*
+ * Unfortunately __attribute__((__sentinel__)) requires (in contrary to the
+ * documentation) the `NULL` to be inside `...`, and not at the argument before
+ * the `...`, so a "-Wformat=" warning will be generated if and empty list is
+ * used when constructing a sentence. Therefore, by default this attribute is
+ * not used, as the user may have configured the compiler to fail if the warning
+ * is generated.
+ */
+#else
+# define LIBNORMALFORM_USE_RESULT__
+# define LIBNORMALFORM_FREE_WITH__(DESTRUCTOR)
+# define LIBNORMALFORM_USE_FREE__
+# define LIBNORMALFORM_NONNULL_INPUT__
+# define LIBNORMALFORM_ONE_NONNULL_INPUT__(INDEX)
+# define LIBNORMALFORM_NULL_LAST__
+#endif
+
+
+#define LIBNORMALFORM_CHECKED__(CONNECTIVE)\
+ size_t i__ = 0;\
+ while (xs[i__])\
+ i__ += 1;\
+ if (i__ < n) {\
+ while (n--)\
+ libnormalform_free(xs[n]);\
+ return NULL;\
+ }\
+ return libnormalform_##CONNECTIVE(xs);
+
+#define LIBNORMALFORM_VALIST__(CONNECTIVE)\
+ size_t n__ = 0, i__ = 0;\
+ va_list args2__;\
+ va_copy(args2__, args);\
+ if (a) {\
+ do {\
+ n__ += 1;\
+ } while (va_arg(args2__, LIBNORMALFORM_SENTENCE *));\
+ }\
+ va_end(args2__);\
+ {\
+ LIBNORMALFORM_SENTENCE *xs[n__ + 1U];\
+ if (n__) {\
+ xs[i__++] = a;\
+ while (--n__)\
+ xs[i__++] = va_arg(args, LIBNORMALFORM_SENTENCE *);\
+ }\
+ xs[i__] = NULL;\
+ return libnormalform_##CONNECTIVE(xs);\
+ }
+
+#define LIBNORMALFORM_ELLIPSIS__(CONNECTIVE)\
+ LIBNORMALFORM_SENTENCE *ret__;\
+ va_list args__;\
+ va_start(args__, a);\
+ ret__ = libnormalform_v##CONNECTIVE(a, args__);\
+ va_end(args__);\
+ return ret__;
+
+#define LIBNORMALFORM_VALIST_CHECKED__(CONNECTIVE)\
+ size_t n__ = 0, i__ = 0;\
+ va_list args2__;\
+ va_copy(args2__, args);\
+ if (a) {\
+ do {\
+ n__ += 1;\
+ } while (va_arg(args2__, LIBNORMALFORM_SENTENCE *));\
+ }\
+ va_end(args2__);\
+ if (n__ < n) {\
+ if (n) {\
+ libnormalform_free(a);\
+ while (--n)\
+ libnormalform_free(va_arg(args2__, LIBNORMALFORM_SENTENCE *));\
+ }\
+ return NULL;\
+ }\
+ {\
+ LIBNORMALFORM_SENTENCE *xs__[n__ + 1U];\
+ if (n__) {\
+ xs__[i__++] = a;\
+ while (--n__)\
+ xs__[i__++] = va_arg(args, LIBNORMALFORM_SENTENCE *);\
+ }\
+ xs__[i__] = NULL;\
+ return libnormalform_##CONNECTIVE(xs__);\
+ }
+
+#define LIBNORMALFORM_ELLIPSIS_CHECKED__(CONNECTIVE)\
+ LIBNORMALFORM_SENTENCE *ret__;\
+ va_list args__;\
+ va_start(args__, a);\
+ ret__ = libnormalform_v##CONNECTIVE##_checked(n, a, args__);\
+ va_end(args__);\
+ return ret__;
+
+#define LIBNORMALFORM_TWO__(CONNECTIVE)\
+ LIBNORMALFORM_SENTENCE *xs__[3] = {l, r, NULL};\
+ if (!l || !r) {\
+ libnormalform_free(l);\
+ libnormalform_free(r);\
+ return NULL;\
+ }\
+ return libnormalform_##CONNECTIVE(xs__);
+
+#define LIBNORMALFORM_MACRO__(CONNECTIVE, ...)\
+ (libnormalform_##CONNECTIVE##_checked(\
+ sizeof((LIBNORMALFORM_SENTENCE *[]){__VA_OPT__(__VA_ARGS__,) NULL}) / sizeof(LIBNORMALFORM_SENTENCE *) - 1U,\
+ (LIBNORMALFORM_SENTENCE *[]){__VA_OPT__(__VA_ARGS__,) NULL}))
+
+/* } */
+
+
+struct libnormalform_term;
+
+
+
+
+
+#define LIBNORMALFORM_AVOID_FOR_ALL (UINT64_C(1) << 0) /**< Prefer ¬∃x.¬φ over ∀x.φ */
+#define LIBNORMALFORM_AVOID_NEGATED_FOR_ALL (UINT64_C(1) << 1) /**< Prefer ∃x.¬φ over ¬∀x.φ */
+#define LIBNORMALFORM_AVOID_FOR_ANY (UINT64_C(1) << 2) /**< Prefer ¬∀x.¬φ over ∃x.φ */
+#define LIBNORMALFORM_AVOID_NEGATED_FOR_ANY (UINT64_C(1) << 3) /**< Prefer ∀x.¬φ over ¬∃x.φ */
+#define LIBNORMALFORM_RELAX_FOR_ONE (UINT64_C(1) << 4) /**< Relax any positive ∃!x.φ into ∃x.φ */
+#define LIBNORMALFORM_REDUCE_XOR (UINT64_C(1) << 5) /**< Rewrite XOR to negation normal form */
+#define LIBNORMALFORM_JOIN_SIDES_IN_FOR_ALL (UINT64_C(1) << 6) /**< Express φ(x)∀x:θ(x) on the form ∀x.(θ(x) → φ(x)) */
+#define LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ALL (UINT64_C(1) << 7) /**< Express ¬(φ(x)∀x:θ(x)) on the form ¬∀x.(θ(x) → φ(x)) */
+#define LIBNORMALFORM_JOIN_SIDES_IN_FOR_ANY (UINT64_C(1) << 8) /**< Express φ(x)∃x:θ(x) on the form ∃x.(θ(x) ∧ φ(x)) */
+#define LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ANY (UINT64_C(1) << 9) /**< Express ¬(φ(x)∃x:θ(x)) on the form ¬∃x.(θ(x) ∧ φ(x)) */
+#define LIBNORMALFORM_JOIN_SIDES_IN_FOR_ONE (UINT64_C(1) << 10) /**< Express φ(x)∃!x:θ(x) on the form ∃!x.(θ(x) ∧ φ(x)) */
+#define LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ONE (UINT64_C(1) << 11) /**< Express ¬(φ(x)∃!x:θ(x)) on the form ¬∃!x.(θ(x) ∧ φ(x)) */
+#define LIBNORMALFORM_ELIMINATE_FOR_ALL (UINT64_C(1) << 12) /**< Relax any non-translatable ∀x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ALL (UINT64_C(1) << 13) /**< Relax any non-translatable ¬∀x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_FOR_ANY (UINT64_C(1) << 14) /**< Relax any non-translatable ∃x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ANY (UINT64_C(1) << 15) /**< Relax any non-translatable ¬∃x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_FOR_ONE (UINT64_C(1) << 16) /**< Relax any non-translatable ∃!x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ONE (UINT64_C(1) << 17) /**< Relax any ¬∃!x.φ to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_VARIABLE (UINT64_C(1) << 18) /**< Relax any positive variable literal to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_NEGATED_VARIABLE (UINT64_C(1) << 19) /**< Relax any negative variable literal to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_FUNCTION (UINT64_C(1) << 20) /**< Relax any positive function literal to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_NEGATED_FUNCTION (UINT64_C(1) << 21) /**< Relax any negative function literal to ⊤ */
+#define LIBNORMALFORM_ELIMINATE_XOR (UINT64_C(1) << 22) /**< Relax any non-reduce XOR to ⊤ */
+#define LIBNORMALFORM_ALL_EXPRESS_FLAGS__ ((UINT64_C(1) << 23) - 1U) /* For internal use */
+
+
+/**
+ * Opaque data type use to express and sentences
+ *
+ * This object incluses caches and preallocated memory
+ * makes object unsafe to be used from two different
+ * threads at the same time
+ *
+ * @seealso libnormalform_clone
+ */
+typedef struct libnormalform_sentence LIBNORMALFORM_SENTENCE;
+
+
+/**
+ * Specification for how application provided strings
+ * shall be interpreted
+ */
+struct libnormalform_representation_spec {
+ /**
+ * Application-specific data that will be passed into
+ * `.get_variable`, `.get_function`, `.get_map`, and
+ * `.get_transformer` (making it reenterant so it can be
+ * called by multiple threads, or to configure the function)
+ *
+ * May be `NULL`
+ */
+ void *user_data;
+
+ /**
+ * Deserialise a variable from a unterminated string
+ *
+ * The function shall be able to return the reference
+ * to any `struct libnormalform_variable` given its
+ * `.identifier` sans NUL-termination
+ *
+ * The function shall be pure in the sence that if
+ * it is called twice (or more) with the identifer
+ * at the beginning of `s` it shall return the same
+ * pointer, not two different copies (`user_data` be
+ * use used to store the variables if they are
+ * constructed dynamically)
+ *
+ * May be `NULL`
+ *
+ * @param s The string beginning with the variable's
+ * identifier
+ * @param end_out Output parameter for the position in
+ * `s` after the last byte in the identifer
+ * @param user_data `.user_data`
+ * @return A pointer to the referenced variable, `NULL`
+ * on failure (`errno` may be set)
+ *
+ * It is recommended that, on failure, `errno` is set
+ * according to the below specification, however it is
+ * up to the application to choose what to do with `errno`
+ *
+ * @throws EINVAL `s` in not formatted to represent a valid variable
+ * @throws ENOENT `s` does not represent any known variable
+ */
+ struct libnormalform_variable *(*get_variable)(char *s, char **end_out, void *user_data);
+
+ /**
+ * Same as `.get_variable` except that it returns
+ * reference to a function (`struct libnormalform_function *`)
+ */
+ struct libnormalform_function *(*get_function)(char *s, char **end_out, void *user_data);
+
+ /**
+ * Same as `.get_variable` except that it returns
+ * reference to a domain (`struct libnormalform_map *`)
+ */
+ struct libnormalform_map *(*get_map)(char *s, char **end_out, void *user_data);
+
+ /**
+ * Same as `.get_transformer` except that it returns
+ * reference to a function (`struct libnormalform_transformer *`)
+ */
+ struct libnormalform_transformer *(*get_transformer)(char *s, char **end_out, void *user_data);
+};
+
+
+/**
+ * Type if a `struct libnormalform_term`, used to
+ * determine which member of `.term` to use and
+ * whether it is negated
+ */
+enum libnormalform_term_type {
+ LIBNORMALFORM_CONJUNCTION, /**< AND clause; empty clause is used for contradiction */
+ LIBNORMALFORM_DISJUNCTION, /**< OR clause; empty clause is used for tautology */
+ LIBNORMALFORM_EXCLUSIVE_DISJUNCTION, /**< XOR clause; never used for empty clauses */
+ LIBNORMALFORM_TRANSFORMATION, /**< Transformater function with input **/
+ LIBNORMALFORM_VARIABLE, /**< Positive variable literal **/
+ LIBNORMALFORM_NEGATED_VARIABLE, /**< Negative variable literal **/
+ LIBNORMALFORM_FUNCTION, /**< Positive boolean function literal **/
+ LIBNORMALFORM_NEGATED_FUNCTION, /**< Negative boolean function literal **/
+ LIBNORMALFORM_FOR_ALL, /**< ∀x∈D.(P(x) → Q(x)) expression */
+ LIBNORMALFORM_NEGATED_FOR_ALL, /**< ¬∀x∈D.(P(x) → Q(x)) expression */
+ LIBNORMALFORM_FOR_ANY, /**< ∃x∈D.(P(x) ∧ Q(x)) expression */
+ LIBNORMALFORM_NEGATED_FOR_ANY, /**< ¬∃x∈D.(P(x) ∧ Q(x)) expression */
+ LIBNORMALFORM_FOR_ONE, /**< ∃!x∈D.(P(x) ∧ Q(x)) expression */
+ LIBNORMALFORM_NEGATED_FOR_ONE /**< ¬∃!x∈D.(P(x) ∧ Q(x)) expression */
+};
+
+
+/**
+ * Value of an atomic sentence
+ */
+enum libnormalform_value {
+ LIBNORMALFORM_FALSE = 0, /**< Variable has the value False */
+ LIBNORMALFORM_TRUE = 1 /**< Variable has the value True */
+};
+
+
+/**
+ * Built in transformer functions
+ */
+enum libnormalform_builtin_transformer {
+ LIBNORMALFORM_NOT_BUILT_IN = 0, /**< Application provided transformer */
+ LIBNORMALFORM_DOMAIN_VIEW, /**< Selects the `.key` from a `struct libnormalform_mapping *` */
+ LIBNORMALFORM_IMAGE_VIEW /**< Selects the `.value` from a `struct libnormalform_mapping *` */
+};
+
+
+/**
+ * Relationship (dependency) between the value of two sentences
+ */
+enum libnormalform_sentences_relationship {
+ /**
+ * Left-hand implies right-hand (⊨ P → Q)
+ *
+ * Example:
+ * - P = ⋀{a, b}, Q = ⋀{a}
+ *
+ * Truthtable:
+ * P: 001
+ * Q: 011
+ * ∧: 001
+ * ∨: 011
+ * ⊕: 010
+ * →: 111
+ * ←: 101
+ *
+ * Consequence:
+ * - P ∧ Q = P
+ * - P ∨ Q = Q
+ * - P ⊕ Q = ¬P ∧ Q
+ * - P → Q = ⊤
+ * - P ← Q cannot be simplified
+ */
+ LIBNORMALFORM_MATERIAL_IMPLICATION = -1,
+
+ /**
+ * The terms are identical (⊨ P ↔ Q)
+ *
+ * Example:
+ * - P = ⋀{a, b}, Q = ⋀{a, b}
+ *
+ * Truthtable:
+ * P: 01
+ * Q: 01
+ * ∧: 01
+ * ∨: 01
+ * ⊕: 00
+ * →: 11
+ * ←: 11
+ *
+ * Consequence:
+ * - P ∧ Q = P = Q
+ * - P ∨ Q = P = Q
+ * - P ⊕ Q = ⊥
+ * - P → Q = ⊤
+ * - P ← Q = ⊤
+ */
+ LIBNORMALFORM_IDENTICAL,
+
+ /**
+ * Right-hand implies left-hand (⊨ P ← Q)
+ *
+ * Example:
+ * - P = ⋀{a}, Q = ⋀{a, b}
+ *
+ * Truthtable:
+ * P: 011
+ * Q: 001
+ * ∧: 001
+ * ∨: 011
+ * ⊕: 010
+ * →: 101
+ * ←: 111
+ *
+ * Consequence:
+ * - P ∧ Q = Q
+ * - P ∨ Q = P
+ * - P ⊕ Q = P ∧ ¬Q
+ * - P → Q cannot be simplified
+ * - P ← Q = ⊤
+ */
+ LIBNORMALFORM_CONVERSE_IMPLICATION,
+
+ /**
+ * The terms are the inverse of each other (⊨ P ↮ Q)
+ *
+ * Example:
+ * - P = a, Q = ¬a
+ *
+ * Truthtable:
+ * P: 01
+ * Q: 10
+ * ∧: 00
+ * ∨: 11
+ * ⊕: 11
+ * →: 10
+ * ←: 01
+ *
+ * Consequence:
+ * - P ∧ Q = ⊥
+ * - P ∨ Q = ⊤
+ * - P ⊕ Q = ⊤
+ * - P → Q = Q
+ * - P ← Q = P
+ */
+ LIBNORMALFORM_MUTUALLY_INVERSE,
+
+ /**
+ * Both terms cannot be satisfied at the same time,
+ * but are not each other's inverse (⊨ P ⊼ Q)
+ *
+ * Example:
+ * - P = ⋀{a, b}, Q = ⋀{¬b}
+ *
+ * Truthtable:
+ * P: 001
+ * Q: 010
+ * ∧: 000
+ * ∨: 011
+ * ⊕: 011
+ * →: 110
+ * ←: 101
+ *
+ * Consequence:
+ * - P ∧ Q = ⊥
+ * - P ∨ Q cannot be simplified
+ * - P ⊕ Q = P ∨ Q
+ * - P → Q = ¬P
+ * - P ← Q = ¬Q
+ */
+ LIBNORMALFORM_MUTUALLY_EXCLUSIVE,
+
+ /**
+ * The terms cannot be unsatisfied at the same time,
+ * but they are not identical (⊨ P ∨ Q)
+ *
+ * Example:
+ * - P = ⋁{a, b}, Q = ⋁{¬b}
+ *
+ * Truthtable:
+ * P: 011
+ * Q: 101
+ * ∧: 001
+ * ∨: 111
+ * ⊕: 110
+ * →: 101
+ * ←: 011
+ *
+ * Consequence:
+ * - P ∧ Q cannot be simplified
+ * - P ∨ Q = ⊤
+ * - P ⊕ Q = ¬P ∨ ¬Q
+ * - P → Q = Q
+ * - P ← Q = P
+ */
+ LIBNORMALFORM_JOINTLY_UNDENIABLE,
+
+ /**
+ * The terms are unrelated to each other
+ *
+ * Example:
+ * - P = a, Q = b
+ *
+ * Truthtable:
+ * P: 0011
+ * Q: 0101
+ * ∧: 0001
+ * ∨: 0111
+ * ⊕: 0110
+ * →: 1101
+ * ←: 1011
+ *
+ * Consequence:
+ * - P ∧ Q cannot be simplified
+ * - P ∨ Q cannot be simplified
+ * - P ⊕ Q cannot be simplified
+ * - P → Q cannot be simplified
+ * - P ← Q cannot be simplified
+ */
+ LIBNORMALFORM_MUTUALLY_INDEPENDENT
+};
+
+
+
+/**
+ * Relationship (commonality) between the value of two domains
+ *
+ * (The documentation uses the term "set", however,
+ * properly they are bags (multisets), however this
+ * is only important for uniqueness qualification;
+ * for the mathematics, you can thing about it as
+ * sets)
+ */
+enum libnormalform_domain_relationship {
+ /**
+ * The left-hand set is a superset of the right-hand set (A ⊇ B)
+ *
+ * Example:
+ * - A = {a, b}, B = {a}
+ *
+ * Euler diagram:
+ * ┌────────────────────────────┐
+ * │ ┌─A────────────────┐ │
+ * │ │╱╱╱╱╱╱╱┌─B──────┐╱│ │
+ * │ │╱╱╱╱╱╱╱│╳╳╳╳╳╳╳╳│╱│ │
+ * │ │╱╱╱╱╱╱╱│╳╳╳╳╳╳╳╳│╱│ │
+ * │ │╱╱╱╱╱╱╱│╳╳╳╳╳╳╳╳│╱│ │
+ * │ │╱╱╱╱╱╱╱└────────┘╱│ │
+ * │ │╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱│ │
+ * │ └──────────────────┘ │
+ * └────────────────────────────┘
+ *
+ * Venn diagram of possibly non-empty intersections:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │███████████████│ │
+ * │ │██████┌────────┼──────┐ │
+ * │ │██████│████████│ │ │
+ * │ │██████│████████│ │ │
+ * │ └──────┼────────┘ │ │
+ * │ │ │ │
+ * │ └─────────────B─┘ │
+ * └────────────────────────────┘
+ *
+ * Consequence:
+ * - A ∩ B = B
+ * - A ∪ B = A
+ * - A ∆ B = A ∖ B
+ * - ⋀A ⊨ ⋀B
+ * - ⋁B ⊨ ⋁A
+ * - ⋀A ∧ ⋀B = ⋀A
+ * - ⋀A ∨ ⋀B = ⋀B
+ * - ⋀A ⊕ ⋀B = ¬⋀A ∧ ⋀B
+ * - ⋀A → ⋀B = ⊤
+ * - ⋀A ← ⋀B cannot be simplified
+ * - ⋁A ∧ ⋁B = ⋁B
+ * - ⋁A ∨ ⋁B = ⋁A
+ * - ⋁A ⊕ ⋁B = ⋁A ∧ ¬⋁B
+ * - ⋁A → ⋁B cannot be simplified
+ * - ⋁A ← ⋁B = ⊤
+ */
+ LIBNORMALFORM_SUPERSET_OF = -1,
+
+ /**
+ * The two terms are identical (A = B)
+ *
+ * Example:
+ * - A = {a, b}, B = {a, b}
+ *
+ * Euler diagram:
+ * ┌────────────────────────────┐
+ * │ ┌─A,B──────────────┐ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ │╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳│ │
+ * │ └──────────────────┘ │
+ * └────────────────────────────┘
+ *
+ * Venn diagram of possibly non-empty intersections:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │ │ │
+ * │ │ ┌────────┼──────┐ │
+ * │ │ │████████│ │ │
+ * │ │ │████████│ │ │
+ * │ └──────┼────────┘ │ │
+ * │ │ │ │
+ * │ └─────────────B─┘ │
+ * └────────────────────────────┘
+ *
+ * Consequence:
+ * - A ∩ B = A = B
+ * - A ∪ B = A = B
+ * - A ∆ B = ∅
+ * - ⋀A = ⋀B
+ * - ⋁A = ⋁B
+ * - ⋀A ∧ ⋀B = ⋀A = ⋀B
+ * - ⋀A ∨ ⋀B = ⋀A = ⋀B
+ * - ⋀A ⊕ ⋀B = ⊥
+ * - ⋀A → ⋀B = ⊤
+ * - ⋀A ← ⋀B = ⊤
+ * - ⋁A ∧ ⋁B = ⋁A = ⋁B
+ * - ⋁A ∨ ⋁B = ⋁A = ⋁B
+ * - ⋁A ⊕ ⋁B = ⊥
+ * - ⋁A → ⋁B = ⊤
+ * - ⋁A ← ⋁B = ⊤
+ */
+ LIBNORMALFORM_SAME_AS,
+
+ /**
+ * The left-hand set is a subset of the right-hand set (A ⊆ B)
+ *
+ * Example:
+ * - A = {a}, B = {a, b}
+ *
+ * Euler diagram:
+ * ┌────────────────────────────┐
+ * │ ┌─B────────────────┐ │
+ * │ │╲┌─A──────┐╲╲╲╲╲╲╲│ │
+ * │ │╲│╳╳╳╳╳╳╳╳│╲╲╲╲╲╲╲│ │
+ * │ │╲│╳╳╳╳╳╳╳╳│╲╲╲╲╲╲╲│ │
+ * │ │╲│╳╳╳╳╳╳╳╳│╲╲╲╲╲╲╲│ │
+ * │ │╲└────────┘╲╲╲╲╲╲╲│ │
+ * │ │╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲│ │
+ * │ └──────────────────┘ │
+ * └────────────────────────────┘
+ *
+ * Venn diagram of possibly non-empty intersections:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │ │ │
+ * │ │ ┌────────┼──────┐ │
+ * │ │ │████████│██████│ │
+ * │ │ │████████│██████│ │
+ * │ └──────┼────────┘██████│ │
+ * │ │███████████████│ │
+ * │ └─────────────B─┘ │
+ * └────────────────────────────┘
+ *
+ * Consequence:
+ * - A ∩ B = A
+ * - A ∪ B = B
+ * - A ∆ B = B ∖ A
+ * - ⋀B ⊨ ⋀A
+ * - ⋁A ⊨ ⋁B
+ * - ⋀A ∧ ⋀B = ⋀B
+ * - ⋀A ∨ ⋀B = ⋀A
+ * - ⋀A ⊕ ⋀B = ⋀A ∧ ¬⋀B
+ * - ⋀A → ⋀B cannot be simplified
+ * - ⋀A ← ⋀B = ⊤
+ * - ⋁A ∧ ⋁B = ⋁A
+ * - ⋁A ∨ ⋁B = ⋁B
+ * - ⋁A ⊕ ⋁B = ¬⋁A ∧ ⋁B
+ * - ⋁A → ⋁B = ⊤
+ * - ⋁A ← ⋁B cannot be simplified
+ */
+ LIBNORMALFORM_SUBSET_OF,
+
+ /**
+ * The sets have no common elements (A ∩ B = ∅)
+ *
+ * Example:
+ * - A = {a}, B = {b}
+ *
+ * Euler diagram:
+ * ┌────────────────────────────┐
+ * │ ┌─A───────┐ ┌─B───────┐ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱╱╱╱│ │╲╲╲╲╲╲╲╲╲│ │
+ * │ └─────────┘ └─────────┘ │
+ * └────────────────────────────┘
+ *
+ * Venn diagram of possibly non-empty intersections:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │███████████████│ │
+ * │ │██████┌────────┼──────┐ │
+ * │ │██████│ │██████│ │
+ * │ │██████│ │██████│ │
+ * │ └──────┼────────┘██████│ │
+ * │ │███████████████│ │
+ * │ └─────────────B─┘ │
+ * └────────────────────────────┘
+ *
+ * Consequence:
+ * - A ∩ B = ∅
+ * - A ∪ B cannot be simplified
+ * - A ∆ B = A ∪ B
+ * - ⋀A ∧ ⋀B = ⋀(A ∪ B)
+ * - ⋀A ∨ ⋀B cannot be simplified
+ * - ⋀A ⊕ ⋀B cannot be simplified
+ * - ⋀A → ⋀B cannot be simplified
+ * - ⋀A ← ⋀B cannot be simplified
+ * - ⋁A ∧ ⋁B cannot be simplified
+ * - ⋁A ∨ ⋁B = ⋁(A ∪ B)
+ * - ⋁A ⊕ ⋁B cannot be simplified
+ * - ⋁A → ⋁B cannot be simplified
+ * - ⋁A ← ⋁B cannot be simplified
+ */
+ LIBNORMALFORM_DISJOINT_WITH,
+
+ /**
+ * Both sets have unique elements (A ⊆ A ∪ B ⊇ B, A ∩ B ⊇ ∅)
+ *
+ * Example:
+ * - A = {a, b}, B = {b, c}
+ *
+ * Euler diagram:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱│ │
+ * │ │╱╱╱╱╱╱┌────────┼────B─┐ │
+ * │ │╱╱╱╱╱╱│╳╳╳╳╳╳╳╳│╲╲╲╲╲╲│ │
+ * │ │╱╱╱╱╱╱│╳╳╳╳╳╳╳╳│╲╲╲╲╲╲│ │
+ * │ └──────┼────────┘╲╲╲╲╲╲│ │
+ * │ │╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲│ │
+ * │ └───────────────┘ │
+ * └────────────────────────────┘
+ *
+ * Venn diagram of possibly non-empty intersections:
+ * ┌────────────────────────────┐
+ * │ ┌─A─────────────┐ │
+ * │ │███████████████│ │
+ * │ │██████┌────────┼──────┐ │
+ * │ │██████│████████│██████│ │
+ * │ │██████│████████│██████│ │
+ * │ └──────┼────────┘██████│ │
+ * │ │███████████████│ │
+ * │ └─────────────B─┘ │
+ * └────────────────────────────┘
+ *
+ * Consequence:
+ * - A ∩ B cannot be simplified
+ * - A ∪ B cannot be simplified
+ * - A ∆ B cannot be simplified
+ * - ⋀A ∧ ⋀B = ⋀(A ∪ B)
+ * - ⋀A ∨ ⋀B cannot be simplified
+ * - ⋀A ⊕ ⋀B cannot be simplified
+ * - ⋀A → ⋀B cannot be simplified
+ * - ⋀A ← ⋀B cannot be simplified
+ * - ⋁A ∧ ⋁B cannot be simplified
+ * - ⋁A ∨ ⋁B = ⋁(A ∪ B)
+ * - ⋁A ⊕ ⋁B cannot be simplified
+ * - ⋁A → ⋁B cannot be simplified
+ * - ⋁A ← ⋁B cannot be simplified
+ */
+ LIBNORMALFORM_CONJOINT_WITH
+};
+
+
+/**
+ * Variable that can be referenced in a sentence
+ */
+struct libnormalform_variable {
+ /**
+ * The value of the sentence
+ *
+ * This need not be sent when the sentence is constructed,
+ * but not be set before it is evaluated; it is only used
+ * by `libnormalform_evaluate`
+ */
+ enum libnormalform_value value;
+
+ /**
+ * Application-specific data that the application
+ * could use to identify the variable
+ *
+ * May be `NULL`
+ */
+ void *user_data;
+
+ /**
+ * Identifier for variable
+ *
+ * This need only be set when `libnormalform_to_string`
+ * is called. The value must be parsable by the application
+ * without any explicit termination
+ */
+ const char *identifier;
+
+ /**
+ * `NULL` or copy of of the object for `libnormalform_clone`
+ *
+ * This field should normally be unset, but before calling
+ * `libnormalform_clone`, the application must set it
+ * either to `NULL` (if the clone can use the same reference)
+ * or to a clone of the variable.
+ */
+ struct libnormalform_variable *copy_for_clone;
+};
+
+
+/**
+ * Boolean-codomain function that can be referenced in a sentence
+ */
+struct libnormalform_function {
+ /**
+ * Function that is used to evaluate the trueness of something
+ *
+ * @param user_data `.user_data`
+ * @param input Input to the function, normally it will
+ * be `NULL`, but it is inside the antecedent
+ * for a qualifier, it will be the `.key`
+ * for some `struct libnormalform_mapping`,
+ * and if it is inside the predicate of a
+ * qualifier, it will be the `.value`
+ * for some `struct libnormalform_mapping`
+ * @return 1 if the function is true,
+ * 0 if the function is false,
+ * -1 on failure (the function may set `errno`)
+ *
+ * For `libnormalform_exists`, `libnormalform_nexists`,
+ * and `libnormalform_unique`, it will only be called
+ * for the `.key` for `struct libnormalform_mapping`'s
+ *
+ * For `libnormalform_universally`, `libnormalform_existentially`,
+ * and `libnormalform_uniquely`, it will only be called
+ * for the `.value` for `struct libnormalform_mapping`'s
+ */
+ int (*evaluate)(void *user_data, void *input);
+
+ /**
+ * Application-specific data that will be passed into
+ * `.evaluate` (making it reenterant so it can be called
+ * by multiple threads, or to configure the function)
+ *
+ * May be `NULL`
+ */
+ void *user_data;
+
+ /**
+ * Identifier for function
+ *
+ * This need only be set when `libnormalform_to_string`
+ * is called. The value must be parsable by the application
+ * without any explicit termination
+ */
+ const char *identifier;
+
+ /**
+ * `NULL` or copy of of the object for `libnormalform_clone`
+ *
+ * This field should normally be unset, but before calling
+ * `libnormalform_clone`, the application must set it
+ * either to `NULL` (if the clone can use the same reference)
+ * or to a clone of the variable.
+ */
+ struct libnormalform_function *copy_for_clone;
+
+ /**
+ * See `.requires_relaxation`
+ */
+ struct libnormalform_function *relaxation;
+
+ /**
+ * Before calling `libnormalform_express`, `libnormalform_dnf`,
+ * or `libnormalform_cnf` this shall be set to 0 if the function
+ * may keep the function as is, or to a non-0 value if it shall
+ * be replaced `.relaxation` or by TRUE if `.relaxation` is `NULL`
+ */
+ int requires_relaxation;
+};
+
+
+/**
+ * Generic function that can be referenced in a sentence
+ *
+ * The function must be pure and distributive over any boolean operator
+ * (it must be an endomorphism). That is, the output is always the
+ * same for the same input without any side effects, and for any sentence
+ * A * B, where * is any boolean operator and F is the function,
+ * F(A * B) = F(A) * F(B).
+ */
+struct libnormalform_transformer {
+ /**
+ * Function that is used to transform input
+ *
+ * @param user_data `.user_data`
+ * @param input Input to the function, normally it will
+ * be `NULL`, but it is inside the antecedent
+ * for a qualifier, it will be the `.key`
+ * for some `struct libnormalform_mapping`,
+ * and if it is inside the predicate of a
+ * qualifier, it will be the `.value`
+ * for some `struct libnormalform_mapping`,
+ * except that during the create of a
+ * `struct libnormalform_term`, the library
+ * may insert builtin transformers that will
+ * have the `struct libnormalform_mapping *`
+ * as input and will output the `.key` or
+ * `.value`
+ * @return The transformation of `input` (not `NULL`),
+ * `NULL` on failure (the function may set `errno`)
+ *
+ * For `libnormalform_exists`, `libnormalform_nexists`,
+ * and `libnormalform_unique`, it will only be called
+ * for the `.key` for `struct libnormalform_mapping`'s
+ *
+ * For `libnormalform_universally`, `libnormalform_existentially`,
+ * and `libnormalform_uniquely`, it will only be called
+ * for the `.value` for `struct libnormalform_mapping`'s
+ */
+ void *(*transform)(void *user_data, void *input);
+
+ /**
+ * The function is called when the output of `.transform`
+ * is not longer needed by the library
+ *
+ * May be `NULL`
+ *
+ * This function is not called if `.transform` output `NULL`
+ *
+ * @param user_data `.user_data`
+ * @param output The pointer that was returned by `.transform`
+ */
+ void (*deallocate)(void *user_data, void *output);
+
+ /**
+ * Application-specific data that will be passed into
+ * `.transform` and `.deallocate` (making them reenterant
+ * so it can be called by multiple threads, or to
+ * configure the function)
+ *
+ * May be `NULL`
+ */
+ void *user_data;
+
+ /**
+ * Identifier for function
+ *
+ * This need only be set when `libnormalform_to_string`
+ * is called. The value must be parsable by the application
+ * without any explicit termination
+ */
+ const char *identifier;
+
+ /**
+ * `NULL` or copy of of the object for `libnormalform_clone`
+ *
+ * This field should normally be unset, but before calling
+ * `libnormalform_clone`, the application must set it
+ * either to `NULL` (if the clone can use the same reference)
+ * or to a clone of the variable.
+ */
+ struct libnormalform_transformer *copy_for_clone;
+
+ /**
+ * Before calling `libnormalform_express`, `libnormalform_dnf`,
+ * or `libnormalform_cnf` this shall be set to 0 if the function
+ * may keep the function, or to a non-0 value if it shall
+ * be replaced with a tautology
+ */
+ int requires_elimination;
+
+ /**
+ * This field will automatically be set to `LIBNORMALFORM_NOT_BUILT_IN`
+ * to indicated that the object originates in the application,
+ * however a `struct libnormalform_term *` created by the library
+ * (via `libnormalform_express`, `libnormalform_dnf`, or
+ * `libnormalform_cnf`) can contain objects with other values in this
+ * field, to describe what it does. In particular, depending on the
+ * flags used when creating the `struct libnormalform_term *`,
+ * qualification sentences may contain `LIBNORMALFORM_DOMAIN_VIEW`
+ * and `LIBNORMALFORM_IMAGE_VIEW` and transformers.
+ */
+ enum libnormalform_builtin_transformer builtin;
+};
+
+
+/**
+ * Key–value pair
+ */
+struct libnormalform_mapping {
+ /**
+ * Key
+ *
+ * In mapped qualifiers (`libnormalform_all`,
+ * `libnormalform_any`, `libnormalform_one`), this
+ * is used as the input for the antecedent
+ *
+ * In unmapped qualifiers (`libnormalform_exists`,
+ * `libnormalform_nexists`, `libnormalform_unique`),
+ * is this used as the input for the predicate
+ *
+ * In premapped qualifiers (`libnormalform_universally`,
+ * `libnormalform_existentially`, `libnormalform_uniquely`),
+ * is this unused
+ *
+ * Unused for domain size checks (`libnormalform_empty`,
+ * `libnormalform_nonempty`, `libnormalform_singleton`)
+ */
+ void *key;
+
+ /**
+ * Value
+ *
+ * In mapped qualifiers (`libnormalform_all`,
+ * `libnormalform_any`, `libnormalform_one`), this
+ * is used as the input for the predicate
+ *
+ * In unmapped qualifiers (`libnormalform_exists`,
+ * `libnormalform_nexists`, `libnormalform_unique`),
+ * is this unused
+ *
+ * In premapped qualifiers (`libnormalform_universally`,
+ * `libnormalform_existentially`, `libnormalform_uniquely`),
+ * is this used as the input for the predicate
+ *
+ * Unused for domain size checks (`libnormalform_empty`,
+ * `libnormalform_nonempty`, `libnormalform_singleton`)
+ */
+ void *value;
+};
+
+
+/**
+ * Domain of interest for qualifiers
+ */
+struct libnormalform_map {
+ /**
+ * Associative array over values in the domain
+ */
+ struct libnormalform_mapping *mappings;
+
+ /**
+ * The number of elements in `.mappings`
+ */
+ size_t nmappings;
+
+ /**
+ * Application-specific data that the application
+ * could use to identify the domain
+ *
+ * May be `NULL`
+ */
+ void *user_data;
+
+ /**
+ * Identifier for domain
+ *
+ * This need only be set when `libnormalform_to_string`
+ * is called. The value must be parsable by the application
+ * without any explicit termination
+ */
+ const char *identifier;
+
+ /**
+ * `NULL` or copy of of the object for `libnormalform_clone`
+ *
+ * This field should normally be unset, but before calling
+ * `libnormalform_clone`, the application must set it
+ * either to `NULL` (if the clone can use the same reference)
+ * or to a clone of the variable.
+ */
+ struct libnormalform_map *copy_for_clone;
+};
+
+
+struct libnormalform_atom_comparsion {
+ int version;
+ enum libnormalform_sentences_relationship relationship;
+};
+
+struct libnormalform_domain_comparsion {
+ int version;
+ enum libnormalform_domain_relationship relationship;
+};
+
+struct libnormalform_analysers { /* TODO doc */
+ void *user_data;
+ int (*compare_variable_to_variable)(void *user_data, struct libnormalform_atom_comparsion *result,
+ struct libnormalform_variable *left, struct libnormalform_variable *right);
+ int (*compare_function_to_function)(void *user_data, struct libnormalform_atom_comparsion *result,
+ struct libnormalform_function *left, struct libnormalform_function *right);
+ int (*compare_variable_to_function)(void *user_data, struct libnormalform_atom_comparsion *result,
+ struct libnormalform_variable *left, struct libnormalform_function *right);
+ int (*compare_domains)(void *user_data, struct libnormalform_domain_comparsion *result,
+ struct libnormalform_map *left, struct libnormalform_map *right);
+};
+
+
+/**
+ * Qualifier
+ */
+struct libnormalform_qualification {
+ struct libnormalform_map *map; /**< The domain of interest */
+ struct libnormalform_term *antecedent; /**< The antecedent, `NULL` if joined with the predicate */
+ struct libnormalform_term *predicate; /**< The predicate */
+};
+
+
+/**
+ * Clause of terms
+ */
+struct libnormalform_clause {
+ struct libnormalform_term *terms; /**< The terms in the clause */
+ size_t nterms; /**< The number of terms in the clause */
+};
+
+
+/**
+ * Transformater function with input
+ */
+struct libnormalform_transformation {
+ struct libnormalform_transformer *transformer; /**< The transformer function */
+ struct libnormalform_term *sentence; /**< The sentence the transformation is over */
+};
+
+
+/**
+ * Application-readable sentence
+ */
+struct libnormalform_term {
+ /**
+ * The type of the clause, qualifier, literal, or transformation
+ */
+ enum libnormalform_term_type type;
+
+ /**
+ * Whether the term or own of its subterms have been reduced;
+ */
+ int reduced;
+
+ /**
+ * The description of the sentence
+ */
+ union {
+ /**
+ * Use if `.type` is `LIBNORMALFORM_DISJUNCTION`
+ */
+ struct libnormalform_clause disjunction;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_CONJUNCTION`
+ */
+ struct libnormalform_clause conjunction;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_EXCLUSIVE_DISJUNCTION`
+ */
+ struct libnormalform_clause exclusive_disjunction;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_DISJUNCTION`,
+ * `LIBNORMALFORM_CONJUNCTION`, or
+ * `LIBNORMALFORM_EXCLUSIVE_DISJUNCTION`
+ */
+ struct libnormalform_clause clause;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_TRANSFORMATION`
+ */
+ struct libnormalform_transformation transformation;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_VARIABLE` or
+ * `LIBNORMALFORM_NEGATED_VARIABLE`
+ */
+ struct libnormalform_variable *variable;
+
+ /**
+ * Use if `.type` is `LIBNORMALFORM_FUNCTION` or
+ * `LIBNORMALFORM_NEGATED_FUNCTION`
+ */
+ struct libnormalform_function *function;
+
+ /**
+ * Use if '.type` is `LIBNORMALFORM_FOR_ALL` or
+ * `LIBNORMALFORM_NEGATED_FOR_ALL`
+ */
+ struct libnormalform_qualification for_all;
+
+ /**
+ * Use if '.type` is `LIBNORMALFORM_FOR_ANY` or
+ * `LIBNORMALFORM_NEGATED_FOR_ANY`
+ */
+ struct libnormalform_qualification for_any;
+
+ /**
+ * Use if '.type` is `LIBNORMALFORM_FOR_ONE` or
+ * `LIBNORMALFORM_NEGATED_FOR_ONE`
+ */
+ struct libnormalform_qualification for_one;
+
+ /**
+ * Use if '.type` is `LIBNORMALFORM_FOR_ALL`,
+ * `LIBNORMALFORM_NEGATED_FOR_ALL`, `LIBNORMALFORM_FOR_ANY`,
+ * `LIBNORMALFORM_NEGATED_FOR_ANY`, `LIBNORMALFORM_FOR_ONT`,
+ * or `LIBNORMALFORM_NEGATED_FOR_ONE`
+ */
+ struct libnormalform_qualification qualification;
+ } term;
+};
+
+
+
+
+
+/**
+ * Increase the reference count of a sentence object by 1
+ *
+ * @param this The sentence object
+ * @return `this` on success, `NULL` on failure
+ *
+ * @throws ENOMEM The reference count is already maximised (at SIZE_MAX)
+ *
+ * Reasonable application can safely assume that `this` is always returned
+ *
+ * Note, `NULL` is returned without `errno` modified if `this` is `NULL`
+ *
+ * Be aware that this function is not thread-safe
+ *
+ * @seealso libnormalform_clone
+ * @seealso libnormalform_free
+ */
+LIBNORMALFORM_SENTENCE *(libnormalform_ref)(LIBNORMALFORM_SENTENCE *this);
+
+
+/**
+ * Create a copy of a sentence object
+ *
+ * This may be necessary as usage of sentence objects are not thread-safe,
+ * creating a copy of a sentence object will allow one thread to safely
+ * use one copy and another thread the other copy
+ *
+ * Before calling this function, the application shall set `.copy_for_clone`
+ * in each `struct libnormalform_variable`, `struct libnormalform_function`,
+ * `struct libnormalform_map` and `struct libnormalform_transformer`, used
+ * by the sentence. `.copy_for_clone` shall either be set to `NULL` or a copy
+ * of object. If `NULL` is used, the copy of `this` will use the same
+ * instance of the object as `this`.
+ *
+ * @param this The sentence object
+ * @return A unique pointer on success or `NULL` if
+ * `this` is `NULL`; `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create a copy of `this`
+ *
+ * Be aware that this function is not thread-safe
+ *
+ * @seealso libnormalform_ref
+ * @seealso libnormalform_free
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_clone)(LIBNORMALFORM_SENTENCE *this);
+
+
+/**
+ * When `this` is a `LIBNORMALFORM_SENTENCE *`:
+ *
+ * Decrease the reference count of a sentence object by 1,
+ * if the object reference count reaches 0, deallocate the
+ * object.
+ *
+ * Because all functions that return a new sentence object
+ * (excluding `libnormalform_clone`), adopt the ownership
+ * of the reference to each input sentence (and may create
+ * it's own subsentences), this function is called recursively,
+ * and the application shall not attempt to call this function
+ * for sentences used to create other sentences
+ *
+ * Be aware the this function will not deallocate any
+ * `struct libnormalform_variable *`,
+ * `struct libnormalform_function *`, or
+ * `struct libnormalform_map *` used in the sentence as
+ * these are owned any managed by the application
+ *
+ * When `this` is a `struct libnormalform_term *`:
+ *
+ * Recursively deallocate `this`. This will only deallocate
+ * `this` and subobjects with the same type
+ *
+ * If `.type` is invalid for `this` or any subobject, the
+ * function's behaviour is undefined
+ *
+ * This function is a no-operation function when `this` is `NULL`
+ *
+ * @param this The object
+ *
+ * This function's behaviour is undefined if `this` is not `NULL`
+ * but not of the one of the above listed types
+ *
+ * Be aware that this function is not thread-safe
+ */
+void (libnormalform_free)(void *this);
+
+
+/**
+ * Evaluate a sentence to determine it's trueness
+ *
+ * Before calling this function, the application code shall
+ * configure any `struct libnormalform_variable`,
+ * `struct libnormalform_function`, and `struct libnormalform_map`
+ * used by the sentence. For each `struct libnormalform_variable`,
+ * `.value` shall either be set to `LIBNORMALFORM_TRUE` or
+ * `LIBNORMALFORM_FALSE` to indicate the value of the atom.
+ * For each `struct libnormalform_function`, `.evaluate` shall
+ * be set to a pointer to function that evaluates the input and
+ * `.user_data` shall be set to any application data the function
+ * needs to operate; `.evaluate` shall return 1, 0, or -1 as
+ * specified for this function; and may set `errno` to indicate
+ * an error (library itself does not look at `errno`, but may
+ * be used to signal the error to the application, of course
+ * the error may also be specified to the application in an
+ * application-specific way). For each `struct libnormalform_map`,
+ * `.mappings` shall be to the associated array of values
+ * that shall be tested by the qualifier-sentence it is
+ * used by, and `.nmappings` shall be set to the number of
+ * pairs in this array.
+ *
+ * @param this The sentence to evaluate
+ * @return 1 if the sentence is true,
+ * 0 if the sentence is false,
+ * -1 on failure
+ *
+ * Only application code may cause this function to
+ * fail, therefore no error codes are predefined for
+ * this function, instead `errno` is only set by
+ * the failing application code
+ *
+ * Be aware that this function is not thread-safe
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+int (libnormalform_evaluate)(LIBNORMALFORM_SENTENCE *this);
+
+
+
+
+
+/**
+ * Return an application readable expression of a statement in negation
+ * normal form, optionally with XOR, and reduce the statement to a
+ * statement that is at least as true (but as false as possible), and
+ * is minimised, according what the application specifies that it can
+ * work with
+ *
+ * Before calling this function, the application shall set `.relaxation`
+ * `.requires_relaxation` in each `struct libnormalform_function` used
+ * used by the sentence. `.requires_relaxation` may be set as 0 if the
+ * function shall not be replaced (in this case `.relaxation` need not
+ * be set), but set to 1 if the function most be replaced with a more
+ * true function. In the latter case, `.relaxation` shall either be set
+ * to the new function or to `NULL`; if set to null, the function is
+ * reduced to a tautological sentence. For any `libnormalform_transformer`
+ * it must also set `.requires_elimination` to 1 if the transformation
+ * must be replaces with a tautology, and 0 otherwise.
+ *
+ * @param this The sentence to describe
+ * @param flags The bitwise OR of any number of the following values:
+ * - LIBNORMALFORM_AVOID_FOR_ALL: Prefer ¬∃x.¬φ over ∀x.φ
+ * - LIBNORMALFORM_AVOID_NEGATED_FOR_ALL: Prefer ∃x.¬φ over ¬∀x.φ
+ * - LIBNORMALFORM_AVOID_FOR_ANY: Prefer ¬∀x.¬φ over ∃x.φ
+ * - LIBNORMALFORM_AVOID_NEGATED_FOR_ANY: Prefer ∀x.¬φ over ¬∃x.φ
+ * - LIBNORMALFORM_RELAX_FOR_ONE: Relax any positive ∃!x.φ into ∃x.φ
+ * - LIBNORMALFORM_REDUCE_XOR: Do not use XOR in the returned statement,
+ * but transform it to negation normal form
+ * - LIBNORMALFORM_JOIN_SIDES_IN_FOR_ALL Express φ(x)∀x:θ(x) on the form ∀x.(θ(x) → φ(x))
+ * - LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ALL Express ¬(φ(x)∀x:θ(x)) on the form ¬∀x.(θ(x) → φ(x))
+ * - LIBNORMALFORM_JOIN_SIDES_IN_FOR_ANY Express φ(x)∃x:θ(x) on the form ∃x.(θ(x) ∧ φ(x))
+ * - LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ANY Express ¬(φ(x)∃x:θ(x)) on the form ¬∃x.(θ(x) ∧ φ(x))
+ * - LIBNORMALFORM_JOIN_SIDES_IN_FOR_ONE Express φ(x)∃!x:θ(x) on the form ∃!x.(θ(x) ∧ φ(x))
+ * - LIBNORMALFORM_JOIN_SIDES_IN_NEGATED_FOR_ONE Express ¬(φ(x)∃!x:θ(x)) on the form ¬∃!x.(θ(x) ∧ φ(x))
+ * - LIBNORMALFORM_ELIMINATE_FOR_ALL Relax any non-translatable ∀x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ALL Relax any non-translatable ¬∀x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_FOR_ANY Relax any non-translatable ∃x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_IN_NEGATED_FOR_ANY Relax any non-translatable ¬∃x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_IN_FOR_ONE Relax any non-translatable ∃!x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_IN_NEGATED_FOR_ONE Relax any ¬∃!x.φ to ⊤
+ * - LIBNORMALFORM_ELIMINATE_VARIABLE Relax any positive variable literal to ⊤
+ * - LIBNORMALFORM_ELIMINATE_NEGATED_VARIABLE Relax any negative variable literal to ⊤
+ * - LIBNORMALFORM_ELIMINATE_FUNCTION Relax any positive function literal to ⊤
+ * - LIBNORMALFORM_ELIMINATE_NEGATED_FUNCTION Relax any negative function literal to ⊤
+ * - LIBNORMALFORM_ELIMINATE_XOR Remove all XOR's (nullified by `LIBNORMALFORM_REDUCE_XOR`)
+ * @param analysers Application provided functions to help reduce the statement, or `NULL`
+ * @return The description of the sentence, `NULL` on failure;
+ * the returned pointer shall be deallocated with
+ * `libnormalform_free` when it is no longer needed
+ *
+ * @seealso libnormalform_dnf
+ * @seealso libnormalform_cnf
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_FREE_WITH__(libnormalform_free) LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+struct libnormalform_term *(libnormalform_express)(LIBNORMALFORM_SENTENCE *this, uint64_t flags, /* TODO man */
+ const struct libnormalform_analysers *analysers);
+
+
+/**
+ * Variant of `libnormalform_express` that always canonicalises
+ * the statement to disjunctive normal form
+ *
+ * @param this The sentence to describe
+ * @param flags See `libnormalform_express`; `LIBNORMALFORM_ELIMINATE_XOR`
+ * is always applied, however `LIBNORMALFORM_REDUCE_XOR` is not,
+ * but the user is adviced use `LIBNORMALFORM_REDUCE_XOR` unless
+ * there is a risk that it would be too costly (EXPSPACE)
+ * @param analysers Application provided functions to help reduce the statement, or `NULL`
+ * @return The description of the sentence, `NULL` on failure;
+ * the returned pointer shall be deallocated with
+ * `libnormalform_free` when it is no longer needed
+ *
+ * @seealso libnormalform_express
+ * @seealso libnormalform_cnf
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_FREE_WITH__(libnormalform_free) LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+struct libnormalform_term *(libnormalform_dnf)(LIBNORMALFORM_SENTENCE *this, uint64_t flags, /* TODO */ /* TODO man */
+ const struct libnormalform_analysers *analysers);
+
+
+/**
+ * Variant of `libnormalform_express` that always canonicalises
+ * the statement to conjuctive normal form
+ *
+ * @param this The sentence to describe
+ * @param flags See `libnormalform_express`; `LIBNORMALFORM_ELIMINATE_XOR`
+ * is always applied, however `LIBNORMALFORM_REDUCE_XOR` is not,
+ * but the user is adviced use `LIBNORMALFORM_REDUCE_XOR` unless
+ * there is a risk that it would be too costly (EXPSPACE)
+ * @param analysers Application provided functions to help reduce the statement, or `NULL`
+ * @return The description of the sentence, `NULL` on failure;
+ * the returned pointer shall be deallocated with
+ * `libnormalform_free` when it is no longer needed
+ *
+ * @seealso libnormalform_express
+ * @seealso libnormalform_dnf
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_FREE_WITH__(libnormalform_free) LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+struct libnormalform_term *(libnormalform_cnf)(LIBNORMALFORM_SENTENCE *this, uint64_t flags, /* TODO */ /* TODO man */
+ const struct libnormalform_analysers *analysers);
+
+
+/**
+ * Convert a sentence object to a string that can be parsed
+ * by `libnormalform_from_string` (and is somewhat human-readable)
+ *
+ * Before calling this function, the application shall set `.identifier`
+ * in each `struct libnormalform_variable`, `struct libnormalform_function`,
+ * `struct libnormalform_map`, and `struct libnormalform_transformer` used
+ * by the sentence to a non-`NULL`, unique string that identifies (but
+ * does not necessarily describe) the object. The application must be able
+ * to find the end of any such string without any explicit termination
+ * (e.g., eithout there being a NUL-terminator)
+ *
+ * Note that when a sentence is created, it is optimised and
+ * reduced into fewer types of connetives (it's reducted into
+ * negation normal form, except with XOR allowed, but not
+ * necessarily to any canonical form) during construction,
+ * so this function will not necessarily reproduce the sentence
+ * as it was specified when it was constructed.
+ *
+ * @param this The sentence object to serialise
+ * @return String-representation of `this`, `NULL` failure;
+ * the caller shall deallocate the returned pointer
+ * with free(3) when it is no longer needed
+ *
+ * @throws ENOMEM Insufficient memory available to serialise `this`
+ *
+ * @seealso libnormalform_from_string
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_USE_FREE__ LIBNORMALFORM_NONNULL_INPUT__
+char *(libnormalform_to_string)(LIBNORMALFORM_SENTENCE *this);
+
+
+/**
+ * Convert a string, created with `libnormalform_to_string`
+ * (or is on similar form), to sentence object
+ *
+ * The string shall be on the whitespace-insensitive form:
+ *
+ * constant ::= "TRUE" | "FALSE";
+ * connective1 ::= "NOT";
+ * connective2 ::= "AND" | "OR" | "XOR" | "IF" | "IMPLY" |
+ * "NAND" | "NOR" | "XNOR" | "NIF" | "NIMPLY";
+ * qualifier0 ::= "EMPTY" | "NONEMPTY" | "SINGLETON";
+ * qualifier1 ::= "EXISTS" | "NEXISTS" | "UNIQUE" |
+ * "EXISTENTIALLY" | "UNIVERSALLY" | "UNIQUELY";
+ * qualifier2 ::= "ALL" | "ANY" | "ONE";
+ * unary ::= connective1 [reference-point] "(" sentence ")";
+ * variadic ::= connective2 [reference-point] "(" [sentence-list] ")";
+ * sentence-list ::= sentence ["," sentence-list];
+ * qualified0 ::= qualifier0 [reference-point] "(" map ")";
+ * qualified1 ::= qualifier1 [reference-point] "(" map "," sentence ")";
+ * qualified2 ::= qualifier2 [reference-point] "(" map "," sentence "," sentence ")";
+ * atom ::= atom-var | atom-fun;
+ * atom-var ::= "VARIABLE" [reference-point] "(" variable ")";
+ * atom-fun ::= "FUNCTION" [reference-point] "(" function ")";
+ * transformation ::= "TRANSFORMATION" [reference-point] "(" transformer "," sentence ")";
+ * reference-point ::= "@" decimal-number;
+ * reference ::= "REF(" decimal-number ")";
+ * one-to-nine ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
+ * zero-to-nine ::= "0" | one-to-nine;
+ * decimal-number ::= "0" | positive-decimal;
+ * positive-decimal ::= one-to-nine [digits];
+ * digits ::= zero-to-nine [digits];
+ * sentence ::= constant | unary | variadic | qualified0 | qualified1 |
+ * qualified2 | atom | transformation | reference;
+ *
+ * where "sentence" is the root, and where "map" is parsed by `*spec->get_map`,
+ * "variable" is parsed by `*spec->get_variable`, "function" is parsed by
+ * `*spec->get_function`, and "transformer" is parsed by `*spec->get_transformer`.
+ * References must start at 0 and increment by one, in left-to-right order,
+ * every time reference point is specified, and forward-references are not allowed
+ * (this includes reference to containing sentence (whose reference ID is lower
+ * because it is written earlier)).
+ *
+ * @param s The representation of the sentence to deserialise
+ * @param end_out Output parameter for the end of `s`;
+ * if `NULL` the function will fail if `s` contains
+ * excess information
+ * @param spec Specification for how application managed objects
+ * are to be deserialise
+ * @return A unique pointer, that is a deserialisation of `s`,
+ * or `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to deserialise `s`
+ * @throws EINVAL `s` was not properly formatted
+ * @throws EINVAL `end_out` was `NULL` and `s` containd excess information
+ * @throws EDOM `s` contained a empty clause for a connective
+ * that does not support empty clauses
+ * @throws EDOM `s` contained a transformation over a qualifer
+ * @throws ENOENT `s` contained a variable but `spec->get_variable` was `NULL`
+ * @throws ENOENT `s` contained a boolean function but `spec->get_function` was `NULL`
+ * @throws ENOENT `s` contained a domain but `spec->get_map` was `NULL`
+ * @throws ENOENT `s` contained a transformation but `spec->get_transformer` was `NULL`
+ *
+ * If function in `spec` fails, this function will fail and keep `errno`
+ * as set by the function that failed
+ *
+ * The function itself does not modify `s` and can operate on read-only
+ * memory, however an offset of the string may be passed into application
+ * code. The application is free to modify `s` if it knows the memory
+ * is indeed writable.
+ *
+ * @seealso libnormalform_to_string
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_ONE_NONNULL_INPUT__(1) LIBNORMALFORM_ONE_NONNULL_INPUT__(3)
+LIBNORMALFORM_SENTENCE *(libnormalform_from_string)(char *s, char **end_out, const struct libnormalform_representation_spec *spec);
+
+
+
+
+
+/**
+ * Create an atomic sentence whose value is externally set
+ *
+ * @param variable The variable the sentence shall express
+ * @return A sentence object referencing `variable`, `NULL` on failure
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_variable)(struct libnormalform_variable *variable);
+
+
+/**
+ * Create an atomic sentence whose value is externally evaluated
+ *
+ * @param function The function the sentence shall express
+ * @return A sentence object referencing `function`, `NULL` on failure
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_function)(struct libnormalform_function *function);
+
+
+/**
+ * Create an endomorphic transformation of the input for a sentence
+ *
+ * `function->builtin` will automatically be initialised to `LIBNORMALFORM_NOT_BUILT_IN`
+ *
+ * @param function The transformation function
+ * @param sentence The sentence whose input shall be transformed
+ * @return A sentence whose input is transformed by `function`, `NULL` on failure
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ * @throws EDOM `sentence` is a qualifier
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+LIBNORMALFORM_SENTENCE *(libnormalform_transformation)(struct libnormalform_transformer *function, LIBNORMALFORM_SENTENCE *sentence);
+
+
+/**
+ * TRUE: Tautology
+ *
+ * Always true
+ *
+ * Commonly written "⊤", "1", or "T"
+ *
+ * @return An atomic sentence with a constant value of True, `NULL` on failure
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_true)(void);
+
+
+/**
+ * FALSE: Contradiction
+ *
+ * Always false
+ *
+ * Commonly written "⊥", "1", or "F"
+ *
+ * @return An atomic sentence with a constant value of False, `NULL` on failure
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_false)(void);
+
+
+/**
+ * NOT: Negation
+ *
+ * The condition must not be met:
+ * ¬0 = 1
+ * ¬1 = 0
+ *
+ * Properties:
+ * - Involution
+ * - Distribution over AND changes connective to OR (De Morgan's law)
+ * - Distribution over OR changes connective to AND (De Morgan's law)
+ *
+ * Commonly written "¬" ("¬x"), "~", or "!", or with overstroke
+ *
+ * @param x The sentence that should be negated;
+ * ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `x` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `x` to
+ * be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_not)(LIBNORMALFORM_SENTENCE *x);
+
+
+/**
+ * FOR ALL: Universal quantification (mapped)
+ *
+ * The predicate must be met for every element that the antecedent
+ * is met for; if no element exists, or the antecedent is false for
+ * every element, the sentence is true
+ *
+ * Properties:
+ * - Vacuous truth
+ * - ∀x∈D.P(x) → ⊤ = ⊤
+ * - ∀x∈D.⊥ → Q(x) = ⊤
+ * - ∀x∈D.⊤ → ⊥ ⊨ D = ∅
+ * - Conjunction
+ *
+ * Commonly written "∀" ("∀x∈d.k(x) → v(x)", "∀x∈d (k(x) → v(x))", or "v(x) ∀ x∈d : k(x)")
+ *
+ * The antecedent is tested for every `.key` in the domain,
+ * and the predicate is tested for every associated `.value`
+ * where the antecedent is met; but the testing is cut short
+ * if the antecedent is met and not the predicate for an element
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The antecedent; ownership is transfered to this function
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` or `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` or
+ * `v` to be `NULL`)
+ *
+ * @seealso libnormalform_nexists
+ * @seealso libnormalform_universally
+ * @seealso libnormalform_any
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+LIBNORMALFORM_SENTENCE *(libnormalform_all)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v);
+
+
+/**
+ * THERE EXISTS: Existential quantification (mapped)
+ *
+ * The predicate must be met for at least one element that the
+ * antecedent is met for; if no element exists, or the antecedent is
+ * false for every element, the sentence is false
+ *
+ * Properties:
+ * - Vacuous falsity
+ * - ∃x∈D.P(x) ∧ ⊥ = ⊥
+ * - ∃x∈D.⊥ ∧ Q(x) = ⊥
+ * - ∃x∈D.⊤ ∧ ⊤ ⊨ D ⊃ ∅
+ * - Disjunction
+ *
+ * Commonly written "∃" ("∃x∈d.k(x) ∧ v(x)", "∃x∈d (k(x) ∧ v(x))", or "v(x) ∃ x∈d : k(x)")
+ *
+ * The antecedent is tested for every `.key` in the domain,
+ * and the predicate is tested for every associated `.value`
+ * where the antecedent is met; but the testing is cut short
+ * if both the antecedent and predicate is met for an element
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The antecedent; ownership is transfered to this function
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` or `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` or
+ * `v` to be `NULL`)
+ *
+ * @seealso libnormalform_exists
+ * @seealso libnormalform_existentially
+ * @seealso libnormalform_one
+ * @seealso libnormalform_all
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+LIBNORMALFORM_SENTENCE *(libnormalform_any)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v);
+
+
+/**
+ * THERE EXISTS ONE: Unique existential quantification (mapped)
+ *
+ * The predicate must be met for exactly one element that the
+ * antecedent is met for; if no element exists, or the antecedent is
+ * false for every element, the sentence is false
+ *
+ * Properties:
+ * - Vacuous falsity
+ * - ∃!x∈D.P(x) ∧ ⊥ = ⊥
+ * - ∃!x∈D.⊥ ∧ Q(x) = ⊥
+ * - ∃!x∈D.⊤ ∧ ⊤ ⊨ |D| = 1
+ *
+ * Commonly written "∃!" ("∃!x∈d.k(x) ∧ v(x)", "∃!x∈d (k(x) ∧ v(x))", or "v(x) ∃! x∈d : k(x)")
+ *
+ * The antecedent is tested for every `.key` in the domain,
+ * and the predicate is tested for every associated `.value`
+ * where the antecedent is met; but the testing is cut short
+ * if both the antecedent and predicate is met for two elements
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The antecedent; ownership is transfered to this function
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` or `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` or
+ * `v` to be `NULL`)
+ *
+ * @seealso libnormalform_unique
+ * @seealso libnormalform_uniquely
+ * @seealso libnormalform_any
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_ONE_NONNULL_INPUT__(1)
+LIBNORMALFORM_SENTENCE *(libnormalform_one)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v);
+
+
+
+
+
+/**
+ * AND: Conjunction
+ *
+ * Also known as BUT
+ *
+ * All conditions must be met
+ *
+ * 0 ∧ 0 = 0
+ * 0 ∧ 1 = 0
+ * 1 ∧ 0 = 0
+ * 1 ∧ 1 = 1
+ *
+ * Properties:
+ * - Vacuous truth
+ * - Identity singleton
+ * - Commutative
+ * - Associative
+ * - Distributive over OR
+ * - Distributive over XOR
+ * - ⊤ as identity
+ * - Dominated by ⊥
+ * - Idempotent
+ * - Connecting complement results in contradiction
+ * - More lax term is absorbed (A ∧ (A ∨ B) simplifies to A)
+ *
+ * Commonly written "∧" ("⋀" if n-ary), "&", or with juxtaposition
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective AND; this can be any number
+ * of arguments, even none; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_AND
+ * @seealso libnormalform_and_checked
+ * @seealso libnormalform_andl_checked
+ * @seealso libnormalform_vand_checked
+ * @seealso libnormalform_andl
+ * @seealso libnormalform_vand
+ * @seealso libnormalform_and2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_and)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * OR: Disjunction
+ *
+ * Also known as Inclusive disjunction
+ *
+ * At least one condition must be met
+ *
+ * 0 ∨ 0 = 0
+ * 0 ∨ 1 = 1
+ * 1 ∨ 0 = 1
+ * 1 ∨ 1 = 1
+ *
+ * Properties:
+ * - Vacuous falsity
+ * - Identity singleton
+ * - Commutative
+ * - Associative
+ * - Distributive over AND
+ * - ⊥ as identity
+ * - Dominated by ⊤
+ * - Idempotent
+ * - Connecting complement results in tautology
+ * - More strict term is absorbed (A ∨ (A ∧ B) simplifies to A)
+ *
+ * Commonly written "∨" ("⋁" if n-ary), "|", or "+"
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective OR; this can be any number
+ * of arguments, even none; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_OR
+ * @seealso libnormalform_or_checked
+ * @seealso libnormalform_orl_checked
+ * @seealso libnormalform_vor_checked
+ * @seealso libnormalform_orl
+ * @seealso libnormalform_vor
+ * @seealso libnormalform_or2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_or)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * XOR: Exclusive disjunction
+ *
+ * Also known as EOR, EXOR, NEQ, NIFF, Parity (especially when n-ary),
+ * Exclusive alternation, Exclusive or, Logical non-equivalence,
+ * Logical non-equivalence, Logical inequality, Not if and only if,
+ * or Unless and only unless
+ *
+ * An odd number of conditions must be met
+ *
+ * 0 ⊕ 0 = 0
+ * 0 ⊕ 1 = 1
+ * 1 ⊕ 0 = 1
+ * 1 ⊕ 1 = 0
+ *
+ * Properties:
+ * - Vacuous falsity
+ * - Identity singleton
+ * - Commutative
+ * - Associative
+ * - ⊥ as identity
+ * - Complemented by ⊤
+ * - Annihiliation by reflextion
+ * - Connecting complement results in tautology
+ * - A ⊕ B is reduced to (A ∨ B) ∧ (¬A ∨ ¬B) or
+ * (A ∧ ¬B) ∨ (¬A ∧ B) in negation normal form
+ *
+ * Commonly written "⊕" ("⨁" if n-ary), "⊻", "↮", "⇎", "≢", or "^"
+ *
+ * Note that the name Exclusive disjunction is misleading
+ * when there are more than two terms, as the statement
+ * will not check that exactly one term is true, but rather
+ * calculate the parity of the terms.
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective XOR; this can be any number
+ * of arguments, even none; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_XOR
+ * @seealso libnormalform_xor_checked
+ * @seealso libnormalform_xorl_checked
+ * @seealso libnormalform_vxor_checked
+ * @seealso libnormalform_xorl
+ * @seealso libnormalform_vxor
+ * @seealso libnormalform_xor2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_xor)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * IF: Converse implication
+ *
+ * Also known as Converse conditional
+ *
+ * Creates a chain where any condition must be at least
+ * as satisfied as the condition left to it (xs[0] ≤ xs[1])
+ * (Not xs[1] without xs[0]); it is false only when only
+ * the left-most term is false
+ *
+ * 0 ← 0 = 1
+ * 0 ← 1 = 0
+ * 1 ← 0 = 1
+ * 1 ← 1 = 1
+ *
+ * Properties:
+ * - Identity singleton
+ * - Non-commutative
+ * - Left-associative: A ← B ← C = (A ← B) ← C ≠ A ← (B ← C)
+ * - Complemented by left ⊥
+ * - Dominated by left ⊤
+ * - Right falsity results in tautology
+ * - ⊤ as right identity
+ * - Contrapositivity: A ← B = ¬B ← ¬A
+ * - Transitive: (A ← B) ∧ (B ← C) ⊨ A ← C
+ * - Reflexive: A ← A = ⊤
+ * - Complete: (A ← B) ∨ (B ← A) = ⊤
+ * - Excluded middle: (A ← B) ∨ (¬A ← B) = ⊤
+ * - Import-export: (A ← B) ← C = A ← (B ∧ C)
+ * - Commutativity of antecedents: (A ← B) ← C = (A ← C) ← B
+ * - Vacuous conditional: ¬A ⊨ B ← A
+ * - Antecedent strengthening: A ← B ⊨ A ← (B ∧ C)
+ * - Right-distributive: (A ← B) ← C = (A ← C) ← (B ← C)
+ * - Distributive over left AND
+ * - Distributive over left OR
+ * - Distributing over right AND changes AND to OR
+ * - Distributing over right OR changes OR to AND
+ * - A ← B is reduced to A ∨ ¬B in negation normal form
+ *
+ * Commonly written "←", "⇐", "<-", "<=", "≤", "⩽", or "⊂"
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective IF; this can be any positive
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ * @throws EDOM `xs` contains no term (but only `NULL`)
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_IF
+ * @seealso libnormalform_if_checked
+ * @seealso libnormalform_ifl_checked
+ * @seealso libnormalform_vif_checked
+ * @seealso libnormalform_ifl
+ * @seealso libnormalform_vif
+ * @seealso libnormalform_if2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_if)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * IMPLY: Material implication
+ *
+ * Also known as IMPLIES, IF..THEN, NOT..WITHOUT or material conditional
+ *
+ * Creates a chain where any condition must be at most
+ * as satisfied as the condition left to it (xs[0] ≥ xs[1])
+ * (Not xs[0] without xs[1]); it is false only when only
+ * the right-most term is false
+ *
+ * 0 → 0 = 1
+ * 0 → 1 = 1
+ * 1 → 0 = 0
+ * 1 → 1 = 1
+ *
+ * Properties:
+ * - Vacuous truth
+ * - Identity singleton
+ * - Non-commutative
+ * - Right-associative: A → B → C = A → (B → C) ≠ (A → B) → C
+ * - Complemented by right ⊥
+ * - Dominated by right ⊤
+ * - Left falsity results in tautology
+ * - ⊤ as left identity
+ * - Contrapositivity: A → B = ¬B → ¬A
+ * - Transitive: (A → B) ∧ (B → C) ⊨ A → C
+ * - Reflexive: A → A = ⊤
+ * - Complete: (A → B) ∨ (B → A) = ⊤
+ * - Excluded middle: (A → B) ∨ (A → ¬B) = ⊤
+ * - Import-export: A → (B → C) = (A ∧ B) → C
+ * - Commutativity of antecedents: A → (B → C) = B → (A → C)
+ * - Vacuous conditional: ¬A ⊨ A → B
+ * - Antecedent strengthening: A → B ⊨ (A ∧ C) → B
+ * - Left-distributive: A → (B → C) = (A → B) → (A → C)
+ * - Distributive over right AND
+ * - Distributive over right OR
+ * - Distributing over left AND changes AND to OR
+ * - Distributing over left OR changes OR to AND
+ * - A → B is reduced to ¬A ∨ B in negation normal form
+ *
+ * Commonly written "→", "⇒", "->", "=>", ">=", "≥", "⩾", or "⊃"
+ *
+ * @param xs `NULL`-terminated list of sentences that should
+ * be joined by the connective IMPLY; this can be any
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_IMPLY
+ * @seealso libnormalform_imply_checked
+ * @seealso libnormalform_implyl_checked
+ * @seealso libnormalform_vimply_checked
+ * @seealso libnormalform_implyl
+ * @seealso libnormalform_vimply
+ * @seealso libnormalform_imply2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_imply)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * NAND: Alternative denial
+ *
+ * Also known as Non-conjunction, Negated conjunction,
+ * Mutual exclusion, or Sheffer stroke
+ *
+ * Creates a left-associated NAND-chain; in NAND, the
+ * two conditions must not be met at the same time
+ *
+ * 0 ⊼ 0 = 1
+ * 0 ⊼ 1 = 1
+ * 1 ⊼ 0 = 1
+ * 1 ⊼ 1 = 0
+ *
+ * There is no good interpretation for a NAND-chain
+ *
+ * Commonly written "⊼", "↑", or as AND with the expression over-struck
+ *
+ * Properties:
+ * - Identity singleton
+ * - Commutative (however A ⊼ B ⊼ C ≠ A ⊼ C ⊼ B due to non-associativity)
+ * - Left-associative: A ⊼ B ⊼ C = (A ⊼ B) ⊼ C ≠ A ⊼ (B ⊼ C)
+ * - Antireflexive: A ⊼ A = ⊥
+ * - Annihilated by ⊥: A ⊼ ⊥ = ⊤
+ * - Complemented by ⊤
+ * - Distributative over AND
+ * - A ⊼ B is reduced to ¬A ∨ ¬B in negation normal form
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective NAND; this can be any positive
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ * @throws EDOM `xs` contains no term (but only `NULL`)
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_NAND
+ * @seealso libnormalform_nand_checked
+ * @seealso libnormalform_nandl_checked
+ * @seealso libnormalform_vnand_checked
+ * @seealso libnormalform_nandl
+ * @seealso libnormalform_vnand
+ * @seealso libnormalform_nand2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_nand)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * NOR: Joint denial
+ *
+ * Also known as Non-disjunction, Negated disjunction, Peirce arrow,
+ * Quine dagger, or Webb operator
+ *
+ * Creates a left-associated NOR-chain; in NOR, the
+ * two conditions must be unmet at same time
+ *
+ * 0 ⊽ 0 = 1
+ * 0 ⊽ 1 = 0
+ * 1 ⊽ 0 = 0
+ * 1 ⊽ 1 = 0
+ *
+ * There is no good interpretation for a NOR-chain
+ *
+ * Commonly written "⊽", "↓", or as OR with the expression over-struck
+ *
+ * Properties:
+ * - Identity singleton
+ * - Commutative (however A ⊽ B ⊽ C ≠ A ⊽ C ⊽ B due to non-associativity)
+ * - Left-associative: A ⊽ B ⊽ C = (A ⊽ B) ⊽ C ≠ A ⊽ (B ⊽ C)
+ * - Anti-idempotent: A ⊽ A = ¬A
+ * - Annihilated by ⊤: A ⊽ ⊤ = ⊥
+ * - Complemented by ⊤
+ * - Distributing over AND changes AND to OR
+ * - Distributing over OR changes OR to AND
+ * - A ⊽ B is reduced to ¬A ∧ ¬B in negation normal form
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective NOR; this can be any positive
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ * @throws EDOM `xs` contains no term (but only `NULL`)
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_NOR
+ * @seealso libnormalform_nor_checked
+ * @seealso libnormalform_norl_checked
+ * @seealso libnormalform_vnor_checked
+ * @seealso libnormalform_norl
+ * @seealso libnormalform_vnor
+ * @seealso libnormalform_nor2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_nor)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * XNOR: Logical bicondition
+ *
+ * Also known as ENOR, EXNOR, NXOR, XAND, EQ, IFF, Exclusive NOR,
+ * Material biconditional, Logical equivalence, Biimplication,
+ * Bientailment, If and only if, Negated XOR, or Exclusive non-disjunction
+ *
+ * Creates a left-associated XNOR-chain; in XNOR, the
+ * two conditions must equally true
+ *
+ * 0 ⊙ 0 = 1
+ * 0 ⊙ 1 = 0
+ * 1 ⊙ 0 = 0
+ * 1 ⊙ 1 = 1
+ *
+ * The interpretation of a XNOR-chain depends on it's length,
+ * if it's length is even, is equivalent to XOR, otherwise,
+ * it's its complement
+ *
+ * Commonly written "⊙" ("⨀" if n-ary), "↔", "⇔", "≡", "==", or
+ * as XOR with the expression over-struck
+ *
+ * Properties:
+ * - Vacuous truth
+ * - Identity singleton
+ * - Commutative
+ * - Associative
+ * - ⊤ as identity
+ * - Complemented by ⊥
+ * - Reflexive: A ⊙ A = ⊤
+ * - Annihilated by ⊤: A ⊽ ⊤ = ⊥
+ * - Connecting complement results in contradiction
+ * - A ⊙ B is reduced to (A ∨ ¬B) ∧ (¬A ∨ B) or
+ * (A ∧ B) ∨ (¬A ∧ ¬B) in negation normal form
+ *
+ * @param xs `NULL`-terminated list of sentences that should
+ * be joined by the connective XNOR; this can be any
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_NXOR
+ * @seealso libnormalform_nxor_checked
+ * @seealso libnormalform_nxorl_checked
+ * @seealso libnormalform_vxnor_checked
+ * @seealso libnormalform_xnorl
+ * @seealso libnormalform_vxnor
+ * @seealso libnormalform_xnor2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_xnor)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * NIF: Converse nonimplication
+ *
+ * Also known as NOT..BUT, Converse abjunction
+ *
+ * Creates a chain where any condition must be less
+ * satisfied than the condition left to it (xs[0] > xs[1])
+ *
+ * 0 ↚ 0 = 0
+ * 0 ↚ 1 = 1
+ * 1 ↚ 0 = 0
+ * 1 ↚ 1 = 0
+ *
+ * Properties:
+ * - Vacuous falsity
+ * - Identity singleton
+ * - Non-commutative
+ * - Left-associative: A ↚ B ↚ C = (A ↚ B) ↚ C ≠ A ↚ (B ↚ C)
+ * - Complemented by right ⊤
+ * - Dominated by right ⊥
+ * - Left truth results in contradiction
+ * - ⊥ as left identity
+ * - Contrapositivity: A ↚ B = ¬B ↚ ¬A
+ * - Antireflexive: A ↚ A = ⊥
+ * - Left-distributive: A ↚ (B ↚ C) = (A ↚ B) ↚ (A ↚ C)
+ * - Distributive over right AND
+ * - Distributive over right OR
+ * - Distributing over left AND changes AND to OR
+ * - Distributing over left OR changes OR to AND
+ * - A ↚ B is reduced to ¬A ∧ B in negation normal form
+ *
+ * Commonly written "↚", "⇍", "!<-", "<-~", "</-", "!<=", "<=~", "</=", "⊄", "⭉" (or "←̃"), or ">"
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective NIF; this can be any number
+ * of arguments, even none; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_NIF
+ * @seealso libnormalform_nif_checked
+ * @seealso libnormalform_nifl_checked
+ * @seealso libnormalform_vnif_checked
+ * @seealso libnormalform_nifl
+ * @seealso libnormalform_vnif
+ * @seealso libnormalform_nif2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_nif)(LIBNORMALFORM_SENTENCE **xs);
+
+
+/**
+ * NIMPLY: Material nonimplication
+ *
+ * Also known as NIMPLIES, BUT NOT or Material abjunction
+ *
+ * Creates a chain where any condition must be more
+ * satisfied than the condition left to it (xs[0] < xs[1])
+ *
+ * 0 ↛ 0 = 0
+ * 0 ↛ 1 = 0
+ * 1 ↛ 0 = 1
+ * 1 ↛ 1 = 0
+ *
+ * Properties:
+ * - Identity singleton
+ * - Non-commutative
+ * - Right-associative: A ↛ B ↛ C = A ↛ (B ↛ C) ≠ (A ↛ B) → C
+ * - Complemented by left ⊤
+ * - Dominated by left ⊥
+ * - Right truth results in contradiction
+ * - ⊥ as right identity
+ * - Contrapositivity: A ↛ B = ¬B ↛ ¬A
+ * - Antireflexive: A ↛ A = ⊥
+ * - Right-distributive: (A ↛ B) ↛ C = (A ↛ C) ↛ (B → C)
+ * - Distributive over left AND
+ * - Distributive over left OR
+ * - Distributing over right AND changes AND to OR
+ * - Distributing over right OR changes OR to AND
+ * - A ↛ B is reduced to A ∧ ¬B in negation normal form
+ *
+ * Commonly written "↛", "⇏", "!->", "->~", "-/>", "!=>", "=>~", "=/>", "⊅", "⥲" (or "→̃"), or "<"
+ *
+ * @param xs `NULL`-terminated list of sentences that should be
+ * joined by the connective NIMPLY; this can be any positive
+ * number of arguments; ownership of object in the array
+ * (but not the array itself) is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ * @throws EDOM `xs` contains no term (but only `NULL`)
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * @seealso LIBNORMALFORM_NIMPLY
+ * @seealso libnormalform_nimply_checked
+ * @seealso libnormalform_nimplyl_checked
+ * @seealso libnormalform_vnimply_checked
+ * @seealso libnormalform_nimplyl
+ * @seealso libnormalform_vnimply
+ * @seealso libnormalform_nimply2
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+LIBNORMALFORM_SENTENCE *(libnormalform_nimply)(LIBNORMALFORM_SENTENCE **xs);
+
+
+
+
+
+/**
+ * THERE EXISTS: Existential quantification (unmapped)
+ *
+ * The predicate must be met for at least one element;
+ * if no element exists, the sentence is false
+ *
+ * Commonly written "∃" ("∃x∈d.k", "∃x∈d k(x)", or "∃ x∈d : k(x)")
+ *
+ * The predicate is tested on `.key` in each element
+ * in the domain, and `.value` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_any
+ * @seealso libnormalform_existentially
+ * @seealso libnormalform_nexists
+ * @seealso libnormalform_unique
+ * @seealso libnormalform_nonempty
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_exists)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k)
+{ return libnormalform_any(d, k, libnormalform_true()); }
+
+
+/**
+ * THERE EXISTS NO: Negated existential quantification (unmapped)
+ *
+ * The predicate must be unmet for every element;
+ * if no element exists, the sentence is true
+ *
+ * Commonly written "∄" ("∄x∈d.k", "∄x∈d k(x)", or "∄ x∈d : k(x)"),
+ * "~∃", "¬∃", or as THERE EXISTS with overline
+ *
+ * The predicate is tested on `.key` in each element
+ * in the domain, and `.value` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_all
+ * @seealso libnormalform_exists
+ * @seealso libnormalform_empty
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nexists)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k)
+{ return libnormalform_all(d, k, libnormalform_false()); }
+
+
+/**
+ * THERE EXISTS ONE: Unique existential quantification (unmapped)
+ *
+ * The predicate must be met for exactly one element;
+ * if no element exists, the sentence is false
+ *
+ * Commonly written "∃!" ("∃!x∈d.k", "∃!x∈d k(x)", or "∃! x∈d : k(x)")
+ *
+ * The predicate is tested on `.key` in each element
+ * in the domain, and `.value` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param k The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `k` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `k` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_one
+ * @seealso libnormalform_uniquely
+ * @seealso libnormalform_exists
+ * @seealso libnormalform_singleton
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_unique)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k)
+{ return libnormalform_one(d, k, libnormalform_true()); }
+
+
+
+
+
+/**
+ * SOMEWHERE: Existential quantification (premapped)
+ *
+ * The predicate must be met for at least one element;
+ * if no element exists, the sentence is false
+ *
+ * Commonly written "∃" ("∃x∈d.v", "∃x∈d v(x)", or "v(x) ∃ x∈d")
+ *
+ * The predicate is tested on `.value` in each element
+ * in the domain, and `.key` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `v` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_any
+ * @seealso libnormalform_exists
+ * @seealso libnormalform_universally
+ * @seealso libnormalform_uniquely
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_existentially)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *v)
+{ return libnormalform_any(d, libnormalform_true(), v); }
+
+
+/**
+ * EVERYWHERE: Universal quantification (premapped)
+ *
+ * The predicate must be unmet for every element;
+ * if no element exists, the sentence is true
+ *
+ * Commonly written "∀" ("∀x∈d.v", "∀x∈d v(x)", or "v(x) ∀ x∈d")
+ *
+ * The predicate is tested on `.value` in each element
+ * in the domain, and `.key` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `v` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_all
+ * @seealso libnormalform_existentially
+ * @seealso libnormalform_uniquely
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_universally)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *v)
+{ return libnormalform_all(d, libnormalform_true(), v); }
+
+
+/**
+ * ONEWHERE: Unique existential quantification (premapped)
+ *
+ * The predicate must be met for exactly one element;
+ * if no element exists, the sentence is false
+ *
+ * Commonly written "∃!" ("∃!x∈d.v", "∃!x∈d v(x)", or "v(x) ∃! x∈d")
+ *
+ * The predicate is tested on `.value` in each element
+ * in the domain, and `.key` is unused
+ *
+ * @param d The domain of interest; ownership is retained by the caller
+ * @param v The predicate; ownership is transfered to this function
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * Ownership is always transfered, even on failure
+ *
+ * If `v` is `NULL`, this function will return `NULL`
+ * (indicating failure) without modified `errno` (expecting
+ * it to be set by a function that failed, causing `v` to
+ * be `NULL`)
+ *
+ * @seealso libnormalform_one
+ * @seealso libnormalform_unique
+ * @seealso libnormalform_existentially
+ * @seealso libnormalform_universially
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_uniquely)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *v)
+{ return libnormalform_one(d, libnormalform_true(), v); }
+
+
+
+
+
+/**
+ * Create a sentence that checks whether a set is empty
+ *
+ * The sentence technically classifies as a qualification
+ *
+ * Commonly written "X=∅" or "|X|=0"
+ *
+ * @param d The set; ownership is retained by the caller
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * @seealso libnormalform_nonempty
+ * @seealso libnormalform_singleton
+ * @seealso libnormalform_nexists
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_empty)(struct libnormalform_map *d)
+{ return libnormalform_all(d, libnormalform_true(), libnormalform_false()); }
+
+
+/**
+ * Create a sentence that checks whether a set is non-empty
+ *
+ * The sentence technically classifies as a qualification
+ *
+ * Commonly written "X≠∅", "X⊃∅", "X⊋∅" "|X|≠0", or "|X|>0"
+ *
+ * @param d The set; ownership is retained by the caller
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * @seealso libnormalform_empty
+ * @seealso libnormalform_singleton
+ * @seealso libnormalform_exists
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nonempty)(struct libnormalform_map *d)
+{ return libnormalform_any(d, libnormalform_true(), libnormalform_true()); }
+
+
+/**
+ * Create a sentence that checks whether a set is a singleton
+ * (contains exactly one element)
+ *
+ * The sentence technically classifies as a qualification
+ *
+ * Commonly written "|X|=1", "∃! x : X={x}", "X={x} ∃! x", or just "X={x}"
+ *
+ * @param d The set; ownership is retained by the caller
+ * @return The constructed sentence, `NULL` on failure
+ *
+ * @throws ENOMEM Insufficient memory available to create the sentence
+ *
+ * @seealso libnormalform_empty
+ * @seealso libnormalform_nonempty
+ * @seealso libnormalform_unique
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_singleton)(struct libnormalform_map *d)
+{ return libnormalform_one(d, libnormalform_true(), libnormalform_true()); }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_and` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_and`
+ * @return See `libnormalform_and`
+ * @throws See `libnormalform_and`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_and_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(and) }
+
+
+/**
+ * Variant of `libnormalform_or` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_or`
+ * @return See `libnormalform_or`
+ * @throws See `libnormalform_or`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_or_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(or) }
+
+
+/**
+ * Variant of `libnormalform_xor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_xor`
+ * @return See `libnormalform_xor`
+ * @throws See `libnormalform_xor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xor_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(xor) }
+
+
+/**
+ * Variant of `libnormalform_if` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_if`
+ * @return See `libnormalform_if`
+ * @throws See `libnormalform_if`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_if_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(if) }
+
+
+/**
+ * Variant of `libnormalform_imply` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_imply`
+ * @return See `libnormalform_imply`
+ * @throws See `libnormalform_imply`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_imply_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(imply) }
+
+
+/**
+ * Variant of `libnormalform_nand` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_nand`
+ * @return See `libnormalform_nand`
+ * @throws See `libnormalform_nand`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nand_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(nand) }
+
+
+/**
+ * Variant of `libnormalform_nor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_nor`
+ * @return See `libnormalform_nor`
+ * @throws See `libnormalform_nor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nor_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(nor) }
+
+
+/**
+ * Variant of `libnormalform_xnor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_xnor`
+ * @return See `libnormalform_xnor`
+ * @throws See `libnormalform_xnor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xnor_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_nif` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_nif`
+ * @return See `libnormalform_nif`
+ * @throws See `libnormalform_nif`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nif_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(nif) }
+
+
+/**
+ * Variant of `libnormalform_nimply` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param xs See `libnormalform_nimply`
+ * @return See `libnormalform_nimply`
+ * @throws See `libnormalform_nimply`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NONNULL_INPUT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nimply_checked)(size_t n, LIBNORMALFORM_SENTENCE **xs)
+{ LIBNORMALFORM_CHECKED__(nimply) }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_andl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_andl`
+ * @throws See `libnormalform_andl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vand)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(and) }
+
+
+/**
+ * Variant of `libnormalform_orl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_orl`
+ * @throws See `libnormalform_orl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vor)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(or) }
+
+
+/**
+ * Variant of `libnormalform_xorl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xorl`
+ * @throws See `libnormalform_xorl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vxor)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(xor) }
+
+
+/**
+ * Variant of `libnormalform_ifl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_ifl`
+ * @throws See `libnormalform_ifl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vif)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(if) }
+
+
+/**
+ * Variant of `libnormalform_implyl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_implyl`
+ * @throws See `libnormalform_implyl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vimply)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(imply) }
+
+
+/**
+ * Variant of `libnormalform_nandl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nandl`
+ * @throws See `libnormalform_nandl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnand)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(nand) }
+
+
+/**
+ * Variant of `libnormalform_norl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_norl`
+ * @throws See `libnormalform_norl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnor)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(nor) }
+
+
+/**
+ * Variant of `libnormalform_xnorl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xnorl`
+ * @throws See `libnormalform_xnorl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vxnor)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_nifl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nifl`
+ * @throws See `libnormalform_nifl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnif)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(nif) }
+
+
+/**
+ * Variant of `libnormalform_nimplyl` that uses `va_list`
+ * instead of variadic arguments
+ *
+ * @param a, args `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nimplyl`
+ * @throws See `libnormalform_nimplyl`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnimply)(LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST__(nimply) }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_and` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_and`
+ * @throws See `libnormalform_and`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_andl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(and) }
+
+
+/**
+ * Variant of `libnormalform_or` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_or`
+ * @throws See `libnormalform_or`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_orl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(or) }
+
+
+/**
+ * Variant of `libnormalform_xor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xor`
+ * @throws See `libnormalform_xor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xorl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(xor) }
+
+
+/**
+ * Variant of `libnormalform_if` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_if`
+ * @throws See `libnormalform_if`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_ifl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(if) }
+
+
+/**
+ * Variant of `libnormalform_imply` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_imply`
+ * @throws See `libnormalform_imply`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_implyl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(imply) }
+
+
+/**
+ * Variant of `libnormalform_nand` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nand`
+ * @throws See `libnormalform_nand`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nandl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(nand) }
+
+
+/**
+ * Variant of `libnormalform_nor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nor`
+ * @throws See `libnormalform_nor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_norl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(nor) }
+
+
+/**
+ * Variant of `libnormalform_xnor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xnor`
+ * @throws See `libnormalform_xnor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xnorl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_nif` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nif`
+ * @throws See `libnormalform_nif`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nifl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(nif) }
+
+
+/**
+ * Variant of `libnormalform_nimply` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nimply`
+ * @throws See `libnormalform_nimply`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nimplyl)(LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS__(nimply) }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_vand` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vand`
+ * @return See `libnormalform_vand`
+ * @throws See `libnormalform_vand`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vand_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(and) }
+
+
+/**
+ * Variant of `libnormalform_vor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vor`
+ * @return See `libnormalform_vor`
+ * @throws See `libnormalform_vor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vor_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(or) }
+
+
+/**
+ * Variant of `libnormalform_vxor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vxor`
+ * @return See `libnormalform_vxor`
+ * @throws See `libnormalform_vxor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vxor_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(xor) }
+
+
+/**
+ * Variant of `libnormalform_vif` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vif`
+ * @return See `libnormalform_vif`
+ * @throws See `libnormalform_vif`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vif_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(if) }
+
+
+/**
+ * Variant of `libnormalform_vimply` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vimply`
+ * @return See `libnormalform_vimply`
+ * @throws See `libnormalform_vimply`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vimply_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(imply) }
+
+
+/**
+ * Variant of `libnormalform_vnand` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vnand`
+ * @return See `libnormalform_vnand`
+ * @throws See `libnormalform_vnand`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnand_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(nand) }
+
+
+/**
+ * Variant of `libnormalform_vnor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vnor`
+ * @return See `libnormalform_vnor`
+ * @throws See `libnormalform_vnor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnor_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(nor) }
+
+
+/**
+ * Variant of `libnormalform_vxnor` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vxnor`
+ * @return See `libnormalform_vxnor`
+ * @throws See `libnormalform_vxnor`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vxnor_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_vnif` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vnif`
+ * @return See `libnormalform_vnif`
+ * @throws See `libnormalform_vnif`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnif_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(nif) }
+
+
+/**
+ * Variant of `libnormalform_vnimply` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, args See `libnormalform_vnimply`
+ * @return See `libnormalform_vnimply`
+ * @throws See `libnormalform_vnimply`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_vnimply_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, va_list args)
+{ LIBNORMALFORM_VALIST_CHECKED__(nimply) }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_andl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_andl`
+ * @return See `libnormalform_andl`
+ * @throws See `libnormalform_andl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_andl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(and) }
+
+
+/**
+ * Variant of `libnormalform_orl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_orl`
+ * @return See `libnormalform_orl`
+ * @throws See `libnormalform_orl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_orl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(or) }
+
+
+/**
+ * Variant of `libnormalform_xorl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_xorl`
+ * @return See `libnormalform_xorl`
+ * @throws See `libnormalform_xorl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xorl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(xor) }
+
+
+/**
+ * Variant of `libnormalform_ifl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_ifl`
+ * @return See `libnormalform_ifl`
+ * @throws See `libnormalform_ifl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_ifl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(if) }
+
+
+/**
+ * Variant of `libnormalform_implyl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_implyl`
+ * @return See `libnormalform_implyl`
+ * @throws See `libnormalform_implyl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_implyl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(imply) }
+
+
+/**
+ * Variant of `libnormalform_nandl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_nandl`
+ * @return See `libnormalform_nandl`
+ * @throws See `libnormalform_nandl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nandl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(nand) }
+
+
+/**
+ * Variant of `libnormalform_norl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_norl`
+ * @return See `libnormalform_norl`
+ * @throws See `libnormalform_norl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_norl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(nor) }
+
+
+/**
+ * Variant of `libnormalform_xnorl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_xnorl`
+ * @return See `libnormalform_xnorl`
+ * @throws See `libnormalform_xnorl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xnorl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_nifl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_nifl`
+ * @return See `libnormalform_nifl`
+ * @throws See `libnormalform_nifl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nifl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(nif) }
+
+
+/**
+ * Variant of `libnormalform_nimplyl` that checks
+ * for unexpected `NULL` input
+ *
+ * @param n The number of expected non-`NULL` terms
+ * @param a, ... See `libnormalform_nimplyl`
+ * @return See `libnormalform_nimplyl`
+ * @throws See `libnormalform_nimplyl`
+ *
+ * If the function finds any unexpected `NULL` terms,
+ * it will call `libnormalform_free` on all non-`NULL` terms
+ * and return `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__ LIBNORMALFORM_NULL_LAST__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nimplyl_checked)(size_t n, LIBNORMALFORM_SENTENCE *a, ...)
+{ LIBNORMALFORM_ELLIPSIS_CHECKED__(nimply) }
+
+
+
+
+
+/**
+ * Variant of `libnormalform_and` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_and`
+ * @throws See `libnormalform_and`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_and2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+
+/**
+ * Variant of `libnormalform_or` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_or`
+ * @throws See `libnormalform_or`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_or2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+
+/**
+ * Variant of `libnormalform_xor` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_xor`
+ * @throws See `libnormalform_xor`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+LIBNORMALFORM_SENTENCE *(libnormalform_xor2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r);
+
+
+/**
+ * Variant of `libnormalform_if` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_if`
+ * @throws See `libnormalform_if`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_if2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(if) }
+
+
+/**
+ * Variant of `libnormalform_imply` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_imply`
+ * @throws See `libnormalform_imply`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_imply2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(imply) }
+
+
+/**
+ * Variant of `libnormalform_nand` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_nand`
+ * @throws See `libnormalform_nand`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nand2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(nand) }
+
+
+/**
+ * Variant of `libnormalform_nor` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_nor`
+ * @throws See `libnormalform_nor`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nor2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(nor) }
+
+
+/**
+ * Variant of `libnormalform_xnor` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_xnor`
+ * @throws See `libnormalform_xnor`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_xnor2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(xnor) }
+
+
+/**
+ * Variant of `libnormalform_nif` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_nif`
+ * @throws See `libnormalform_nif`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nif2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(nif) }
+
+
+/**
+ * Variant of `libnormalform_nimply` that takes exactly two terms
+ *
+ * @param l The left-hand term
+ * @param r The right-hand term
+ * @return See `libnormalform_nimply`
+ * @throws See `libnormalform_nimply`
+ *
+ * If `l` or `r` is `NULL`, the function will call
+ * `libnormalform_free` on both arguments and return
+ * `NULL` (indicating failure) without setting
+ * `errno` (expected to already be set in indicate the
+ * error that caused one of the terms to be `NULL`)
+ */
+LIBNORMALFORM_USE_RESULT__
+inline LIBNORMALFORM_SENTENCE *
+(libnormalform_nimply2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{ LIBNORMALFORM_TWO__(nimply) }
+
+
+
+
+
+/* These are macro versions of function with the same name */
+
+/**
+ * Variant of `libnormalform_and` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_and`
+ * @throws See `libnormalform_and`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_andl(...) (libnormalform_and((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_or` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_or`
+ * @throws See `libnormalform_or`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_orl(...) (libnormalform_or((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_xor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xor`
+ * @throws See `libnormalform_xor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_xorl(...) (libnormalform_xor((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_if` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_if`
+ * @throws See `libnormalform_if`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_ifl(...) (libnormalform_if((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_imply` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_imply`
+ * @throws See `libnormalform_imply`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_implyl(...) (libnormalform_imply((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_nand` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nand`
+ * @throws See `libnormalform_nand`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_nandl(...) (libnormalform_nand((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_nor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nor`
+ * @throws See `libnormalform_nor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_norl(...) (libnormalform_nor((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_xnor` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_xnor`
+ * @throws See `libnormalform_xnor`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_xnorl(...) (libnormalform_xnor((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_nif` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nif`
+ * @throws See `libnormalform_nif`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_nifl(...) (libnormalform_nif((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+/**
+ * Variant of `libnormalform_nimply` that uses variadic arguments
+ *
+ * @param a, ... `NULL`-terminated list of terms; the function
+ * will take ownership of each input reference,
+ * so the caller shall not attempt to deallocate
+ * or use them again.
+ * @return See `libnormalform_nimply`
+ * @throws See `libnormalform_nimply`
+ *
+ * Be careful not in inputing any `NULL` apart from
+ * the list terminator as the function will not be
+ * able to see them.
+ */
+#define libnormalform_nimplyl(...) (libnormalform_nimply((LIBNORMALFORM_SENTENCE *[]){__VA_ARGS__}))
+
+
+
+
+
+/**
+ * Macro variant of `libnormalform_andl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_andl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_AND(...) LIBNORMALFORM_MACRO__(and, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_orl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_orl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_OR(...) LIBNORMALFORM_MACRO__(or, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_xorl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_xorl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_XOR(...) LIBNORMALFORM_MACRO__(xor, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_ifl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_ifl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_IF(...) LIBNORMALFORM_MACRO__(if, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_implyl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_implyl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_IMPLY(...) LIBNORMALFORM_MACRO__(imply, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_nandl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_nandl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_NAND(...) LIBNORMALFORM_MACRO__(nand, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_norl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_norl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_NOR(...) LIBNORMALFORM_MACRO__(nor, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_xnorl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_xnorl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_XNOR(...) LIBNORMALFORM_MACRO__(xnor, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_nifl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_nifl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_NIF(...) LIBNORMALFORM_MACRO__(nif, __VA_ARGS__)
+
+
+/**
+ * Macro variant of `libnormalform_nimplyl_checked`, that
+ * will add the `NULL` to the end of the list and, before
+ * the list, the number of terms in the list
+ *
+ * @param ... List of terms, _without_ `NULL`-terinator
+ * @return See `libnormalform_nimplyl_checked`
+ *
+ * Either any argument is `NULL`, `libnormalform_free` on
+ * will be called for all non-`NULL` terms, the `NULL`
+ * with be return (indicating failure) without `errno`
+ * being modified (it is expected to already be set in
+ * indicate the error that caused one of the terms to be
+ * `NULL`)
+ */
+#define LIBNORMALFORM_NIMPLY(...) LIBNORMALFORM_MACRO__(nimply, __VA_ARGS__)
+
+
+
+
+
+#undef LIBNORMALFORM_CHECKED__
+#undef LIBNORMALFORM_VALIST__
+#undef LIBNORMALFORM_ELLIPSIS__
+#undef LIBNORMALFORM_VALIST_CHECKED__
+#undef LIBNORMALFORM_ELLIPSIS_CHECKED__
+#undef LIBNORMALFORM_TWO__
+
+#undef LIBNORMALFORM_USE_RESULT__
+#undef LIBNORMALFORM_FREE_WITH__
+#undef LIBNORMALFORM_USE_FREE__
+#undef LIBNORMALFORM_NONNULL_INPUT__
+#undef LIBNORMALFORM_ONE_NONNULL_INPUT__
+#undef LIBNORMALFORM_NULL_LAST__
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+#endif
diff --git a/libnormalform_all.c b/libnormalform_all.c
new file mode 100644
index 0000000..1b8d6fc
--- /dev/null
+++ b/libnormalform_all.c
@@ -0,0 +1,452 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (FOR ALL implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+all_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬∀x.φ ⇒ ∃x.¬φ */
+ LIBNORMALFORM_SENTENCE *ret;
+ ret = libnormalform_any(this->data.qualifier.domain,
+ libnormalform_ref(this->data.qualifier.antecedent),
+ this->data.qualifier.predicate->inverse(this->data.qualifier.predicate));
+ if (ret && this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (FOR ALL implementation)
+ */
+static int
+all_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ int r, inv_expect;
+
+ if (this->hash != other->hash)
+ return 0;
+
+ if (other->type == TYPE_ALL)
+ inv_expect = 0;
+ else if (other->type == TYPE_ANY)
+ inv_expect = 1;
+ else
+ return 0;
+
+ if (this->data.qualifier.domain != other->data.qualifier.domain)
+ return 0;
+
+ r = this->data.qualifier.antecedent->equals(this->data.qualifier.antecedent, other->data.qualifier.antecedent, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out)
+ return 0;
+
+ r = this->data.qualifier.predicate->equals(this->data.qualifier.predicate, other->data.qualifier.predicate, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out != inv_expect)
+ return 0;
+
+ return r;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (FOR ALL implementation)
+ */
+static int
+all_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ size_t i, n = this->data.qualifier.domain->nmappings;
+ void *k, *v;
+ int r;
+
+ (void) input;
+
+ for (i = 0; i < n; i++) {
+ k = this->data.qualifier.domain->mappings[i].key;
+ v = this->data.qualifier.domain->mappings[i].value;
+ r = this->data.qualifier.antecedent->evaluate(this->data.qualifier.antecedent, k);
+ if (r <= 0) {
+ if (!r)
+ continue;
+ return r;
+ }
+ r = this->data.qualifier.predicate->evaluate(this->data.qualifier.predicate, v);
+ if (r <= 0)
+ return r;
+ }
+
+ return 1;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_all)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_ALL,
+ .inverse = &all_inverse,
+ .equals = &all_equals,
+ .evaluate = &all_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret;
+
+ if (!k || !v) {
+ libnormalform_free(k);
+ libnormalform_free(v);
+ return NULL;
+ }
+
+ if (v->type == TYPE_TRUE) {
+ libnormalform_free(k);
+ return v;
+ }
+ if (k->type == TYPE_FALSE) {
+ libnormalform_free(k);
+ libnormalform_free(v);
+ return libnormalform_true();
+ }
+
+ ret = malloc(sizeof(*ret));
+ if (ret) {
+ *ret = prototype;
+ ret->hash = ANY_ALL_HASH(d, k, v);
+ ret->data.qualifier.domain = d;
+ ret->data.qualifier.antecedent = k;
+ ret->data.qualifier.predicate = v;
+ }
+ return ret;
+}
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[2];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1, *v2;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+
+ errno = 0;
+ ASSERT(!libnormalform_all(&dom1, NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_all(&dom1, NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_all(&dom1, REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_all(&dom1, REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_all(&dom1, NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_all(&dom1, NULL, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∀x.(⊥ → φ(x)) = ⊤ */
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_false(), REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* ∀x.(⊤ → φ(x)) irreducable */
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ /* ∀x.(φ(x) → ⊤) = ⊤ */
+ ASSUME(a = libnormalform_all(&dom1, REF(v1), libnormalform_true()));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* ∀x.(φ(x) → ⊥) irreducable */
+ ASSUME(a = libnormalform_all(&dom1, REF(v1), libnormalform_false()));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_all(&dom1, REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∀x∈{a}.(⊥ → ⊥) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{a}.(⊥ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{a}.(⊤ → ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{a}.(⊤ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∀x∈{a,b}.(⊥ → ⊥) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{a,b}.(⊥ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{a,b}.(⊤ → ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{a,b}.(⊤ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ /* ∀x∈∅.(⊥ → ⊥) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈∅.(⊥ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈∅.(⊤ → ⊥) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈∅.(⊤ → ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_true(), a));
+ ASSERT(a->type == TYPE_ALL);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∀x∈{⊥, ⊥}.(⊤ → x) = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊥, ⊤}.(⊤ → x) = ⊥ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊥}.(⊤ → x) = ⊥ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊤}.(⊤ → x) = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_all(&dom1, a, libnormalform_false()));
+ ASSERT(a->type == TYPE_ALL);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∀x∈{⊥, ⊥}.(x → ⊥) = ⊤ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{⊥, ⊤}.(x → ⊥) = ⊥ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊥}.(x → ⊥) = ⊥ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊤}.(x → ⊥) = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_all(&dom1, REF(v1), REF(v2)));
+
+ /* ∀x∈X.(P(x) → Q(x)) = ∀x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from ∀x∈X.(Q(x) → P(x)) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from ∀x∈Y.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_all(&dom2, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) = ¬(∃x∈X.(P(x) → ¬Q(x))) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_not(REF(v2))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∀x∈X.(P(x) → Q(x)) = ∃x∈X.(P(x) → ¬Q(x)) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_not(REF(v2))));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ANY);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from ∃x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from ∃!x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from ¬∃!x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from AND (P, Q) */
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from OR (P, Q) */
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.(P(x) → Q(x)) independent from XOR (P, Q) */
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* P = Q ⊭ ∀{x,y}∈X.(P(x) → Q(y)) = ∀{x,y}∈X.(P(x) → P(y)) = ∀{x,y}∈X.⊤ = ⊤ ∵ P → Q ⊭ P(x) → Q(y) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* P = ¬Q ⊭ ∀{x,y}∈X.(P(x) → Q(y)) = ∀{x,y}∈X.(¬Q(x) → Q(y)) = ∀{x,y}∈X.(⊤ → Q(y)) ∵ P → ¬Q ⊭ P(x) → ¬Q(y) */
+ /* P = ¬Q ⊭ ∀{x,y}∈X.(P(x) → Q(y)) = ∀{x,y}∈X.(P(x) → ¬P(y)) = ∀{x,y}∈X.(⊤ → ¬P(y)) ∵ P → ¬Q ⊭ P(x) → ¬Q(y) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), libnormalform_not(REF(v1))));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_and.c b/libnormalform_and.c
new file mode 100644
index 0000000..d94836d
--- /dev/null
+++ b/libnormalform_and.c
@@ -0,0 +1,578 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Check if a sentence equivalent to a specific sentence
+ * or its inverse is in an array of sentences
+ *
+ * @param x The sentence to look for
+ * @param among The array of sentences to look in
+ * @param n The number of sentences in `among`
+ * @param inv_out Output parameter for equivalency of `x` and the
+ * sentence found in `among`; set to 0 if the two
+ * are equivalent or 1 if `x` is equivalent to the
+ * inverse of the sentence found in `among`
+ * @return 1 if a sentence equivalent to `x` or its inverse
+ * was found, 0 otherwise
+ */
+static int
+is_in(LIBNORMALFORM_SENTENCE *x, LIBNORMALFORM_SENTENCE **among, size_t n, int *inv_out)
+{
+ while (n--)
+ if (x->equals(x, among[n], inv_out))
+ return 1;
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_and)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret, **saved = xs;
+ size_t n = 0;
+ int inv;
+
+ for (; (x = *xs); xs++) {
+ if (x->type == TYPE_FALSE) {
+ /* ⋀X ∧ 0 = 0 */
+ ret = *xs++;
+ goto return_asis;
+
+ } else if (x->type == TYPE_TRUE) {
+ redundant:
+ /* ⋀X ∧ 1 = ⋀X */
+ libnormalform_free(x);
+
+ } else if (is_in(x, saved, n, &inv)) {
+ if (!inv) {
+ /* x ∧ x = x */
+ goto redundant;
+
+ } else {
+ /* x ∧ ¬x = 0 */
+ libnormalform_free(*xs++);
+ ret = libnormalform_false();
+ goto return_asis;
+ }
+
+ } else {
+ saved[n++] = x;
+ }
+ }
+
+ if (!n) {
+ /* ⋀∅ = ∀x∈∅.x = {vacuous truth} = 1 */
+ return libnormalform_true();
+ }
+
+ saved[n] = NULL;
+ /* ⋀{x} = x */
+ ret = *saved++;
+ /* ⋀(X ∪ x) = ⋀X ∧ x */
+ for (; *saved; saved++)
+ ret = libnormalform_and2(ret, *saved);
+
+ return ret;
+
+return_asis:
+ while (*xs)
+ libnormalform_free(*xs++);
+ while (n)
+ libnormalform_free(saved[--n]);
+ return ret;
+}
+
+
+#else
+
+
+#define AND(...) LIBNORMALFORM_AND(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3, var4;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3, *v4, *f1, *f2;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(v4 = libnormalform_variable(&var4));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!AND(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!AND(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!AND(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!AND(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!AND(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!AND(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = AND(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = AND(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL3(VALUE, V1, V2, V3)\
+ do {\
+ ASSUME(a = AND(REF(v1), REF(v2), REF(v3)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ var3.value = V3##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(TRUE); /* AND () -> TRUE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* AND (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* AND (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(FALSE, F, F); /* AND (FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, F, T); /* AND (FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, F); /* AND (TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T); /* AND (TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL2(FALSE, F, F); /* AND (var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(FALSE, F, T); /* AND (var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL2(FALSE, T, F); /* AND (var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, T, T); /* AND (var(TRUE), var(TRUE)) => TRUE */
+
+#ifndef USE_TWO
+
+ ASSERT_CONST(FALSE, F, F, F); /* AND (FALSE, FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, F, F, T); /* AND (FALSE, FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, F, T, F); /* AND (FALSE, TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, F, T, T); /* AND (FALSE, TRUE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, F, F); /* AND (TRUE, FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, T, F, T); /* AND (TRUE, FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, T, F); /* AND (TRUE, TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T, T); /* AND (TRUE, TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL3(FALSE, F, F, F); /* AND (var(FALSE), var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(FALSE, F, F, T); /* AND (var(FALSE), var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(FALSE, F, T, F); /* AND (var(FALSE), var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(FALSE, F, T, T); /* AND (var(FALSE), var(TRUE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(FALSE, T, F, F); /* AND (var(TRUE), var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(FALSE, T, F, T); /* AND (var(TRUE), var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(FALSE, T, T, F); /* AND (var(TRUE), var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, T, T, T); /* AND (var(TRUE), var(TRUE), var(TRUE)) => TRUE */
+
+ /* AND (x) -> x */
+ ASSUME(a = AND(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* AND (x, FALSE) -> FALSE */
+ ASSUME(a = AND(REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (FALSE, x) -> FALSE */
+ ASSUME(a = AND(F, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (x, TRUE) -> x */
+ ASSUME(a = AND(REF(v1), T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* AND (TRUE, x) -> x */
+ ASSUME(a = AND(T, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* AND (x, FALSE, FALSE) -> FALSE */
+ ASSUME(a = AND(REF(v1), F, F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (x, FALSE, TRUE) -> FALSE x */
+ ASSUME(a = AND(REF(v1), F, T));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (x, TRUE, FALSE) -> FALSE */
+ ASSUME(a = AND(REF(v1), T, F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (x, TRUE, TRUE) -> x */
+ ASSUME(a = AND(REF(v1), T, T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* AND (FALSE, x, FALSE) -> FALSE */
+ ASSUME(a = AND(F, REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (FALSE, x, TRUE) -> FALSE */
+ ASSUME(a = AND(F, REF(v1), T));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (TRUE, x, FALSE) -> FALSE */
+ ASSUME(a = AND(T, REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (TRUE, x, TRUE) -> x */
+ ASSUME(a = AND(T, REF(v1), T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* AND (FALSE, FALSE, x) -> FALSE */
+ ASSUME(a = AND(F, F, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (FALSE, TRUE, x) -> FALSE */
+ ASSUME(a = AND(F, T, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (TRUE, FALSE, x) -> FALSE */
+ ASSUME(a = AND(T, F, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (TRUE, TRUE, x) -> x */
+ ASSUME(a = AND(T, T, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#endif
+
+ /* AND (x, x) -> x */
+ ASSUME(a = AND(REF(v1), REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* AND (x, NOT x) -> FALSE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = AND(REF(v1), a));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* AND (x, y) -> AND (x, y) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = AND(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (y, x) -> AND (x, y) */
+ ASSUME(a = AND(REF(v2), REF(v1)));
+ ASSUME(b = AND(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NOT AND (x, y) -> OR (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(a = libnormalform_not(a));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from AND (x, z) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v3)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from AND (z, x) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2(REF(v3), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from AND (z, w) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2(REF(v3), REF(v4)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from OR (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from or (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from OR (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from XOR (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from XOR (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from XOR (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from XOR (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from TRUE */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from FALSE */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from variable1 */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* AND (x, y) -> independence from NOT variable1 */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from function1 */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, f1);
+ libnormalform_free(a);
+
+ /* AND (x, y) -> independence from NOT function1 */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from T(function1) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_transformation(&trans, REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from ALL (domain1, function1, function2) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_all(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from ANY (domain1, function1, function2) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_any(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from ONE (domain1, function1, function2) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (x, y) -> independence from NOT ONE (domain1, function1, function2) */
+ ASSUME(a = AND(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (OR (x, NOT y), OR (NOT x, y)) -> NOT XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(a = AND(a, b));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (OR (NOT x, y), OR (x, NOT y)) -> NOT XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(a = AND(b, a));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (OR (x, y), OR (NOT x, NOT y)) -> XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(a = AND(a, b));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* AND (OR (NOT x, NOT y), OR (x, y)) -> XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(a = AND(b, a));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+#undef ASSERT_EVAL3
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(v4);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_and2.c b/libnormalform_and2.c
new file mode 100644
index 0000000..62d8db3
--- /dev/null
+++ b/libnormalform_and2.c
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_and2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ LIBNORMALFORM_SENTENCE *ll, *lr;
+ LIBNORMALFORM_SENTENCE *rl, *rr;
+ int inv;
+
+ if (!l || !r) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+
+ if (l->equals(l, r, &inv)) {
+ if (!inv) {
+ /* x ∧ x = x */
+ goto return_either;
+
+ } else {
+ /* x ∧ ¬x = 0 */
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return libnormalform_false();
+ }
+
+ } else if (l->type == TYPE_FALSE || r->type == TYPE_TRUE) {
+ /* 0 ∧ x = 0 */
+ /* x ∧ 1 = x */
+ return_either:
+ libnormalform_free(r);
+ return l;
+
+ } else if (r->type == TYPE_FALSE || l->type == TYPE_TRUE) {
+ /* x ∧ 0 = 0 */
+ /* 1 ∧ x = x */
+ libnormalform_free(l);
+ return r;
+
+ } else if (l->hash == r->hash && l->type == TYPE_OR && r->type == TYPE_OR) {
+ /* (x ∨ y) ∧ (¬x ∨ ¬y) = (x ∨ y) ∧ ¬(x ∧ y) = x ⊕ y */
+ ll = l->data.binary.l;
+ lr = l->data.binary.r;
+ rl = r->data.binary.l;
+ rr = r->data.binary.r;
+ if (ll->hash != rl->hash || lr->hash != rr->hash)
+ goto fallback;
+ if (!ll->equals(ll, rl, &inv)) {
+ if (ll->hash == lr->hash) {
+ rl = r->data.binary.r;
+ rr = r->data.binary.l;
+ if (!ll->equals(ll, rl, &inv))
+ goto fallback;
+ } else {
+ goto fallback;
+ }
+ }
+ if (!inv || !lr->equals(lr, rr, &inv) || !inv)
+ goto fallback;
+ l->data.binary.l = NULL;
+ l->data.binary.r = NULL;
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return libnormalform_xor2__(ll, lr);
+
+ } else {
+ fallback:
+ return libnormalform_and2__(l, r);
+ }
+}
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_and2__.c b/libnormalform_and2__.c
new file mode 100644
index 0000000..c20eb3e
--- /dev/null
+++ b/libnormalform_and2__.c
@@ -0,0 +1,119 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (AND implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+and_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬(ab) = ¬a + ¬b */
+ LIBNORMALFORM_SENTENCE *l, *r, *ret;
+ l = this->data.binary.l->inverse(this->data.binary.l);
+ if (!l)
+ return NULL;
+ r = this->data.binary.r->inverse(this->data.binary.r);
+ if (!r) {
+ libnormalform_free(l);
+ return NULL;
+ }
+ ret = libnormalform_or2__(l, r);
+ if (ret && this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (AND implementation)
+ */
+static int
+and_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ LIBNORMALFORM_SENTENCE *tl, *tr;
+ LIBNORMALFORM_SENTENCE *ol, *or;
+ int inv;
+
+ if (other->type != TYPE_AND && other->type != TYPE_OR)
+ return other->type == TYPE_XOR && other->equals(other, this, inv_out);
+
+ tl = this->data.binary.l;
+ tr = this->data.binary.r;
+ ol = other->data.binary.l;
+ or = other->data.binary.r;
+
+ if (tl->hash != ol->hash || tr->hash != or->hash)
+ return 0;
+
+ if (!tl->equals(tl, ol, inv_out)) {
+ if (tl->hash == tr->hash) {
+ ol = other->data.binary.r;
+ or = other->data.binary.l;
+ if (!tl->equals(tl, ol, inv_out))
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+ if (!tr->equals(tr, or, &inv))
+ return 0;
+ return inv == *inv_out && inv == (other->type == TYPE_OR);
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (AND implementation)
+ */
+static int
+and_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ int r = this->data.binary.l->evaluate(this->data.binary.l, input);
+ return r <= 0 ? r : this->data.binary.r->evaluate(this->data.binary.r, input);
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_and2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_AND,
+ .inverse = &and_inverse,
+ .equals = &and_equals,
+ .evaluate = &and_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (!ret) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+ *ret = prototype;
+ ret->hash = AND_OR_HASH(l, r);
+ if (l->hash <= r->hash) {
+ ret->data.binary.l = l;
+ ret->data.binary.r = r;
+ } else {
+ ret->data.binary.l = r;
+ ret->data.binary.r = l;
+ }
+ return ret;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ return 0; /* indirectly tested */
+}
+
+
+#endif
diff --git a/libnormalform_and_checked.c b/libnormalform_and_checked.c
new file mode 100644
index 0000000..5a1b1d2
--- /dev/null
+++ b/libnormalform_and_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_and_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_andl.c b/libnormalform_andl.c
new file mode 100644
index 0000000..7388efb
--- /dev/null
+++ b/libnormalform_andl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_andl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_andl_checked.c b/libnormalform_andl_checked.c
new file mode 100644
index 0000000..aa72709
--- /dev/null
+++ b/libnormalform_andl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_andl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_andl_macro_test.c b/libnormalform_andl_macro_test.c
new file mode 100644
index 0000000..63a40c8
--- /dev/null
+++ b/libnormalform_andl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_and.c"
+#endif
diff --git a/libnormalform_any.c b/libnormalform_any.c
new file mode 100644
index 0000000..7e69210
--- /dev/null
+++ b/libnormalform_any.c
@@ -0,0 +1,451 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (FOR ANY implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+any_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬∃x.φ ⇒ ∀x.¬φ */
+ LIBNORMALFORM_SENTENCE *ret;
+ ret = libnormalform_all(this->data.qualifier.domain,
+ libnormalform_ref(this->data.qualifier.antecedent),
+ this->data.qualifier.predicate->inverse(this->data.qualifier.predicate));
+ if (ret && this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (FOR ANY implementation)
+ */
+static int
+any_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ int r, inv_expect;
+
+ if (this->hash != other->hash)
+ return 0;
+
+ if (other->type == TYPE_ANY)
+ inv_expect = 0;
+ else if (other->type == TYPE_ALL)
+ inv_expect = 1;
+ else
+ return 0;
+
+ if (this->data.qualifier.domain != other->data.qualifier.domain)
+ return 0;
+
+ r = this->data.qualifier.antecedent->equals(this->data.qualifier.antecedent, other->data.qualifier.antecedent, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out)
+ return 0;
+
+ r = this->data.qualifier.predicate->equals(this->data.qualifier.predicate, other->data.qualifier.predicate, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out != inv_expect)
+ return 0;
+
+ return r;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (FOR ANY implementation)
+ */
+static int
+any_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ size_t i, n = this->data.qualifier.domain->nmappings;
+ void *k, *v;
+ int r;
+
+ (void) input;
+
+ for (i = 0; i < n; i++) {
+ k = this->data.qualifier.domain->mappings[i].key;
+ v = this->data.qualifier.domain->mappings[i].value;
+ r = this->data.qualifier.antecedent->evaluate(this->data.qualifier.antecedent, k);
+ if (r <= 0) {
+ if (!r)
+ continue;
+ return r;
+ }
+ r = this->data.qualifier.predicate->evaluate(this->data.qualifier.predicate, v);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_any)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_ANY,
+ .inverse = &any_inverse,
+ .equals = &any_equals,
+ .evaluate = &any_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret;
+
+ if (!k || !v) {
+ libnormalform_free(k);
+ libnormalform_free(v);
+ return NULL;
+ }
+
+ if (k->type == TYPE_FALSE) {
+ libnormalform_free(v);
+ return k;
+ }
+ if (v->type == TYPE_FALSE) {
+ libnormalform_free(k);
+ return v;
+ }
+
+ ret = malloc(sizeof(*ret));
+ if (ret) {
+ *ret = prototype;
+ ret->hash = ANY_ALL_HASH(d, k, v);
+ ret->data.qualifier.domain = d;
+ ret->data.qualifier.antecedent = k;
+ ret->data.qualifier.predicate = v;
+ }
+ return ret;
+}
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[2];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1, *v2;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+
+ errno = 0;
+ ASSERT(!libnormalform_any(&dom1, NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_any(&dom1, NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_any(&dom1, REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_any(&dom1, REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_any(&dom1, NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_any(&dom1, NULL, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃x.(⊥ ∧ φ(x)) = ⊥ */
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_false(), REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃x.(⊤ ∧ φ(x)) irreducable */
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ /* ∃x.(φ(x) ∧ ⊤) irreducable */
+ ASSUME(a = libnormalform_any(&dom1, REF(v1), libnormalform_true()));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ /* ∃x.(φ(x) ∧ ⊥) = ⊥ */
+ ASSUME(a = libnormalform_any(&dom1, REF(v1), libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_any(&dom1, REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_ANY);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃x∈{a}.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a}.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a}.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a}.(⊤ ∧ ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃x∈{a,b}.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a,b}.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a,b}.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a,b}.(⊤ ∧ ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ /* ∃x∈∅.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈∅.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈∅.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈∅.(⊤ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_true(), a));
+ ASSERT(a->type == TYPE_ANY);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∃x∈{⊥, ⊥}.(⊤ ∧ x) = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{⊥, ⊤}.(⊤ ∧ x) = ⊤ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊥}.(⊤ ∧ x) = ⊤ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤}.(⊤ ∧ x) = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_any(&dom1, a, libnormalform_true()));
+ ASSERT(a->type == TYPE_ANY);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∃x∈{⊥, ⊥}.(x ∧ ⊤) = ⊥ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{⊥, ⊤}.(x ∧ ⊤) = ⊤ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊥}.(x ∧ ⊤) = ⊤ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤}.(x ∧ ⊤) = ⊤ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_any(&dom1, REF(v1), REF(v2)));
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) = ∃x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from ∃x∈X.(Q(x) ∧ P(x)) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from ∃x∈Y.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_any(&dom2, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) = ¬(∀x∈X.(P(x) → ¬Q(x))) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_not(REF(v2))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃x∈X.(P(x) ∧ Q(x)) = ∀x∈X.(P(x) → ¬Q(x)) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_not(REF(v2))));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ALL);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) = ∀x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from ∃!x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from ¬∃!x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from AND (P, Q) */
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from OR (P, Q) */
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.(P(x) ∧ Q(x)) independent from XOR (P, Q) */
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* P = Q ⊭ ∃{x,y}∈X.(P(x) ∧ Q(y)) = ∃{x,y}∈X.(P(x) ∧ P(y)) = ∃{x,y}∈X.⊤ = ⊤ ∵ P = Q ⊭ P(x) = Q(y) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* P = ¬Q ⊭ ∃{x,y}∈X.(P(x) ∧ Q(y)) = ∃{x,y}∈X.(¬Q(x) ∧ Q(y)) = ∃{x,y}∈X.⊥ = ⊥ ∵ P = ¬Q ⊭ P(x) = ¬Q(y) */
+ /* P = ¬Q ⊭ ∃{x,y}∈X.(P(x) ∧ Q(y)) = ∃{x,y}∈X.(P(x) ∧ ¬P(y)) = ∃{x,y}∈X.⊥ = ⊥ ∵ P = ¬Q ⊭ P(x) = ¬Q(y) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_false(), libnormalform_false()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_clone.c b/libnormalform_clone.c
new file mode 100644
index 0000000..3f8d01f
--- /dev/null
+++ b/libnormalform_clone.c
@@ -0,0 +1,558 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Create a shallow clone of a sentence
+ *
+ * Any non-`NULL` `.copy_for_clone` in any
+ * `struct libnormalform_variable *`,
+ * `struct libnormalform_function *`, or
+ * `struct libnormalform_map *` will be applied
+ *
+ * @param this The sentence
+ * @param map Sufficiently large array of already constructed clones,
+ * will be updated with a the clone if `this` has a slot
+ * and `NULL` is stored in the slot
+ * @param ap_out Output parameter for one of `this`'s substatements;
+ * `NULL` will be stored in `*ap_out` if `this` does not
+ * have any substatements
+ * @param bp_out Output parameter for `this`'s other substatement;
+ * `NULL` will be stored in `*bp_out` if `this` does not
+ * have any substatements
+ * @return The clone of `this`, `NULL` on failure
+ */
+USE_RESULT SOME_NONNULL_INPUT(1, 3, 4)
+static LIBNORMALFORM_SENTENCE *
+shallow_clone(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE **map,
+ LIBNORMALFORM_SENTENCE ***ap_out, LIBNORMALFORM_SENTENCE ***bp_out)
+{
+ LIBNORMALFORM_SENTENCE *ret;
+
+ *ap_out = NULL;
+ *bp_out = NULL;
+
+ if (this->travel_count > 1 && map[this->travel_index]) {
+ map[this->travel_index]->refcount += 1;
+ return map[this->travel_index];
+ }
+
+ ret = malloc(sizeof(*ret));
+ if (!ret)
+ return NULL;
+
+ memcpy(ret, this, sizeof(*ret));
+ ret->refcount = 1;
+ ret->atom = NULL;
+
+ switch (ret->type) {
+ case TYPE_ALL:
+ case TYPE_ANY:
+ case TYPE_ONE:
+ case TYPE_NOT_ONE:
+ if (ret->data.qualifier.domain->copy_for_clone)
+ ret->data.qualifier.domain = ret->data.qualifier.domain->copy_for_clone;
+ /* fall through */
+
+ case TYPE_AND:
+ case TYPE_OR:
+ case TYPE_XOR:
+ *ap_out = &LEFT(ret);
+ *bp_out = &RIGHT(ret);
+ /* fall through */
+
+ case TYPE_TRUE:
+ case TYPE_FALSE:
+ break;
+
+ case TYPE_VARIABLE:
+ if (ret->data.literal.atom.variable->copy_for_clone)
+ ret->data.literal.atom.variable = ret->data.literal.atom.variable->copy_for_clone;
+ break;
+
+ case TYPE_FUNCTION:
+ if (ret->data.literal.atom.function->copy_for_clone)
+ ret->data.literal.atom.function = ret->data.literal.atom.function->copy_for_clone;
+ break;
+
+ case TYPE_TRANS:
+ if (ret->data.trans.function->copy_for_clone)
+ ret->data.trans.function = ret->data.trans.function->copy_for_clone;
+ *bp_out = &ret->data.trans.input;
+ break;
+
+ default:
+ abort();
+ }
+
+ if (this->travel_count > 1)
+ map[this->travel_index] = ret;
+
+ return ret;
+}
+
+
+/**
+ * Create a deep clone of a sentence
+ *
+ * @param ret_out Output parameter for the clone
+ * @param this The sentence
+ * @param map Sufficiently large array of already constructed clones,
+ * will be updated with a the clone if `this` has a slot
+ * and `NULL` is stored in the slot
+ * @return 0 on success, -1 on failure
+ */
+USE_RESULT SOME_NONNULL_INPUT(1, 2)
+static int
+deep_clone(LIBNORMALFORM_SENTENCE **ret_out, LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE **map)
+{
+ LIBNORMALFORM_SENTENCE **ap, **bp;
+ LIBNORMALFORM_SENTENCE *a = NULL, *b = NULL; /* initialised to silence compiler */
+
+ for (;;) {
+ *ret_out = shallow_clone(this, map, &ap, &bp);
+ if (ap && !bp)
+ bp = ap, ap = NULL;
+ if (ap)
+ a = *ap, *ap = NULL;
+ if (bp)
+ b = *bp, *bp = NULL;
+ if (!*ret_out)
+ return -1;
+
+ if (ap)
+ if (deep_clone(ap, a, map))
+ return -1;
+
+ if (!bp)
+ break;
+ ret_out = bp;
+ this = b;
+ }
+
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_clone)(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *ret = NULL;
+ LIBNORMALFORM_SENTENCE **map;
+ size_t dupcount;
+
+ if (!this)
+ return ret;
+
+ map = NULL;
+ dupcount = libnormalform_set_indices_and_counts__(this);
+ if (dupcount) {
+ /* ideality, we would skip this part and let shallow_clone,
+ * store the first clone, however since there is no guarantee
+ * that NULL and 0 have the same representation, we cannot reuse
+ * the memory of `.travel_index` (needed by libnormalform_to_string)
+ * for this purpose, so either we do this, or we add another
+ * seldomly used member to `struct libnormalform_sentence` */
+ map = malloc(dupcount * sizeof(*map));
+ if (!map)
+ goto out;
+ while (dupcount--)
+ map[dupcount] = NULL;
+ }
+ if (deep_clone(&ret, this, map)) {
+ libnormalform_free(ret);
+ ret = NULL;
+ }
+ free(map);
+out:
+ libnormalform_reset_indices_and_counts__(this);
+ return ret;
+}
+
+
+#else
+
+
+static void
+test_simple(void)
+{
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map dom1, dom2;
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ ASSUME(a = libnormalform_true());
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(a = libnormalform_false());
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_variable(&var1));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ var1.copy_for_clone = &var2;
+ ASSUME(a = libnormalform_variable(&var1));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_VARIABLE);
+ ASSERT(b->type == TYPE_VARIABLE);
+ ASSERT(a->data.literal.atom.variable == &var1);
+ ASSERT(b->data.literal.atom.variable == &var2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.literal.atom.variable = &var1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ fun1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ fun1.copy_for_clone = &fun2;
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_FUNCTION);
+ ASSERT(b->type == TYPE_FUNCTION);
+ ASSERT(a->data.literal.atom.function == &fun1);
+ ASSERT(b->data.literal.atom.function == &fun2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.literal.atom.function = &fun1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ var1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_and2(libnormalform_variable(&var1), libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ var1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_or2(libnormalform_variable(&var1), libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ var1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_xor2(libnormalform_variable(&var1), libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = &dom2;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(b->type == TYPE_ALL);
+ ASSERT(a->data.qualifier.domain == &dom1);
+ ASSERT(b->data.qualifier.domain == &dom2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.qualifier.domain = &dom1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = &dom2;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_ANY);
+ ASSERT(b->type == TYPE_ANY);
+ ASSERT(a->data.qualifier.domain == &dom1);
+ ASSERT(b->data.qualifier.domain == &dom2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.qualifier.domain = &dom1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = &dom2;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_ONE);
+ ASSERT(b->type == TYPE_ONE);
+ ASSERT(a->data.qualifier.domain == &dom1);
+ ASSERT(b->data.qualifier.domain == &dom2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.qualifier.domain = &dom1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = NULL;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(a = libnormalform_not(a));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ dom1.copy_for_clone = &dom2;
+ fun1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_function(&fun1), libnormalform_variable(&var1)));
+ ASSUME(a = libnormalform_not(a));
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(a->type == TYPE_NOT_ONE);
+ ASSERT(b->type == TYPE_NOT_ONE);
+ ASSERT(a->data.qualifier.domain == &dom1);
+ ASSERT(b->data.qualifier.domain == &dom2);
+ ASSERT_NOTEQUAL(a, b);
+ b->data.qualifier.domain = &dom1;
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+}
+
+
+static void
+test_multientry(void)
+{
+ struct libnormalform_variable var1;
+ struct libnormalform_map dom1;
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ dom1.copy_for_clone = NULL;
+ var1.copy_for_clone = NULL;
+ ASSUME(a = libnormalform_variable(&var1));
+ ASSUME(a = libnormalform_all(&dom1, REF(a), a));
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(LEFT(a) == RIGHT(a));
+ ASSERT(LEFT(a)->refcount = 2);
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT(a->refcount == 1);
+ ASSERT(b->refcount == 1);
+ ASSERT(b->type == TYPE_ALL);
+ ASSERT(LEFT(b) == RIGHT(b));
+ ASSERT(LEFT(b) != LEFT(a));
+ ASSERT(LEFT(b)->refcount = 2);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+ libnormalform_free(a);
+}
+
+
+static void
+test_complex(void)
+{
+#define U(NAME) NAME = {.copy_for_clone = NULL, .identifier = #NAME}
+
+ struct libnormalform_variable U(var1), U(var2);
+ struct libnormalform_function U(fun1), U(fun2);
+ struct libnormalform_map U(dom1);
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ ASSUME(a =
+ libnormalform_any(&dom1,
+ libnormalform_true(),
+ LIBNORMALFORM_AND(
+ libnormalform_function(&fun1),
+ libnormalform_function(&fun2),
+ libnormalform_variable(&var1),
+ libnormalform_variable(&var2)
+ )
+ )
+ );
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT_EQUAL(a, b);
+
+ ASSUME(a =
+ LIBNORMALFORM_OR(
+ a,
+ libnormalform_xor2(
+ libnormalform_function(&fun1),
+ libnormalform_variable(&var1)),
+ libnormalform_function(&fun2),
+ libnormalform_all(&dom1,
+ LIBNORMALFORM_AND(
+ libnormalform_function(&fun1),
+ libnormalform_function(&fun2),
+ libnormalform_variable(&var1),
+ libnormalform_variable(&var2)
+ ),
+ libnormalform_xor2(
+ libnormalform_function(&fun1),
+ b)
+ )
+ )
+ );
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT_EQUAL(a, b);
+
+ libnormalform_free(b);
+ libnormalform_free(a);
+
+#undef U
+}
+
+
+static void
+test_complex_multientry(void)
+{
+#define U(NAME) NAME = {.copy_for_clone = NULL, .identifier = #NAME}
+
+ struct libnormalform_variable U(var1), U(var2);
+ struct libnormalform_function U(fun1), U(fun2);
+ struct libnormalform_map U(dom1);
+ LIBNORMALFORM_SENTENCE *a, *b, *ref1, *ref2, *ref3, *ref4;
+
+ ASSUME(a =
+ libnormalform_any(&dom1,
+ libnormalform_true(),
+ ref4 = REF(LIBNORMALFORM_AND(
+ ref1 = REF(libnormalform_function(&fun1)),
+ ref2 = REF(libnormalform_function(&fun2)),
+ ref3 = REF(libnormalform_variable(&var1)),
+ libnormalform_variable(&var2)
+ ))
+ )
+ );
+ ASSUME(a =
+ LIBNORMALFORM_OR(
+ a,
+ libnormalform_xor2(REF(ref1), REF(ref3)),
+ REF(ref2),
+ libnormalform_all(&dom1,
+ REF(ref4),
+ libnormalform_xor2(REF(ref1), REF(a))
+ )
+ )
+ );
+
+ libnormalform_free(ref1);
+ libnormalform_free(ref2);
+ libnormalform_free(ref3);
+ libnormalform_free(ref4);
+
+ ASSUME(b = libnormalform_clone(a));
+ ASSERT(a != b);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+ libnormalform_free(a);
+
+#undef U
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ test_simple();
+ test_multientry();
+ test_complex();
+ test_complex_multientry();
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_empty.c b/libnormalform_empty.c
new file mode 100644
index 0000000..e9f4622
--- /dev/null
+++ b/libnormalform_empty.c
@@ -0,0 +1,69 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_empty)(struct libnormalform_map *);
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ ASSUME(a = libnormalform_empty(&dom1));
+ ASSERT(a->type == TYPE_ALL);
+
+ ASSUME(b = libnormalform_empty(&dom2));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_empty(&dom1));
+ ASSERT(b != a);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_nonempty(&dom1));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_singleton(&dom1));
+ ASSERT_NOTEQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_not(libnormalform_empty(&dom1)));
+ ASSERT(b->type == TYPE_ANY);
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ dom1.nmappings = 0;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 1;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 2;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 3;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_evaluate.c b/libnormalform_evaluate.c
new file mode 100644
index 0000000..80749ed
--- /dev/null
+++ b/libnormalform_evaluate.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+(libnormalform_evaluate)(LIBNORMALFORM_SENTENCE *this)
+{
+ return this->evaluate(this, NULL);
+}
+
+
+#else
+
+
+#define EVALUATION_RESULT 5
+
+static LIBNORMALFORM_SENTENCE *evaluation_this;
+
+static int
+evaluation(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ ASSERT(this);
+ ASSERT(this == evaluation_this);
+ ASSERT(input == NULL);
+ return EVALUATION_RESULT;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ LIBNORMALFORM_SENTENCE a;
+ evaluation_this = &a;
+ a.evaluate = &evaluation;
+ ASSERT(libnormalform_evaluate(&a) == EVALUATION_RESULT);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_existentially.c b/libnormalform_existentially.c
new file mode 100644
index 0000000..16ff9fa
--- /dev/null
+++ b/libnormalform_existentially.c
@@ -0,0 +1,235 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_existentially)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_existentially(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_existentially(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃x.⊥ = ⊥ */
+ ASSUME(a = libnormalform_existentially(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃x.φ irreducable */
+ ASSUME(a = libnormalform_existentially(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ /* ∃x.⊤ irreducable */
+ ASSUME(a = libnormalform_existentially(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_existentially(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ANY);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃x∈{a}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃x∈{a,b}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a,b}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ /* ∃x∈∅.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈∅.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_existentially(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ANY);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∃x∈{⊥, ⊥}.x = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{⊥, ⊤}.x = ⊤ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊥}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤, ⊤}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ map[2].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_existentially(&dom1, REF(v1)));
+
+ /* ∃x∈X.P(x) = ∃x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃x∈X.Q(x) */
+ ASSUME(b = libnormalform_existentially(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃x∈Y.P(x)) */
+ ASSUME(b = libnormalform_existentially(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) = ¬(∀x∈X.(⊤ → ¬P(x))) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), libnormalform_not(REF(v1))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃x∈X.P(x) = ∀x∈X.(⊤ → ¬P(x)) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), libnormalform_not(REF(v1))));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ALL);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∃x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∀x∈X.(⊤ → P(x)) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃!x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_one(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∃x∈X.P(x) independent from ¬∃!x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_exists.c b/libnormalform_exists.c
new file mode 100644
index 0000000..0c40729
--- /dev/null
+++ b/libnormalform_exists.c
@@ -0,0 +1,243 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_exists)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_exists(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_exists(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃x.⊥ = ⊥ */
+ ASSUME(a = libnormalform_exists(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃x.φ irreducable */
+ ASSUME(a = libnormalform_exists(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ /* ∃x.⊤ irreducable */
+ ASSUME(a = libnormalform_exists(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_ANY);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_exists(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ANY);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃x∈{a}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃x∈{a,b}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{a,b}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ /* ∃x∈∅.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈∅.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_exists(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ANY);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∃x∈{⊥, ⊥}.x = ⊥ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃x∈{⊥, ⊤}.x = ⊤ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊥}.x = ⊤ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤}.x = ⊤ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃x∈{⊤, ⊤, ⊤}.x = ⊤ */
+ map[0].key = &t;
+ map[1].key = &t;
+ map[2].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_exists(&dom1, REF(v1)));
+
+ /* ∃x∈X.P(x) = ∃x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃x∈X.Q(x) */
+ ASSUME(b = libnormalform_exists(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃x∈Y.P(x)) */
+ ASSUME(b = libnormalform_exists(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) = ¬(∀x∈X.(P(x) → ⊥)) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_false()));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃x∈X.P(x) = ∀x∈X.(P(x) → ⊥) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_false()));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ALL);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∃x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∀x∈X.(P(x) → ⊤) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from ∃!x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∃x∈X.P(x) independent from ¬∃!x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃x∈X.P(x) = ¬∄x∈X.P(x) */
+ ASSUME(b = libnormalform_nexists(&dom1, REF(v1)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_not(libnormalform_nexists(&dom1, REF(v1))));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_express.c b/libnormalform_express.c
new file mode 100644
index 0000000..2532488
--- /dev/null
+++ b/libnormalform_express.c
@@ -0,0 +1,849 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+struct expression {
+ enum libnormalform_term_type type;
+ unsigned char reduced;
+ unsigned char invert;
+ size_t nterms;
+ struct expression **terms;
+ void *user_item;
+};
+
+
+static enum libnormalform_sentences_relationship
+get_relationship(struct expression *l, struct expression *r, const struct libnormalform_analysers *analysers) /* TODO */
+{
+ (void) l;
+ (void) r;
+ (void) analysers;
+ return LIBNORMALFORM_MUTUALLY_INDEPENDENT;
+}
+
+
+static void
+free_expression(struct expression *this)
+{
+ if (this->terms) {
+ while (this->nterms)
+ free_expression(this->terms[--this->nterms]);
+ free(this->terms);
+ }
+ free(this);
+}
+
+
+static struct expression *
+sentence_to_expression(LIBNORMALFORM_SENTENCE *this, uint64_t flags)
+{
+ LIBNORMALFORM_SENTENCE *left, *right, *free1 = NULL, *free2 = NULL;
+ struct expression *ret, *sub;
+
+ ret = malloc(sizeof(*ret));
+ if (!ret)
+ return NULL;
+ ret->reduced = 0;
+ ret->invert = 0;
+ ret->nterms = 0;
+ ret->terms = NULL;
+ ret->user_item = NULL;
+
+ switch (this->type) {
+ case TYPE_TRUE:
+ tautology:
+ ret->type = LIBNORMALFORM_CONJUNCTION;
+ goto out;
+
+ case TYPE_FALSE:
+ contradiction:
+ ret->type = LIBNORMALFORM_DISJUNCTION;
+ goto out;
+
+ case TYPE_XOR:
+ if (flags & LIBNORMALFORM_REDUCE_XOR) {
+ /* x ⊕ y = (x ∨ y) ∧ ¬(x ∧ y) = (x ∨ y) ∧ (¬x ∨ ¬y) */
+ free1 = left = libnormalform_or2(libnormalform_ref(LEFT(this)), libnormalform_ref(RIGHT(this)));
+ free2 = right = libnormalform_or2(LEFT(this)->inverse(LEFT(this)), RIGHT(this)->inverse(RIGHT(this)));
+ if (!left || !right)
+ goto fail;
+ ret->type = LIBNORMALFORM_CONJUNCTION;
+ goto clause_prefetched_branches;
+ }
+ ret->type = LIBNORMALFORM_EXCLUSIVE_DISJUNCTION;
+ goto clause;
+ case TYPE_AND:
+ ret->type = LIBNORMALFORM_CONJUNCTION;
+ goto clause;
+ case TYPE_OR:
+ ret->type = LIBNORMALFORM_DISJUNCTION;
+ clause:
+ left = LEFT(this);
+ right = RIGHT(this);
+ clause_prefetched_branches:
+ sub = sentence_to_expression(left, flags);
+ if (sub)
+ goto fail;
+ if (sub->type == ret->type) {
+ free(ret);
+ ret = sub;
+ } else if (!sub->nterms && ret->type == LIBNORMALFORM_CONJUNCTION && sub->type == LIBNORMALFORM_DISJUNCTION) {
+ /* ⋀(X ∪ {⋁∅}) = ⋀(X ∪ {⊥}) = ⋀X ∧ ⊥ = ⊥ */
+ if (!sub->reduced) {
+ free_expression(sub);
+ goto contradiction;
+ }
+ free_expression(sub);
+ ret->reduced |= 1;
+ } else if (!sub->nterms && ret->type == LIBNORMALFORM_DISJUNCTION && sub->type == LIBNORMALFORM_CONJUNCTION) {
+ /* ⋁(X ∪ {⋀∅}) = ⋁(X ∪ {⊤}) = ⋁X ∨ ⊤ = ⊤ */
+ free_expression(ret);
+ ret = sub;
+ goto tautology;
+ } else {
+ ret->terms = malloc(sizeof(*ret->terms));
+ if (!ret->terms) {
+ free_expression(sub);
+ goto fail;
+ }
+ ret->nterms = 1;
+ ret->terms[0] = sub;
+ }
+ sub = sentence_to_expression(right, flags);
+ if (sub)
+ goto fail;
+ if (sub->type == ret->type) {
+ void *new = realloc(ret->terms, (ret->nterms + sub->nterms) * sizeof(*ret->terms));
+ if (!new) {
+ free_expression(sub);
+ goto fail;
+ }
+ ret->terms = new;
+ memcpy(&ret->terms[ret->nterms], sub->terms, sub->nterms * sizeof(sub->terms));
+ ret->nterms += sub->nterms;
+ free(sub->terms);
+ free(sub);
+ } else if (!sub->nterms && ret->type == LIBNORMALFORM_CONJUNCTION && sub->type == LIBNORMALFORM_DISJUNCTION) {
+ /* ⋀(X ∪ {⋁∅}) = ⋀(X ∪ {⊥}) = ⋀X ∧ ⊥ = ⊥ */
+ if (!sub->reduced) {
+ free_expression(sub);
+ goto contradiction;
+ }
+ free_expression(sub);
+ ret->reduced |= 1;
+ } else if (!sub->nterms && ret->type == LIBNORMALFORM_DISJUNCTION && sub->type == LIBNORMALFORM_CONJUNCTION) {
+ /* ⋁(X ∪ {⋀∅}) = ⋁(X ∪ {⊤}) = ⋁X ∨ ⊤ = ⊤ */
+ free_expression(ret);
+ ret = sub;
+ goto tautology;
+ } else {
+ void *new = realloc(ret->terms, (ret->nterms + 1U) * sizeof(*ret->terms));
+ if (!new) {
+ free_expression(sub);
+ goto fail;
+ }
+ ret->terms = new;
+ ret->terms[ret->nterms++] = sub;
+ }
+ break;
+
+ case TYPE_ALL:
+ ret->type = LIBNORMALFORM_FOR_ALL;
+ goto qualifier;
+ case TYPE_ANY:
+ ret->type = LIBNORMALFORM_FOR_ANY;
+ goto qualifier;
+ case TYPE_ONE:
+ ret->type = LIBNORMALFORM_FOR_ONE;
+ goto qualifier;
+ case TYPE_NOT_ONE:
+ ret->type = LIBNORMALFORM_NEGATED_FOR_ONE;
+ qualifier:
+ ret->user_item = this->data.qualifier.domain;
+ ret->nterms = 2;
+ ret->terms = malloc(2 * sizeof(*ret->terms));
+ if (!ret->terms)
+ goto fail;
+ ret->terms[0] = sentence_to_expression(this->data.qualifier.antecedent, flags);
+ if (!ret->terms[0])
+ goto fail;
+ ret->terms[1] = sentence_to_expression(this->data.qualifier.predicate, flags);
+ if (!ret->terms[1])
+ goto fail;
+ break;
+
+ case TYPE_VARIABLE:
+ if ((flags & LIBNORMALFORM_ELIMINATE_VARIABLE) && (flags & LIBNORMALFORM_ELIMINATE_NEGATED_VARIABLE))
+ goto eliminated;
+ ret->user_item = this->data.literal.atom.variable;
+ ret->type = LIBNORMALFORM_VARIABLE + this->data.literal.inverted;
+ break;
+
+ case TYPE_FUNCTION:
+ if ((flags & LIBNORMALFORM_ELIMINATE_FUNCTION) && (flags & LIBNORMALFORM_ELIMINATE_NEGATED_FUNCTION))
+ goto eliminated;
+ ret->reduced = !!this->data.literal.atom.function->requires_relaxation;
+ if (!ret->reduced)
+ ret->user_item = this->data.literal.atom.function;
+ else if (this->data.literal.atom.function->relaxation)
+ ret->user_item = this->data.literal.atom.function->relaxation;
+ else
+ goto eliminated;
+ ret->type = LIBNORMALFORM_FUNCTION + this->data.literal.inverted;
+ break;
+
+ case TYPE_TRANS:
+ if (this->data.trans.function->requires_elimination)
+ goto eliminated;
+ ret->type = LIBNORMALFORM_TRANSFORMATION;
+ ret->user_item = this->data.trans.function;
+ ret->terms = malloc(sizeof(*ret->terms));
+ if (ret->terms)
+ goto fail;
+ ret->terms[0] = sentence_to_expression(this->data.trans.input, flags);
+ if (!ret->terms[0])
+ goto fail;
+ break;
+
+ default:
+ abort();
+ }
+
+ goto out;
+
+eliminated:
+ ret->reduced = 1;
+ ret->type = LIBNORMALFORM_CONJUNCTION;
+out:
+ libnormalform_free(free1);
+ libnormalform_free(free2);
+ return ret;
+
+fail:
+ libnormalform_free(free1);
+ libnormalform_free(free2);
+ free_expression(ret);
+ return NULL;
+}
+
+
+static struct expression *
+make_binary(int invert_left, struct expression *left, int invert_right, struct expression *right, enum libnormalform_term_type type)
+{
+ struct expression *ret;
+ ret = malloc(sizeof(*ret));
+ if (!ret)
+ return NULL;
+ ret->type = type;
+ ret->reduced = 0;
+ ret->invert = 0;
+ ret->user_item = NULL;
+ ret->nterms = 0;
+ ret->terms = malloc(2 * sizeof(*ret->terms));
+ if (!ret->terms) {
+ free(ret);
+ return NULL;
+ }
+ (ret->terms[0] = left)->invert ^= (unsigned char)invert_left;
+ (ret->terms[1] = right)->invert ^= (unsigned char)invert_right;
+ return ret;
+}
+
+
+static int
+reduce_expression(struct expression *this, const struct libnormalform_analysers *analysers)
+{
+ enum libnormalform_sentences_relationship relationship;
+ struct expression *new;
+ size_t i, j;
+
+ for (i = 0; i < this->nterms; i++)
+ if (reduce_expression(this->terms[i], analysers))
+ return -1;
+
+ switch (this->type) {
+ case LIBNORMALFORM_CONJUNCTION:
+ for (i = 0; i < this->nterms; i++) {
+ left_eliminated_conjunction:
+ for (j = i + 1; j < this->nterms; j++) {
+ relationship = get_relationship(this->terms[i], this->terms[j], analysers);
+ switch (relationship) {
+ case LIBNORMALFORM_MATERIAL_IMPLICATION:
+ case LIBNORMALFORM_IDENTICAL:
+ free_expression(this->terms[j]);
+ this->terms[j--] = this->terms[--this->nterms];
+ break;
+ case LIBNORMALFORM_CONVERSE_IMPLICATION:
+ free_expression(this->terms[i]);
+ this->terms[i--] = this->terms[--this->nterms];
+ goto left_eliminated_conjunction;
+ case LIBNORMALFORM_MUTUALLY_INVERSE:
+ case LIBNORMALFORM_MUTUALLY_EXCLUSIVE:
+ goto contradiction;
+ case LIBNORMALFORM_JOINTLY_UNDENIABLE:
+ case LIBNORMALFORM_MUTUALLY_INDEPENDENT:
+ default:
+ break;
+ }
+ }
+ }
+ break;
+ contradiction:
+ for (i = 0; i < this->nterms; i++)
+ free_expression(this->terms[i]);
+ free(this->terms);
+ this->terms = NULL;
+ this->nterms = 0;
+ this->type = LIBNORMALFORM_DISJUNCTION;
+ break;
+
+ case LIBNORMALFORM_DISJUNCTION:
+ for (i = 0; i < this->nterms; i++) {
+ left_eliminated_disjunction:
+ for (j = i + 1; j < this->nterms; j++) {
+ relationship = get_relationship(this->terms[i], this->terms[j], analysers);
+ switch (relationship) {
+ case LIBNORMALFORM_IDENTICAL:
+ case LIBNORMALFORM_CONVERSE_IMPLICATION:
+ free_expression(this->terms[j]);
+ this->terms[j--] = this->terms[--this->nterms];
+ break;
+ case LIBNORMALFORM_MATERIAL_IMPLICATION:
+ free_expression(this->terms[i]);
+ this->terms[i--] = this->terms[--this->nterms];
+ goto left_eliminated_disjunction;
+ case LIBNORMALFORM_MUTUALLY_INVERSE:
+ case LIBNORMALFORM_JOINTLY_UNDENIABLE:
+ goto tautology;
+ case LIBNORMALFORM_MUTUALLY_EXCLUSIVE:
+ case LIBNORMALFORM_MUTUALLY_INDEPENDENT:
+ default:
+ break;
+ }
+ }
+ }
+ break;
+ tautology:
+ for (i = 0; i < this->nterms; i++)
+ free_expression(this->terms[i]);
+ free(this->terms);
+ this->terms = NULL;
+ this->nterms = 0;
+ this->type = LIBNORMALFORM_CONJUNCTION;
+ break;
+
+ case LIBNORMALFORM_EXCLUSIVE_DISJUNCTION:
+ i = 0;
+ both_eliminated_exclusive_disjunction:
+ for (; i < this->nterms; i++) {
+ for (j = i + 1; j < this->nterms; j++) {
+ relationship = get_relationship(this->terms[i], this->terms[j], analysers);
+ switch (relationship) {
+ case LIBNORMALFORM_MATERIAL_IMPLICATION:
+ new = make_binary(1, this->terms[i], 0, this->terms[j], LIBNORMALFORM_CONJUNCTION);
+ if (!new)
+ return -1;
+ goto xor_reduced;
+ case LIBNORMALFORM_CONVERSE_IMPLICATION:
+ new = make_binary(0, this->terms[i], 1, this->terms[j], LIBNORMALFORM_CONJUNCTION);
+ if (!new)
+ return -1;
+ goto xor_reduced;
+ case LIBNORMALFORM_MUTUALLY_INVERSE:
+ this->invert ^= 1;
+ /* fall-through */
+ case LIBNORMALFORM_IDENTICAL:
+ free_expression(this->terms[i]);
+ free_expression(this->terms[j]);
+ this->terms[i--] = this->terms[--this->nterms];
+ this->terms[j--] = this->terms[--this->nterms];
+ goto both_eliminated_exclusive_disjunction;
+ case LIBNORMALFORM_MUTUALLY_EXCLUSIVE:
+ new = make_binary(0, this->terms[i], 0, this->terms[j], LIBNORMALFORM_DISJUNCTION);
+ if (!new)
+ return -1;
+ goto xor_reduced;
+ case LIBNORMALFORM_JOINTLY_UNDENIABLE:
+ new = make_binary(1, this->terms[i], 1, this->terms[j], LIBNORMALFORM_DISJUNCTION);
+ if (!new)
+ return -1;
+ goto xor_reduced;
+ case LIBNORMALFORM_MUTUALLY_INDEPENDENT:
+ default:
+ break;
+ xor_reduced:
+ free_expression(this->terms[i]);
+ free_expression(this->terms[j]);
+ this->terms[j--] = this->terms[--this->nterms];
+ this->terms[i] = new;
+ goto both_eliminated_exclusive_disjunction;
+ }
+ }
+ }
+ if (!this->nterms) {
+ if (this->invert) {
+ this->invert = 0;
+ this->type = LIBNORMALFORM_CONJUNCTION;
+ } else {
+ this->type = LIBNORMALFORM_DISJUNCTION;
+ }
+ }
+ break;
+
+ case LIBNORMALFORM_TRANSFORMATION:
+ case LIBNORMALFORM_VARIABLE:
+ case LIBNORMALFORM_NEGATED_VARIABLE:
+ case LIBNORMALFORM_FUNCTION:
+ case LIBNORMALFORM_NEGATED_FUNCTION:
+ case LIBNORMALFORM_FOR_ALL:
+ case LIBNORMALFORM_NEGATED_FOR_ALL:
+ case LIBNORMALFORM_FOR_ANY:
+ case LIBNORMALFORM_NEGATED_FOR_ANY:
+ case LIBNORMALFORM_FOR_ONE:
+ case LIBNORMALFORM_NEGATED_FOR_ONE:
+ default:
+ return 0;
+ }
+
+ if (this->nterms == 1) {
+ new = this->terms[0];
+ free(this->terms);
+ new->invert ^= this->invert;
+ *this = *new;
+ free(new);
+ }
+
+ return 0;
+}
+
+
+static void *
+domain_view_transform(void *user_data, void *input)
+{
+ struct libnormalform_mapping *kvpair = input;
+ (void) user_data;
+ return kvpair->key;
+}
+
+
+static void *
+image_view_transform(void *user_data, void *input)
+{
+ struct libnormalform_mapping *kvpair = input;
+ (void) user_data;
+ return kvpair->value;
+}
+
+
+static int
+apply_transformation(struct expression *this, enum libnormalform_builtin_transformer transformer)
+{
+ /* Only .builtin is actually needed, but the others
+ * could be useful for debugging by the user */
+ static struct libnormalform_transformer domain_view = {
+ .transform = &domain_view_transform,
+ .deallocate = NULL,
+ .user_data = NULL,
+ .identifier = "<builtin:domain-view>",
+ .builtin = LIBNORMALFORM_DOMAIN_VIEW
+ };
+ static struct libnormalform_transformer image_view = {
+ .transform = &image_view_transform,
+ .deallocate = NULL,
+ .user_data = NULL,
+ .identifier = "<builtin:image-view>",
+ .builtin = LIBNORMALFORM_IMAGE_VIEW
+ };
+
+ size_t i;
+ struct expression *new, **term_singleton;
+
+ switch (this->type) {
+ case LIBNORMALFORM_CONJUNCTION:
+ case LIBNORMALFORM_DISJUNCTION:
+ case LIBNORMALFORM_EXCLUSIVE_DISJUNCTION:
+ /* transformations are morphism */
+ for (i = 0; i < this->nterms; i++)
+ if (apply_transformation(this->terms[i], transformer))
+ return -1;
+ break;
+
+ case LIBNORMALFORM_TRANSFORMATION:
+ case LIBNORMALFORM_FUNCTION:
+ case LIBNORMALFORM_NEGATED_FUNCTION:
+ new = malloc(sizeof(*new));
+ if (!new)
+ return -1;
+ term_singleton = malloc(sizeof(*term_singleton));
+ if (!term_singleton) {
+ free(new);
+ return -1;
+ }
+ *new = *this;
+ this->type = LIBNORMALFORM_TRANSFORMATION;
+ this->reduced = 0;
+ this->invert = 0;
+ this->nterms = 1;
+ this->terms = term_singleton;
+ this->terms[0] = new;
+ switch (transformer) {
+ case LIBNORMALFORM_DOMAIN_VIEW:
+ this->user_item = &domain_view;
+ break;
+ case LIBNORMALFORM_IMAGE_VIEW:
+ this->user_item = &image_view;
+ break;
+ default:
+ case LIBNORMALFORM_NOT_BUILT_IN:
+ abort();
+ }
+ break;
+
+ case LIBNORMALFORM_VARIABLE:
+ case LIBNORMALFORM_NEGATED_VARIABLE:
+ /* variables are input-independent leafs */
+ case LIBNORMALFORM_FOR_ALL:
+ case LIBNORMALFORM_NEGATED_FOR_ALL:
+ case LIBNORMALFORM_FOR_ANY:
+ case LIBNORMALFORM_NEGATED_FOR_ANY:
+ case LIBNORMALFORM_FOR_ONE:
+ case LIBNORMALFORM_NEGATED_FOR_ONE:
+ /* qualifiers reset input */
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+static int
+fix_presentation(struct expression *this, uint64_t flags, unsigned char *require_inversion, int *reduced)
+{
+#define WILL_ELIMINATE(BASETYPE, INVERT)\
+ (flags & (LIBNORMALFORM_ELIMINATE_##BASETYPE <<\
+ ((this->type ^ ((LIBNORMALFORM_##BASETYPE ^ LIBNORMALFORM_NEGATED_##BASETYPE) *\
+ (INVERT))) -\
+ LIBNORMALFORM_##BASETYPE)))
+
+ size_t i;
+ unsigned char zero = 0;
+ struct expression *new;
+
+ switch (this->type) {
+ case LIBNORMALFORM_CONJUNCTION:
+ case LIBNORMALFORM_DISJUNCTION:
+ this->type ^= LIBNORMALFORM_CONJUNCTION ^ LIBNORMALFORM_DISJUNCTION;
+ if (this->invert) {
+ if (*require_inversion) {
+ this->invert ^= 1;
+ *require_inversion = 0;
+ }
+ for (i = 0; i < this->nterms; i++)
+ this->terms[i]->invert ^= 1;
+ }
+ require_inversion = &zero;
+ break;
+
+ case LIBNORMALFORM_EXCLUSIVE_DISJUNCTION:
+ if (!this->nterms)
+ abort();
+ if (*require_inversion) {
+ this->invert ^= 1;
+ *require_inversion = 0;
+ }
+ if (flags & LIBNORMALFORM_ELIMINATE_XOR)
+ goto eliminate;
+ for (i = 0; i < this->nterms; i++)
+ if (fix_presentation(this->terms[i], flags, &this->invert, reduced))
+ return -1;
+ if (this->invert) {
+ this->invert = 0;
+ this->terms[0]->invert ^= 1;
+ if (fix_presentation(this->terms[0], flags, &zero, reduced))
+ return -1;
+ }
+ return 0;
+
+ case LIBNORMALFORM_VARIABLE:
+ case LIBNORMALFORM_NEGATED_VARIABLE:
+ if (!WILL_ELIMINATE(VARIABLE, this->invert ^ *require_inversion)) {
+ this->invert ^= *require_inversion;
+ *require_inversion = 0;
+ } else if (WILL_ELIMINATE(VARIABLE, this->invert)) {
+ goto eliminate;
+ }
+ this->type ^= (LIBNORMALFORM_VARIABLE ^ LIBNORMALFORM_NEGATED_VARIABLE) * this->invert;
+ break;
+
+ case LIBNORMALFORM_FUNCTION:
+ case LIBNORMALFORM_NEGATED_FUNCTION:
+ if (!WILL_ELIMINATE(FUNCTION, this->invert ^ *require_inversion)) {
+ this->invert ^= *require_inversion;
+ *require_inversion = 0;
+ } else if (WILL_ELIMINATE(FUNCTION, this->invert)) {
+ goto eliminate;
+ }
+ this->type ^= (LIBNORMALFORM_FUNCTION ^ LIBNORMALFORM_NEGATED_FUNCTION) * this->invert;
+ break;
+
+ case LIBNORMALFORM_TRANSFORMATION:
+ this->terms[0]->invert ^= this->invert;
+ break;
+
+ case LIBNORMALFORM_FOR_ALL:
+ case LIBNORMALFORM_NEGATED_FOR_ALL:
+ if (!WILL_ELIMINATE(FOR_ALL, this->invert ^ *require_inversion)) {
+ this->invert ^= *require_inversion;
+ *require_inversion = 0;
+ } else if (WILL_ELIMINATE(FOR_ALL, this->invert)) {
+ goto eliminate;
+ }
+ this->type ^= (LIBNORMALFORM_FOR_ALL ^ LIBNORMALFORM_NEGATED_FOR_ALL) * this->invert;
+ this->invert = 0;
+ if (flags & (LIBNORMALFORM_AVOID_FOR_ALL << (this->type - LIBNORMALFORM_FOR_ALL))) {
+ this->terms[this->nterms - 1]->invert ^= 1;
+ this->type = LIBNORMALFORM_AVOID_NEGATED_FOR_ANY - (this->type - LIBNORMALFORM_FOR_ALL);
+ goto for_any_maybe_join_sides;
+ }
+ for_all_maybe_join_sides:
+ if (this->nterms > 1)
+ break;
+ if (flags & (LIBNORMALFORM_JOIN_SIDES_IN_FOR_ALL << (this->type - LIBNORMALFORM_FOR_ALL)))
+ goto for_all_join_sides;
+ break;
+
+ case LIBNORMALFORM_FOR_ANY:
+ case LIBNORMALFORM_NEGATED_FOR_ANY:
+ if (!WILL_ELIMINATE(FOR_ANY, this->invert ^ *require_inversion)) {
+ this->invert ^= *require_inversion;
+ *require_inversion = 0;
+ } else if (WILL_ELIMINATE(FOR_ANY, this->invert)) {
+ goto eliminate;
+ }
+ this->type ^= (LIBNORMALFORM_FOR_ANY ^ LIBNORMALFORM_NEGATED_FOR_ANY) * this->invert;
+ for_any:
+ this->invert = 0;
+ if (flags & (LIBNORMALFORM_AVOID_FOR_ANY << (this->type - LIBNORMALFORM_FOR_ANY))) {
+ this->terms[this->nterms - 1]->invert ^= 1;
+ this->type = LIBNORMALFORM_AVOID_NEGATED_FOR_ALL - (this->type - LIBNORMALFORM_FOR_ANY);
+ goto for_all_maybe_join_sides;
+ }
+ for_any_maybe_join_sides:
+ if (this->nterms > 1)
+ break;
+ if (flags & (LIBNORMALFORM_JOIN_SIDES_IN_FOR_ANY << (this->type - LIBNORMALFORM_FOR_ANY)))
+ goto for_any_join_sides;
+ break;
+
+ case LIBNORMALFORM_FOR_ONE:
+ case LIBNORMALFORM_NEGATED_FOR_ONE:
+ if (!WILL_ELIMINATE(FOR_ONE, this->invert ^ *require_inversion)) {
+ this->invert ^= *require_inversion;
+ *require_inversion = 0;
+ } else if (WILL_ELIMINATE(FOR_ONE, this->invert)) {
+ goto eliminate;
+ }
+ this->type ^= (LIBNORMALFORM_FOR_ONE ^ LIBNORMALFORM_NEGATED_FOR_ONE) * this->invert;
+ if (this->type == LIBNORMALFORM_FOR_ONE) {
+ if (flags & LIBNORMALFORM_RELAX_FOR_ONE) {
+ this->type = LIBNORMALFORM_FOR_ANY;
+ goto for_any;
+ }
+ }
+ if (this->nterms > 1)
+ break;
+ if (flags & (LIBNORMALFORM_JOIN_SIDES_IN_FOR_ONE << (this->type - LIBNORMALFORM_FOR_ONE)))
+ goto for_any_join_sides;
+ break;
+
+ default:
+ abort();
+ }
+
+ this->invert = 0;
+
+ for (i = 0; i < this->nterms; i++)
+ if (fix_presentation(this->terms[i], flags, require_inversion, reduced))
+ return -1;
+
+ return 0;
+
+eliminate:
+ *require_inversion = 0;
+ this->reduced = 0;
+ this->invert = 0;
+ while (this->nterms)
+ free_expression(this->terms[--this->nterms]);
+ free(this->terms);
+ this->terms = NULL;
+ this->type = LIBNORMALFORM_CONJUNCTION;
+ *reduced = 1;
+ return 0;
+
+for_all_join_sides:
+ new = make_binary(1, this->terms[0], 0, this->terms[1], LIBNORMALFORM_DISJUNCTION);
+ goto join_sides;
+
+for_any_join_sides:
+ new = make_binary(0, this->terms[0], 0, this->terms[1], LIBNORMALFORM_CONJUNCTION);
+join_sides:
+ if (!new)
+ return -1;
+ this->terms[0] = new;
+ this->nterms--;
+ if (apply_transformation(this->terms[0]->terms[0], LIBNORMALFORM_DOMAIN_VIEW))
+ return -1;
+ if (apply_transformation(this->terms[0]->terms[1], LIBNORMALFORM_IMAGE_VIEW))
+ return -1;
+ *reduced = 1;
+ return 0;
+
+#undef WILL_ELIMINATE
+}
+
+
+static int
+expression_to_term(struct libnormalform_term *out, struct expression *this)
+{
+ size_t i;
+
+ out->type = this->type;
+ out->reduced = this->reduced;
+
+ switch (this->type) {
+ case LIBNORMALFORM_CONJUNCTION:
+ case LIBNORMALFORM_DISJUNCTION:
+ case LIBNORMALFORM_EXCLUSIVE_DISJUNCTION:
+ out->term.clause.nterms = 0;
+ out->term.clause.terms = calloc(this->nterms, sizeof(*out->term.clause.terms));
+ if (!out->term.clause.terms)
+ return -1;
+ for (i = 0; i < this->nterms; i++)
+ if (expression_to_term(&out->term.clause.terms[out->term.clause.nterms++], this->terms[i]))
+ return -1;
+ break;
+
+ case LIBNORMALFORM_TRANSFORMATION:
+ out->term.transformation.transformer = this->user_item;
+ out->term.transformation.sentence = calloc(1, sizeof(*out->term.transformation.sentence));
+ if (!out->term.transformation.sentence ||
+ expression_to_term(out->term.transformation.sentence, this->terms[0]))
+ return -1;
+ break;
+
+ case LIBNORMALFORM_VARIABLE:
+ case LIBNORMALFORM_NEGATED_VARIABLE:
+ out->term.variable = this->user_item;
+ break;
+
+ case LIBNORMALFORM_FUNCTION:
+ case LIBNORMALFORM_NEGATED_FUNCTION:
+ out->term.function = this->user_item;
+ break;
+
+ case LIBNORMALFORM_FOR_ALL:
+ case LIBNORMALFORM_NEGATED_FOR_ALL:
+ case LIBNORMALFORM_FOR_ANY:
+ case LIBNORMALFORM_NEGATED_FOR_ANY:
+ case LIBNORMALFORM_FOR_ONE:
+ case LIBNORMALFORM_NEGATED_FOR_ONE:
+ out->term.qualification.map = this->user_item;
+ if (this->nterms == 1) {
+ out->term.qualification.antecedent = NULL;
+ out->term.qualification.predicate = calloc(1, sizeof(*out->term.qualification.predicate));
+ if (!out->term.qualification.predicate ||
+ expression_to_term(out->term.qualification.predicate, this->terms[0]))
+ return -1;
+ } else {
+ out->term.qualification.antecedent = calloc(1, sizeof(*out->term.qualification.antecedent));
+ if (!out->term.qualification.antecedent)
+ return -1;
+ out->term.qualification.predicate = calloc(1, sizeof(*out->term.qualification.predicate));
+ if (!out->term.qualification.predicate ||
+ expression_to_term(out->term.qualification.antecedent, this->terms[0]) ||
+ expression_to_term(out->term.qualification.predicate, this->terms[1]))
+ return -1;
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ return 0;
+}
+
+
+struct libnormalform_term *
+(libnormalform_express)(LIBNORMALFORM_SENTENCE *this, uint64_t flags, const struct libnormalform_analysers *analysers)
+{
+ struct libnormalform_term *ret;
+ struct expression *expression;
+ int reduced;
+
+ if (flags & ~LIBNORMALFORM_ALL_EXPRESS_FLAGS__) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (flags & LIBNORMALFORM_REDUCE_XOR)
+ flags &= ~LIBNORMALFORM_ELIMINATE_XOR;
+ if ((flags & LIBNORMALFORM_ELIMINATE_FOR_ANY) && (flags & LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ALL))
+ flags &= ~LIBNORMALFORM_RELAX_FOR_ONE;
+ if (flags & LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ALL)
+ flags &= ~LIBNORMALFORM_AVOID_FOR_ANY;
+ if (flags & LIBNORMALFORM_ELIMINATE_FOR_ALL)
+ flags &= ~LIBNORMALFORM_AVOID_NEGATED_FOR_ANY;
+ if (flags & LIBNORMALFORM_ELIMINATE_NEGATED_FOR_ANY)
+ flags &= ~LIBNORMALFORM_AVOID_FOR_ALL;
+ if (flags & LIBNORMALFORM_ELIMINATE_FOR_ANY)
+ flags &= ~LIBNORMALFORM_AVOID_NEGATED_FOR_ALL;
+ if ((flags & LIBNORMALFORM_AVOID_FOR_ANY) && (flags & LIBNORMALFORM_AVOID_NEGATED_FOR_ALL))
+ flags ^= LIBNORMALFORM_AVOID_FOR_ANY | LIBNORMALFORM_AVOID_NEGATED_FOR_ALL;
+ if ((flags & LIBNORMALFORM_AVOID_FOR_ALL) && (flags & LIBNORMALFORM_AVOID_NEGATED_FOR_ANY))
+ flags ^= LIBNORMALFORM_AVOID_FOR_ALL | LIBNORMALFORM_AVOID_NEGATED_FOR_ANY;
+
+ expression = sentence_to_expression(this, flags);
+ if (!expression)
+ return NULL;
+ do {
+ if (reduce_expression(expression, analysers)) {
+ free_expression(expression);
+ return NULL;
+ }
+ reduced = 0;
+ if (fix_presentation(expression, flags, &(unsigned char){0}, &reduced)) {
+ free_expression(expression);
+ return NULL;
+ }
+ } while (reduced);
+
+ ret = malloc(sizeof(*ret));
+ if (!ret) {
+ free_expression(expression);
+ return NULL;
+ }
+ if (expression_to_term(ret, expression)) {
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wmismatched-dealloc"
+#endif
+ libnormalform_free(ret);
+ ret = NULL;
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+ }
+ free_expression(expression);
+
+ return ret;
+}
+
+
+#else
+
+TODO_TEST
+
+#endif
diff --git a/libnormalform_false.c b/libnormalform_false.c
new file mode 100644
index 0000000..e0708f5
--- /dev/null
+++ b/libnormalform_false.c
@@ -0,0 +1,155 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (FALSE implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+false_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ (void) this;
+ return libnormalform_true();
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (FALSE implementation)
+ */
+static int
+false_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ (void) this;
+ if (other->type == TYPE_FALSE) {
+ *inv_out = 0;
+ return 1;
+ } else if (other->type == TYPE_TRUE) {
+ *inv_out = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (FALSE implementation)
+ */
+CONST static int
+false_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ (void) this;
+ (void) input;
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_false)(void)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_FALSE,
+ .hash = TRUE_FALSE_HASH,
+ .inverse = &false_inverse,
+ .equals = &false_equals,
+ .evaluate = &false_evaluate
+ };
+
+ /*
+ * During normalisation, some fields may be set,
+ * therefore a new allocation is returned instead
+ * of a reference to a static allocation, so that
+ * converation can be done from the threads at
+ * the same time on different sentences, both
+ * including this constant.
+ */
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (ret)
+ *ret = prototype;
+ return ret;
+}
+
+
+#else
+
+
+static int
+tautology(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 1;
+}
+
+
+static int
+contradiction(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 0;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *f1, *f2;
+
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ fun1.evaluate = &tautology;
+ fun2.evaluate = &contradiction;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+ ASSUME(a = libnormalform_false());
+ ASSERT(a->type == TYPE_FALSE);
+ ASSERT(a->refcount == 1);
+ ASSERT_EQUAL(a, a);
+
+ ASSERT(a->evaluate(a, NULL) == 0);
+
+#define CHECK(CMP, X)\
+ do {\
+ ASSUME(b = (X));\
+ ASSERT_##CMP(a, b);\
+ libnormalform_free(b);\
+ } while (0)
+
+ CHECK(EQUAL, libnormalform_false());
+ CHECK(INVEQUAL, libnormalform_true());
+ CHECK(NOTEQUAL, libnormalform_and2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_or2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_xor2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_all(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_any(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_one(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_not(libnormalform_one(&domain, REF(f1), REF(f2))));
+ CHECK(NOTEQUAL, REF(v1));
+ CHECK(NOTEQUAL, REF(f1));
+
+#undef CHECK
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+#endif
diff --git a/libnormalform_free.c b/libnormalform_free.c
new file mode 100644
index 0000000..3bce4df
--- /dev/null
+++ b/libnormalform_free.c
@@ -0,0 +1,123 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Recursively deallocate a `LIBNORMALFORM_SENTENCE *`
+ * according to `libnormalform_free`
+ *
+ * @param this The object to deallocate
+ */
+NONNULL_INPUT static void
+free_sentence(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *head = NULL, *a, *b;
+
+ if (--this->refcount)
+ return;
+
+ do {
+ if (this->atom && !--this->atom->refcount)
+ free(this->atom);
+
+ if (IS_BRANCH(this)) {
+ a = LEFT(this);
+ b = RIGHT(this);
+ if (b && !--b->refcount)
+ PUSH(&head, b);
+ push_a:
+ if (a && !--a->refcount)
+ PUSH(&head, a);
+ } else if (this->type == TYPE_TRANS) {
+ a = this->data.trans.input;
+ goto push_a;
+ }
+
+ free(this);
+ } while (POP(&head, &this));
+}
+
+
+/**
+ * Recursively deallocate a `LIBNORMALFORM_SENTENCE *`
+ * according to `libnormalform_free`, but do not
+ * deallocate the pointer itself
+ *
+ * @param this The object to deallocate
+ */
+NONNULL_INPUT static void
+destroy_term(struct libnormalform_term *this)
+{
+ switch (this->type) {
+ case LIBNORMALFORM_DISJUNCTION:
+ case LIBNORMALFORM_CONJUNCTION:
+ case LIBNORMALFORM_EXCLUSIVE_DISJUNCTION:
+ while (this->term.clause.nterms)
+ destroy_term(&this->term.clause.terms[--this->term.clause.nterms]);
+ free(this->term.clause.terms);
+ free(this);
+ break;
+
+ case LIBNORMALFORM_TRANSFORMATION:
+ free(this->term.transformation.sentence);
+ free(this);
+ return;
+
+ case LIBNORMALFORM_FOR_ALL:
+ case LIBNORMALFORM_NEGATED_FOR_ALL:
+ case LIBNORMALFORM_FOR_ANY:
+ case LIBNORMALFORM_NEGATED_FOR_ANY:
+ case LIBNORMALFORM_FOR_ONE:
+ case LIBNORMALFORM_NEGATED_FOR_ONE:
+ destroy_term(this->term.qualification.antecedent);
+ destroy_term(this->term.qualification.predicate);
+ free(this->term.qualification.antecedent);
+ free(this->term.qualification.predicate);
+ /* fall through */
+
+ case LIBNORMALFORM_VARIABLE:
+ case LIBNORMALFORM_NEGATED_VARIABLE:
+ case LIBNORMALFORM_FUNCTION:
+ case LIBNORMALFORM_NEGATED_FUNCTION:
+ free(this);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+
+void
+(libnormalform_free)(void *this)
+{
+ if (!this)
+ return;
+
+ if (*(enum libnormalform_term_type *)this >= SENTENCE_TYPE_OFFSET) {
+ free_sentence(this);
+ } else {
+ destroy_term(this);
+ free(this);
+ }
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ libnormalform_free(NULL);
+
+ /* Tested in other tests */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_from_string.c b/libnormalform_from_string.c
new file mode 100644
index 0000000..8d64c41
--- /dev/null
+++ b/libnormalform_from_string.c
@@ -0,0 +1,918 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#define OPT_SPACE\
+ do {\
+ while (isspace(*s))\
+ s++;\
+ } while (0);
+
+#define LEFT_BRACKET\
+ do {\
+ if (!STARTS("("))\
+ goto einval;\
+ OPT_SPACE;\
+ } while (0)
+
+#define COMMA\
+ do {\
+ OPT_SPACE;\
+ if (!STARTS(","))\
+ goto einval;\
+ OPT_SPACE;\
+ } while (0)
+
+#define RIGHT_BRACKET\
+ do {\
+ OPT_SPACE;\
+ if (!STARTS(")"))\
+ goto einval;\
+ } while (0)
+
+
+/**
+ * Parse a reference number
+ *
+ * @param s String beginning with the first digit in the number
+ * @param end_out Output parameter for the end of the number
+ * @param id_out Output parameter for the reference number
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL The string does not begin with a number
+ * @throws EINVAL The number is too large
+ */
+USE_RESULT NONNULL_INPUT
+static int
+get_reference_id(char *s, char **end_out, size_t *id_out)
+{
+ size_t digit;
+
+ *id_out = 0;
+
+ if (*s == '0') {
+ *end_out = &s[1];
+ return 0;
+ }
+
+ if ('1' > *s || *s > '9')
+ goto einval;
+
+ while (isdigit(*s)) {
+ digit = (size_t)(*s & 15);
+
+ if (*id_out > (SIZE_MAX / sizeof(void *) - 1U - digit) / 10U)
+ goto einval;
+ *id_out = *id_out * 10U + digit;
+ }
+
+ *end_out = s;
+ return 0;
+
+einval:
+ errno = EINVAL;
+ return -1;
+}
+
+
+/**
+ * Parse a reference number and allocate a reference slot for it
+ *
+ * The reference slot will be initialised to `NULL`
+ *
+ * @param s String beginning with the first digit in the number
+ * @param end_out Output parameter for the end of the number
+ * @param id_out Output parameter for the reference number
+ * @param referencesp Reference to array of references points
+ * @param nreferencesp Reference to the number of elements in `*referencesp`,
+ * will be incremented by 1 on success
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL The string does not begin with a number
+ * @throws EINVAL The number is not `*nreferencesp`
+ * @throws ENOMEM Insufficient memory available to allocate the slot
+ */
+USE_RESULT NONNULL_INPUT
+static int
+get_reference_id_and_allocate_slot(char *s, char **end_out, size_t *id_out,
+ LIBNORMALFORM_SENTENCE ***referencesp, size_t *nreferencesp)
+{
+ void *new;
+
+ OPT_SPACE;
+ if (get_reference_id(s, &s, id_out))
+ return -1;
+ if (*id_out != *nreferencesp) {
+ errno = EINVAL;
+ return -1;
+ }
+ OPT_SPACE;
+
+ new = realloc(*referencesp, (*id_out + 1U) * sizeof(**referencesp));
+ if (!new)
+ return -1;
+ *referencesp = new;
+ (*referencesp)[(*nreferencesp)++] = NULL;
+
+ *end_out = s;
+ return 0;
+}
+
+
+/**
+ * `libnormalform_from_string` with some additional parameters:
+ *
+ * @param truep Reference to any already constructed TRUE sentence (`*truep` is `NULL` until then)
+ * @param falsep Reference to any already constructed FALSE sentence (`*falsep` is `NULL` until then)
+ * @param referencesp Reference to array of references points
+ * @param nreferencesp Reference to the number of elements in `*referencesp`
+ */
+USE_RESULT SOME_NONNULL_INPUT(1, 3, 4, 5, 6, 7)
+static LIBNORMALFORM_SENTENCE *
+from_string(char *s, char **end_out, const struct libnormalform_representation_spec *spec,
+ LIBNORMALFORM_SENTENCE **truep, LIBNORMALFORM_SENTENCE **falsep,
+ LIBNORMALFORM_SENTENCE ***referencesp, size_t *nreferencesp)
+{
+#define STARTS(WANT) (!strncmp(s, WANT, (n = sizeof(WANT) - 1U)) ? (s = &s[n]) : NULL)
+
+ LIBNORMALFORM_SENTENCE *(*qualifier2)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *) = NULL;
+ LIBNORMALFORM_SENTENCE *(*qualifier1)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *) = NULL;
+ LIBNORMALFORM_SENTENCE *(*qualifier0)(struct libnormalform_map *) = NULL;
+ LIBNORMALFORM_SENTENCE *(*variadic)(LIBNORMALFORM_SENTENCE **) = NULL;
+ LIBNORMALFORM_SENTENCE *(*unary)(LIBNORMALFORM_SENTENCE *) = NULL;
+ LIBNORMALFORM_SENTENCE *ret = NULL, *k = NULL, *v = NULL, *x = NULL;
+ size_t n, refid = SIZE_MAX;
+
+ if (STARTS("TRUE")) {
+ if (*truep)
+ ret = libnormalform_ref(*truep);
+ else
+ ret = *truep = libnormalform_true();
+ } else if (STARTS("FALSE")) {
+ if (*falsep)
+ ret = libnormalform_ref(*falsep);
+ else
+ ret = *falsep = libnormalform_false();
+ } else if (STARTS("REF")) {
+ LEFT_BRACKET;
+ if (get_reference_id(s, &s, &refid))
+ return NULL;
+ RIGHT_BRACKET;
+ if (refid >= *nreferencesp)
+ goto einval;
+ ret = (*referencesp)[refid];
+ if (!ret)
+ goto einval;
+ ret = libnormalform_ref(ret);
+ if (!ret)
+ return NULL;
+ } else if (STARTS("NOT")) {
+ unary = &libnormalform_not;
+ } else if (STARTS("AND")) {
+ variadic = &libnormalform_and;
+ } else if (STARTS("OR")) {
+ variadic = &libnormalform_or;
+ } else if (STARTS("XOR")) {
+ variadic = &libnormalform_xor;
+ } else if (STARTS("IF")) {
+ variadic = &libnormalform_if;
+ } else if (STARTS("IMPLY")) {
+ variadic = &libnormalform_imply;
+ } else if (STARTS("NAND")) {
+ variadic = &libnormalform_nand;
+ } else if (STARTS("NOR")) {
+ variadic = &libnormalform_nor;
+ } else if (STARTS("XNOR")) {
+ variadic = &libnormalform_xnor;
+ } else if (STARTS("NIF")) {
+ variadic = &libnormalform_nif;
+ } else if (STARTS("NIMPLY")) {
+ variadic = &libnormalform_nimply;
+ } else if (STARTS("ALL")) {
+ qualifier2 = &libnormalform_all;
+ } else if (STARTS("ANY")) {
+ qualifier2 = &libnormalform_any;
+ } else if (STARTS("ONE")) {
+ qualifier2 = &libnormalform_one;
+ } else if (STARTS("EXISTENTIALLY")) {
+ qualifier1 = &libnormalform_existentially;
+ } else if (STARTS("UNIVERSALLY")) {
+ qualifier1 = &libnormalform_universally;
+ } else if (STARTS("UNIQUELY")) {
+ qualifier1 = &libnormalform_uniquely;
+ } else if (STARTS("EXISTS")) {
+ qualifier1 = &libnormalform_exists;
+ } else if (STARTS("NEXISTS")) {
+ qualifier1 = &libnormalform_nexists;
+ } else if (STARTS("UNIQUE")) {
+ qualifier1 = &libnormalform_unique;
+ } else if (STARTS("EMPTY")) {
+ qualifier0 = &libnormalform_empty;
+ } else if (STARTS("NONEMPTY")) {
+ qualifier0 = &libnormalform_nonempty;
+ } else if (STARTS("SINGLETON")) {
+ qualifier0 = &libnormalform_singleton;
+ } else if (STARTS("VARIABLE")) {
+ struct libnormalform_variable *a;
+ OPT_SPACE;
+ if (*s == '@' && get_reference_id_and_allocate_slot(&s[1], &s, &refid, referencesp, nreferencesp))
+ return NULL;
+ LEFT_BRACKET;
+ if (!spec->get_variable)
+ goto enoent;
+ a = spec->get_variable(s, &s, spec->user_data);
+ if (!a)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = libnormalform_variable(a);
+ } else if (STARTS("FUNCTION")) {
+ struct libnormalform_function *a;
+ OPT_SPACE;
+ if (*s == '@' && get_reference_id_and_allocate_slot(&s[1], &s, &refid, referencesp, nreferencesp))
+ return NULL;
+ LEFT_BRACKET;
+ if (!spec->get_function)
+ goto enoent;
+ a = spec->get_function(s, &s, spec->user_data);
+ if (!a)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = libnormalform_function(a);
+ } else if (STARTS("TRANSFORMATION")) {
+ struct libnormalform_transformer *a;
+ OPT_SPACE;
+ if (*s == '@' && get_reference_id_and_allocate_slot(&s[1], &s, &refid, referencesp, nreferencesp))
+ return NULL;
+ LEFT_BRACKET;
+ if (!spec->get_transformer)
+ goto enoent;
+ a = spec->get_transformer(s, &s, spec->user_data);
+ if (!a)
+ return NULL;
+ COMMA;
+ x = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!x)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = libnormalform_transformation(a, x);
+ x = NULL;
+ } else {
+ goto einval;
+ }
+
+ if (!ret) {
+ OPT_SPACE;
+ if (*s == '@' && get_reference_id_and_allocate_slot(&s[1], &s, &refid, referencesp, nreferencesp))
+ return NULL;
+ LEFT_BRACKET;
+ }
+
+ if (qualifier2) {
+ struct libnormalform_map *d;
+ if (!spec->get_map)
+ goto enoent;
+ d = spec->get_map(s, &s, spec->user_data);
+ if (!d)
+ return NULL;
+ COMMA;
+ k = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!k)
+ return NULL;
+ COMMA;
+ v = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!v) {
+ libnormalform_free(k);
+ return NULL;
+ }
+ RIGHT_BRACKET;
+ ret = (*qualifier2)(d, k, v);
+ k = v = NULL;
+
+ } else if (qualifier1) {
+ struct libnormalform_map *d;
+ if (!spec->get_map)
+ goto enoent;
+ d = spec->get_map(s, &s, spec->user_data);
+ if (!d)
+ return NULL;
+ COMMA;
+ k = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!k)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = (*qualifier1)(d, k);
+
+ } else if (qualifier0) {
+ struct libnormalform_map *d;
+ if (!spec->get_map)
+ goto enoent;
+ d = spec->get_map(s, &s, spec->user_data);
+ if (!d)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = (*qualifier0)(d);
+
+ } else if (unary) {
+ x = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!x)
+ return NULL;
+ RIGHT_BRACKET;
+ ret = (*unary)(x);
+ x = NULL;
+
+ } else if (variadic) {
+ LIBNORMALFORM_SENTENCE **xs = NULL;
+ size_t nxs = 0;
+ void *new;
+ goto fit_one_more;
+ do {
+ xs[nxs] = from_string(s, &s, spec, truep, falsep, referencesp, nreferencesp);
+ if (!xs[nxs])
+ goto fail_variadic;
+ nxs++;
+
+ OPT_SPACE;
+ if (STARTS(",")) {
+ OPT_SPACE;
+ } else if (*s != ')') {
+ errno = EINVAL;
+ fail_variadic:
+ while (nxs--)
+ libnormalform_free(xs[nxs]);
+ free(xs);
+ return NULL;
+ }
+
+ fit_one_more:
+ new = realloc(xs, (nxs + 1U) * sizeof(*xs));
+ if (!new)
+ goto fail_variadic;
+ xs = new;
+ } while (!STARTS(")"));
+ xs[nxs] = NULL;
+ ret = (*variadic)(xs);
+ free(xs);
+ }
+
+ if (end_out) {
+ *end_out = s;
+ } else if (*s) {
+ OPT_SPACE;
+ if (*s) {
+ libnormalform_free(ret);
+ einval:
+ libnormalform_free(k);
+ libnormalform_free(v);
+ libnormalform_free(x);
+ errno = EINVAL;
+ return NULL;
+ }
+ }
+
+ if (refid != SIZE_MAX)
+ (*referencesp)[refid] = ret;
+
+ return ret;
+
+enoent:
+ errno = ENOENT;
+ return NULL;
+
+#undef STARTS
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_from_string)(char *s, char **end_out, const struct libnormalform_representation_spec *spec)
+{
+ LIBNORMALFORM_SENTENCE *true_obj = NULL;
+ LIBNORMALFORM_SENTENCE *false_obj = NULL;
+ LIBNORMALFORM_SENTENCE **references = NULL;
+ size_t nreferences = 0;
+ LIBNORMALFORM_SENTENCE *ret;
+ while (isspace(*s))
+ s++;
+ ret = from_string(s, end_out, spec, &true_obj, &false_obj, &references, &nreferences);
+ free(references);
+ return ret;
+}
+
+
+#undef OPT_SPACE
+#undef LEFT_BRACKET
+#undef COMMA
+#undef RIGHT_BRACKET
+
+
+#else
+
+
+static struct libnormalform_variable var1 = {.identifier = "var1"};
+static struct libnormalform_variable var2 = {.identifier = "var2"};
+static struct libnormalform_variable var3 = {.identifier = "var3"};
+static struct libnormalform_function fun1 = {.identifier = "fun1"};
+static struct libnormalform_function fun2 = {.identifier = "fun2"};
+static struct libnormalform_map dom1 = {.identifier = "dom1"};
+static struct libnormalform_map dom2 = {.identifier = "dom2"};
+static struct libnormalform_transformer trans1 = {.identifier = "trans1"};
+
+
+static struct libnormalform_variable *
+get_variable(char *s, char **end_out, void *user_data)
+{
+ struct libnormalform_variable *ret;
+ (void) user_data;
+ if (!strncmp(s, "var1", 4))
+ ret = &var1;
+ else if (!strncmp(s, "var2", 4))
+ ret = &var2;
+ else if (!strncmp(s, "var3", 4))
+ ret = &var3;
+ else
+ return NULL;
+ *end_out = &s[4];
+ return ret;
+}
+
+
+static struct libnormalform_function *
+get_function(char *s, char **end_out, void *user_data)
+{
+ struct libnormalform_function *ret;
+ (void) user_data;
+ if (!strncmp(s, "fun1", 4))
+ ret = &fun1;
+ else if (!strncmp(s, "fun2", 4))
+ ret = &fun2;
+ else
+ return NULL;
+ *end_out = &s[4];
+ return ret;
+}
+
+
+static struct libnormalform_map *
+get_map(char *s, char **end_out, void *user_data)
+{
+ struct libnormalform_map *ret;
+ (void) user_data;
+ if (!strncmp(s, "dom1", 4))
+ ret = &dom1;
+ else if (!strncmp(s, "dom2", 4))
+ ret = &dom2;
+ else
+ return NULL;
+ *end_out = &s[4];
+ return ret;
+}
+
+
+static struct libnormalform_transformer *
+get_transformer(char *s, char **end_out, void *user_data)
+{
+ struct libnormalform_transformer *ret;
+ (void) user_data;
+ if (!strncmp(s, "trans1", 6))
+ ret = &trans1;
+ else
+ return NULL;
+ *end_out = &s[6];
+ return ret;
+}
+
+
+struct libnormalform_representation_spec spec = {
+ .get_variable = get_variable,
+ .get_function = get_function,
+ .get_map = get_map,
+ .get_transformer = get_transformer
+};
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ LIBNORMALFORM_SENTENCE *a, *b;
+ char *s, *p;
+
+ ASSUME(s = strdup("AND(VARIABLE(var1), VARIABLE(var2))"));
+ ASSUME(a = libnormalform_and2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("AND(VARIABLE(var1), VARIABLE(var2))"));
+ spec.get_variable = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_variable = &get_variable;
+ free(s);
+
+ ASSUME(s = strdup("OR(VARIABLE(var1), VARIABLE(var2))"));
+ ASSUME(a = libnormalform_or2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("XOR(VARIABLE(var1), VARIABLE(var2))"));
+ ASSUME(a = libnormalform_xor2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_function(&fun1), libnormalform_function(&fun2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_function = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_function = &get_function;
+ free(s);
+
+ ASSUME(s = strdup("ANY(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ ASSUME(a = libnormalform_any(&dom1, libnormalform_function(&fun1), libnormalform_function(&fun2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ANY(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_function = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_function = &get_function;
+ free(s);
+
+ ASSUME(s = strdup("ONE(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_function(&fun1), libnormalform_function(&fun2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ONE(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_function = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_function = &get_function;
+ free(s);
+
+ ASSUME(s = strdup("NOT(ONE(dom1, TRUE, FALSE))"));
+ ASSUME(a = libnormalform_not(libnormalform_one(&dom1, libnormalform_true(), libnormalform_false())));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("NOT(ONE(dom1, FUNCTION(fun1), FUNCTION(fun2)))"));
+ spec.get_function = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_function = &get_function;
+ free(s);
+
+ ASSUME(s = strdup(" XNOR(VARIABLE(var1), VARIABLE(var2))"));
+ ASSUME(a = libnormalform_xnor2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("IF(VARIABLE(var1), VARIABLE(var2)) "));
+ ASSUME(a = libnormalform_if2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("IMPLY ( VARIABLE ( var1 ) , VARIABLE ( var2 ) )"));
+ ASSUME(a = libnormalform_imply2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("\nNIF(VARIABLE(var1),VARIABLE(var2))\t"));
+ ASSUME(a = libnormalform_nif2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("\tNIMPLY(VARIABLE(var1)\n,\t VARIABLE(var2))\n"));
+ ASSUME(a = libnormalform_nimply2(libnormalform_variable(&var1), libnormalform_variable(&var2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("EXISTS(dom1, FUNCTION(fun1))"));
+ ASSUME(a = libnormalform_exists(&dom1, libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("NEXISTS(dom1, FUNCTION(fun1))"));
+ ASSUME(a = libnormalform_nexists(&dom1, libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("UNIQUE(dom2, FUNCTION(fun2))"));
+ ASSUME(a = libnormalform_unique(&dom2, libnormalform_function(&fun2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("EXISTENTIALLY(dom1, FUNCTION(fun1))"));
+ ASSUME(a = libnormalform_existentially(&dom1, libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("UNIVERSALLY(dom1, FUNCTION(fun1))"));
+ ASSUME(a = libnormalform_universally(&dom1, libnormalform_function(&fun1)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("UNIQUELY(dom2, FUNCTION(fun2))"));
+ ASSUME(a = libnormalform_uniquely(&dom2, libnormalform_function(&fun2)));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("EMPTY(dom1)"));
+ ASSUME(a = libnormalform_empty(&dom1));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("NONEMPTY(dom1)"));
+ ASSUME(a = libnormalform_nonempty(&dom1));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("SINGLETON(dom2)"));
+ ASSUME(a = libnormalform_singleton(&dom2));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION@0(fun1), REF(0))"));
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_ref(a));
+ ASSUME(a = libnormalform_all(&dom1, a, b));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ free(s);
+ ASSERT_EQUAL(a, b);
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(b->type == TYPE_ALL);
+ ASSERT(a->data.qualifier.antecedent->refcount == 2);
+ ASSERT(a->data.qualifier.predicate->refcount == 2);
+ ASSERT(a->data.qualifier.antecedent == a->data.qualifier.predicate);
+ ASSERT(b->data.qualifier.antecedent->refcount == 2);
+ ASSERT(b->data.qualifier.predicate->refcount == 2);
+ ASSERT(b->data.qualifier.antecedent == b->data.qualifier.predicate);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1), FALSE)"));
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, ZZRIABLE(var1), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1), FALSE) X"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1), FALSE) X"));
+ ASSERT(a = libnormalform_from_string(s, &p, &spec));
+ libnormalform_free(a);
+ ASSERT(!strcmp(p, " X"));
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1), FALSE"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("AL"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1), FALSE,"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(var1) FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1 VARIABLE(var1), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, REF(0), FUNCTION@0(fun1))"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION@1(fun1), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, REF(2), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, AND@0(VARIABLE(var1), REF(0)), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EINVAL);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, VARIABLE(varx), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 1);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION(funx), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 1);
+ free(s);
+
+ ASSUME(s = strdup("ALL(domx, VARIABLE(var1), FALSE)"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == 1);
+ free(s);
+
+ ASSUME(s = strdup("ALL(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_map = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_map = &get_map;
+ free(s);
+
+ ASSUME(s = strdup("ANY(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_map = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_map = &get_map;
+ free(s);
+
+ ASSUME(s = strdup("ONE(dom1, FUNCTION(fun1), FUNCTION(fun2))"));
+ spec.get_map = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_map = &get_map;
+ free(s);
+
+ ASSUME(s = strdup("NOT(ONE(dom1, FUNCTION(fun1), FUNCTION(fun2)))"));
+ spec.get_map = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_map = &get_map;
+ free(s);
+
+ ASSUME(s = strdup("AND()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("OR()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("XOR()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("XNOR()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("IMPLY()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("NIF()"));
+ errno = 0;
+ ASSUME(a = libnormalform_from_string(s, NULL, &spec));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+ free(s);
+
+ ASSUME(s = strdup("NIMPLY()"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EDOM);
+ free(s);
+
+ ASSUME(s = strdup("IF()"));
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == EDOM);
+ free(s);
+
+ ASSUME(s = strdup("XOR(ALL(dom1, TRUE, FALSE), ALL(dom2, TRUE, FALSE))"));
+ ASSUME(a = libnormalform_all(&dom1, libnormalform_true(), libnormalform_false()));
+ ASSUME(b = libnormalform_all(&dom2, libnormalform_true(), libnormalform_false()));
+ ASSUME(a = libnormalform_xor2(a, b));
+ ASSUME(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ ASSUME(s = strdup("TRANSFORMATION(trans1, FUNCTION(fun1))"));
+ spec.get_transformer = NULL;
+ errno = 0;
+ ASSERT(!libnormalform_from_string(s, NULL, &spec) && errno == ENOENT);
+ spec.get_transformer = &get_transformer;
+ free(s);
+
+ ASSUME(s = strdup("TRANSFORMATION(trans1, FUNCTION(fun1))"));
+ ASSUME(a = libnormalform_transformation(&trans1, libnormalform_function(&fun1)));
+ ASSERT(b = libnormalform_from_string(s, NULL, &spec));
+ ASSERT_EQUAL(a, b);
+ free(s);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_function.c b/libnormalform_function.c
new file mode 100644
index 0000000..3978a4a
--- /dev/null
+++ b/libnormalform_function.c
@@ -0,0 +1,287 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (Function literal implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+function_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *ret = libnormalform_function(this->data.literal.atom.function);
+ if (ret)
+ ret->data.literal.inverted = this->data.literal.inverted ^ 1;
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (Function literal implementation)
+ */
+static int
+function_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ if (other->type != TYPE_FUNCTION || this->data.literal.atom.function != other->data.literal.atom.function)
+ return 0;
+ *inv_out = this->data.literal.inverted ^ other->data.literal.inverted;
+ return 1;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (Function literal implementation)
+ */
+static int
+function_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ int r = this->data.literal.atom.function->evaluate(this->data.literal.atom.function->user_data, input);
+ return r < 0 ? r : ((r > 0) ^ this->data.literal.inverted);
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_function)(struct libnormalform_function *function)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_FUNCTION,
+ .inverse = &function_inverse,
+ .equals = &function_equals,
+ .evaluate = &function_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (ret) {
+ *ret = prototype;
+ ret->hash = LITERAL_HASH(function);
+ ret->data.literal.atom.function = function;
+ ret->data.literal.inverted = 0;
+ }
+ return ret;
+}
+
+
+#else
+
+
+static int
+tautology(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 1;
+}
+
+
+static int
+contradiction(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 0;
+}
+
+
+static int
+einval(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ errno = EINVAL;
+ return -1;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_variable var1;
+ struct libnormalform_mapping map[1];
+ struct libnormalform_map domain;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *f1, *f2;
+
+ domain.nmappings = 0;
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSERT(a->refcount == 1);
+ ASSERT(a->travel_index == 0);
+ ASSERT(a->travel_count == 0);
+ ASSERT(a->type == TYPE_FUNCTION);
+ ASSERT(a->data.literal.inverted == 0);
+ ASSERT(a->data.literal.atom.function == &fun1);
+ ASSERT_EQUAL(a, a);
+ fun1.evaluate = tautology;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ fun1.evaluate = contradiction;
+ ASSERT(libnormalform_evaluate(a) == 0);
+ fun1.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_EQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT_INVEQUAL(b, a);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+ ASSERT_NOTEQUAL(a, v1);
+
+ ASSUME(b = libnormalform_and2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_or2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_xor2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_all(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_any(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_and2(a, b));
+ fun1.evaluate = tautology;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = tautology;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = tautology;
+ fun2.evaluate = tautology;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_or2(a, b));
+ fun1.evaluate = contradiction;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = contradiction;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = contradiction;
+ fun2.evaluate = contradiction;
+ ASSERT(libnormalform_evaluate(a) == 0);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_xor2(a, b));
+ fun1.evaluate = contradiction;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = contradiction;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = contradiction;
+ fun2.evaluate = contradiction;
+ ASSERT(libnormalform_evaluate(a) == 0);
+ libnormalform_free(a);
+
+ domain.nmappings = 1;
+ domain.mappings = map;
+ map[0].key = NULL;
+ map[0].value = NULL;
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_all(&domain, a, b));
+ fun1.evaluate = tautology;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = tautology;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = tautology;
+ fun2.evaluate = tautology;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_any(&domain, a, b));
+ fun1.evaluate = tautology;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = tautology;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = tautology;
+ fun2.evaluate = tautology;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_one(&domain, a, b));
+ fun1.evaluate = tautology;
+ fun2.evaluate = einval;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = einval;
+ fun2.evaluate = tautology;
+ errno = 0;
+ ASSERT(libnormalform_evaluate(a) == -1 && errno == EINVAL);
+ fun1.evaluate = tautology;
+ fun2.evaluate = tautology;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_if.c b/libnormalform_if.c
new file mode 100644
index 0000000..17bb380
--- /dev/null
+++ b/libnormalform_if.c
@@ -0,0 +1,249 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_if)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret;
+ int inv;
+
+ /* 0 ← x = ¬x, but 1 ← x = 1, so at least one argument is required */
+ ret = *xs++;
+ if (!ret) {
+ errno = EDOM;
+ return NULL;
+ }
+
+ if (ret->type == TYPE_TRUE) {
+ /* 1 ← x = 1 */
+ goto return_asis;
+ }
+
+ for (; (x = *xs); xs++) {
+ if (!ret) {
+ /* error handling */
+ libnormalform_free(x);
+
+ } else if (x->type == TYPE_FALSE) {
+ /* x ← 0 ← y = 1 ← y = 1 */
+ goto return_true;
+
+ } else if (x->type == TYPE_TRUE) {
+ /* x ← 1 = x */
+ libnormalform_free(x);
+
+ } else if (x->equals(x, ret, &inv)) {
+ if (!inv) {
+ /* x ← x ← y = 1 ← y = 1 */
+ goto return_true;
+
+ } else {
+ /* x ← ¬x = x */
+ libnormalform_free(x);
+ }
+
+ } else if (ret->type == TYPE_FALSE) {
+ /* 0 ← x = ¬x */
+ libnormalform_free(ret);
+ ret = libnormalform_not(x);
+
+ } else {
+ /* l ← r = r → l = ¬(r ∧ ¬l) = ¬r ∨ l = l ∨ ¬r */
+ ret = libnormalform_or2(ret, libnormalform_not(x));
+ if (ret && ret->type == TYPE_TRUE) {
+ /* 1 ← x = 1 */
+ goto return_true;
+ }
+ }
+ }
+
+ return ret;
+
+return_true:
+ libnormalform_free(ret);
+ ret = libnormalform_true();
+return_asis:
+ while (*xs)
+ libnormalform_free(*xs++);
+ return ret;
+}
+
+
+#else
+
+
+#define IF(...) LIBNORMALFORM_IF(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!IF(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!IF(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!IF(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!IF(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!IF(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!IF(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = IF(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = IF(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ /* IF () -> EDOM */
+ errno = 0;
+ ASSERT(!IF() && errno == EDOM);
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* IF (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* IF (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(TRUE, F, F); /* IF (FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, F, T); /* IF (FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(TRUE, T, F); /* IF (TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, T, T); /* IF (TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL2(TRUE, F, F); /* IF (var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, F, T); /* IF (var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL2(TRUE, T, F); /* IF (var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(TRUE, T, T); /* IF (var(TRUE), var(TRUE)) => TRUE */
+
+#ifndef USE_TWO
+
+ /* IF (x) -> x */
+ ASSUME(a = IF(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* IF (x, TRUE) -> x */
+ ASSUME(a = IF(REF(v1), T));
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* IF (TRUE, x) -> TRUE */
+ ASSUME(a = IF(T, REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* IF (x, FALSE) -> TRUE */
+ ASSUME(a = IF(REF(v1), F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* IF (FALSE, x) -> NOT x */
+ ASSUME(a = IF(F, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* IF (x, x) -> TRUE */
+ ASSUME(a = IF(REF(v1), REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* IF (x, NOT x) -> x */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = IF(REF(v1), a));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* IF (x, y) -> OR (x, NOT y) */
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(REF(v1), b));
+ ASSUME(a = IF(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* IF (x, y, z) -> NOT AND (NOT x, y, z) */
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_andl(b, REF(v2), REF(v3), NULL));
+ ASSUME(a = IF(REF(v1), REF(v2), REF(v3)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* IF (x, y) -> independence from IF (y, x) */
+ ASSUME(a = IF(REF(v1), REF(v2)));
+ ASSUME(b = IF(REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_if2.c b/libnormalform_if2.c
new file mode 100644
index 0000000..208c789
--- /dev/null
+++ b/libnormalform_if2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_if2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_if_checked.c b/libnormalform_if_checked.c
new file mode 100644
index 0000000..2a91f2b
--- /dev/null
+++ b/libnormalform_if_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_if_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_ifl.c b/libnormalform_ifl.c
new file mode 100644
index 0000000..b811d7e
--- /dev/null
+++ b/libnormalform_ifl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_ifl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_ifl_checked.c b/libnormalform_ifl_checked.c
new file mode 100644
index 0000000..25ab4f9
--- /dev/null
+++ b/libnormalform_ifl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_ifl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_ifl_macro_test.c b/libnormalform_ifl_macro_test.c
new file mode 100644
index 0000000..a911999
--- /dev/null
+++ b/libnormalform_ifl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_if.c"
+#endif
diff --git a/libnormalform_imply.c b/libnormalform_imply.c
new file mode 100644
index 0000000..823abf7
--- /dev/null
+++ b/libnormalform_imply.c
@@ -0,0 +1,166 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_imply)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *t;
+ size_t n = 0, i;
+
+ while (xs[n])
+ n += 1;
+
+ if (!n) {
+ /* 1 → x = x and 0 → x = 1 */
+ return libnormalform_true();
+ }
+
+ /* a → b → c = a → (b → c) = (b → c) ← a = (c ← b) ← a = c ← b ← a */
+ for (i = 0; i < n--; i++) {
+ t = xs[i];
+ xs[i] = xs[n];
+ xs[n] = t;
+ }
+ return libnormalform_if(xs);
+}
+
+
+#else
+
+
+#define IMPLY(...) LIBNORMALFORM_IMPLY(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!IMPLY(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!IMPLY(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!IMPLY(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!IMPLY(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!IMPLY(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!IMPLY(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = IMPLY(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = IMPLY(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(TRUE); /* IMPLY () -> TRUE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* IMPLY (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* IMPLY (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(TRUE, F, F); /* IMPLY (FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T); /* IMPLY (FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, T, F); /* IMPLY (TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T); /* IMPLY (TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL2(TRUE, F, F); /* IMPLY (var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(TRUE, F, T); /* IMPLY (var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL2(FALSE, T, F); /* IMPLY (var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, T, T); /* IMPLY (var(TRUE), var(TRUE)) => TRUE */
+
+#ifndef USE_TWO
+
+ /* IMPLY (x) -> x */
+ ASSUME(a = IMPLY(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* IMPLY (x, y) -> IF (y, x) */
+ ASSUME(a = IMPLY(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_if2(REF(v2), REF(v1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* IMPLY (x, y, z) -> IF (z, y, x) */
+ ASSUME(a = IMPLY(REF(v1), REF(v2), REF(v3)));
+ ASSUME(b = libnormalform_ifl(REF(v3), REF(v2), REF(v1), NULL));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* IMPLY (x, y) -> independence from IMPLY (y, x) */
+ ASSUME(a = IMPLY(REF(v1), REF(v2)));
+ ASSUME(b = IMPLY(REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+ return 0;
+}
+
+
+#endif
diff --git a/libnormalform_imply2.c b/libnormalform_imply2.c
new file mode 100644
index 0000000..63b7562
--- /dev/null
+++ b/libnormalform_imply2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_imply2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_imply_checked.c b/libnormalform_imply_checked.c
new file mode 100644
index 0000000..8bf9a3e
--- /dev/null
+++ b/libnormalform_imply_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_imply_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_implyl.c b/libnormalform_implyl.c
new file mode 100644
index 0000000..9e13bbe
--- /dev/null
+++ b/libnormalform_implyl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_implyl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_implyl_checked.c b/libnormalform_implyl_checked.c
new file mode 100644
index 0000000..0a538f9
--- /dev/null
+++ b/libnormalform_implyl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_implyl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_implyl_macro_test.c b/libnormalform_implyl_macro_test.c
new file mode 100644
index 0000000..960b4b1
--- /dev/null
+++ b/libnormalform_implyl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_imply.c"
+#endif
diff --git a/libnormalform_nand.c b/libnormalform_nand.c
new file mode 100644
index 0000000..efe7161
--- /dev/null
+++ b/libnormalform_nand.c
@@ -0,0 +1,235 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_nand)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret;
+ int inv;
+
+ /* 1 ⊼ x = ¬x, but 0 ⊼ x = 1, so at least one argument is required */
+ ret = *xs++;
+ if (!ret) {
+ errno = EDOM;
+ return NULL;
+ }
+
+ for (; (x = *xs); xs++) {
+ if (!ret) {
+ /* error handling */
+ libnormalform_free(x);
+
+ } else if (x->type == TYPE_FALSE || ret->type == TYPE_FALSE) {
+ set_to_true:
+ /* x ⊼ 0 = 0 ⊼ x = 1 */
+ libnormalform_free(ret);
+ libnormalform_free(x);
+ ret = libnormalform_true();
+
+ } else if (x->type == TYPE_TRUE) {
+ /* x ⊼ 1 = ¬x */
+ libnormalform_free(x);
+ ret = libnormalform_not(ret);
+
+ } else if (ret->type == TYPE_TRUE) {
+ set_to_not_x:
+ /* 1 ⊼ x = ¬x */
+ libnormalform_free(ret);
+ ret = libnormalform_not(x);
+
+ } else if (ret->equals(ret, x, &inv)) {
+ if (!inv) {
+ /* x ⊼ x = ¬x */
+ goto set_to_not_x;
+ } else {
+ /* x ⊼ ¬x = 1 */
+ goto set_to_true;
+ }
+
+ } else {
+ /* a ⊼ b = ¬(a ∧ b) = ¬a ∨ ¬b */
+ ret = libnormalform_or2(libnormalform_not(ret), libnormalform_not(x));
+ }
+ }
+
+ return ret;
+}
+
+
+#else
+
+
+#define NAND(...) LIBNORMALFORM_NAND(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!NAND(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NAND(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!NAND(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!NAND(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!NAND(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NAND(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = NAND(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = NAND(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ /* NAND () -> EDOM */
+ errno = 0;
+ ASSERT(!NAND() && errno == EDOM);
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* NAND (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* NAND (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(TRUE, F, F); /* NAND (FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T); /* NAND (FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, T, F); /* NAND (TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, T, T); /* NAND (TRUE, TRUE) -> FALSE */
+
+ ASSERT_EVAL2(TRUE, F, F); /* NAND (var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(TRUE, F, T); /* NAND (var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL2(TRUE, T, F); /* NAND (var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, T, T); /* NAND (var(TRUE), var(TRUE)) => FALSE */
+
+#ifndef USE_TWO
+
+ /* NAND (x) -> x */
+ ASSUME(a = NAND(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* NAND (x, FALSE) -> TRUE */
+ ASSUME(a = NAND(REF(v1), F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* NAND (FALSE, x) -> TRUE */
+ ASSUME(a = NAND(F, REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* NAND (x, TRUE) -> NOT x */
+ ASSUME(a = NAND(REF(v1), T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NAND (TRUE, x) -> NOT x */
+ ASSUME(a = NAND(T, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NAND (x, x) -> NOT x */
+ ASSUME(a = NAND(REF(v1), REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NAND (x, NOT x) -> TRUE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = NAND(REF(v1), a));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* NAND (x, y) -> NOT AND (x, y) */
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(a = NAND(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NAND (y, x) -> NAND (x, y) */
+ ASSUME(a = NAND(REF(v2), REF(v1)));
+ ASSUME(b = NAND(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* NAND (x, y, z) -> NOT AND (NOT AND (x, y), z) */
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSUME(b = libnormalform_and2(b, REF(v3)));
+ ASSUME(a = NAND(REF(v1), REF(v2), REF(v3)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nand2.c b/libnormalform_nand2.c
new file mode 100644
index 0000000..dbc94f2
--- /dev/null
+++ b/libnormalform_nand2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nand2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_nand_checked.c b/libnormalform_nand_checked.c
new file mode 100644
index 0000000..4b80cb9
--- /dev/null
+++ b/libnormalform_nand_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nand_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_nandl.c b/libnormalform_nandl.c
new file mode 100644
index 0000000..a785dd1
--- /dev/null
+++ b/libnormalform_nandl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nandl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_nandl_checked.c b/libnormalform_nandl_checked.c
new file mode 100644
index 0000000..eea01cc
--- /dev/null
+++ b/libnormalform_nandl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nandl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_nandl_macro_test.c b/libnormalform_nandl_macro_test.c
new file mode 100644
index 0000000..2c181a0
--- /dev/null
+++ b/libnormalform_nandl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_nand.c"
+#endif
diff --git a/libnormalform_nexists.c b/libnormalform_nexists.c
new file mode 100644
index 0000000..3996498
--- /dev/null
+++ b/libnormalform_nexists.c
@@ -0,0 +1,243 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nexists)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_exists(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_exists(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∄x.⊥ = ⊤ */
+ ASSUME(a = libnormalform_nexists(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* ∄x.φ irreducable */
+ ASSUME(a = libnormalform_nexists(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ /* ∄x.⊤ irreducable */
+ ASSUME(a = libnormalform_nexists(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_nexists(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∄x∈{a}.⊥ = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∄x∈{a}.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 2;
+
+ /* ∄x∈{a,b}.⊥ = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∄x∈{a,b}.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ /* ∄x∈∅.⊥ = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∄x∈∅.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_nexists(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ALL);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∄x∈{⊥, ⊥}.x = ⊤ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∄x∈{⊥, ⊤}.x = ⊥ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∄x∈{⊤, ⊥}.x = ⊥ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∄x∈{⊤, ⊤}.x = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∄x∈{⊤, ⊤, ⊤}.x = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ map[2].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_nexists(&dom1, REF(v1)));
+
+ /* ∄x∈X.P(x) = ¬∃x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from ∄x∈X.Q(x) */
+ ASSUME(b = libnormalform_nexists(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from ∄x∈Y.P(x)) */
+ ASSUME(b = libnormalform_nexists(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) = ∀x∈X.(P(x) → ⊥) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_false()));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∄x∈X.P(x) = ∃x∈X.(P(x) ∧ ⊤) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ANY);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∄x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from ∀x∈X.(P(x) → ⊤) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from ∃!x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∄x∈X.P(x) independent from ¬∃!x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∄x∈X.P(x) = ¬∃x∈X.P(x) */
+ ASSUME(b = libnormalform_exists(&dom1, REF(v1)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_not(libnormalform_exists(&dom1, REF(v1))));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nif.c b/libnormalform_nif.c
new file mode 100644
index 0000000..5b2ae24
--- /dev/null
+++ b/libnormalform_nif.c
@@ -0,0 +1,228 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_nif)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret;
+ int inv;
+
+ /* 0 ↚ x = x, but 1 ↚ x = 0 */
+ ret = libnormalform_false();
+
+ for (; (x = *xs); xs++) {
+ if (!ret) {
+ /* error handling */
+ libnormalform_free(x);
+
+ } else if (ret->type == TYPE_TRUE || x->type == TYPE_FALSE) {
+ set_to_false:
+ /* 1 ↚ x = 0 */
+ /* x ↚ 0 = 0 */
+ libnormalform_free(x);
+ libnormalform_free(ret);
+ ret = libnormalform_false();
+
+ } else if (ret->type == TYPE_FALSE) {
+ set_to_x:
+ /* 0 ↚ x = x */
+ libnormalform_free(ret);
+ ret = x;
+
+ } else if (ret->equals(ret, x, &inv)) {
+ if (!inv) {
+ /* x ↚ x = 0 */
+ goto set_to_false;
+
+ } else {
+ /* ¬x ↚ x = x (0 ↚ 1 = 1, 1 ↚ 0 = 0) */
+ goto set_to_x;
+ }
+
+ } else {
+ /* l ↚ r = ¬(l ← r) = ¬(r → l) = ¬¬(r ∧ ¬l) = r ∧ ¬l = ¬l ∧ r */
+ ret = libnormalform_and2(libnormalform_not(ret), x);
+ }
+ }
+
+ return ret;
+}
+
+
+#else
+
+
+#define NIF(...) LIBNORMALFORM_NIF(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!NIF(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NIF(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!NIF(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!NIF(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!NIF(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NIF(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = NIF(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = NIF(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(FALSE); /* NIF () -> FALSE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* NIF (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* NIF (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(FALSE, F, F); /* NIF (FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, T); /* NIF (FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, T, F); /* NIF (TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, T, T); /* NIF (TRUE, TRUE) -> FALSE */
+
+ ASSERT_EVAL2(FALSE, F, F); /* NIF (var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, F, T); /* NIF (var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL2(FALSE, T, F); /* NIF (var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(FALSE, T, T); /* NIF (var(TRUE), var(TRUE)) => FALSE */
+
+#ifndef USE_TWO
+
+ /* NIF (x) -> x */
+ ASSUME(a = NIF(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* NIF (x, TRUE) -> NOT x */
+ ASSUME(a = NIF(REF(v1), T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NIF (TRUE, x) -> FALSE */
+ ASSUME(a = NIF(T, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NIF (x, FALSE) -> FALSE */
+ ASSUME(a = NIF(REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NIF (FALSE, x) -> x */
+ ASSUME(a = NIF(F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NIF (x, x) -> FALSE */
+ ASSUME(a = NIF(REF(v1), REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NIF (NOT x, x) -> x */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = NIF(a, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* NIF (x, y) -> AND (NOT x, y) */
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(a = NIF(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* NIF (x, y, z) -> AND (NOT AND (NOT x, y), z) */
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_nand2(b, REF(v2)));
+ ASSUME(b = libnormalform_and2(b, REF(v3)));
+ ASSUME(a = NIF(REF(v1), REF(v2), REF(v3)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NIF (x, y) -> independence from NIF (y, x) */
+ ASSUME(a = NIF(REF(v1), REF(v2)));
+ ASSUME(b = NIF(REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nif2.c b/libnormalform_nif2.c
new file mode 100644
index 0000000..7a10793
--- /dev/null
+++ b/libnormalform_nif2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nif2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_nif_checked.c b/libnormalform_nif_checked.c
new file mode 100644
index 0000000..b197188
--- /dev/null
+++ b/libnormalform_nif_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nif_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_nifl.c b/libnormalform_nifl.c
new file mode 100644
index 0000000..d2c401f
--- /dev/null
+++ b/libnormalform_nifl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nifl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_nifl_checked.c b/libnormalform_nifl_checked.c
new file mode 100644
index 0000000..dac7871
--- /dev/null
+++ b/libnormalform_nifl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nifl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_nifl_macro_test.c b/libnormalform_nifl_macro_test.c
new file mode 100644
index 0000000..641d40e
--- /dev/null
+++ b/libnormalform_nifl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_nif.c"
+#endif
diff --git a/libnormalform_nimply.c b/libnormalform_nimply.c
new file mode 100644
index 0000000..5469e62
--- /dev/null
+++ b/libnormalform_nimply.c
@@ -0,0 +1,172 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_nimply)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *t;
+ size_t n = 0, i;
+
+ while (xs[n])
+ n += 1;
+
+ if (!n) {
+ /* 0 ↛ x = 0, but 1 ↛ x = ¬x, so at least one argument is required */
+ errno = EDOM;
+ return NULL;
+ }
+
+ /* a ↛ b ↛ c = a ↛ (b ↛ c) = (b ↛ c) ↚ a = (c ↚ b) ↚ a = c ↚ b ↚ a */
+ for (i = 0; i < n--; i++) {
+ t = xs[i];
+ xs[i] = xs[n];
+ xs[n] = t;
+ }
+ return libnormalform_nif(xs);
+}
+
+
+#else
+
+
+#define NIMPLY(...) LIBNORMALFORM_NIMPLY(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!NIMPLY(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NIMPLY(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!NIMPLY(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!NIMPLY(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!NIMPLY(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NIMPLY(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = NIMPLY(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = NIMPLY(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ /* NIMPLY () -> EDOM */
+ errno = 0;
+ ASSERT(!NIMPLY() && errno == EDOM);
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* NIMPLY (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* NIMPLY (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(FALSE, F, F); /* NIMPLY (FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, F, T); /* NIMPLY (FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(TRUE, T, F); /* NIMPLY (TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, T, T); /* NIMPLY (TRUE, TRUE) -> FALSE */
+
+ ASSERT_EVAL2(FALSE, F, F); /* NIMPLY (var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(FALSE, F, T); /* NIMPLY (var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL2(TRUE, T, F); /* NIMPLY (var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, T, T); /* NIMPLY (var(TRUE), var(TRUE)) => FALSE */
+
+#ifndef USE_TWO
+
+ /* NIMPLY (x) -> x */
+ ASSUME(a = NIMPLY(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* NIMPLY (x, y) -> NIF (y, x) */
+ ASSUME(a = NIMPLY(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_nif2(REF(v2), REF(v1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* NIMPLY (x, y, z) -> NIF (z, y, x) */
+ ASSUME(a = NIMPLY(REF(v1), REF(v2), REF(v3)));
+ ASSUME(b = libnormalform_nifl(REF(v3), REF(v2), REF(v1), NULL));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NIMPLY (x, y) -> independence from NIMPLY (y, x) */
+ ASSUME(a = NIMPLY(REF(v1), REF(v2)));
+ ASSUME(b = NIMPLY(REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nimply2.c b/libnormalform_nimply2.c
new file mode 100644
index 0000000..eb0a8df
--- /dev/null
+++ b/libnormalform_nimply2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nimply2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_nimply_checked.c b/libnormalform_nimply_checked.c
new file mode 100644
index 0000000..74736e2
--- /dev/null
+++ b/libnormalform_nimply_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nimply_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_nimplyl.c b/libnormalform_nimplyl.c
new file mode 100644
index 0000000..ba0786f
--- /dev/null
+++ b/libnormalform_nimplyl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nimplyl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_nimplyl_checked.c b/libnormalform_nimplyl_checked.c
new file mode 100644
index 0000000..fae5ce0
--- /dev/null
+++ b/libnormalform_nimplyl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nimplyl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_nimplyl_macro_test.c b/libnormalform_nimplyl_macro_test.c
new file mode 100644
index 0000000..a343038
--- /dev/null
+++ b/libnormalform_nimplyl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_nimply.c"
+#endif
diff --git a/libnormalform_nonempty.c b/libnormalform_nonempty.c
new file mode 100644
index 0000000..a120bec
--- /dev/null
+++ b/libnormalform_nonempty.c
@@ -0,0 +1,69 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nonempty)(struct libnormalform_map *);
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ ASSUME(a = libnormalform_nonempty(&dom1));
+ ASSERT(a->type == TYPE_ANY);
+
+ ASSUME(b = libnormalform_nonempty(&dom2));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_nonempty(&dom1));
+ ASSERT(b != a);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_empty(&dom1));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_singleton(&dom1));
+ ASSERT_NOTEQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_not(libnormalform_nonempty(&dom1)));
+ ASSERT(b->type == TYPE_ALL);
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ dom1.nmappings = 0;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 1;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 3;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nor.c b/libnormalform_nor.c
new file mode 100644
index 0000000..dbee41b
--- /dev/null
+++ b/libnormalform_nor.c
@@ -0,0 +1,235 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_nor)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret;
+ int inv;
+
+ /* 0 ⊽ x = ¬x, but 1 ⊽ x = 0, so at least one argument is required */
+ ret = *xs++;
+ if (!ret) {
+ errno = EDOM;
+ return NULL;
+ }
+
+ for (; (x = *xs); xs++) {
+ if (!ret) {
+ /* error handling */
+ libnormalform_free(x);
+
+ } else if (x->type == TYPE_TRUE || ret->type == TYPE_TRUE) {
+ set_to_false:
+ /* x ⊽ 1 = 1 ⊽ x = 0 */
+ libnormalform_free(ret);
+ libnormalform_free(x);
+ ret = libnormalform_false();
+
+ } else if (x->type == TYPE_FALSE) {
+ /* x ⊽ 0 = ¬x */
+ libnormalform_free(x);
+ ret = libnormalform_not(ret);
+
+ } else if (ret->type == TYPE_FALSE) {
+ set_to_not_x:
+ /* 0 ⊽ x = ¬x */
+ libnormalform_free(ret);
+ ret = libnormalform_not(x);
+
+ } else if (ret->equals(ret, x, &inv)) {
+ if (!inv) {
+ /* x ⊽ x = ¬x */
+ goto set_to_not_x;
+ } else {
+ /* x ⊽ ¬x = 0 */
+ goto set_to_false;
+ }
+
+ } else {
+ /* a ⊽ b = ¬(a ∨ b) = ¬a ∧ ¬b */
+ ret = libnormalform_and2(libnormalform_not(ret), libnormalform_not(x));
+ }
+ }
+
+ return ret;
+}
+
+
+#else
+
+
+#define NOR(...) LIBNORMALFORM_NOR(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!NOR(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NOR(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!NOR(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!NOR(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!NOR(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!NOR(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = NOR(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = NOR(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ /* NOR () -> EDOM */
+ errno = 0;
+ ASSERT(!NOR() && errno == EDOM);
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* NOR (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* NOR (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(TRUE, F, F); /* NOR (FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, F, T); /* NOR (FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, F); /* NOR (TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(FALSE, T, T); /* NOR (TRUE, TRUE) -> FALSE */
+
+ ASSERT_EVAL2(TRUE, F, F); /* NOR (var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, F, T); /* NOR (var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL2(FALSE, T, F); /* NOR (var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(FALSE, T, T); /* NOR (var(TRUE), var(TRUE)) => FALSE */
+
+#ifndef USE_TWO
+
+ /* NOR (x) -> x */
+ ASSUME(a = NOR(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* NOR (x, TRUE) -> FALSE */
+ ASSUME(a = NOR(REF(v1), T));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NOR (TRUE, x) -> FALSE */
+ ASSUME(a = NOR(T, REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NOR (x, FALSE) -> NOT x */
+ ASSUME(a = NOR(REF(v1), F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NOR (FALSE, x) -> NOT x */
+ ASSUME(a = NOR(F, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NOR (x, x) -> NOT x */
+ ASSUME(a = NOR(REF(v1), REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* NOR (x, NOT x) -> FALSE x */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = NOR(REF(v1), a));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* NOR (x, y) -> NOT OR (x, y) */
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(a = NOR(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NOR (y, x) -> NOR (x, y) */
+ ASSUME(a = NOR(REF(v2), REF(v1)));
+ ASSUME(b = NOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* NOR (x, y, z) -> NOT OR (NOT OR (x, y), z) */
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSUME(b = libnormalform_or2(b, REF(v3)));
+ ASSUME(a = NOR(REF(v1), REF(v2), REF(v3)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_nor2.c b/libnormalform_nor2.c
new file mode 100644
index 0000000..c9ee0aa
--- /dev/null
+++ b/libnormalform_nor2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nor2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_nor_checked.c b/libnormalform_nor_checked.c
new file mode 100644
index 0000000..8ba7a9f
--- /dev/null
+++ b/libnormalform_nor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_nor_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_norl.c b/libnormalform_norl.c
new file mode 100644
index 0000000..b6fa4df
--- /dev/null
+++ b/libnormalform_norl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_norl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_norl_checked.c b/libnormalform_norl_checked.c
new file mode 100644
index 0000000..fb20f16
--- /dev/null
+++ b/libnormalform_norl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_norl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_norl_macro_test.c b/libnormalform_norl_macro_test.c
new file mode 100644
index 0000000..07b33d1
--- /dev/null
+++ b/libnormalform_norl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_nor.c"
+#endif
diff --git a/libnormalform_not.c b/libnormalform_not.c
new file mode 100644
index 0000000..fcc9604
--- /dev/null
+++ b/libnormalform_not.c
@@ -0,0 +1,115 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_not)(LIBNORMALFORM_SENTENCE *x)
+{
+ LIBNORMALFORM_SENTENCE *ret;
+ if (!x)
+ return NULL;
+ ret = x->inverse(x);
+ libnormalform_free(x);
+ return ret;
+}
+
+
+#else
+
+
+static int
+tautology(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 1;
+}
+
+
+static int
+contradiction(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 0;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *f1, *f2;
+ int r1, r2;
+
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ fun1.evaluate = &tautology;
+ fun2.evaluate = &contradiction;
+ domain.nmappings = 0;
+
+ errno = 0;
+ ASSERT(!libnormalform_not(NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_not(NULL) && errno == 1);
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+#define CHECK(X)\
+ do {\
+ ASSUME(a = libnormalform_not(REF(X)));\
+ ASSERT((X)->refcount == 1);\
+ ASSERT(a->refcount == 1);\
+ ASSERT_INVEQUAL(a, (X));\
+ ASSERT((r1 = a->evaluate(a, NULL)) >= 0);\
+ ASSERT((r2 = (X)->evaluate((X), NULL)) >= 0);\
+ ASSERT(r1 == 1 - r2);\
+ libnormalform_free(a);\
+ } while (0)
+
+ CHECK(v1);
+ CHECK(f1);
+
+#undef CHECK
+
+#define CHECK(X)\
+ do {\
+ ASSUME(b = (X));\
+ ASSUME(a = libnormalform_not(REF(b)));\
+ ASSERT(b->refcount == 1);\
+ ASSERT(a->refcount == 1);\
+ ASSERT_INVEQUAL(a, b);\
+ ASSERT((r1 = a->evaluate(a, NULL)) >= 0);\
+ ASSERT((r2 = b->evaluate(b, NULL)) >= 0);\
+ ASSERT(r1 == 1 - r2);\
+ libnormalform_free(a);\
+ libnormalform_free(b);\
+ } while (0)
+
+ CHECK(libnormalform_and2(REF(v1), REF(v2)));
+ CHECK(libnormalform_or2(REF(v1), REF(v2)));
+ CHECK(libnormalform_xor2(REF(v1), REF(v2)));
+ CHECK(libnormalform_all(&domain, REF(f1), REF(f2)));
+ CHECK(libnormalform_any(&domain, REF(f1), REF(f2)));
+ CHECK(libnormalform_one(&domain, REF(f1), REF(f2)));
+
+#undef CHECK
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_one.c b/libnormalform_one.c
new file mode 100644
index 0000000..b5c17f1
--- /dev/null
+++ b/libnormalform_one.c
@@ -0,0 +1,449 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (FOR ONE implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+one_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬∃x.φ ⇒ ∀x.¬φ */
+ LIBNORMALFORM_SENTENCE *ret;
+ ret = libnormalform_one(this->data.qualifier.domain,
+ libnormalform_ref(this->data.qualifier.antecedent),
+ libnormalform_ref(this->data.qualifier.predicate));
+ if (ret) {
+ if (this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ ret->type = TYPE_ONE ^ TYPE_NOT_ONE ^ this->type;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (FOR ONE implementation)
+ */
+static int
+one_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ int r;
+
+ if (this->hash != other->hash)
+ return 0;
+
+ if (other->type != TYPE_ONE && other->type != TYPE_NOT_ONE)
+ return 0;
+
+ if (this->data.qualifier.domain != other->data.qualifier.domain)
+ return 0;
+
+ r = this->data.qualifier.antecedent->equals(this->data.qualifier.antecedent, other->data.qualifier.antecedent, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out)
+ return 0;
+
+ r = this->data.qualifier.predicate->equals(this->data.qualifier.predicate, other->data.qualifier.predicate, inv_out);
+ if (r <= 0)
+ return r;
+ if (*inv_out)
+ return 0;
+
+ *inv_out = this->type != other->type;
+ return r;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (FOR ONE implementation)
+ */
+static int
+one_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ size_t i, n = this->data.qualifier.domain->nmappings;
+ void *k, *v;
+ int r, ret = 0;
+
+ (void) input;
+
+ for (i = 0; i < n; i++) {
+ k = this->data.qualifier.domain->mappings[i].key;
+ v = this->data.qualifier.domain->mappings[i].value;
+ r = this->data.qualifier.antecedent->evaluate(this->data.qualifier.antecedent, k);
+ if (r <= 0) {
+ if (!r)
+ continue;
+ return r;
+ }
+ r = this->data.qualifier.predicate->evaluate(this->data.qualifier.predicate, v);
+ if (r) {
+ if (r < 0)
+ return r;
+ if (++ret == 2)
+ return this->type == TYPE_NOT_ONE;
+ }
+ }
+
+ return ret ^ (this->type == TYPE_NOT_ONE);
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_one)(struct libnormalform_map *d, LIBNORMALFORM_SENTENCE *k, LIBNORMALFORM_SENTENCE *v)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_ONE,
+ .inverse = &one_inverse,
+ .equals = &one_equals,
+ .evaluate = &one_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret;
+
+ if (!k || !v) {
+ libnormalform_free(k);
+ libnormalform_free(v);
+ return NULL;
+ }
+
+ if (k->type == TYPE_FALSE) {
+ libnormalform_free(v);
+ return k;
+ }
+ if (v->type == TYPE_FALSE) {
+ libnormalform_free(k);
+ return v;
+ }
+
+ ret = malloc(sizeof(*ret));
+ if (ret) {
+ *ret = prototype;
+ ret->hash = ONE_HASH(d, k, v);
+ ret->data.qualifier.domain = d;
+ ret->data.qualifier.antecedent = k;
+ ret->data.qualifier.predicate = v;
+ }
+ return ret;
+}
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1, *v2;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+
+ errno = 0;
+ ASSERT(!libnormalform_one(&dom1, NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_one(&dom1, NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_one(&dom1, REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_one(&dom1, REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!libnormalform_one(&dom1, NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_one(&dom1, NULL, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃!x.(⊥ ∧ φ(x)) = ⊥ */
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_false(), REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃!x.(⊤ ∧ φ(x)) irreducable */
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ /* ∃!x.(φ(x) ∧ ⊤) irreducable */
+ ASSUME(a = libnormalform_one(&dom1, REF(v1), libnormalform_true()));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ /* ∃!x.(φ(x) ∧ ⊥) = ⊥ */
+ ASSUME(a = libnormalform_one(&dom1, REF(v1), libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_one(&dom1, REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_ONE);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃!x∈{a}.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a}.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a}.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a}.(⊤ ∧ ⊤) = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃!x∈{a,b}.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a,b}.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a,b}.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a,b}.(⊤ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ /* ∃!x∈∅.(⊥ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈∅.(⊥ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈∅.(⊤ ∧ ⊥) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈∅.(⊤ ∧ ⊤) = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ var2.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_one(&dom1, libnormalform_true(), a));
+ ASSERT(a->type == TYPE_ONE);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∃!x∈{⊥, ⊥}.(⊤ ∧ x) = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊥, ⊤}.(⊤ ∧ x) = ⊤ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊥}.(⊤ ∧ x) = ⊤ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊤}.(⊤ ∧ x) = ⊥ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(a = libnormalform_one(&dom1, a, libnormalform_true()));
+ ASSERT(a->type == TYPE_ONE);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∃!x∈{⊥, ⊥}.(x ∧ ⊤) = ⊥ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊥, ⊤}.(x ∧ ⊤) = ⊤ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊥}.(x ∧ ⊤) = ⊤ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊤}.(x ∧ ⊤) = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊤, ⊤, ⊤}.(x ∧ ⊤) = ⊥ */
+ dom1.nmappings = 3;
+ map[0].key = &t;
+ map[1].key = &t;
+ map[2].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_one(&dom1, REF(v1), REF(v2)));
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) = ∃!x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from ∃!x∈X.(Q(x) ∧ P(x)) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v2), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from ∃!x∈Y.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_one(&dom2, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃!x∈X.(P(x) ∧ Q(x)) = ¬∃!x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_not(libnormalform_one(&dom1, REF(v1), REF(v2))));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_NOT_ONE);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) = ∀x∈X.(P(x) → Q(x)) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) = ∃x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) = ¬∃x∈X.(P(x) ∧ Q(x)) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from AND (P, Q) */
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from OR (P, Q) */
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.(P(x) ∧ Q(x)) independent from XOR (P, Q) */
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* P = ¬Q ⊭ ∃!{x,y}∈X.(P(x) ∧ Q(y)) = ∃!{x,y}∈X.(¬Q(x) ∧ Q(y)) = ∃!{x,y}∈X.⊥ = ⊥ ∵ P = ¬Q ⊭ P(x) = ¬Q(y) */
+ /* P = ¬Q ⊭ ∃!{x,y}∈X.(P(x) ∧ Q(y)) = ∃!{x,y}∈X.(P(x) ∧ ¬P(y)) = ∃!{x,y}∈X.⊥ = ⊥ ∵ P = ¬Q ⊭ P(x) = ¬Q(y) */
+ ASSUME(b = libnormalform_one(&dom1, libnormalform_false(), libnormalform_false()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_or.c b/libnormalform_or.c
new file mode 100644
index 0000000..16a99a5
--- /dev/null
+++ b/libnormalform_or.c
@@ -0,0 +1,562 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Check if a sentence equivalent to a specific sentence
+ * or its inverse is in an array of sentences
+ *
+ * @param x The sentence to look for
+ * @param among The array of sentences to look in
+ * @param n The number of sentences in `among`
+ * @param inv_out Output parameter for equivalency of `x` and the
+ * sentence found in `among`; set to 0 if the two
+ * are equivalent or 1 if `x` is equivalent to the
+ * inverse of the sentence found in `among`
+ * @return 1 if a sentence equivalent to `x` or its inverse
+ * was found, 0 otherwise
+ */
+static int
+is_in(LIBNORMALFORM_SENTENCE *x, LIBNORMALFORM_SENTENCE **among, size_t n, int *inv_out)
+{
+ while (n--)
+ if (x->equals(x, among[n], inv_out))
+ return 1;
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_or)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret, **saved = xs;
+ size_t n = 0;
+ int inv;
+
+ for (; (x = *xs); xs++) {
+ if (x->type == TYPE_TRUE) {
+ /* ⋁x ∨ 0 = 0 */
+ ret = *xs++;
+ goto return_asis;
+
+ } else if (x->type == TYPE_FALSE) {
+ redundant:
+ /* ⋁X ∨ 0 = ⋁X */
+ libnormalform_free(x);
+
+ } else if (is_in(x, saved, n, &inv)) {
+ if (!inv) {
+ /* x ∨ x = x */
+ goto redundant;
+
+ } else {
+ /* x ∨ ¬x = 1 */
+ libnormalform_free(*xs++);
+ ret = libnormalform_true();
+ goto return_asis;
+ }
+
+ } else {
+ saved[n++] = x;
+ }
+ }
+
+ if (!n) {
+ /* ⋁∅ = ¬¬⋁∅ = ¬⋀∅ = ¬∀x∈∅.x = {vacuous truth} = ¬1 = 0 */
+ return libnormalform_false();
+ }
+
+ saved[n] = NULL;
+ /* ⋁{x} = x */
+ ret = *saved++;
+ /* ⋁(X ∪ x) = ⋁X ∨ x */
+ for (; *saved; saved++)
+ ret = libnormalform_or2(ret, *saved);
+
+ return ret;
+
+return_asis:
+ while (*xs)
+ libnormalform_free(*xs++);
+ while (n)
+ libnormalform_free(saved[--n]);
+ return ret;
+}
+
+
+#else
+
+
+#define OR(...) LIBNORMALFORM_OR(__VA_ARGS__)
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3, var4;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3, *v4, *f1, *f2;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(v4 = libnormalform_variable(&var4));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!OR(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!OR(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!OR(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!OR(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!OR(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!OR(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = OR(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = OR(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL3(VALUE, V1, V2, V3)\
+ do {\
+ ASSUME(a = OR(REF(v1), REF(v2), REF(v3)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ var3.value = V3##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(FALSE); /* OR () -> FALSE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* OR (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* OR (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(FALSE, F, F); /* OR (FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, T); /* OR (FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, T, F); /* OR (TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, T, T); /* OR (TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL2(FALSE, F, F); /* OR (var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, F, T); /* OR (var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL2(TRUE, T, F); /* OR (var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(TRUE, T, T); /* OR (var(TRUE), var(TRUE)) => TRUE */
+
+#ifndef USE_TWO
+
+ ASSERT_CONST(FALSE, F, F, F); /* OR (FALSE, FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, F, T); /* OR (FALSE, FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T, F); /* OR (FALSE, TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T, T); /* OR (FALSE, TRUE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, T, F, F); /* OR (TRUE, FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, T, F, T); /* OR (TRUE, FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, T, T, F); /* OR (TRUE, TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(TRUE, T, T, T); /* OR (TRUE, TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL3(FALSE, F, F, F); /* OR (var(FALSE), var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, F, F, T); /* OR (var(FALSE), var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL3(TRUE, F, T, F); /* OR (var(FALSE), var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(TRUE, F, T, T); /* OR (var(FALSE), var(TRUE), var(TRUE)) => TRUE */
+ ASSERT_EVAL3(TRUE, T, F, F); /* OR (var(TRUE), var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(TRUE, T, F, T); /* OR (var(TRUE), var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL3(TRUE, T, T, F); /* OR (var(TRUE), var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(TRUE, T, T, T); /* OR (var(TRUE), var(TRUE), var(TRUE)) => TRUE */
+
+ /* OR (x) -> x */
+ ASSUME(a = OR(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* OR (x, TRUE) -> TRUE */
+ ASSUME(a = OR(REF(v1), T));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (TRUE, x) -> TRUE */
+ ASSUME(a = OR(T, REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (x, FALSE) -> x */
+ ASSUME(a = OR(REF(v1), F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* OR (FALSE, x) -> x */
+ ASSUME(a = OR(F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* OR (x, FALSE, FALSE) -> x */
+ ASSUME(a = OR(REF(v1), F, F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* OR (x, FALSE, TRUE) -> TRUE */
+ ASSUME(a = OR(REF(v1), F, T));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (x, TRUE, FALSE) -> TRUE */
+ ASSUME(a = OR(REF(v1), T, F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (FALSE, x, FALSE) -> x */
+ ASSUME(a = OR(F, REF(v1), F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* OR (FALSE, x, TRUE) -> TRUE */
+ ASSUME(a = OR(F, REF(v1), T));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (TRUE, x, FALSE) -> TRUE */
+ ASSUME(a = OR(T, REF(v1), F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (FALSE, FALSE, x) -> x */
+ ASSUME(a = OR(F, F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* OR (FALSE, TRUE, x) -> TRUE */
+ ASSUME(a = OR(F, T, REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (TRUE, FALSE, x) -> TRUE */
+ ASSUME(a = OR(T, F, REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+#endif
+
+ /* OR (x, x) -> x */
+ ASSUME(a = OR(REF(v1), REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* OR (x, NOT x) -> TRUE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = OR(REF(v1), a));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* OR (x, y) -> OR (x, y) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = OR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (y, x) -> OR (x, y) */
+ ASSUME(a = OR(REF(v2), REF(v1)));
+ ASSUME(b = OR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NOT OR (x, y) -> AND (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(a = libnormalform_not(a));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from OR (x, z) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v3)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from OR (z, x) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2(REF(v3), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from OR (z, w) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2(REF(v3), REF(v4)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from AND (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from AND (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from AND (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from XOR (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from XOR (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from XOR (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from XOR (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_xor2(a, b));
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from TRUE */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from FALSE */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from variable1 */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* OR (x, y) -> independence from NOT variable1 */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from function1 */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, f1);
+ libnormalform_free(a);
+
+ /* OR (x, y) -> independence from NOT function2 */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from T(function1) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_transformation(&trans, REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from ALL (domain1, function1, function2) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_all(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from ANY (domain1, function1, function2) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_any(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from ONE (domain1, function1, function2) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (x, y) -> independence from NOT ONE (domain1, function1, function2) */
+ ASSUME(a = OR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (AND (x, NOT y), AND (NOT x, y)) -> XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(a = OR(a, b));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (AND (NOT x, y), AND (x, NOT y)) -> XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(a = OR(b, a));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (AND (x, y), AND (NOT x, NOT y)) -> NOT XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(a = OR(a, b));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* OR (AND (NOT x, NOT y), AND (x, y)) -> NOT XOR (x, y) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(a = OR(b, a));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(a->type == TYPE_XOR);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+#undef ASSERT_EVAL3
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(v4);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_or2.c b/libnormalform_or2.c
new file mode 100644
index 0000000..51ed628
--- /dev/null
+++ b/libnormalform_or2.c
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_or2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ LIBNORMALFORM_SENTENCE *ll, *lr;
+ LIBNORMALFORM_SENTENCE *rl, *rr;
+ int inv;
+
+ if (!l || !r) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+
+ if (l->equals(l, r, &inv)) {
+ if (!inv) {
+ /* x ∨ x = x */
+ goto return_either;
+
+ } else {
+ /* x ∨ ¬x = 1 */
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return libnormalform_true();
+ }
+
+ } else if (l->type == TYPE_TRUE || r->type == TYPE_FALSE) {
+ /* 1 ∨ x = 1 */
+ /* x ∨ 0 = x */
+ return_either:
+ libnormalform_free(r);
+ return l;
+
+ } else if (r->type == TYPE_TRUE || l->type == TYPE_FALSE) {
+ /* x ∨ 1 = 1 */
+ /* 0 ∨ x = x */
+ libnormalform_free(l);
+ return r;
+
+ } else if (l->hash == r->hash && l->type == TYPE_AND && r->type == TYPE_AND) {
+ /* (x ∧ ¬y) ∨ (¬x ∧ y) = x ⊕ y */
+ ll = l->data.binary.l;
+ lr = l->data.binary.r;
+ rl = r->data.binary.l;
+ rr = r->data.binary.r;
+ if (ll->hash != rl->hash || lr->hash != rr->hash)
+ goto fallback;
+ if (!ll->equals(ll, rl, &inv)) {
+ if (ll->hash == lr->hash) {
+ rl = r->data.binary.r;
+ rr = r->data.binary.l;
+ if (!ll->equals(ll, rl, &inv))
+ goto fallback;
+ } else {
+ goto fallback;
+ }
+ }
+ if (!inv || !lr->equals(lr, rr, &inv) || !inv)
+ goto fallback;
+ l->data.binary.l = NULL;
+ r->data.binary.r = NULL;
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return libnormalform_xor2__(ll, rr);
+
+ } else {
+ fallback:
+ return libnormalform_or2__(l, r);
+ }
+}
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_or2__.c b/libnormalform_or2__.c
new file mode 100644
index 0000000..7d093a0
--- /dev/null
+++ b/libnormalform_or2__.c
@@ -0,0 +1,119 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (OR implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+or_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬(a + b) = ¬a¬b */
+ LIBNORMALFORM_SENTENCE *l, *r, *ret;
+ l = this->data.binary.l->inverse(this->data.binary.l);
+ if (!l)
+ return NULL;
+ r = this->data.binary.r->inverse(this->data.binary.r);
+ if (!r) {
+ libnormalform_free(l);
+ return NULL;
+ }
+ ret = libnormalform_and2__(l, r);
+ if (ret && this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (OR implementation)
+ */
+static int
+or_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ LIBNORMALFORM_SENTENCE *tl, *tr;
+ LIBNORMALFORM_SENTENCE *ol, *or;
+ int inv;
+
+ if (other->type != TYPE_OR && other->type != TYPE_AND)
+ return other->type == TYPE_XOR && other->equals(other, this, inv_out);
+
+ tl = this->data.binary.l;
+ tr = this->data.binary.r;
+ ol = other->data.binary.l;
+ or = other->data.binary.r;
+
+ if (tl->hash != ol->hash || tr->hash != or->hash)
+ return 0;
+
+ if (!tl->equals(tl, ol, inv_out)) {
+ if (tl->hash == tr->hash) {
+ ol = other->data.binary.r;
+ or = other->data.binary.l;
+ if (!tl->equals(tl, ol, inv_out))
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+ if (!tr->equals(tr, or, &inv))
+ return 0;
+ return inv == *inv_out && inv == (other->type == TYPE_AND);
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (OR implementation)
+ */
+static int
+or_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ int r = this->data.binary.l->evaluate(this->data.binary.l, input);
+ return r ? r : this->data.binary.r->evaluate(this->data.binary.r, input);
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_or2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_OR,
+ .inverse = &or_inverse,
+ .equals = &or_equals,
+ .evaluate = &or_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (!ret) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+ *ret = prototype;
+ ret->hash = AND_OR_HASH(l, r);
+ if (l->hash <= r->hash) {
+ ret->data.binary.l = l;
+ ret->data.binary.r = r;
+ } else {
+ ret->data.binary.l = r;
+ ret->data.binary.r = l;
+ }
+ return ret;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ return 0; /* indirectly tested */
+}
+
+
+#endif
diff --git a/libnormalform_or_checked.c b/libnormalform_or_checked.c
new file mode 100644
index 0000000..6223727
--- /dev/null
+++ b/libnormalform_or_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_or_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_orl.c b/libnormalform_orl.c
new file mode 100644
index 0000000..540fd70
--- /dev/null
+++ b/libnormalform_orl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_orl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_orl_checked.c b/libnormalform_orl_checked.c
new file mode 100644
index 0000000..227064a
--- /dev/null
+++ b/libnormalform_orl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_orl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_orl_macro_test.c b/libnormalform_orl_macro_test.c
new file mode 100644
index 0000000..8cc0d20
--- /dev/null
+++ b/libnormalform_orl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_or.c"
+#endif
diff --git a/libnormalform_ref.c b/libnormalform_ref.c
new file mode 100644
index 0000000..6d703eb
--- /dev/null
+++ b/libnormalform_ref.c
@@ -0,0 +1,57 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_ref)(LIBNORMALFORM_SENTENCE *this)
+{
+ if (this) {
+ if (this->refcount == SIZE_MAX) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ this->refcount += 1;
+ }
+ return this;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ LIBNORMALFORM_SENTENCE *a;
+
+ errno = 0;
+ ASSERT(!libnormalform_ref(NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_ref(NULL) && errno == 1);
+
+ ASSUME(a = libnormalform_true());
+
+ a->refcount = SIZE_MAX;
+ errno = 0;
+ ASSERT(!libnormalform_ref(a) && errno == ENOMEM);
+ a->refcount -= 1;
+ ASSERT(libnormalform_ref(a) == a);
+ ASSERT(a->refcount == SIZE_MAX);
+
+ a->refcount = 1;
+ ASSERT(libnormalform_ref(a) == a);
+ ASSERT(a->refcount == 2);
+ ASSERT(libnormalform_ref(a) == a);
+ ASSERT(a->refcount == 3);
+
+ a->refcount = 1;
+ libnormalform_free(a);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_reset_indices_and_counts__.c b/libnormalform_reset_indices_and_counts__.c
new file mode 100644
index 0000000..f6b1790
--- /dev/null
+++ b/libnormalform_reset_indices_and_counts__.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Undo `libnormalform_set_indices_and_counts__`
+ *
+ * This is needed because `libnormalform_set_indices_and_counts__`
+ * requires some otherwise unused memory to be properly initialised
+ *
+ * @param this The sentence that shall be recursively restored
+ */
+void
+(libnormalform_reset_indices_and_counts__)(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *head = NULL;
+
+ this->travel_count -= 1;
+ do {
+ this->travel_index = 0;
+ if (IS_BRANCH(this)) {
+ if (!--RIGHT(this)->travel_count)
+ PUSH(&head, RIGHT(this));
+ if (!--LEFT(this)->travel_count)
+ PUSH(&head, LEFT(this));
+ }
+ } while (POP(&head, &this));
+
+#undef UNMARK
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ return 0; /* indirectly tested */
+}
+
+
+#endif
diff --git a/libnormalform_set_indices_and_counts__.c b/libnormalform_set_indices_and_counts__.c
new file mode 100644
index 0000000..2dbc9aa
--- /dev/null
+++ b/libnormalform_set_indices_and_counts__.c
@@ -0,0 +1,56 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Annotate every sentence that appear multiple times
+ * with a unique index (counted from 0 up)
+ *
+ * @param this The sentence that shall be inspected
+ * recursively for annotation
+ * @return The number of annotated sentences
+ */
+size_t
+(libnormalform_set_indices_and_counts__)(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *head = NULL, *a, *b;
+ size_t count = 0;
+
+ this->travel_count = 1;
+ do {
+ if (IS_BRANCH(this)) {
+ a = LEFT(this);
+ b = RIGHT(this);
+
+ a->travel_count += 1;
+ if (a->travel_count == 2)
+ a->travel_index = count++;
+
+ b->travel_count += 1;
+ if (b->travel_count == 2)
+ b->travel_index = count++;
+
+ if (b->travel_count == 1)
+ PUSH(&head, b);
+
+ if (a->travel_count == 1)
+ PUSH(&head, a);
+ }
+ } while (POP(&head, &this));
+
+ return count;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ return 0; /* indirectly tested */
+}
+
+
+#endif
diff --git a/libnormalform_singleton.c b/libnormalform_singleton.c
new file mode 100644
index 0000000..b850b98
--- /dev/null
+++ b/libnormalform_singleton.c
@@ -0,0 +1,71 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_singleton)(struct libnormalform_map *);
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ LIBNORMALFORM_SENTENCE *a, *b;
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ ASSUME(a = libnormalform_singleton(&dom1));
+ ASSERT(a->type == TYPE_ONE);
+
+ ASSUME(b = libnormalform_singleton(&dom2));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_singleton(&dom1));
+ ASSERT(b != a);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_empty(&dom1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_nonempty(&dom1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_not(libnormalform_singleton(&dom1)));
+ ASSERT(b->type == TYPE_NOT_ONE);
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_not(libnormalform_singleton(&dom2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ dom1.nmappings = 0;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 1;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 3;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_to_string.c b/libnormalform_to_string.c
new file mode 100644
index 0000000..74aa839
--- /dev/null
+++ b/libnormalform_to_string.c
@@ -0,0 +1,519 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * String under construct
+ */
+struct string_construction {
+ const char **strings; /**< Strings to right-concatenate one-by-one */
+ char **dyn_strings; /**< List of dynamically allocated strings that appear in `.strings` */
+ size_t n; /**< The number of elements in `.strings` */
+ size_t size; /**< The number of elements allocated to `.strings` */
+ size_t dyn_n; /**< The number of elements in `.dyn_strings` */
+ size_t dyn_size; /**< The number of elements allocated to `.dyn_strings` */
+ size_t length; /**< The total length of the string */
+};
+
+
+/**
+ * Append a string (with its length already known)
+ *
+ * @param s The string to append `str` to
+ * @param str The string to append to `s`
+ * @param len The length of `str`
+ * @return 0 on sucess, -1 on failure
+ */
+USE_RESULT NONNULL_INPUT
+static int
+add_string_n(struct string_construction *s, const char *str, size_t len)
+{
+ if (s->n == s->size) {
+ void *new = realloc(s->strings, (s->size += 128) * sizeof(*s->strings));
+ if (!new)
+ return -1;
+ s->strings = new;
+ }
+ s->strings[s->n++] = str;
+ s->length += len;
+ return 0;
+}
+
+
+/**
+ * Append a string
+ *
+ * @param s The string to append `str` to
+ * @param str The string to append to `s`
+ * @return 0 on sucess, -1 on failure
+ */
+USE_RESULT NONNULL_INPUT
+static int
+add_string(struct string_construction *s, const char *str)
+{
+ return add_string_n(s, str, strlen(str));
+}
+
+
+/**
+ * Append `size_t` encoded in decimal
+ *
+ * @param s The string to append `num` to
+ * @param num The number to append to `s`
+ * @return 0 on sucess, -1 on failure
+ */
+USE_RESULT NONNULL_INPUT
+static int
+add_number(struct string_construction *s, size_t n)
+{
+ char buf[3 * sizeof(size_t)], *str;
+ int len = sprintf(buf, "%zu", n);
+ if (len < 0)
+ abort();
+ if (s->dyn_n == s->dyn_size) {
+ void *new = realloc(s->dyn_strings, (s->dyn_size += 8) * sizeof(*s->dyn_strings));
+ if (!new)
+ return -1;
+ s->dyn_strings = new;
+ }
+ s->dyn_strings[s->dyn_n++] = str = malloc((size_t)len + 1U);
+ if (!str)
+ return -1;
+ memcpy(str, buf, (size_t)len + 1U);
+ return add_string_n(s, str, (size_t)len);
+}
+
+
+/**
+ * Append the serialisation of a sentence to a string
+ *
+ * @param this The sentence
+ * @param s The string to append the serialisation to
+ * @param map `.travel_index` to reference ID map, any sentence that has
+ * not be been serialised will have its slot set to `SIZE_MAX`;
+ * set to `NULL` (could also mean that there are not duplicates)
+ * by `libnormalform__debug_print__` to allow printing during
+ * indexing and restoration
+ * @param nextrefp Reference to the next reference ID
+ * @param embed Whether the sentence may be embed as additional terms
+ * (1, 0 otherwise); -1 is used by `libnormalform__debug_print__`
+ * to globally disable embedding
+ * @param depth_limit Decreases by 1 for each level of recursion, when 0 is
+ * reached "..." will be appended to `s`, instead of the
+ * the serialisation of `this`; this is only used by
+ * `libnormalform__debug_print__`, for
+ * `libnormalform_to_string` it is initially set to `SIZE_MAX`
+ * to nullify it's effect (you cannot possibility have more
+ * levels of recursion than bytes in your stack memory)
+ * @return 0 on success, -1 on failure
+ */
+USE_RESULT SOME_NONNULL_INPUT(1, 2, 4)
+static int
+to_string(LIBNORMALFORM_SENTENCE *this, struct string_construction *s,
+ size_t *map, size_t *nextrefp, int embed, size_t depth_limit)
+{
+#define ADD_STRING(SC, STRING) add_string_n((SC), STRING, sizeof(STRING) - 1U)
+#define IF_MAY_EMBED(X) (embed < 0 ? -1 : (X))
+
+ LIBNORMALFORM_SENTENCE *term1 = NULL, *term2 = NULL;
+ int inv = 0, associative = 0;
+ const char *function, *identifier = NULL;
+
+ if (!depth_limit)
+ return ADD_STRING(s, "...");
+
+ if (this->travel_count > 1 && map) {
+ embed = -(embed < 0);
+ if (map[this->travel_index] != SIZE_MAX) {
+ if (ADD_STRING(s, "REF(") || add_number(s, map[this->travel_index]))
+ return -1;
+ return ADD_STRING(s, ")");
+ }
+ }
+
+ switch (this->type) {
+ case TYPE_TRUE:
+ return ADD_STRING(s, "TRUE");
+
+ case TYPE_FALSE:
+ return ADD_STRING(s, "FALSE");
+
+ case TYPE_AND:
+ function = "AND";
+ goto binary;
+
+ case TYPE_OR:
+ function = "OR";
+ goto binary;
+
+ case TYPE_XOR:
+ function = "XOR";
+ binary:
+ term1 = this->data.binary.l;
+ term2 = this->data.binary.r;
+ associative = 1;
+ break;
+
+ case TYPE_ALL:
+ function = "ALL";
+ goto qualifier;
+
+ case TYPE_ANY:
+ function = "ANY";
+ goto qualifier;
+
+ case TYPE_NOT_ONE:
+ inv = 1;
+ /* fall through */
+
+ case TYPE_ONE:
+ function = "ONE";
+ qualifier:
+ identifier = this->data.qualifier.domain->identifier;
+ term1 = this->data.qualifier.antecedent;
+ term2 = this->data.qualifier.predicate;
+ break;
+
+ case TYPE_VARIABLE:
+ inv = this->data.literal.inverted;
+ function = "VARIABLE";
+ identifier = this->data.literal.atom.variable->identifier;
+ break;
+
+ case TYPE_FUNCTION:
+ inv = this->data.literal.inverted;
+ function = "FUNCTION";
+ identifier = this->data.literal.atom.function->identifier;
+ break;
+
+ case TYPE_TRANS:
+ function = "TRANSFORMATION";
+ identifier = this->data.trans.function->identifier;
+ term1 = this->data.trans.input;
+ break;
+
+ default:
+ abort();
+ }
+
+ if (embed == 1)
+ goto add_terms;
+
+ if (inv) {
+ if (this->travel_count > 1 && map) {
+ if (ADD_STRING(s, "NOT@") || add_number(s, *nextrefp) || ADD_STRING(s, "("))
+ return -1;
+ map[this->travel_index] = (*nextrefp)++;
+ } else {
+ if (ADD_STRING(s, "NOT("))
+ return -1;
+ }
+ }
+ if (add_string(s, function))
+ return -1;
+ if (!inv && this->travel_count > 1 && map) {
+ if (ADD_STRING(s, "@") || add_number(s, *nextrefp))
+ return -1;
+ map[this->travel_index] = (*nextrefp)++;
+ }
+ if (ADD_STRING(s, "("))
+ return -1;
+
+ if (identifier) {
+ if (add_string(s, identifier))
+ return -1;
+ if (term1 && ADD_STRING(s, ", "))
+ return -1;
+ }
+add_terms:
+ if (term1) {
+ if (to_string(term1, s, map, nextrefp, IF_MAY_EMBED(associative && term1->type == this->type), depth_limit - 1))
+ return -1;
+ if (term2 && ADD_STRING(s, ", "))
+ return -1;
+ }
+ if (term2) {
+ if (to_string(term2, s, map, nextrefp, IF_MAY_EMBED(associative && term2->type == this->type), depth_limit - 1))
+ return -1;
+ }
+
+ return embed == 1 ? 0 : inv ? ADD_STRING(s, "))") : ADD_STRING(s, ")");
+
+#undef ADD_STRING
+#undef IF_MAY_EMBED
+}
+
+
+#ifdef DEBUG
+
+void
+libnormalform__debug_print__(LIBNORMALFORM_SENTENCE *this, size_t depth_limit)
+{
+ struct string_construction s;
+ char *res = NULL, *p;
+ size_t i, nextref = 0;
+
+ if (!this) {
+ fprintf(stderr, "<debug error: libnormalform__debug_print__: %s>\n", "Null pointer input");
+ return;
+ }
+
+ s.strings = NULL;
+ s.dyn_strings = NULL;
+ s.n = 0;
+ s.size = 0;
+ s.length = 0;
+ s.dyn_n = 0;
+ s.dyn_size = 0;
+
+ if (to_string(this, &s, NULL, &nextref, -1, depth_limit ? depth_limit : SIZE_MAX))
+ goto out;
+ res = malloc(s.length + 1U);
+ if (!res)
+ goto out;
+
+ p = res;
+ for (i = 0; i < s.n; i++)
+ p = stpcpy(p, s.strings[i]);
+ *p = '\0';
+
+out:
+ while (s.dyn_n)
+ free(s.dyn_strings[--s.dyn_n]);
+ free(s.strings);
+ free(s.dyn_strings);
+
+ if (res) {
+ fprintf(stderr, "%s\n", res);
+ free(res);
+ } else {
+ fprintf(stderr, "<debug error: libnormalform__debug_print__: %s>\n", strerror(errno));
+ }
+ fflush(stderr);
+}
+
+#endif
+
+
+char *
+(libnormalform_to_string)(LIBNORMALFORM_SENTENCE *this)
+{
+ struct string_construction s;
+ char *ret = NULL, *p;
+ size_t i, dupcount, *map = NULL, nextref = 0;
+
+ dupcount = libnormalform_set_indices_and_counts__(this);
+ if (dupcount) {
+ map = malloc(dupcount * sizeof(*map));
+ if (!map)
+ goto out;
+ while (dupcount--)
+ map[dupcount] = SIZE_MAX;
+ }
+
+ s.strings = NULL;
+ s.dyn_strings = NULL;
+ s.n = 0;
+ s.size = 0;
+ s.length = 0;
+ s.dyn_n = 0;
+ s.dyn_size = 0;
+
+ if (to_string(this, &s, map, &nextref, 0, SIZE_MAX))
+ goto out;
+ ret = malloc(s.length + 1U);
+ if (!ret)
+ goto out;
+
+ p = ret;
+ for (i = 0; i < s.n; i++)
+ p = stpcpy(p, s.strings[i]);
+ *p = '\0';
+
+out:
+ while (s.dyn_n)
+ free(s.dyn_strings[--s.dyn_n]);
+ free(s.strings);
+ free(s.dyn_strings);
+ free(map);
+ libnormalform_reset_indices_and_counts__(this);
+ return ret;
+}
+
+
+#else
+
+
+NONNULL_INPUT static LIBNORMALFORM_SENTENCE *
+set_order(size_t order, LIBNORMALFORM_SENTENCE *x)
+{
+ x->hash = order;
+ return x;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_transformer trans1;
+ LIBNORMALFORM_SENTENCE *ref0, *ref1, *ref2, *ref3, *ref4, *a, *b, *t, *f;
+ LIBNORMALFORM_SENTENCE *pref0, *pref1, *ref0a, *ref0b;
+ char *s;
+
+ var1.identifier = "var1";
+ var2.identifier = "var2";
+ var3.identifier = "var3";
+ fun1.identifier = "fun1";
+ fun2.identifier = "fun2";
+ dom1.identifier = "dom1";
+ dom2.identifier = "dom2";
+ trans1.identifier = "trans1";
+
+ ASSUME(t = libnormalform_true());
+ ASSUME(f = libnormalform_false());
+
+#define REF1P "VARIABLE(var1)"
+ ASSUME(ref1 = libnormalform_variable(&var1));
+ ASSUME(s = libnormalform_to_string(ref1));
+ ASSERT(!strcmp(s, REF1P));
+ free(s);
+
+#define REF0 "AND@0(VARIABLE@1(var1), VARIABLE(var2), VARIABLE(var3))"
+#define REF0P "AND(VARIABLE(var1), VARIABLE(var2), VARIABLE(var3))"
+ ASSUME(ref0 =
+ libnormalform_and2(
+ set_order(1, REF(ref1)),
+ set_order(2, libnormalform_and2(
+ set_order(3, libnormalform_variable(&var2)),
+ set_order(4, libnormalform_variable(&var3))
+ ))
+ )
+ );
+ ASSUME(s = libnormalform_to_string(ref0));
+ ASSERT(!strcmp(s, REF0P));
+ free(s);
+
+ ref0a = libnormalform_ref(ref0);
+
+#define REF2 "NOT@2(FUNCTION(fun1))"
+#define REF2P "NOT(FUNCTION(fun1))"
+ ASSUME(ref2 = libnormalform_not(libnormalform_function(&fun1)));
+ ASSUME(s = libnormalform_to_string(ref2));
+ ASSERT(!strcmp(s, REF2P));
+ free(s);
+
+#define A1 "AND("REF2", REF(1), TRANSFORMATION(trans1, FUNCTION(fun2)))"
+#define A1P "AND("REF2P", "REF1P", TRANSFORMATION(trans1, FUNCTION(fun2)))"
+#define A1Q "AND("REF2P", REF(1), TRANSFORMATION(trans1, FUNCTION(fun2)))"
+ ASSUME(a =
+ libnormalform_and2__(
+ set_order(1, libnormalform_and2__(
+ set_order(2, REF(ref2)),
+ set_order(3, REF(ref1))
+ )),
+ set_order(4, (pref0 = libnormalform_transformation(&trans1, libnormalform_function(&fun2))))
+ )
+ );
+ ASSUME(s = libnormalform_to_string(a));
+ ASSERT(!strcmp(s, A1P));
+ free(s);
+
+ ref0b = libnormalform_ref(ref0);
+
+#define A2 "XOR("REF0", ALL(dom1, REF(0), "A1"))"
+#define A2P "XOR("REF0", ALL(dom1, REF(0), "A1Q"))"
+ ASSUME(a =
+ libnormalform_xor2(
+ REF(ref0),
+ libnormalform_all(&dom1, REF(ref0), a)
+ )
+ );
+ ASSUME(s = libnormalform_to_string(a));
+ ASSERT(!strcmp(s, A2P));
+ free(s);
+
+ libnormalform_free(ref1);
+
+#define REF3 "ONE@3(dom2, TRUE, TRUE)"
+#define REF3P "ONE(dom2, TRUE, TRUE)"
+ ASSUME(ref3 = libnormalform_one(&dom2, REF(t), REF(t)));
+ ASSUME(s = libnormalform_to_string(ref3));
+ ASSERT(!strcmp(s, REF3P));
+ free(s);
+
+#define REF4 "NOT@4(ONE(dom2, TRUE, TRUE))"
+#define REF4P "NOT(ONE(dom2, TRUE, TRUE))"
+ ASSUME(ref4 = libnormalform_not(REF(ref3)));
+ ASSUME(s = libnormalform_to_string(ref4));
+ ASSERT(!strcmp(s, REF4P));
+ free(s);
+
+#define B1P "XOR("REF3P", "REF4P")"
+#define B1R "XOR(REF(3), REF(4))"
+ ASSUME(b = libnormalform_xor2__(set_order(1, REF(ref3)), set_order(2, REF(ref4))));
+ ASSUME(s = libnormalform_to_string(b));
+ ASSERT(!strcmp(s, B1P));
+ free(s);
+
+#define A3P "OR("A2", REF(2), ANY(dom1, TRUE, TRUE), "REF3P", "REF4P")"
+#define A3 A2", REF(2), ANY(dom1, TRUE, TRUE), "REF3", "REF4
+ ASSUME(a =
+ set_order(9, libnormalform_or2(
+ set_order(7, libnormalform_or2(
+ set_order(5, libnormalform_or2(
+ set_order(3, libnormalform_or2(
+ set_order(1, a),
+ set_order(2, REF(ref2))
+ )),
+ set_order(4, (pref1 = libnormalform_any(&dom1, REF(t), REF(t))))
+ )),
+ set_order(6, REF(ref3))
+ )),
+ set_order(8, REF(ref4))
+ ))
+ );
+ ASSUME(s = libnormalform_to_string(a));
+ ASSERT(!strcmp(s, A3P));
+ free(s);
+
+#define EXPECTED "OR("A3", "B1R")"
+ ASSUME(a = libnormalform_or2(set_order(1, a), set_order(2, b)));
+
+ pref0 = libnormalform_ref(pref0);
+ pref1 = libnormalform_ref(pref1);
+
+ ASSUME(s = libnormalform_to_string(a));
+ ASSERT(!strcmp(s, EXPECTED));
+ free(s);
+
+ libnormalform_free(pref0);
+ libnormalform_free(pref1);
+
+ libnormalform_free(a);
+ libnormalform_free(ref0);
+ libnormalform_free(ref0a);
+ libnormalform_free(ref0b);
+ libnormalform_free(ref2);
+ libnormalform_free(ref3);
+ libnormalform_free(ref4);
+
+ ASSUME(a = libnormalform_all(&dom1, REF(t), REF(f)));
+ ASSUME(s = libnormalform_to_string(a));
+ ASSERT(!strcmp(s, "ALL(dom1, TRUE, FALSE)"));
+ free(s);
+ libnormalform_free(a);
+
+ libnormalform_free(t);
+ libnormalform_free(f);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_transformation.c b/libnormalform_transformation.c
new file mode 100644
index 0000000..3a1218b
--- /dev/null
+++ b/libnormalform_transformation.c
@@ -0,0 +1,419 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (Transformation function implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+trans_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ return libnormalform_transformation(this->data.trans.function, this->data.trans.input->inverse(this->data.trans.input));
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (Transformation function implementation)
+ */
+static int
+trans_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ if (other->type != TYPE_TRANS || this->data.trans.function != other->data.trans.function)
+ return 0;
+ return this->data.trans.input->equals(this->data.trans.input, other->data.trans.input, inv_out);
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (Transformation function implementation)
+ */
+static int
+trans_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ int r;
+ input = this->data.trans.function->transform(this->data.trans.function->user_data, input);
+ if (!input)
+ return -1;
+ r = this->data.trans.input->evaluate(this->data.trans.input, input);
+ if (this->data.trans.function->deallocate)
+ this->data.trans.function->deallocate(this->data.trans.function->user_data, input);
+ return r;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_transformation)(struct libnormalform_transformer *function, LIBNORMALFORM_SENTENCE *sentence)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_TRANS,
+ .inverse = &trans_inverse,
+ .equals = &trans_equals,
+ .evaluate = &trans_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret, *left, *right;
+ LIBNORMALFORM_SENTENCE *(*connective)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+ if (!sentence)
+ return NULL;
+
+ function->builtin = LIBNORMALFORM_NOT_BUILT_IN;
+
+ switch (sentence->type) {
+ case TYPE_ALL:
+ case TYPE_ANY:
+ case TYPE_ONE:
+ case TYPE_NOT_ONE:
+ libnormalform_free(sentence);
+ errno = EDOM;
+ return NULL;
+
+ case TYPE_TRUE:
+ case TYPE_FALSE:
+ case TYPE_VARIABLE:
+ return sentence;
+
+ case TYPE_FUNCTION:
+ case TYPE_TRANS:
+ ret = malloc(sizeof(*ret));
+ if (!ret) {
+ libnormalform_free(sentence);
+ return NULL;
+ }
+ *ret = prototype;
+ ret->hash = TRANS_HASH(function, sentence);
+ ret->data.trans.function = function;
+ ret->data.trans.input = sentence;
+ return ret;
+
+ case TYPE_AND:
+ connective = &libnormalform_and2__;
+ break;
+ case TYPE_OR:
+ connective = &libnormalform_or2__;
+ break;
+ case TYPE_XOR:
+ connective = &libnormalform_xor2__;
+ break;
+
+ default:
+ abort();
+ }
+
+ left = LEFT(sentence), LEFT(sentence) = NULL;
+ right = RIGHT(sentence), RIGHT(sentence) = NULL;
+ libnormalform_free(sentence);
+ left = libnormalform_transformation(function, left);
+ if (!left) {
+ libnormalform_free(right);
+ return NULL;
+ }
+ right = libnormalform_transformation(function, right);
+ if (!right) {
+ libnormalform_free(left);
+ return NULL;
+ }
+
+ return (*connective)(left, right);
+}
+
+
+#else
+
+
+static void *
+uppercase(void *user_data, void *input)
+{
+ char *s = strdup(input), *p;
+ (void) user_data;
+ if (s)
+ for (p = s; *p; p++)
+ *p = (char)toupper(*p);
+ return s;
+}
+
+
+static void *
+constuppercase(void *user_data, void *input)
+{
+ static char ret[] = "HELLO WORLD";
+ (void) user_data;
+ (void) input;
+ return ret;
+}
+
+
+static void *
+einval(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ errno = EINVAL;
+ return NULL;
+}
+
+
+static void
+deallocate(void *user_data, void *input)
+{
+ (void) user_data;
+ free(input);
+}
+
+
+static int
+validate(void *expected, void *input)
+{
+ return input ? !strcmp(input, expected) : (errno = EDOM, -1);
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_transformer trans1, trans2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_variable var1;
+ struct libnormalform_map dom1;
+ LIBNORMALFORM_SENTENCE *a, *b, *f1, *f2;
+
+ errno = 0;
+ ASSERT(!libnormalform_transformation(&trans1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_transformation(&trans1, NULL) && errno == 1);
+
+ /* F(⊤) = ⊤ */
+ ASSUME(a = libnormalform_true());
+ ASSUME(b = libnormalform_transformation(&trans1, REF(a)));
+ ASSERT(b->refcount == 2);
+ ASSERT(b->type == TYPE_TRUE);
+ ASSERT(b == a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* F(⊥) = ⊥ */
+ ASSUME(a = libnormalform_false());
+ ASSUME(b = libnormalform_transformation(&trans1, REF(a)));
+ ASSERT(b->refcount == 2);
+ ASSERT(b->type == TYPE_FALSE);
+ ASSERT(b == a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* F(a) = a */
+ ASSUME(a = libnormalform_variable(&var1));
+ ASSUME(b = libnormalform_transformation(&trans1, REF(a)));
+ ASSERT(b->refcount == 2);
+ ASSERT(b->type == TYPE_VARIABLE);
+ ASSERT(b == a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* F(f(x)) irreducible */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_transformation(&trans1, REF(a)));
+ ASSERT(b->refcount == 1);
+ ASSERT(a->refcount == 2);
+ ASSERT(b->type == TYPE_TRANS);
+ ASSERT(b->data.trans.function == &trans1);
+ ASSERT(b->data.trans.input == a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* F(f(x) ∧ g(x)) = F(f(x)) ∧ F(g(x)) */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_and2(a, b));
+ ASSUME(b = libnormalform_transformation(&trans1, a));
+ ASSERT(b->refcount == 1);
+ ASSERT(b->type == TYPE_AND);
+ ASSERT(LEFT(b)->type == TYPE_TRANS);
+ ASSERT(RIGHT(b)->type == TYPE_TRANS);
+ ASSERT(LEFT(b)->refcount == 1);
+ ASSERT(RIGHT(b)->refcount == 1);
+ ASSERT(LEFT(b)->data.trans.function == &trans1);
+ ASSERT(RIGHT(b)->data.trans.function == &trans1);
+ ASSERT(LEFT(b)->data.trans.input);
+ ASSERT(RIGHT(b)->data.trans.input);
+ ASSERT(LEFT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(RIGHT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(LEFT(b)->data.trans.input != RIGHT(b)->data.trans.input);
+ libnormalform_free(b);
+
+ /* F(f(x) ∨ g(x)) = F(f(x)) ∨ F(g(x)) */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_or2(a, b));
+ ASSUME(b = libnormalform_transformation(&trans1, a));
+ ASSERT(b->refcount == 1);
+ ASSERT(b->type == TYPE_OR);
+ ASSERT(LEFT(b)->type == TYPE_TRANS);
+ ASSERT(RIGHT(b)->type == TYPE_TRANS);
+ ASSERT(LEFT(b)->refcount == 1);
+ ASSERT(RIGHT(b)->refcount == 1);
+ ASSERT(LEFT(b)->data.trans.function == &trans1);
+ ASSERT(RIGHT(b)->data.trans.function == &trans1);
+ ASSERT(LEFT(b)->data.trans.input);
+ ASSERT(RIGHT(b)->data.trans.input);
+ ASSERT(LEFT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(RIGHT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(LEFT(b)->data.trans.input != RIGHT(b)->data.trans.input);
+ libnormalform_free(b);
+
+ /* F(f(x) ⊕ g(x)) = F(f(x)) ⊕ F(g(x)) */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_xor2(a, b));
+ ASSUME(b = libnormalform_transformation(&trans1, a));
+ ASSERT(b->refcount == 1);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT(LEFT(b)->type == TYPE_TRANS);
+ ASSERT(RIGHT(b)->type == TYPE_TRANS);
+ ASSERT(LEFT(b)->refcount == 1);
+ ASSERT(RIGHT(b)->refcount == 1);
+ ASSERT(LEFT(b)->data.trans.function == &trans1);
+ ASSERT(RIGHT(b)->data.trans.function == &trans1);
+ ASSERT(LEFT(b)->data.trans.input);
+ ASSERT(RIGHT(b)->data.trans.input);
+ ASSERT(LEFT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(RIGHT(b)->data.trans.input->type == TYPE_FUNCTION);
+ ASSERT(LEFT(b)->data.trans.input != RIGHT(b)->data.trans.input);
+ libnormalform_free(b);
+
+ /* F(Q(q)∀{p,q}:P(p)) illegal construct */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_all(&dom1, a, b));
+ errno = 0;
+ ASSERT(!libnormalform_transformation(&trans1, a) && errno == EDOM);
+
+ /* F(Q(q)∃{p,q}:P(p)) illegal construct */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_any(&dom1, a, b));
+ errno = 0;
+ ASSERT(!libnormalform_transformation(&trans1, a) && errno == EDOM);
+
+ /* F(Q(q)∃!{p,q}:P(p)) illegal construct */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_one(&dom1, a, b));
+ errno = 0;
+ ASSERT(!libnormalform_transformation(&trans1, a) && errno == EDOM);
+
+ /* F(¬(Q(q)∃!{p,q}:P(p))) illegal construct */
+ ASSUME(a = libnormalform_function(&fun1));
+ ASSUME(b = libnormalform_function(&fun2));
+ ASSUME(a = libnormalform_one(&dom1, a, b));
+ ASSUME(a = libnormalform_not(a));
+ errno = 0;
+ ASSERT(!libnormalform_transformation(&trans1, a) && errno == EDOM);
+
+ ASSUME(a = libnormalform_function(&fun1));
+ trans1.transform = &uppercase;
+ trans1.deallocate = &deallocate;
+
+ fun1.evaluate = validate;
+ ASSUME(fun1.user_data = strdup("hello world"));
+ ASSERT(a->evaluate(a, (char []){"hello world"}) == 1);
+ ASSERT(a->evaluate(a, (char []){"HELLO WORLD"}) == 0);
+ ASSERT(a->evaluate(a, (char []){"x"}) == 0);
+ errno = 0;
+ ASSERT(a->evaluate(a, NULL) == -1 && errno == EDOM);
+ free(fun1.user_data);
+
+ fun1.evaluate = validate;
+ ASSUME(fun1.user_data = strdup("HELLO WORLD"));
+ ASSUME(a = libnormalform_transformation(&trans1, a));
+ ASSERT(a->evaluate(a, (char []){"hello world"}) == 1);
+ ASSERT(a->evaluate(a, (char []){"HELLO WORLD"}) == 1);
+ ASSERT(a->evaluate(a, (char []){"x"}) == 0);
+ trans1.transform = constuppercase;
+ trans1.deallocate = NULL;
+ ASSERT(a->evaluate(a, (char []){"hello world"}) == 1);
+ free(fun1.user_data);
+
+ libnormalform_free(a);
+
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+ ASSUME(a = libnormalform_transformation(&trans1, REF(f1)));
+ trans1.transform = einval;
+ trans1.deallocate = NULL;
+ errno = 0;
+ ASSERT(a->evaluate(a, (char []){"hello world"}) == -1 && errno == EINVAL);
+
+ ASSERT_NOTEQUAL(a, f1);
+
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_transformation(&trans1, REF(f1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_transformation(&trans1, libnormalform_not(REF(f1))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_transformation(&trans1, REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_transformation(&trans2, REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_and2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_or2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_xor2(REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_all(&dom1, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_any(&dom1, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_one(&dom1, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_not(libnormalform_transformation(&trans1, REF(f1))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_true.c b/libnormalform_true.c
new file mode 100644
index 0000000..e716cc5
--- /dev/null
+++ b/libnormalform_true.c
@@ -0,0 +1,156 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (TRUE implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+true_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ (void) this;
+ return libnormalform_false();
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (TRUE implementation)
+ */
+static int
+true_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ (void) this;
+ if (other->type == TYPE_TRUE) {
+ *inv_out = 0;
+ return 1;
+ } else if (other->type == TYPE_FALSE) {
+ *inv_out = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (TRUE implementation)
+ */
+CONST static int
+true_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ (void) this;
+ (void) input;
+ return 1;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_true)(void)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_TRUE,
+ .hash = TRUE_FALSE_HASH,
+ .inverse = &true_inverse,
+ .equals = &true_equals,
+ .evaluate = &true_evaluate
+ };
+
+ /*
+ * During normalisation, some fields may be set,
+ * therefore a new allocation is returned instead
+ * of a reference to a static allocation, so that
+ * converation can be done from the threads at
+ * the same time on different sentences, both
+ * including this constant.
+ */
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (ret)
+ *ret = prototype;
+ return ret;
+}
+
+
+#else
+
+
+static int
+tautology(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 1;
+}
+
+
+static int
+contradiction(void *user_data, void *input)
+{
+ (void) user_data;
+ (void) input;
+ return 0;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *f1, *f2;
+
+ var1.value = LIBNORMALFORM_FALSE;
+ var2.value = LIBNORMALFORM_TRUE;
+ fun1.evaluate = &tautology;
+ fun2.evaluate = &contradiction;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+ ASSUME(a = libnormalform_true());
+ ASSERT(a->type == TYPE_TRUE);
+ ASSERT(a->refcount == 1);
+ ASSERT_EQUAL(a, a);
+
+ ASSERT(a->evaluate(a, NULL) == 1);
+
+#define CHECK(CMP, X)\
+ do {\
+ ASSUME(b = (X));\
+ ASSERT_##CMP(a, b);\
+ libnormalform_free(b);\
+ } while (0)
+
+ CHECK(EQUAL, libnormalform_true());
+ CHECK(INVEQUAL, libnormalform_false());
+ CHECK(NOTEQUAL, libnormalform_and2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_or2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_xor2(REF(v1), REF(v2)));
+ CHECK(NOTEQUAL, libnormalform_all(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_any(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_one(&domain, REF(f1), REF(f2)));
+ CHECK(NOTEQUAL, libnormalform_not(libnormalform_one(&domain, REF(f1), REF(f2))));
+ CHECK(NOTEQUAL, REF(v1));
+ CHECK(NOTEQUAL, REF(f1));
+
+#undef CHECK
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_unique.c b/libnormalform_unique.c
new file mode 100644
index 0000000..4f0b467
--- /dev/null
+++ b/libnormalform_unique.c
@@ -0,0 +1,234 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_unique)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_unique(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_unique(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃!x.⊥ = ⊥ */
+ ASSUME(a = libnormalform_unique(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃!x.φ irreducable */
+ ASSUME(a = libnormalform_unique(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ /* ∃!x.⊤ irreducable */
+ ASSUME(a = libnormalform_unique(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_unique(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ONE);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃!x∈{a}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃!x∈{a,b}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a,b}.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ /* ∃!x∈∅.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈∅.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_unique(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ONE);
+ map[0].value = NULL;
+ map[1].value = NULL;
+
+ /* ∃!x∈{⊥, ⊥}.x = ⊥ */
+ map[0].key = &f;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊥, ⊤}.x = ⊤ */
+ map[0].key = &f;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊥}.x = ⊤ */
+ map[0].key = &t;
+ map[1].key = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊤}.x = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊤, ⊤, ⊤}.x = ⊥ */
+ map[0].key = &t;
+ map[1].key = &t;
+ map[2].key = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_unique(&dom1, REF(v1)));
+
+ /* ∃!x∈X.P(x) = ∃!x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_one(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃!x∈X.P(x) = ¬∃!x∈X.P(x) */
+ ASSUME(b = libnormalform_not(REF(a)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(b->type == TYPE_NOT_ONE);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃!x∈X.Q(x) */
+ ASSUME(b = libnormalform_unique(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃!x∈Y.P(x)) */
+ ASSUME(b = libnormalform_unique(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∀x∈X.(P(x) → ⊤) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∀x∈X.(P(x) → ⊥) */
+ ASSUME(b = libnormalform_all(&dom1, REF(v1), libnormalform_false()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃x∈X.(P(x) ∧ ⊥) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_false()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃x∈X.(P(x) ∧ ⊤) */
+ ASSUME(b = libnormalform_any(&dom1, REF(v1), libnormalform_true()));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_uniquely.c b/libnormalform_uniquely.c
new file mode 100644
index 0000000..53e88ba
--- /dev/null
+++ b/libnormalform_uniquely.c
@@ -0,0 +1,224 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_uniquely)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_uniquely(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_uniquely(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∃!x.⊥ = ⊥ */
+ ASSUME(a = libnormalform_uniquely(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* ∃!x.φ irreducable */
+ ASSUME(a = libnormalform_uniquely(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ /* ∃!x.⊤ irreducable */
+ ASSUME(a = libnormalform_uniquely(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_ONE);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_uniquely(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ONE);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∃!x∈{a}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∃!x∈{a,b}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{a,b}.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ /* ∃!x∈∅.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈∅.⊤ = ⊥ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ dom1.nmappings = 0;
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_uniquely(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ONE);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∃!x∈{⊥, ⊥}.x = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊥, ⊤}.x = ⊤ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊥}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∃!x∈{⊤, ⊤}.x = ⊥ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∃!x∈{⊤, ⊤, ⊤}.x = ⊥ */
+ map[0].value = &t;
+ map[1].value = &t;
+ map[2].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_uniquely(&dom1, REF(v1)));
+
+ /* ∃!x∈X.P(x) = ∃!x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_one(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∃!x∈X.P(x) = ¬∃!x∈X.P(x) */
+ ASSUME(b = libnormalform_not(REF(a)));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT(b->type == TYPE_NOT_ONE);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃!x∈X.Q(x) */
+ ASSUME(b = libnormalform_uniquely(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃!x∈Y.P(x)) */
+ ASSUME(b = libnormalform_uniquely(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∀x∈X.(⊤ → P(x)) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from ∃x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∃!x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_universally.c b/libnormalform_universally.c
new file mode 100644
index 0000000..922437c
--- /dev/null
+++ b/libnormalform_universally.c
@@ -0,0 +1,233 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_universally)(struct libnormalform_map *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+
+static int
+evalbool(void *user_data, void *input)
+{
+ int *vp = input;
+ (void) user_data;
+ return *vp;
+}
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1;
+ struct libnormalform_function fun1;
+ struct libnormalform_map dom1, dom2;
+ struct libnormalform_mapping map[3];
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *c, *v1;
+ int t = 1, f = 0;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+
+ errno = 0;
+ ASSERT(!libnormalform_universally(&dom1, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!libnormalform_universally(&dom1, NULL) && errno == 1);
+
+ dom1.mappings = map;
+ memset(map, 0, sizeof(map));
+
+ /* ∀x.⊤ = ⊤ */
+ ASSUME(a = libnormalform_universally(&dom1, libnormalform_true()));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* ∀x.φ irreducable */
+ ASSUME(a = libnormalform_universally(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ /* ∀x.⊥ irreducable */
+ ASSUME(a = libnormalform_universally(&dom1, libnormalform_false()));
+ ASSERT(a->type == TYPE_ALL);
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_universally(&dom1, REF(v1)));
+ ASSERT(a->type == TYPE_ALL);
+ ASSERT(a->refcount == 1);
+
+ dom1.nmappings = 1;
+
+ /* ∀x∈{a}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{a}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 2;
+
+ /* ∀x∈{a,b}.⊥ = ⊥ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{a,b}.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ dom1.nmappings = 0;
+
+ /* ∀x∈∅.⊥ = ⊤ */
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈∅.⊤ = ⊤ */
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ fun1.evaluate = &evalbool;
+ dom1.nmappings = 2;
+
+ ASSUME(a = libnormalform_universally(&dom1, libnormalform_function(&fun1)));
+ ASSERT(a->type == TYPE_ALL);
+ map[0].key = NULL;
+ map[1].key = NULL;
+
+ /* ∀x∈{⊥, ⊥}.x = ⊥ */
+ map[0].value = &f;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊥, ⊤}.x = ⊥ */
+ map[0].value = &f;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊥}.x = ⊥ */
+ map[0].value = &t;
+ map[1].value = &f;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ /* ∀x∈{⊤, ⊤}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ /* ∀x∈{⊤, ⊤, ⊤}.x = ⊤ */
+ map[0].value = &t;
+ map[1].value = &t;
+ map[2].value = &t;
+ ASSERT(libnormalform_evaluate(a) == 1);
+
+ libnormalform_free(a);
+
+ ASSUME(a = libnormalform_universally(&dom1, REF(v1)));
+
+ /* ∀x∈X.P(x) = ¬∃x∈X.(⊤ ∧ ¬P(x)) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), libnormalform_not(REF(v1))));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from ∀x∈X.Q(x) */
+ ASSUME(b = libnormalform_universally(&dom1, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from ∀x∈Y.P(x)) */
+ ASSUME(b = libnormalform_universally(&dom2, REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) = ∀x∈X.(⊤ → P(x)) */
+ ASSUME(b = libnormalform_all(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ¬∀x∈X.P(x) = ∃x∈X.(⊤ ∧ ¬P(x)) */
+ ASSUME(c = libnormalform_not(REF(a)));
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), libnormalform_not(REF(v1))));
+ ASSERT_EQUAL(c, b);
+ ASSERT(c->type == TYPE_ANY);
+ libnormalform_free(b);
+ libnormalform_free(c);
+
+ /* ∀x∈X.P(x) independent from TRUE */
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from FALSE */
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from variable1 */
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from NOT variable1 */
+ ASSUME(b = libnormalform_not(libnormalform_variable(&var1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from function1 */
+ ASSUME(b = libnormalform_function(&fun1));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from NOT function1 */
+ ASSUME(b = libnormalform_not(libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from T(function1) */
+ ASSUME(b = libnormalform_transformation(&trans, libnormalform_function(&fun1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from ∃x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_any(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from ∃!x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_one(&dom1, libnormalform_true(), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+
+ /* ∀x∈X.P(x) independent from ¬∃!x∈X.(⊤ ∧ P(x)) */
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from AND (P, P) */
+ ASSUME(b = libnormalform_and2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(X) independent from OR (P, P) */
+ ASSUME(b = libnormalform_or2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ /* ∀x∈X.P(x) independent from XOR (P, P) */
+ ASSUME(b = libnormalform_xor2__(REF(v1), REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+
+ libnormalform_free(v1);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_vand.c b/libnormalform_vand.c
new file mode 100644
index 0000000..e550c37
--- /dev/null
+++ b/libnormalform_vand.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vand)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_vand_checked.c b/libnormalform_vand_checked.c
new file mode 100644
index 0000000..437fbde
--- /dev/null
+++ b/libnormalform_vand_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vand_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_and.c"
+
+#endif
diff --git a/libnormalform_variable.c b/libnormalform_variable.c
new file mode 100644
index 0000000..0c8b0bf
--- /dev/null
+++ b/libnormalform_variable.c
@@ -0,0 +1,153 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (Variable literal implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+variable_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ LIBNORMALFORM_SENTENCE *ret = libnormalform_variable(this->data.literal.atom.variable);
+ if (ret)
+ ret->data.literal.inverted = this->data.literal.inverted ^ 1;
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (Variable literal implementation)
+ */
+static int
+variable_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ if (other->type != TYPE_VARIABLE || this->data.literal.atom.variable != other->data.literal.atom.variable)
+ return 0;
+ *inv_out = this->data.literal.inverted ^ other->data.literal.inverted;
+ return 1;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (Variable literal implementation)
+ */
+PURE static int
+variable_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ (void) input;
+ return (this->data.literal.atom.variable->value != LIBNORMALFORM_FALSE) ^ this->data.literal.inverted;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_variable)(struct libnormalform_variable *variable)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_VARIABLE,
+ .inverse = &variable_inverse,
+ .equals = &variable_equals,
+ .evaluate = &variable_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (ret) {
+ *ret = prototype;
+ ret->hash = LITERAL_HASH(variable);
+ ret->data.literal.atom.variable = variable;
+ ret->data.literal.inverted = 0;
+ }
+ return ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *f1, *f2;
+
+ ASSUME(a = libnormalform_variable(&var1));
+ ASSERT(a->refcount == 1);
+ ASSERT(a->travel_index == 0);
+ ASSERT(a->travel_count == 0);
+ ASSERT(a->type == TYPE_VARIABLE);
+ ASSERT(a->data.literal.inverted == 0);
+ ASSERT(a->data.literal.atom.variable == &var1);
+ ASSERT_EQUAL(a, a);
+ var1.value = LIBNORMALFORM_TRUE;
+ ASSERT(libnormalform_evaluate(a) == 1);
+ var1.value = LIBNORMALFORM_FALSE;
+ ASSERT(libnormalform_evaluate(a) == 0);
+
+ ASSUME(b = libnormalform_variable(&var1));
+ ASSERT_EQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_INVEQUAL(a, b);
+ ASSERT_INVEQUAL(b, a);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_variable(&var2));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+
+ ASSERT_NOTEQUAL(a, f1);
+
+ ASSUME(b = libnormalform_and2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_or2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_all(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_any(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(b);
+
+ libnormalform_free(a);
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_vif.c b/libnormalform_vif.c
new file mode 100644
index 0000000..711ff4b
--- /dev/null
+++ b/libnormalform_vif.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vif)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_vif_checked.c b/libnormalform_vif_checked.c
new file mode 100644
index 0000000..6abe89d
--- /dev/null
+++ b/libnormalform_vif_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vif_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_if.c"
+
+#endif
diff --git a/libnormalform_vimply.c b/libnormalform_vimply.c
new file mode 100644
index 0000000..4ec2b3a
--- /dev/null
+++ b/libnormalform_vimply.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vimply)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_vimply_checked.c b/libnormalform_vimply_checked.c
new file mode 100644
index 0000000..9cf1884
--- /dev/null
+++ b/libnormalform_vimply_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vimply_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_imply.c"
+
+#endif
diff --git a/libnormalform_vnand.c b/libnormalform_vnand.c
new file mode 100644
index 0000000..61a5d83
--- /dev/null
+++ b/libnormalform_vnand.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnand)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_vnand_checked.c b/libnormalform_vnand_checked.c
new file mode 100644
index 0000000..a884e25
--- /dev/null
+++ b/libnormalform_vnand_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnand_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_nand.c"
+
+#endif
diff --git a/libnormalform_vnif.c b/libnormalform_vnif.c
new file mode 100644
index 0000000..b76b4cb
--- /dev/null
+++ b/libnormalform_vnif.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnif)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_vnif_checked.c b/libnormalform_vnif_checked.c
new file mode 100644
index 0000000..fbd71f2
--- /dev/null
+++ b/libnormalform_vnif_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnif_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_nif.c"
+
+#endif
diff --git a/libnormalform_vnimply.c b/libnormalform_vnimply.c
new file mode 100644
index 0000000..f4f9931
--- /dev/null
+++ b/libnormalform_vnimply.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnimply)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_vnimply_checked.c b/libnormalform_vnimply_checked.c
new file mode 100644
index 0000000..e2ad87e
--- /dev/null
+++ b/libnormalform_vnimply_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnimply_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_nimply.c"
+
+#endif
diff --git a/libnormalform_vnor.c b/libnormalform_vnor.c
new file mode 100644
index 0000000..5752706
--- /dev/null
+++ b/libnormalform_vnor.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnor)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_vnor_checked.c b/libnormalform_vnor_checked.c
new file mode 100644
index 0000000..4c2543b
--- /dev/null
+++ b/libnormalform_vnor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vnor_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_nor.c"
+
+#endif
diff --git a/libnormalform_vor.c b/libnormalform_vor.c
new file mode 100644
index 0000000..83f70bd
--- /dev/null
+++ b/libnormalform_vor.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vor)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_vor_checked.c b/libnormalform_vor_checked.c
new file mode 100644
index 0000000..99e218b
--- /dev/null
+++ b/libnormalform_vor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vor_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_or.c"
+
+#endif
diff --git a/libnormalform_vxnor.c b/libnormalform_vxnor.c
new file mode 100644
index 0000000..7c28cb6
--- /dev/null
+++ b/libnormalform_vxnor.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vxnor)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_vxnor_checked.c b/libnormalform_vxnor_checked.c
new file mode 100644
index 0000000..8d64bb6
--- /dev/null
+++ b/libnormalform_vxnor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vxnor_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_vxor.c b/libnormalform_vxor.c
new file mode 100644
index 0000000..5ea8ab9
--- /dev/null
+++ b/libnormalform_vxor.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vxor)(LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_VALIST
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_vxor_checked.c b/libnormalform_vxor_checked.c
new file mode 100644
index 0000000..a9db606
--- /dev/null
+++ b/libnormalform_vxor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_vxor_checked)(size_t, LIBNORMALFORM_SENTENCE *, va_list);
+
+
+#else
+
+#define USE_CHECKED_VALIST
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_xnor.c b/libnormalform_xnor.c
new file mode 100644
index 0000000..3b0bff6
--- /dev/null
+++ b/libnormalform_xnor.c
@@ -0,0 +1,437 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_xnor)(LIBNORMALFORM_SENTENCE **xs)
+{
+ size_t n = 0;
+
+ while (xs[n])
+ n += 1;
+
+ /* ⨀(X ∪ {x}) = ⨀X ⊙ x = ¬(⨀X ⊕ x) */
+ /* ⨀∅ = ⨀∅ ⊙ 1 = ⨀∅ ⊙ 1 ⊙ 1 = ⨀(∅ ∪ {1, 1}) = ⨀{1, 1} = 1 ⊙ 1 = 1 = ¬0 = ¬⨁∅ */
+ if (n & 1U)
+ return libnormalform_xor(xs);
+ else
+ return libnormalform_not(libnormalform_xor(xs));
+}
+
+
+#else
+
+
+#define XNOR(...) LIBNORMALFORM_XNOR(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!XNOR(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!XNOR(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!XNOR(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!XNOR(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!XNOR(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!XNOR(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = XNOR(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = XNOR(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL3(VALUE, V1, V2, V3)\
+ do {\
+ ASSUME(a = XNOR(REF(v1), REF(v2), REF(v3)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ var3.value = V3##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(TRUE); /* XNOR () -> TRUE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* XNOR (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* XNOR (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(TRUE, F, F); /* XNOR (FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, F, T); /* XNOR (FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, F); /* XNOR (TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T); /* XNOR (TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL2(TRUE, F, F); /* XNOR (var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, F, T); /* XNOR (var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL2(FALSE, T, F); /* XNOR (var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, T, T); /* XNOR (var(TRUE), var(TRUE)) => TRUE */
+
+#ifndef USE_TWO
+
+ ASSERT_CONST(FALSE, F, F, F); /* XNOR (FALSE, FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, F, T); /* XNOR (FALSE, FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T, F); /* XNOR (FALSE, TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, F, T, T); /* XNOR (FALSE, TRUE, TRUE) -> FALSE */
+ ASSERT_CONST(TRUE, T, F, F); /* XNOR (TRUE, FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, T, F, T); /* XNOR (TRUE, FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, T, F); /* XNOR (TRUE, TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T, T); /* XNOR (TRUE, TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL3(FALSE, F, F, F); /* XNOR (var(FALSE), var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, F, F, T); /* XNOR (var(FALSE), var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL3(TRUE, F, T, F); /* XNOR (var(FALSE), var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(FALSE, F, T, T); /* XNOR (var(FALSE), var(TRUE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(TRUE, T, F, F); /* XNOR (var(TRUE), var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(FALSE, T, F, T); /* XNOR (var(TRUE), var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(FALSE, T, T, F); /* XNOR (var(TRUE), var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, T, T, T); /* XNOR (var(TRUE), var(TRUE), var(TRUE)) => TRUE */
+
+ /* XNOR (x) -> x */
+ ASSUME(a = XNOR(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* XNOR (x, FALSE) -> NOT x */
+ ASSUME(a = XNOR(REF(v1), F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (FALSE, x) -> NOT x */
+ ASSUME(a = XNOR(F, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (x, TRUE) -> x */
+ ASSUME(a = XNOR(REF(v1), T));
+ ASSERT_EQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (TRUE, x) -> x */
+ ASSUME(a = XNOR(T, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* XNOR (x, FALSE, FALSE) -> x */
+ ASSUME(a = XNOR(REF(v1), F, F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XNOR (x, FALSE, TRUE) -> NOT x */
+ ASSUME(a = XNOR(REF(v1), F, T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (x, TRUE, FALSE) -> NOT x */
+ ASSUME(a = XNOR(REF(v1), T, F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (x, TRUE, TRUE) -> x */
+ ASSUME(a = XNOR(REF(v1), T, T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XNOR (FALSE, x, FALSE) -> x */
+ ASSUME(a = XNOR(F, REF(v1), F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XNOR (FALSE, x, TRUE) -> NOT x */
+ ASSUME(a = XNOR(F, REF(v1), T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (TRUE, x, FALSE) -> NOT x */
+ ASSUME(a = XNOR(T, REF(v1), F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (TRUE, x, TRUE) -> x */
+ ASSUME(a = XNOR(T, REF(v1), T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XNOR (FALSE, FALSE, x) -> x */
+ ASSUME(a = XNOR(F, F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XNOR (FALSE, TRUE, x) -> NOT x */
+ ASSUME(a = XNOR(F, T, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (TRUE, FALSE, x) -> NOT x */
+ ASSUME(a = XNOR(T, F, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XNOR (TRUE, TRUE, x) -> x */
+ ASSUME(a = XNOR(T, T, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#endif
+
+ /* XNOR (x, x) -> TRUE */
+ ASSUME(a = XNOR(REF(v1), REF(v1)));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* XNOR (x, x, FALSE) -> FALSE */
+ ASSUME(a = XNOR(REF(v1), REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* XNOR (x, x, FALSE, FALSE) -> TRUE */
+ ASSUME(a = XNOR(REF(v1), REF(v1), F, F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* XNOR (x, x, TRUE) -> TRUE */
+ ASSUME(a = XNOR(REF(v1), REF(v1), T));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+#endif
+
+ /* XNOR (x, NOT x) -> FALSE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = XNOR(REF(v1), a));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* XNOR (x, NOT x, FALSE) -> TRUE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = XNOR(REF(v1), a, F));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* XNOR (x, NOT x, FALSE, FALSE) -> FALSE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = XNOR(REF(v1), a, F, F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* XNOR (x, NOT x, TRUE) -> FALSE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = XNOR(REF(v1), a, T));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+#endif
+
+ /* XNOR (x, y) -> NOT XOR (x, y) */
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* XNOR (x, y, z) -> NOT XOR (NOT XOR (x, y), z) */
+ ASSUME(a = XNOR(REF(v1), REF(v2), REF(v3)));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSUME(b = libnormalform_xor2(b, REF(v3)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+ /* XNOR (x, y) -> OR (AND (x, y), AND (NOT x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2__(a, b));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_OR);
+ ASSERT_EQUAL(a, b);
+ ASSERT_EQUAL(b, a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> AND (OR (x, NOT y), OR (NOT x, y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(b = libnormalform_and2__(a, b));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_AND);
+ ASSERT_EQUAL(a, b);
+ ASSERT_EQUAL(b, a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> OR (AND (x, y), AND (NOT x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> AND (OR (x, NOT y), OR (NOT x, y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> OR (AND (NOT x, NOT y), AND (x, y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2__(b, a));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_OR);
+ ASSERT_EQUAL(a, b);
+ ASSERT_EQUAL(b, a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> AND (OR (NOT x, y), OR (x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(b = libnormalform_and2__(b, a));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_AND);
+ ASSERT_EQUAL(a, b);
+ ASSERT_EQUAL(b, a);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> OR (AND (NOT x, NOT y), AND (x, y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = libnormalform_and2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_or2(b, a));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XNOR (x, y) -> AND (OR (NOT x, y), OR (x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_or2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_or2(b, REF(v2)));
+ ASSUME(b = libnormalform_and2(b, a));
+ ASSUME(a = XNOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+#undef ASSERT_EVAL3
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_xnor2.c b/libnormalform_xnor2.c
new file mode 100644
index 0000000..65939e7
--- /dev/null
+++ b/libnormalform_xnor2.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xnor2)(LIBNORMALFORM_SENTENCE *, LIBNORMALFORM_SENTENCE *);
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_xnor_checked.c b/libnormalform_xnor_checked.c
new file mode 100644
index 0000000..d559019
--- /dev/null
+++ b/libnormalform_xnor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xnor_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_xnorl.c b/libnormalform_xnorl.c
new file mode 100644
index 0000000..7646191
--- /dev/null
+++ b/libnormalform_xnorl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xnorl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_xnorl_checked.c b/libnormalform_xnorl_checked.c
new file mode 100644
index 0000000..5d4eb88
--- /dev/null
+++ b/libnormalform_xnorl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xnorl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_xnor.c"
+
+#endif
diff --git a/libnormalform_xnorl_macro_test.c b/libnormalform_xnorl_macro_test.c
new file mode 100644
index 0000000..af4857f
--- /dev/null
+++ b/libnormalform_xnorl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_xnor.c"
+#endif
diff --git a/libnormalform_xor.c b/libnormalform_xor.c
new file mode 100644
index 0000000..451fbf9
--- /dev/null
+++ b/libnormalform_xor.c
@@ -0,0 +1,710 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * Check if a sentence equivalent to a specific sentence
+ * or its inverse is in an array of sentences
+ *
+ * @param x The sentence to look for
+ * @param among The array of sentences to look in
+ * @param n The number of sentences in `among`
+ * @param index_out Output parameter for the offset, in `among`,
+ * of the found sentence
+ * @param inv_out Output parameter for equivalency of `x` and the
+ * sentence found in `among`; set to 0 if the two
+ * are equivalent or 1 if `x` is equivalent to the
+ * inverse of the sentence found in `among`
+ * @return 1 if a sentence equivalent to `x` or its inverse
+ * was found, 0 otherwise
+ */
+static int
+find(LIBNORMALFORM_SENTENCE *x, LIBNORMALFORM_SENTENCE **among, size_t n, size_t *index_out, int *inv_out)
+{
+ for (*index_out = 0; *index_out < n; ++*index_out)
+ if (x->equals(x, among[*index_out], inv_out))
+ return 1;
+ return 0;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_xor)(LIBNORMALFORM_SENTENCE **xs)
+{
+ LIBNORMALFORM_SENTENCE *x, *ret, **saved = xs;
+ size_t n = 0, i;
+ int inv;
+
+ /* ⨁∅ = {⨁X ⊕ (x ⊕ x) = ⨁X ⊕ 0 = ⨁X ⇒ ⨁X = ⨁(X ∪ {x, x})} = x ⊕ x = 0 */
+ ret = libnormalform_false();
+
+ for (; (x = *xs); xs++) {
+ if (x->type == TYPE_FALSE) {
+ /* ⨁X ⊕ 0 = ⨁X */
+ libnormalform_free(x);
+
+ } else if (x->type == TYPE_TRUE) {
+ /* ⨁X ⊕ 1 = ¬⨁X */
+ libnormalform_free(x);
+ invert:
+ ret = libnormalform_not(ret);
+
+ } else if (find(x, saved, n, &i, &inv)) {
+ /* ⨁X ⊕ x ⊕ x = ⨁X ⊕ (x ⊕ x) = ⨁X ⊕ 0 = ⨁X */
+ /* ⨁X ⊕ x ⊕ ¬x = ⨁X ⊕ (x ⊕ ¬x) = ⨁X ⊕ 1 = ¬⨁X */
+ libnormalform_free(saved[i]);
+ libnormalform_free(x);
+ saved[i] = saved[--n];
+ if (inv)
+ goto invert;
+
+ } else {
+ saved[n++] = x;
+ }
+ }
+
+ saved[n] = NULL;
+ /* ⨁(X ∪ x) = ⨁X ⊕ x */
+ for (; *saved; saved++)
+ ret = libnormalform_xor2(ret, *saved);
+
+ return ret;
+}
+
+
+#else
+
+
+#define XOR(...) LIBNORMALFORM_XOR(__VA_ARGS__)
+
+
+int
+main(void)
+{
+ TEST_BEGIN;
+
+ struct libnormalform_variable var1, var2, var3, var4;
+ struct libnormalform_function fun1, fun2;
+ struct libnormalform_map domain;
+ struct libnormalform_transformer trans;
+ LIBNORMALFORM_SENTENCE *a, *b, *v1, *v2, *v3, *v4, *f1, *f2;
+ LIBNORMALFORM_SENTENCE *ts, *fs;
+
+ ASSUME(v1 = libnormalform_variable(&var1));
+ ASSUME(v2 = libnormalform_variable(&var2));
+ ASSUME(v3 = libnormalform_variable(&var3));
+ ASSUME(v4 = libnormalform_variable(&var4));
+ ASSUME(f1 = libnormalform_function(&fun1));
+ ASSUME(f2 = libnormalform_function(&fun2));
+ ASSUME(ts = libnormalform_true());
+ ASSUME(fs = libnormalform_false());
+
+#ifdef USE_CHECKED_VERSION
+ errno = 0;
+ ASSERT(!XOR(REF(v1), NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!XOR(REF(v1), NULL) && errno == 1);
+ errno = 0;
+ ASSERT(!XOR(NULL, REF(v1)) && errno == 0);
+ errno = 1;
+ ASSERT(!XOR(NULL, REF(v1)) && errno == 1);
+ errno = 0;
+ ASSERT(!XOR(NULL, NULL) && errno == 0);
+ errno = 1;
+ ASSERT(!XOR(NULL, NULL) && errno == 1);
+#endif
+
+#define T REF(ts)
+#define F REF(fs)
+#define TV LIBNORMALFORM_TRUE
+#define FV LIBNORMALFORM_FALSE
+
+#define ASSERT_CONST(VALUE, ...)\
+ do {\
+ ASSUME(a = XOR(__VA_ARGS__));\
+ ASSERT(a->type == TYPE_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL2(VALUE, V1, V2)\
+ do {\
+ ASSUME(a = XOR(REF(v1), REF(v2)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#define ASSERT_EVAL3(VALUE, V1, V2, V3)\
+ do {\
+ ASSUME(a = XOR(REF(v1), REF(v2), REF(v3)));\
+ ASSERT(a->type != TYPE_TRUE && a->type != TYPE_FALSE);\
+ var1.value = V1##V;\
+ var2.value = V2##V;\
+ var3.value = V3##V;\
+ ASSERT(libnormalform_evaluate(a) == (int)LIBNORMALFORM_##VALUE);\
+ libnormalform_free(a);\
+ } while (0)
+
+#ifndef USE_TWO
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+ ASSERT_CONST(FALSE); /* XOR () -> FALSE */
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ ASSERT_CONST(TRUE, T); /* XOR (TRUE) -> TRUE */
+ ASSERT_CONST(FALSE, F); /* XOR (FALSE) -> FALSE */
+
+#endif
+
+ ASSERT_CONST(FALSE, F, F); /* XOR (FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, T); /* XOR (FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, T, F); /* XOR (TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, T, T); /* XOR (TRUE, TRUE) -> FALSE */
+
+ ASSERT_EVAL2(FALSE, F, F); /* XOR (var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL2(TRUE, F, T); /* XOR (var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL2(TRUE, T, F); /* XOR (var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL2(FALSE, T, T); /* XOR (var(TRUE), var(TRUE)) => FALSE */
+
+#ifndef USE_TWO
+
+ ASSERT_CONST(FALSE, F, F, F); /* XOR (FALSE, FALSE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, F, F, T); /* XOR (FALSE, FALSE, TRUE) -> TRUE */
+ ASSERT_CONST(TRUE, F, T, F); /* XOR (FALSE, TRUE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, F, T, T); /* XOR (FALSE, TRUE, TRUE) -> FALSE */
+ ASSERT_CONST(TRUE, T, F, F); /* XOR (TRUE, FALSE, FALSE) -> TRUE */
+ ASSERT_CONST(FALSE, T, F, T); /* XOR (TRUE, FALSE, TRUE) -> FALSE */
+ ASSERT_CONST(FALSE, T, T, F); /* XOR (TRUE, TRUE, FALSE) -> FALSE */
+ ASSERT_CONST(TRUE, T, T, T); /* XOR (TRUE, TRUE, TRUE) -> TRUE */
+
+ ASSERT_EVAL3(FALSE, F, F, F); /* XOR (var(FALSE), var(FALSE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, F, F, T); /* XOR (var(FALSE), var(FALSE), var(TRUE)) => TRUE */
+ ASSERT_EVAL3(TRUE, F, T, F); /* XOR (var(FALSE), var(TRUE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(FALSE, F, T, T); /* XOR (var(FALSE), var(TRUE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(TRUE, T, F, F); /* XOR (var(TRUE), var(FALSE), var(FALSE)) => TRUE */
+ ASSERT_EVAL3(FALSE, T, F, T); /* XOR (var(TRUE), var(FALSE), var(TRUE)) => FALSE */
+ ASSERT_EVAL3(FALSE, T, T, F); /* XOR (var(TRUE), var(TRUE), var(FALSE)) => FALSE */
+ ASSERT_EVAL3(TRUE, T, T, T); /* XOR (var(TRUE), var(TRUE), var(TRUE)) => TRUE */
+
+ /* XOR (x) -> x */
+ ASSUME(a = XOR(REF(v1)));
+ ASSERT(a == v1);
+ ASSERT(a->refcount == 2);
+ libnormalform_free(a);
+
+#endif
+
+ /* XOR (x, TRUE) -> NOT x */
+ ASSUME(a = XOR(REF(v1), T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (TRUE, x) -> NOT x */
+ ASSUME(a = XOR(T, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (x, FALSE) -> x */
+ ASSUME(a = XOR(REF(v1), F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (FALSE, x) -> x */
+ ASSUME(a = XOR(F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* XOR (x, FALSE, FALSE) -> x */
+ ASSUME(a = XOR(REF(v1), F, F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (x, FALSE, TRUE) -> NOT x */
+ ASSUME(a = XOR(REF(v1), F, T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (x, TRUE, FALSE) -> NOT x */
+ ASSUME(a = XOR(REF(v1), T, F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (x, TRUE, TRUE) -> x */
+ ASSUME(a = XOR(REF(v1), T, T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (FALSE, x, FALSE) -> x */
+ ASSUME(a = XOR(F, REF(v1), F));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (FALSE, x, TRUE) -> NOT x */
+ ASSUME(a = XOR(F, REF(v1), T));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (TRUE, x, FALSE) -> NOT x */
+ ASSUME(a = XOR(T, REF(v1), F));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (TRUE, x, TRUE) -> x */
+ ASSUME(a = XOR(T, REF(v1), T));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (FALSE, FALSE, x) -> x */
+ ASSUME(a = XOR(F, F, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+ /* XOR (FALSE, TRUE, x) -> NOT x */
+ ASSUME(a = XOR(F, T, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (TRUE, FALSE, x) -> NOT x */
+ ASSUME(a = XOR(T, F, REF(v1)));
+ ASSERT_INVEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (TRUE, TRUE, x) -> x */
+ ASSUME(a = XOR(T, T, REF(v1)));
+ ASSERT_EQUAL(a, v1);
+ ASSERT(a == v1);
+ libnormalform_free(a);
+
+#endif
+
+ /* XOR (x, x) -> FALSE */
+ ASSUME(a = XOR(REF(v1), REF(v1)));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+#ifndef USE_TWO
+
+ /* XOR (x, x, FALSE) -> FALSE */
+ ASSUME(a = XOR(REF(v1), REF(v1), F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* XOR (x, x, FALSE, FALSE) -> FALSE */
+ ASSUME(a = XOR(REF(v1), REF(v1), F, F));
+ ASSERT(a->type == TYPE_FALSE);
+ libnormalform_free(a);
+
+ /* XOR (x, x, TRUE) -> TRUE */
+ ASSUME(a = XOR(REF(v1), REF(v1), T));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+#endif
+
+ /* XOR (x, NOT x) -> TRUE */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(a = XOR(REF(v1), a));
+ ASSERT(a->type == TYPE_TRUE);
+ libnormalform_free(a);
+
+ /* XOR (x, y) -> XOR (x, y) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (y, x) -> XOR (x, y) */
+ ASSUME(a = XOR(REF(v2), REF(v1)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#ifndef USE_TWO
+
+ /* XOR (TRUE, x, y) -> NOT XOR (x, y) */
+ ASSUME(a = XOR(T, REF(v1), REF(v2)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, TRUE, y) -> NOT XOR (x, y) */
+ ASSUME(a = XOR(REF(v1), T, REF(v2)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y, TRUE) -> NOT XOR (x, y) */
+ ASSUME(a = XOR(REF(v1), REF(v2), T));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_INVEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (FALSE, x, y) -> XOR (x, y) */
+ ASSUME(a = XOR(F, REF(v1), REF(v2)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, FALSE, y) -> XOR (x, y) */
+ ASSUME(a = XOR(REF(v1), F, REF(v2)));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y, FALSE) -> XOR (x, y) */
+ ASSUME(a = XOR(REF(v1), REF(v2), F));
+ ASSUME(b = XOR(REF(v1), REF(v2)));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#endif
+
+ /* NOT XOR (x, y) -> XOR (NOT x, y) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(a = libnormalform_not(a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_xorl(b, REF(v2), NULL));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NOT XOR (x, y) -> XOR (x, NOT y) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(a = libnormalform_not(a));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_xorl(REF(v1), b, NULL));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* NOT XOR (x, y) -> XOR (x, y, TRUE) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(a = libnormalform_not(a));
+ ASSUME(b = libnormalform_xorl(REF(v1), REF(v2), T, NULL));
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from XOR (x, z) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_xor2(REF(v1), REF(v3)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from XOR (z, x) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_xor2(REF(v3), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from XOR (z, w) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_xor2(REF(v3), REF(v4)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from AND (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from AND (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from AND (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from AND (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from OR (x, y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from OR (x, NOT y) */
+ ASSUME(a = REF(v1));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from OR (NOT x, y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = REF(v2));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from OR (NOT x, NOT y) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from TRUE */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_true());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from FALSE */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_false());
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from variable1 */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, v1);
+ libnormalform_free(a);
+
+ /* XOR (x, y) -> independence from NOT variable1 */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from function1 */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT_NOTEQUAL(a, f1);
+ libnormalform_free(a);
+
+ /* XOR (x, y) -> independence from NOT function1 */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_not(REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from T(function1) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_transformation(&trans, REF(f1)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from ALL (domain1, function1, function2) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_all(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from ANY (domain1, function1, function2) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_any(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from ONE (domain1, function1, function2) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> independence from NOT ONE (domain1, function1, function2) */
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_one(&domain, REF(f1), REF(f2)));
+ ASSUME(b = libnormalform_not(b));
+ ASSERT_NOTEQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> OR (AND (x, NOT y), AND (NOT x, y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(b = libnormalform_or2__(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_OR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> AND (OR (x, y), OR (NOT x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2__(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_AND);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> OR (AND (x, NOT y), AND (NOT x, y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> AND (OR (x, y), OR (NOT x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2(a, b));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> OR (AND (NOT x, y), AND (x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(b = libnormalform_or2__(b, a));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_OR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> AND (OR (NOT x, NOT y), OR (x, y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2__(b, a));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_AND);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> OR (AND (NOT x, y), AND (x, NOT y)) */
+ ASSUME(a = libnormalform_not(REF(v2)));
+ ASSUME(a = libnormalform_and2(REF(v1), a));
+ ASSUME(b = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_and2(b, REF(v2)));
+ ASSUME(b = libnormalform_or2(b, a));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+ /* XOR (x, y) -> AND (OR (NOT x, NOT y), OR (x, y)) */
+ ASSUME(a = libnormalform_not(REF(v1)));
+ ASSUME(b = libnormalform_not(REF(v2)));
+ ASSUME(b = libnormalform_or2(a, b));
+ ASSUME(a = libnormalform_or2(REF(v1), REF(v2)));
+ ASSUME(b = libnormalform_and2(b, a));
+ ASSUME(a = XOR(REF(v1), REF(v2)));
+ ASSERT(a->type == TYPE_XOR);
+ ASSERT(b->type == TYPE_XOR);
+ ASSERT_EQUAL(a, b);
+ libnormalform_free(a);
+ libnormalform_free(b);
+
+#undef T
+#undef F
+#undef TV
+#undef FV
+
+#undef ASSERT_CONST
+#undef ASSERT_EVAL2
+#undef ASSERT_EVAL3
+
+ libnormalform_free(v1);
+ libnormalform_free(v2);
+ libnormalform_free(v3);
+ libnormalform_free(v4);
+ libnormalform_free(f1);
+ libnormalform_free(f2);
+ libnormalform_free(ts);
+ libnormalform_free(fs);
+
+ /* cascading of evaluation failure is tested in libnormalform_function.c */
+
+ TEST_END;
+}
+
+
+#endif
diff --git a/libnormalform_xor2.c b/libnormalform_xor2.c
new file mode 100644
index 0000000..a00c575
--- /dev/null
+++ b/libnormalform_xor2.c
@@ -0,0 +1,63 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_xor2)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ int inv;
+
+ if (!l || !r) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+
+ if (l->equals(l, r, &inv)) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+
+ if (!inv) {
+ /* x ⊕ x = 0 */
+ return libnormalform_false();
+
+ } else {
+ /* x ⊕ ¬x = 1 */
+ return libnormalform_true();
+ }
+
+ } else if (l->type == TYPE_TRUE) {
+ /* 1 ⊕ x = (1 ∨ x) ∧ ¬(1 ∧ x) = 1 ∧ ¬x = ¬x */
+ r = libnormalform_not(r);
+ return_r:
+ libnormalform_free(l);
+ return r;
+
+ } else if (l->type == TYPE_FALSE) {
+ /* 0 ⊕ x = (0 ∨ x) ∧ ¬(0 ∧ x) = x ∧ ¬0 = x ∧ 1 = x */
+ goto return_r;
+
+ } else if (r->type == TYPE_TRUE) {
+ /* x ⊕ 1 = 1 ⊕ x = ¬x */
+ l = libnormalform_not(l);
+ return_l:
+ libnormalform_free(r);
+ return l;
+
+ } else if (r->type == TYPE_FALSE) {
+ /* x ⊕ 0 = 0 ⊕ x = x */
+ goto return_l;
+
+ } else {
+ return libnormalform_xor2__(l, r);
+ }
+}
+
+
+#else
+
+#define USE_TWO
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_xor2__.c b/libnormalform_xor2__.c
new file mode 100644
index 0000000..c495428
--- /dev/null
+++ b/libnormalform_xor2__.c
@@ -0,0 +1,174 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+/**
+ * See `.inverse` in `struct libnormalform_sentence` (XOR implementation)
+ */
+static LIBNORMALFORM_SENTENCE *
+xor_inverse(LIBNORMALFORM_SENTENCE *this)
+{
+ /* ¬(l ⊕ r) = ¬l ⊕ r, (¬l ⊕ ¬r = l ⊕ r) */
+ LIBNORMALFORM_SENTENCE *l, *r, *ret;
+ l = this->data.binary.l->inverse(this->data.binary.l);
+ if (!l)
+ return NULL;
+ r = libnormalform_ref(this->data.binary.r);
+ if (!r) {
+ libnormalform_free(l);
+ return NULL;
+ }
+ ret = libnormalform_xor2__(l, r);
+ if (ret && this->atom) {
+ ret->atom = this->atom;
+ ret->atom->refcount += 1;
+ }
+ return ret;
+}
+
+
+/**
+ * See `.equals` in `struct libnormalform_sentence` (XOR implementation)
+ */
+static int
+xor_equals(LIBNORMALFORM_SENTENCE *this, LIBNORMALFORM_SENTENCE *other, int *inv_out)
+{
+ LIBNORMALFORM_SENTENCE *tl, *tr, *oll, *olr;
+ LIBNORMALFORM_SENTENCE *ol, *or, *orl, *orr;
+ int inv;
+
+ if (other->type != TYPE_XOR) {
+ int invll, invrl, invlr, invrr;
+ if (this->hash != other->hash)
+ return 0;
+ if (other->type != TYPE_AND && other->type != TYPE_OR)
+ return 0;
+ /* a ⊕ b = (a ∨ b) ∧ (¬a ∨ ¬b) */
+ /* ¬(a ⊕ b) = (a ∧ b) ∨ (¬a ∧ ¬b) */
+ /* ¬(a ⊕ b) = (a ∨ ¬b) ∧ (¬a ∨ b) */
+ /* a ⊕ b = (a ∧ ¬b) ∨ (¬a ∧ b) */
+ tl = this->data.binary.l;
+ tr = this->data.binary.r;
+ ol = other->data.binary.l;
+ or = other->data.binary.r;
+ if (ol->hash != or->hash)
+ return 0;
+ if (ol->type != or->type)
+ return 0;
+ if ((ol->type ^ other->type) != (TYPE_AND ^ TYPE_OR))
+ return 0;
+ oll = ol->data.binary.l;
+ olr = ol->data.binary.r;
+ orl = or->data.binary.l;
+ orr = or->data.binary.r;
+ if (!tl->equals(tl, oll, &invll)) {
+ oll = ol->data.binary.r;
+ olr = ol->data.binary.l;
+ if (!tl->equals(tl, oll, &invll))
+ return 0;
+ }
+ if (!tl->equals(tl, orl, &invrl)) {
+ orl = or->data.binary.r;
+ orr = or->data.binary.l;
+ if (!tl->equals(tl, orl, &invrl))
+ return 0;
+ }
+ if (invll == invrl)
+ return 0;
+ if (!tr->equals(tr, olr, &invlr))
+ return 0;
+ if (!tr->equals(tr, orr, &invrr))
+ return 0;
+ if (invlr == invrr)
+ return 0;
+ *inv_out = (invll == invlr) == (other->type == TYPE_OR);
+ return 1;
+ }
+
+ tl = this->data.binary.l;
+ tr = this->data.binary.r;
+ ol = other->data.binary.l;
+ or = other->data.binary.r;
+
+ if (tl->hash != ol->hash || tr->hash != or->hash)
+ return 0;
+
+ if (!tl->equals(tl, ol, inv_out)) {
+ if (tl->hash == tr->hash) {
+ ol = other->data.binary.r;
+ or = other->data.binary.l;
+ if (!tl->equals(tl, ol, inv_out))
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+ if (!tr->equals(tr, or, &inv))
+ return 0;
+ *inv_out ^= inv;
+ return 1;
+}
+
+
+/**
+ * See `.evaluate` in `struct libnormalform_sentence` (XOR implementation)
+ */
+static int
+xor_evaluate(LIBNORMALFORM_SENTENCE *this, void *input)
+{
+ int l, r;
+
+ l = this->data.binary.l->evaluate(this->data.binary.l, input);
+ if (l < 0)
+ return l;
+
+ r = this->data.binary.r->evaluate(this->data.binary.r, input);
+ if (r < 0)
+ return r;
+
+ return l ^ r;
+}
+
+
+LIBNORMALFORM_SENTENCE *
+(libnormalform_xor2__)(LIBNORMALFORM_SENTENCE *l, LIBNORMALFORM_SENTENCE *r)
+{
+ static const struct libnormalform_sentence prototype = {
+ PROTOTYPE_COMMON,
+ .type = TYPE_XOR,
+ .inverse = &xor_inverse,
+ .equals = &xor_equals,
+ .evaluate = &xor_evaluate
+ };
+
+ LIBNORMALFORM_SENTENCE *ret = malloc(sizeof(*ret));
+ if (!ret) {
+ libnormalform_free(l);
+ libnormalform_free(r);
+ return NULL;
+ }
+ *ret = prototype;
+ ret->hash = XOR_HASH(l, r);
+ if (l->hash <= r->hash) {
+ ret->data.binary.l = l;
+ ret->data.binary.r = r;
+ } else {
+ ret->data.binary.l = r;
+ ret->data.binary.r = l;
+ }
+ return ret;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ return 0; /* indirectly tested */
+}
+
+
+#endif
diff --git a/libnormalform_xor_checked.c b/libnormalform_xor_checked.c
new file mode 100644
index 0000000..cad6bd3
--- /dev/null
+++ b/libnormalform_xor_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xor_checked)(size_t, LIBNORMALFORM_SENTENCE **);
+
+
+#else
+
+#define USE_CHECKED
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_xorl.c b/libnormalform_xorl.c
new file mode 100644
index 0000000..8a5e5c6
--- /dev/null
+++ b/libnormalform_xorl.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xorl)(LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_VARARGS
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_xorl_checked.c b/libnormalform_xorl_checked.c
new file mode 100644
index 0000000..eff35c4
--- /dev/null
+++ b/libnormalform_xorl_checked.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+#include "common.h"
+
+
+extern inline LIBNORMALFORM_SENTENCE *(libnormalform_xorl_checked)(size_t, LIBNORMALFORM_SENTENCE *, ...);
+
+
+#else
+
+#define USE_CHECKED_VARARGS
+#include "libnormalform_xor.c"
+
+#endif
diff --git a/libnormalform_xorl_macro_test.c b/libnormalform_xorl_macro_test.c
new file mode 100644
index 0000000..21ce6ed
--- /dev/null
+++ b/libnormalform_xorl_macro_test.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef TEST
+#define USE_VARARGS_MACRO
+#include "libnormalform_xor.c"
+#endif
diff --git a/man3/LIBNORMALFORM_AND.3 b/man3/LIBNORMALFORM_AND.3
new file mode 120000
index 0000000..1cc3b47
--- /dev/null
+++ b/man3/LIBNORMALFORM_AND.3
@@ -0,0 +1 @@
+libnormalform_and.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_IF.3 b/man3/LIBNORMALFORM_IF.3
new file mode 120000
index 0000000..f5174b0
--- /dev/null
+++ b/man3/LIBNORMALFORM_IF.3
@@ -0,0 +1 @@
+libnormalform_if.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_IMPLY.3 b/man3/LIBNORMALFORM_IMPLY.3
new file mode 120000
index 0000000..8956d98
--- /dev/null
+++ b/man3/LIBNORMALFORM_IMPLY.3
@@ -0,0 +1 @@
+libnormalform_imply.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_NAND.3 b/man3/LIBNORMALFORM_NAND.3
new file mode 120000
index 0000000..e25f19a
--- /dev/null
+++ b/man3/LIBNORMALFORM_NAND.3
@@ -0,0 +1 @@
+libnormalform_nand.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_NIF.3 b/man3/LIBNORMALFORM_NIF.3
new file mode 120000
index 0000000..d269483
--- /dev/null
+++ b/man3/LIBNORMALFORM_NIF.3
@@ -0,0 +1 @@
+libnormalform_nif.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_NIMPLY.3 b/man3/LIBNORMALFORM_NIMPLY.3
new file mode 120000
index 0000000..47711fc
--- /dev/null
+++ b/man3/LIBNORMALFORM_NIMPLY.3
@@ -0,0 +1 @@
+libnormalform_nimply.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_NOR.3 b/man3/LIBNORMALFORM_NOR.3
new file mode 120000
index 0000000..dd76dbe
--- /dev/null
+++ b/man3/LIBNORMALFORM_NOR.3
@@ -0,0 +1 @@
+libnormalform_nor.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_OR.3 b/man3/LIBNORMALFORM_OR.3
new file mode 120000
index 0000000..25bea64
--- /dev/null
+++ b/man3/LIBNORMALFORM_OR.3
@@ -0,0 +1 @@
+libnormalform_or.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_SENTENCE.3 b/man3/LIBNORMALFORM_SENTENCE.3
new file mode 100644
index 0000000..8af097e
--- /dev/null
+++ b/man3/LIBNORMALFORM_SENTENCE.3
@@ -0,0 +1,61 @@
+.TH LIBNORMALFORM_SENTENCE 3 LIBNORMALFORM
+.SH NAME
+LIBNORMALFORM_SENTENCE \- Logical sentence description object
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+typedef struct libnormalform_sentence LIBNORMALFORM_SENTENCE;
+.fi
+
+.SH DESCRIPTION
+The
+.B LIBNORMALFORM_SENTENCE
+type is a reference counted opaque data type used to
+describe logical sentences of the first order.
+.PP
+Instances of this type can have there references count
+increased using the
+.BR libnormalform_ref (3)
+function and decreased (and deallocated if 0 is reached)
+using the
+.BR libnormalform_free (3)
+function.
+.PP
+Because this type contains temporary memory and caches,
+and not mechanisms to avoid race conditions, instances
+of this type cannot be safely used by any function from
+two threads at the same time. However, the function
+.BR libnormalform_clone (3)
+can be used to create a brand new but identical instance
+which can safely be used in another thread.
+.PP
+Instances can be serialised, as human-readable strings,
+and deserialised using the
+.BR libnormalform_to_string (3)
+and
+.BR libnormalform_from_string (3)
+functions.
+.PP
+Apart from the
+.BR libnormalform_ref (3)
+and
+.BR libnormalform_clone (3)
+functions, every function that return objects of this
+type, will acquire the ownership of the any reference
+to instances of this type passed into the function,
+they will also fail without modifying
+.I errno
+if any of them are
+.IR NULL ,
+but they will still acquire the ownership of the
+references in this case or any other time they fail.
+
+.SH NOTES
+The name
+.B struct libnormalform_sentence
+is exposed only for type safety and should not be used.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/LIBNORMALFORM_XNOR.3 b/man3/LIBNORMALFORM_XNOR.3
new file mode 120000
index 0000000..49b2b6c
--- /dev/null
+++ b/man3/LIBNORMALFORM_XNOR.3
@@ -0,0 +1 @@
+libnormalform_xnor.3 \ No newline at end of file
diff --git a/man3/LIBNORMALFORM_XOR.3 b/man3/LIBNORMALFORM_XOR.3
new file mode 120000
index 0000000..7da4f32
--- /dev/null
+++ b/man3/LIBNORMALFORM_XOR.3
@@ -0,0 +1 @@
+libnormalform_xor.3 \ No newline at end of file
diff --git a/man3/enum_libnormalform_builtin_transformer.3 b/man3/enum_libnormalform_builtin_transformer.3
new file mode 120000
index 0000000..9bb1a97
--- /dev/null
+++ b/man3/enum_libnormalform_builtin_transformer.3
@@ -0,0 +1 @@
+struct_libnormalform_transformer.3 \ No newline at end of file
diff --git a/man3/enum_libnormalform_value.3 b/man3/enum_libnormalform_value.3
new file mode 120000
index 0000000..8d964dc
--- /dev/null
+++ b/man3/enum_libnormalform_value.3
@@ -0,0 +1 @@
+struct_libnormalform_variable.3 \ No newline at end of file
diff --git a/man3/libnormalform_all.3 b/man3/libnormalform_all.3
new file mode 100644
index 0000000..901bba2
--- /dev/null
+++ b/man3/libnormalform_all.3
@@ -0,0 +1,202 @@
+.TH LIBNORMALFORM_ALL 3 LIBNORMALFORM
+.SH NAME
+libnormalform_all \- Universal qualifier
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+struct libnormalform_mapping {
+ void *\fIkey\fP;
+ void *\fIvalue\fP;
+};
+
+struct libnormalform_map {
+ struct libnormalform_mapping *\fImappings\fP;
+ size_t \fInmappings\fP;
+ void *\fIuser_data\fP;
+ const char *\fIidentifier\fP;
+ struct libnormalform_map *\fIcopy_for_clone\fP;
+};
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_all(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_universally(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_all ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+for every element in
+.I d
+for which
+.I k
+is also true for the
+.I .key
+of the element.
+.PP
+The
+.BR libnormalform_universally ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+of every element in
+.IR d .
+.PP
+.I d->mappings
+and
+.I d->nmappings
+is used only be the
+.BR libnormalform_evaluate (3)
+function and must be set before
+.BR libnormalform_evaluate (3)
+is called, but need not be set
+earlier.
+.I d->nmappings
+shall be set to number of elements in
+the qualifers domain of interest, and
+.I d->mappings
+shall be set to the list of elements
+in the domain. Each element shall have its
+.I .key
+set to the value the antecedent formula
+.RI ( k )
+is tested on, and
+.I .value
+set to the value the predicate formula
+.RI ( v )
+is tested on; these fields may be
+.IR NULL ,
+and are ultimately evaluated via
+arguments passed to the
+.BR libnormalform_function (3)
+function but may undergo transformation
+via arguments passed to the
+.BR libnormalform_transformation (3)
+function on the way.
+.PP
+The values
+.I d->mappings
+and
+.I d->nmappings
+may be set differently every time the
+.BR libnormalform_evaluate (3)
+is called.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR d->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR d->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+The application can set
+.I d->user_data
+set freely, and can opt to leave it
+unset. It is never used or reference
+by the library, but it could be used
+by the application to identify the
+domain.
+.PP
+.I d
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_all ()
+and
+.BR libnormalform_universally ()
+functions return an object representing
+the sentence; otherwise, the functions
+return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_all ()
+and
+.BR libnormalform_universally ()
+functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+These functions will also fail without setting
+.I errno
+if
+.I k
+or
+.I v
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_all (),
+.br
+.BR libnormalform_universally ()
+T} Thread safety MT-Safe race:\fIk\fP,\fIv\fP
+T{
+.BR libnormalform_all (),
+.br
+.BR libnormalform_universally ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_all (),
+.br
+.BR libnormalform_universally ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_function (3)
diff --git a/man3/libnormalform_and.3 b/man3/libnormalform_and.3
new file mode 100644
index 0000000..bb65a74
--- /dev/null
+++ b/man3/libnormalform_and.3
@@ -0,0 +1,233 @@
+.TH LIBNORMALFORM_AND 3 LIBNORMALFORM
+.SH NAME
+libnormalform_and \- Conjunction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_and(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_andl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vand(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_and_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_andl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vand_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_and2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_AND(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_and ()
+function creates a sentence that is logically
+equivalent to the conjunction of the arguments,
+that is, a sentence that is true when and only
+when all subsentences are true.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_andl ()
+function is a variant of
+.BR libnormalform_and ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vand ()
+function is a variant of
+.BR libnormalform_andl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_and_checked (),
+.BR libnormalform_andl_checked (),
+and
+.BR libnormalform_vand_checked ()
+functions are variants of the
+.BR libnormalform_and (),
+.BR libnormalform_andl (),
+and
+.BR libnormalform_vand ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_and2(p, q)"
+is equivalent to
+.IR "libnormalform_andl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_AND
+macro is a wrapper for the
+.BR libnormalform_and_checked ()
+but is more similar to
+.BR libnormalform_andl ().
+Unlike
+.BR libnormalform_andl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_and ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_and_checked (),
+.BR libnormalform_andl_checked (),
+and
+.BR libnormalform_vand_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_and2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_and (),
+.br
+.BR libnormalform_and_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_andl (),
+.br
+.BR libnormalform_andl_checked (),
+.br
+.BR libnormalform_and2 (),
+.br
+.BR LIBNORMALFORM_AND ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vand (),
+.br
+.BR libnormalform_vand_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_and (),
+.br
+.BR libnormalform_andl (),
+.br
+.BR libnormalform_vand (),
+.br
+.BR libnormalform_and_checked (),
+.br
+.BR libnormalform_andl_checked (),
+.br
+.BR libnormalform_vand_checked (),
+.br
+.BR libnormalform_vand2 (),
+.br
+.BR LIBNORMALFORM_AND ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_and (),
+.br
+.BR libnormalform_andl (),
+.br
+.BR libnormalform_vand (),
+.br
+.BR libnormalform_and_checked (),
+.br
+.BR libnormalform_andl_checked (),
+.br
+.BR libnormalform_vand_checked (),
+.br
+.BR libnormalform_vand2 (),
+.br
+.BR LIBNORMALFORM_AND ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a tautology.
+.PP
+The connective is commutative and associative. It's truthtable is (0001).
+.PP
+The
+.BR LIBNORMALFORM_AND ()
+macro requires ISO C23 support.
+.PP
+Using
+.BR libnormalform_and2 (),
+has greatest performance, however
+.BR libnormalform_and ()
+is better at simplifying the sentence.
+The other functions are just wrappers for the
+.BR libnormalform_and ()
+function.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_AND ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_and2.3 b/man3/libnormalform_and2.3
new file mode 120000
index 0000000..85d5d8b
--- /dev/null
+++ b/man3/libnormalform_and2.3
@@ -0,0 +1 @@
+libnormalform_and_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_and_checked.3 b/man3/libnormalform_and_checked.3
new file mode 120000
index 0000000..1cc3b47
--- /dev/null
+++ b/man3/libnormalform_and_checked.3
@@ -0,0 +1 @@
+libnormalform_and.3 \ No newline at end of file
diff --git a/man3/libnormalform_andl.3 b/man3/libnormalform_andl.3
new file mode 120000
index 0000000..1cc3b47
--- /dev/null
+++ b/man3/libnormalform_andl.3
@@ -0,0 +1 @@
+libnormalform_and.3 \ No newline at end of file
diff --git a/man3/libnormalform_andl_checked.3 b/man3/libnormalform_andl_checked.3
new file mode 120000
index 0000000..85d5d8b
--- /dev/null
+++ b/man3/libnormalform_andl_checked.3
@@ -0,0 +1 @@
+libnormalform_and_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_any.3 b/man3/libnormalform_any.3
new file mode 100644
index 0000000..ac44b0b
--- /dev/null
+++ b/man3/libnormalform_any.3
@@ -0,0 +1,294 @@
+.TH LIBNORMALFORM_ANY 3 LIBNORMALFORM
+.SH NAME
+libnormalform_any \- Existential qualifier
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+struct libnormalform_mapping {
+ void *\fIkey\fP;
+ void *\fIvalue\fP;
+};
+
+struct libnormalform_map {
+ struct libnormalform_mapping *\fImappings\fP;
+ size_t \fInmappings\fP;
+ void *\fIuser_data\fP;
+ const char *\fIidentifier\fP;
+ struct libnormalform_map *\fIcopy_for_clone\fP;
+};
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_any(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_exists(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_nexists(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_existentially(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_empty(struct libnormalform_map *\fId\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_nonempty(struct libnormalform_map *\fId\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_any ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+for at least one element in
+.I d
+for which
+.I k
+is also true for the
+.I .key
+of the element.
+.PP
+The
+.BR libnormalform_exists ()
+function creates a sentence that is true
+when and only when the sentence
+.I k
+is true for the
+.I .key
+of at least one element in
+.IR d .
+.PP
+The
+.BR libnormalform_nexists ()
+function creates a sentence that is true
+when and only when the sentence
+.I k
+is false for the
+.I .key
+of every element in
+.IR d .
+.PP
+The
+.BR libnormalform_existentially ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+of at least one element in
+.IR d .
+.PP
+The
+.BR libnormalform_empty ()
+function creates a sentence that is true
+when and only when
+.IR d
+contains no elements.
+.PP
+The
+.BR libnormalform_nonempty ()
+function creates a sentence that is true
+when and only when
+.IR d
+contains at least one element.
+.PP
+.I d->mappings
+and
+.I d->nmappings
+is used only be the
+.BR libnormalform_evaluate (3)
+function and must be set before
+.BR libnormalform_evaluate (3)
+is called, but need not be set
+earlier.
+.I d->nmappings
+shall be set to number of elements in
+the qualifers domain of interest, and
+.I d->mappings
+shall be set to the list of elements
+in the domain. Each element shall have its
+.I .key
+set to the value the antecedent formula
+.RI ( k )
+is tested on, and
+.I .value
+set to the value the predicate formula
+.RI ( v )
+is tested on; these fields may be
+.IR NULL ,
+and are ultimately evaluated via
+arguments passed to the
+.BR libnormalform_function (3)
+function but may undergo transformation
+via arguments passed to the
+.BR libnormalform_transformation (3)
+function on the way.
+.PP
+The values
+.I d->mappings
+and
+.I d->nmappings
+may be set differently every time the
+.BR libnormalform_evaluate (3)
+is called.
+.PP
+Although unused, the
+.BR libnormalform_empty ()
+and
+.BR libnormalform_nonempty ()
+functions require that
+.I d->mappings
+is properly set up (although the values
+in it can all be
+.IR NULL )
+before the
+.BR libnormalform_evaluate (3)
+function is called.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR d->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR d->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+The application can set
+.I d->user_data
+set freely, and can opt to leave it
+unset. It is never used or reference
+by the library, but it could be used
+by the application to identify the
+domain.
+.PP
+.I d
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_any (),
+.BR libnormalform_exists (),
+.BR libnormalform_nexists (),
+.BR libnormalform_existentially (),
+.BR libnormalform_empty (),
+and
+.BR libnormalform_nonempty (),
+functions return an object representing
+the sentence; otherwise, the functions
+return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_any (),
+.BR libnormalform_exists (),
+.BR libnormalform_nexists (),
+.BR libnormalform_existentially (),
+.BR libnormalform_empty (),
+and
+.BR libnormalform_nonempty (),
+functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+These functions will also fail without setting
+.I errno
+if
+.I k
+or
+.I v
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_any (),
+.br
+.BR libnormalform_exists (),
+.br
+.BR libnormalform_nexists (),
+.br
+.BR libnormalform_existentially ()
+T} Thread safety MT-Safe race:\fIk\fP,\fIv\fP
+T{
+.BR libnormalform_empty (),
+.br
+.BR libnormalform_nonempty ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_any (),
+.br
+.BR libnormalform_exists (),
+.br
+.BR libnormalform_nexists (),
+.br
+.BR libnormalform_existentially (),
+.br
+.BR libnormalform_empty (),
+.br
+.BR libnormalform_nonempty ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_any (),
+.br
+.BR libnormalform_exists (),
+.br
+.BR libnormalform_nexists (),
+.br
+.BR libnormalform_existentially (),
+.br
+.BR libnormalform_empty (),
+.br
+.BR libnormalform_nonempty ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_function (3)
diff --git a/man3/libnormalform_builtin_transformer.3 b/man3/libnormalform_builtin_transformer.3
new file mode 120000
index 0000000..935c5c4
--- /dev/null
+++ b/man3/libnormalform_builtin_transformer.3
@@ -0,0 +1 @@
+enum_libnormalform_builtin_transformer.3 \ No newline at end of file
diff --git a/man3/libnormalform_clone.3 b/man3/libnormalform_clone.3
new file mode 100644
index 0000000..0906a44
--- /dev/null
+++ b/man3/libnormalform_clone.3
@@ -0,0 +1,104 @@
+.TH LIBNORMALFORM_CLONE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_clone \- Create a deep clone
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_clone(LIBNORMALFORM_SENTENCE *\fIx\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_clone ()
+function creates a deep clone of
+.IR x ,
+letting the application use the same
+sentence in concurrently from two threads.
+.PP
+Before calling the
+.BR libnormalform_clone ()
+function, application provided objects in
+.I x
+must be configured for cloning:
+.I .copy_for_clone
+in each
+.IR "struct libnormalform_variable" ,
+.IR "struct libnormalform_function" ,
+.IR "struct libnormalform_map" ,
+and
+.IR "struct libnormalform_transformer" ,
+shall either be set to the copy of the
+object to use in the clone of
+.IR x ,
+or to
+.I NULL
+if the object can safely be used by the
+clone of
+.IR x .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_clone ()
+function returns a deep clone of
+.IR x ;
+otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_clone ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_clone ()
+function also fails without setting
+.I errno
+if
+.I x
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_clone ()
+T} Thread safety MT-Safe race:\fIx\fP
+T{
+.BR libnormalform_clone ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_clone ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_ref (3),
+.BR libnormalform_free (3)
diff --git a/man3/libnormalform_empty.3 b/man3/libnormalform_empty.3
new file mode 120000
index 0000000..899f515
--- /dev/null
+++ b/man3/libnormalform_empty.3
@@ -0,0 +1 @@
+libnormalform_any.3 \ No newline at end of file
diff --git a/man3/libnormalform_evaluate.3 b/man3/libnormalform_evaluate.3
new file mode 100644
index 0000000..12675ee
--- /dev/null
+++ b/man3/libnormalform_evaluate.3
@@ -0,0 +1,78 @@
+.TH LIBNORMALFORM_EVALUATE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_evaluate \- Determine value of a formula
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+int libnormalform_evaluate(LIBNORMALFORM_SENTENCE *\fIformula\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_evaluate ()
+function determines the value of
+.IR formula .
+.PP
+Before calling the
+.BR libnormalform_evaluate ()
+function, the application must configure application
+defined parts of the formula. That is, any
+.IR "struct libnormalform_variable" ,
+.IR "struct libnormalform_function" ,
+.IR "struct libnormalform_map" ,
+and
+.IR "struct libnormalform_transformer"
+must be set configuared accord to the specifications
+specified for the function it was used in.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_evaluate ()
+function returns 1 if the
+.I formula
+is true for configured input, 0 if the
+.I formula
+is false for configured input;
+otherwise, the function returns
+.IR -1 .
+
+.SH ERRORS
+The
+.BR libnormalform_evaluate ()
+function fails if an application function fails.
+The
+.BR libnormalform_evaluate ()
+function does not modify
+.IR errno ,
+but lets application functions modify
+.IR errno .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_evaluate ()
+T} Thread safety MT-Safe race:\fIsentence\fP
+T{
+.BR libnormalform_evaluate ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_evaluate ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_existentially.3 b/man3/libnormalform_existentially.3
new file mode 120000
index 0000000..899f515
--- /dev/null
+++ b/man3/libnormalform_existentially.3
@@ -0,0 +1 @@
+libnormalform_any.3 \ No newline at end of file
diff --git a/man3/libnormalform_exists.3 b/man3/libnormalform_exists.3
new file mode 120000
index 0000000..899f515
--- /dev/null
+++ b/man3/libnormalform_exists.3
@@ -0,0 +1 @@
+libnormalform_any.3 \ No newline at end of file
diff --git a/man3/libnormalform_false.3 b/man3/libnormalform_false.3
new file mode 100644
index 0000000..83dd97a
--- /dev/null
+++ b/man3/libnormalform_false.3
@@ -0,0 +1,70 @@
+.TH LIBNORMALFORM_FALSE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_false \- Contradiction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_false(void);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_false ()
+function creates a contradictory sentence:
+a sentence that is always false, regardless
+of the its containing formula's input.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_false ()
+function returns an object representing
+the sentence; otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_false ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_false ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_false ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_false ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_free.3 b/man3/libnormalform_free.3
new file mode 100644
index 0000000..7846dc5
--- /dev/null
+++ b/man3/libnormalform_free.3
@@ -0,0 +1,73 @@
+.TH LIBNORMALFORM_FREE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_free \- Release a reference
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+void libnormalform_free(/* LIBNORMALFORM_SENTENCE | struct libnormalform_term */ *\fIobj\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_free ()
+function can operate on both the type
+.I LIBNORMALFORM_SENTENCE *
+and the type
+.IR "struct libnormalform_term *" .
+The function recursively deallocates
+.IR obj ,
+however sence
+.I LIBNORMALFORM_SENTENCE *
+is reference counted,
+.IR obj ,
+if it is a
+.IR "LIBNORMALFORM_SENTENCE *" ,
+and any
+.IR "LIBNORMALFORM_SENTENCE *"
+stored in side it, will have it's reference
+count decreased by one, and the object is
+only deallocated (recursively) once the the
+reference count reaches 0.
+.PP
+No action is taken if
+.I obj
+is
+.IR NULL .
+
+.SH RETURN VALUE
+None.
+
+.SH ERRORS
+None.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_free ()
+T} Thread safety MT-Safe race:\fIobj\fP
+T{
+.BR libnormalform_free ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_free ()
+T} Async-cancel safety AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_ref (3),
+.BR libnormalform_free (3)
diff --git a/man3/libnormalform_from_string.3 b/man3/libnormalform_from_string.3
new file mode 100644
index 0000000..f0bfc98
--- /dev/null
+++ b/man3/libnormalform_from_string.3
@@ -0,0 +1,265 @@
+.TH LIBNORMALFORM_FROM_STRING 3 LIBNORMALFORM
+.SH NAME
+libnormalform_from_string \- Deserialise a sentence from a string
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+struct libnormalform_representation_spec {
+ void *\fIuser_data\fP;
+ struct libnormalform_variable *(*\fIget_variable\fP)(char *, char **, void *);
+ struct libnormalform_function *(*\fIget_function\fP)(char *, char **, void *);
+ struct libnormalform_map *(*\fIget_map\fP)(char *, char **, void *);
+ struct libnormalform_transformer *(*\fIget_transformer\fP)(char *, char **, void *);
+};
+
+LIBNORMALFORM_SENTENCE *libnormalform_from_string(/* optionally const */ char *\fIs\fP,
+ /* at least as const as \fIs\fP */ char **\fIend_out\fP,
+ const struct libnormalform_representation_spec *\fIspec\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_from_string ()
+function parse
+\fIs\fP
+and returns an equivalent sentence.
+.P
+If
+.I end_out
+is
+.RI non- NULL ,
+the position in
+.I s
+after the end of the representation is stored in
+.IR *end_out
+upon successful terminate (unspecified on failure),
+otherwise the function will validate that it
+only contains whitespace after the end of the
+representation.
+.PP
+.I s
+will not be modified by the
+.BR libnormalform_from_string ()
+function, however it will let user provided
+functions modify
+.IR s ,
+therefore
+.I s
+may be read-only if, and only if, the user provided
+functions do not modify
+.IR s .
+.PP
+.IR spec->get_variable ,
+.IR spec->get_function ,
+.IR spec->get_map ,
+and
+.IR spec->get_transformer ,
+may each be either
+.I NULL
+or a function pointers returns an object,
+according to their return type, described in
+the sentence. The functions shall return
+.I NULL
+on and only on failure. When these functions
+are called by the
+.BR libnormalform_from_string ()
+function, the first argument will be the beginning
+of the string they shall parse — these strings will
+not be properly terminated, — the second argument
+will be pointer that the end (the position immediately
+after the last byte) of the parsed string shall be
+stored (on success), and the third argument will be
+.I spec->user_data
+which is only used by the application for
+application-defined purposes.
+.I spec->user_data
+may be
+.IR NULL .
+.PP
+Neither
+.I s
+nor
+.I spec
+may be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_from_string ()
+sentence object equivalent to the sentence
+described by
+.IR s ;
+otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_from_string ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EINVAL
+The representation is invalid.
+.TP
+.I EINVAL
+.I end_out
+is
+.I NULL
+but there are non-whitespace characters in
+.I s
+after the detected end of the representation.
+.TP
+.I EDOM
+The described sentence contains an empty clause
+for a connective that does not support empty
+clauses.
+.TP
+.I EDOM
+The described sentence contains a transformation
+somewhere over a qualifer.
+.TP
+.I ENOENT
+The described sentence contains a boolean variable but
+.I spec->get_variable
+was
+.IR NULL .
+.TP
+.I ENOENT
+The described sentence contains a boolean function but
+.I spec->get_function
+was
+.IR NULL .
+.TP
+.I ENOENT
+The described sentence contains (a domain of interest
+for a) qualifier but
+.I spec->get_map
+was
+.IR NULL .
+.TP
+.I ENOENT
+The described sentence contains a transformation
+(a input transformation function) but
+.I spec->get_transformer
+was
+.IR NULL .
+.PP
+The
+.BR libnormalform_from_string ()
+function will also fail if
+.IR spec->get_variable ,
+.IR spec->get_function ,
+.IR spec->get_map ,
+or
+.IR spec->get_transformer
+fails, and will retain
+.I errno
+as set by the function that failed, or leave
+.I errno
+unmodified if the failing function did not
+modify
+.IR errno .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_from_string ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_from_string ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_from_string ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH EXTENDED DESCRIPTION
+The representation shall be on the whitespace-insensitive form:
+.PP
+.RS
+.nf
+\fIconstant\fP ::= "\fBTRUE\fP" | "\fBFALSE\fP";
+\fIconnective1\fP ::= "\fBNOT\fP";
+\fIconnective2\fP ::= "\fBAND\fP" | "\fBOR\fP" | "\fBXOR\fP" | "\fBIF\fP" | "\fBIMPLY\fP" |
+ "\fBNAND\fP" | "\fBNOR\fP" | "\fBXNOR\fP" | "\fBNIF\fP" | "\fBNIMPLY\fP";
+\fIqualifier0\fP ::= "\fBEMPTY\fP" | "\fBNONEMPTY\fP" | "\fBSINGLETON\fP";
+\fIqualifier1\fP ::= "\fBEXISTS\fP" | "\fBNEXISTS\fP" | "\fBUNIQUE\fP"
+ "\fBEXISTENTIALLY\fP" | "\fBUNIVERSALLY\fP" | "\fBUNIQUELY\fP";
+\fIqualifier2\fP ::= "\fBALL\fP" | "\fBANY\fP" | "\fBONE\fP";
+\fIunary\fP ::= \fIconnective1\fP [\fIreference-point\fP] "\fB(\fP" \fIsentence\fP "\fB)\fP";
+\fIvariadic\fP ::= \fIconnective2\fP [\fIreference-point\fP] "\fB(\fP" [\fIsentence-list\fP] "\fB)\fP";
+\fIsentence-list\fP ::= \fIsentence\fP ["\fB,\fP" \fIsentence-list\fP];
+\fIqualified0\fP ::= \fIqualifier0\fP [\fIreference-point\fP] "\fB(\fP" \fImap\fP "\fB)\fP";
+\fIqualified1\fP ::= \fIqualifier1\fP [\fIreference-point\fP] "\fB(\fP" \fImap\fP "\fB,\fP" \fIsentence\fP "\fB)\fP";
+\fIqualified2\fP ::= \fIqualifier2\fP [\fIreference-point\fP] "\fB(\fP" \fImap\fP "\fB,\fP" \fIsentence\fP "\fB,\fP" \fIsentence\fP "\fB)\fP";
+\fIatom\fP ::= \fIatom-var\fP | \fIatom-fun\fP;
+\fIatom-var\fP ::= "\fBVARIABLE\fP" [\fIreference-point\fP] "\fB(\fP" \fIvariable\fP "\fB)\fP";
+\fIatom-fun\fP ::= "\fBFUNCTION\fP" [\fIreference-point\fP] "\fB(\fP" \fIfunction\fP "\fB)\fP";
+\fItransformation\fP ::= "\fBTRANSFORMATION\fP" [\fIreference-point\fP] "\fB(\fP" \fItransformer\fP "\fB,\fP" \fIsentence\fP "\fB)\fP";
+\fIreference-point\fP ::= "\fB@\fP" \fIdecimal-number\fP;
+\fIreference\fP ::= "\fBREF(\fP" \fIdecimal-number\fP "\fB)\fP";
+\fIone-to-nine\fP ::= "\fB1\fP" | "\fB2\fP" | "\fB3\fP" | "\fB4\fP" | "\fB5\fP" | "\fB6\fP" | "\fB7\fP" | "\fB8\fP" | "\fB9\fP";
+\fIzero-to-nine\fP ::= "\fB0\fP" | \fIone-to-nine\fP;
+\fIdecimal-number\fP ::= "\fB0\fP" | \fIpositive-decimal\fP;
+\fIpositive-decimal\fP ::= \fIone-to-nine\fP [\fIdigits\fP];
+\fIdigits\fP ::= \fIzero-to-nine\fP [\fIdigits\fP];
+\fIsentence\fP ::= \fIconstant\fP | \fIunary\fP | \fIvariadic\fP | \fIqualified0\fP | \fIqualified1\fP |
+ \fIqualified2\fP |\fIatom\fP | \fItransformation\fP | \fIreference\fP;
+.fi
+.RE
+.PP
+where
+.I sentence
+is the root, and where
+.I map
+is parsed by
+.IR *spec->get_map ,
+.I variable
+is parsed by
+.IR *spec->get_variable ,
+.I function
+is parsed by
+.IR spec->get_function ,
+and
+.I transformer
+is parsed by
+.IR *spec->get_transformer .
+References must start at 0 and increment by one,
+in left-to-right order, every time a reference point
+is specified, and forward-references are not allowed
+(this includes reference to containing sentence (whose
+reference ID is lower because it is written earlier)).
+
+.SH NOTES
+The function will not attempt to deduplicate identical
+subsentences, but it will use any explicit deduplication.
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_from_string (3)
diff --git a/man3/libnormalform_function.3 b/man3/libnormalform_function.3
new file mode 100644
index 0000000..d89c0e9
--- /dev/null
+++ b/man3/libnormalform_function.3
@@ -0,0 +1,152 @@
+.TH LIBNORMALFORM_FUNCTION 3 LIBNORMALFORM
+.SH NAME
+libnormalform_function \- Function with boolean codomain
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+struct libnormalform_function {
+ int (*\fIevaluate\fP)(void *user_data, void *input);
+ void *\fIuser_data\fP;
+ const char *\fIidentifier\fP;
+ struct libnormalform_function *\fIcopy_for_clone\fP;
+ struct libnormalform_function *\fIrelaxation\fP;
+ int \fIrequires_relaxation\fP;
+};
+
+LIBNORMALFORM_SENTENCE *libnormalform_function(struct libnormalform_function *\fIfunction\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_function ()
+function creates a sentence object out
+of an application-managed function with
+a boolean output.
+.PP
+.I function->evaluate
+is used only be the
+.BR libnormalform_evaluate (3)
+function to determine the
+sentence's value for that specific
+call to the
+.BR libnormalform_evaluate (3)
+function.
+.I function->user_data is passed to
+.I *function->evaluate
+as its first argument, and the function
+input is passed as its second argument.
+The function is expected to return
+.I 1
+if the function is true for the input,
+.I 0
+if the function is false for the input,
+and
+.I -1
+on failure; on failure the function may
+optionally set
+.IR errno .
+.I function->user_data
+is only used by the application for
+application-defined purposes.
+.I function->user_data
+may, unlike
+.IR function->evalute ,
+be
+.IR NULL ;
+however
+.I function->evalute
+and
+.I function->user_data
+need not be set before the
+.BR libnormalform_evaluate (3)
+function is called.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR function->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR function->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+See
+.BR libnormalform_express (3)
+for the purpose of
+.I function->relaxation
+and
+.IR function->requires_relaxation ,
+they need not be set before any of the
+.BR libnormalform_express (3),
+.BR libnormalform_dnf (3),
+and
+.BR libnormalform_cnf (3)
+functions are called.
+.PP
+.I function
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_function ()
+function returns an sentence object of
+the function; otherwise, the function
+returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_function ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_function ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_function ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_function ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_variable (3)
diff --git a/man3/libnormalform_if.3 b/man3/libnormalform_if.3
new file mode 100644
index 0000000..86fde19
--- /dev/null
+++ b/man3/libnormalform_if.3
@@ -0,0 +1,233 @@
+.TH LIBNORMALFORM_IF 3 LIBNORMALFORM
+.SH NAME
+libnormalform_if \- Converse implication
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_if(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_ifl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vif(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_if_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_ifl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vif_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_if2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_IF(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_if ()
+function creates a sentence that is logically
+equivalent to the converse implication of the
+arguments, that is, a clause that is only false
+when the first term is false despite all other
+terms being true.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_ifl ()
+function is a variant of
+.BR libnormalform_if ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vif ()
+function is a variant of
+.BR libnormalform_ifl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_if_checked (),
+.BR libnormalform_ifl_checked (),
+and
+.BR libnormalform_vif_checked ()
+functions are variants of the
+.BR libnormalform_if (),
+.BR libnormalform_ifl (),
+and
+.BR libnormalform_vif ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_if2(p, q)"
+is equivalent to
+.IR "libnormalform_ifl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_IF
+macro is a wrapper for the
+.BR libnormalform_if_checked ()
+but is more similar to
+.BR libnormalform_ifl ().
+Unlike
+.BR libnormalform_ifl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_if ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EDOM
+The function was not given any subsentences.
+.PP
+The
+.BR libnormalform_if_checked (),
+.BR libnormalform_ifl_checked (),
+and
+.BR libnormalform_vif_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_if2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_if (),
+.br
+.BR libnormalform_if_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_ifl (),
+.br
+.BR libnormalform_ifl_checked (),
+.br
+.BR libnormalform_if2 (),
+.br
+.BR LIBNORMALFORM_IF ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vif (),
+.br
+.BR libnormalform_vif_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_if (),
+.br
+.BR libnormalform_ifl (),
+.br
+.BR libnormalform_vif (),
+.br
+.BR libnormalform_if_checked (),
+.br
+.BR libnormalform_ifl_checked (),
+.br
+.BR libnormalform_vif_checked (),
+.br
+.BR libnormalform_vif2 (),
+.br
+.BR LIBNORMALFORM_IF ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_if (),
+.br
+.BR libnormalform_ifl (),
+.br
+.BR libnormalform_vif (),
+.br
+.BR libnormalform_if_checked (),
+.br
+.BR libnormalform_ifl_checked (),
+.br
+.BR libnormalform_vif_checked (),
+.br
+.BR libnormalform_vif2 (),
+.br
+.BR LIBNORMALFORM_IF ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+The connective is non-commutative and left-associative. It's truthtable is (1101).
+.PP
+The
+.BR LIBNORMALFORM_IF ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_if ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_if ()
+function.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_IF ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_if2.3 b/man3/libnormalform_if2.3
new file mode 120000
index 0000000..5bc98a1
--- /dev/null
+++ b/man3/libnormalform_if2.3
@@ -0,0 +1 @@
+libnormalform_if_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_if_checked.3 b/man3/libnormalform_if_checked.3
new file mode 120000
index 0000000..f5174b0
--- /dev/null
+++ b/man3/libnormalform_if_checked.3
@@ -0,0 +1 @@
+libnormalform_if.3 \ No newline at end of file
diff --git a/man3/libnormalform_ifl.3 b/man3/libnormalform_ifl.3
new file mode 120000
index 0000000..f5174b0
--- /dev/null
+++ b/man3/libnormalform_ifl.3
@@ -0,0 +1 @@
+libnormalform_if.3 \ No newline at end of file
diff --git a/man3/libnormalform_ifl_checked.3 b/man3/libnormalform_ifl_checked.3
new file mode 120000
index 0000000..5bc98a1
--- /dev/null
+++ b/man3/libnormalform_ifl_checked.3
@@ -0,0 +1 @@
+libnormalform_if_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_imply.3 b/man3/libnormalform_imply.3
new file mode 100644
index 0000000..b6e0284
--- /dev/null
+++ b/man3/libnormalform_imply.3
@@ -0,0 +1,236 @@
+.TH LIBNORMALFORM_IMPLY 3 LIBNORMALFORM
+.SH NAME
+libnormalform_imply \- Material implication
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_imply(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_implyl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vimply(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_imply_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_implyl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vimply_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_imply2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_IMPLY(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_imply ()
+function creates a sentence that is logically
+equivalent to the material implication of the
+arguments, that is, a clause that is only false
+when the last term is false despite all other
+terms being true.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_implyl ()
+function is a variant of
+.BR libnormalform_imply ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vimply ()
+function is a variant of
+.BR libnormalform_implyl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_imply_checked (),
+.BR libnormalform_implyl_checked (),
+and
+.BR libnormalform_vimply_checked ()
+functions are variants of the
+.BR libnormalform_imply (),
+.BR libnormalform_implyl (),
+and
+.BR libnormalform_vimply ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_imply2(p, q)"
+is equivalent to
+.IR "libnormalform_implyl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_IMPLY
+macro is a wrapper for the
+.BR libnormalform_imply_checked ()
+but is more similar to
+.BR libnormalform_implyl ().
+Unlike
+.BR libnormalform_implyl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_imply ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_imply_checked (),
+.BR libnormalform_implyl_checked (),
+and
+.BR libnormalform_vimply_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_imply2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_imply (),
+.br
+.BR libnormalform_imply_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_implyl (),
+.br
+.BR libnormalform_implyl_checked (),
+.br
+.BR libnormalform_imply2 (),
+.br
+.BR LIBNORMALFORM_IMPLY ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vimply (),
+.br
+.BR libnormalform_vimply_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_imply (),
+.br
+.BR libnormalform_implyl (),
+.br
+.BR libnormalform_vimply (),
+.br
+.BR libnormalform_imply_checked (),
+.br
+.BR libnormalform_implyl_checked (),
+.br
+.BR libnormalform_vimply_checked (),
+.br
+.BR libnormalform_vimply2 (),
+.br
+.BR LIBNORMALFORM_IMPLY ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_imply (),
+.br
+.BR libnormalform_implyl (),
+.br
+.BR libnormalform_vimply (),
+.br
+.BR libnormalform_imply_checked (),
+.br
+.BR libnormalform_implyl_checked (),
+.br
+.BR libnormalform_vimply_checked (),
+.br
+.BR libnormalform_vimply2 (),
+.br
+.BR LIBNORMALFORM_IMPLY ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a tautology.
+.PP
+The connective is non-commutative and right-associative. It's truthtable is (1011).
+.PP
+The
+.BR LIBNORMALFORM_IMPLY ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_imply ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_imply ()
+function, however
+.BR libnormalform_imply ()
+itself uses the
+.BR libnormalform_if ()
+function if it has subsentences.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_IMPLY ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_imply2.3 b/man3/libnormalform_imply2.3
new file mode 120000
index 0000000..d9d72bd
--- /dev/null
+++ b/man3/libnormalform_imply2.3
@@ -0,0 +1 @@
+libnormalform_imply_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_imply_checked.3 b/man3/libnormalform_imply_checked.3
new file mode 120000
index 0000000..8956d98
--- /dev/null
+++ b/man3/libnormalform_imply_checked.3
@@ -0,0 +1 @@
+libnormalform_imply.3 \ No newline at end of file
diff --git a/man3/libnormalform_implyl.3 b/man3/libnormalform_implyl.3
new file mode 120000
index 0000000..8956d98
--- /dev/null
+++ b/man3/libnormalform_implyl.3
@@ -0,0 +1 @@
+libnormalform_imply.3 \ No newline at end of file
diff --git a/man3/libnormalform_implyl_checked.3 b/man3/libnormalform_implyl_checked.3
new file mode 120000
index 0000000..d9d72bd
--- /dev/null
+++ b/man3/libnormalform_implyl_checked.3
@@ -0,0 +1 @@
+libnormalform_imply_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_map.3 b/man3/libnormalform_map.3
new file mode 120000
index 0000000..907aac0
--- /dev/null
+++ b/man3/libnormalform_map.3
@@ -0,0 +1 @@
+struct_libnormalform_map.3 \ No newline at end of file
diff --git a/man3/libnormalform_mapping.3 b/man3/libnormalform_mapping.3
new file mode 120000
index 0000000..7609d21
--- /dev/null
+++ b/man3/libnormalform_mapping.3
@@ -0,0 +1 @@
+struct_libnormalform_mapping.3 \ No newline at end of file
diff --git a/man3/libnormalform_nand.3 b/man3/libnormalform_nand.3
new file mode 100644
index 0000000..a3604ae
--- /dev/null
+++ b/man3/libnormalform_nand.3
@@ -0,0 +1,250 @@
+.TH LIBNORMALFORM_NAND 3 LIBNORMALFORM
+.SH NAME
+libnormalform_nand \- Alternative denial
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_nand(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nandl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnand(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nand_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nandl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnand_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nand2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_NAND(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_nand ()
+function creates a sentence where each subsentence
+is connected with a NAND (alternative denial) operator.
+In the case that exactly two subsentences are given,
+this creates a clause that is true when and only when
+at least one is false. There is no generally meaningful
+interpretation if a NAND-clause containing more than
+two terms.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_nandl ()
+function is a variant of
+.BR libnormalform_nand ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vnand ()
+function is a variant of
+.BR libnormalform_nandl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_nand_checked (),
+.BR libnormalform_nandl_checked (),
+and
+.BR libnormalform_vnand_checked ()
+functions are variants of the
+.BR libnormalform_nand (),
+.BR libnormalform_nandl (),
+and
+.BR libnormalform_vnand ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_nand2(p, q)"
+is equivalent to
+.IR "libnormalform_nandl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_NAND
+macro is a wrapper for the
+.BR libnormalform_nand_checked ()
+but is more similar to
+.BR libnormalform_nandl ().
+Unlike
+.BR libnormalform_nandl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_nand ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EDOM
+The function was not given any subsentences.
+.PP
+The
+.BR libnormalform_nand_checked (),
+.BR libnormalform_nandl_checked (),
+and
+.BR libnormalform_vnand_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_nand2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_nand (),
+.br
+.BR libnormalform_nand_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_nandl (),
+.br
+.BR libnormalform_nandl_checked (),
+.br
+.BR libnormalform_nand2 (),
+.br
+.BR LIBNORMALFORM_NAND ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vnand (),
+.br
+.BR libnormalform_vnand_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_nand (),
+.br
+.BR libnormalform_nandl (),
+.br
+.BR libnormalform_vnand (),
+.br
+.BR libnormalform_nand_checked (),
+.br
+.BR libnormalform_nandl_checked (),
+.br
+.BR libnormalform_vnand_checked (),
+.br
+.BR libnormalform_vnand2 (),
+.br
+.BR LIBNORMALFORM_NAND ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_nand (),
+.br
+.BR libnormalform_nandl (),
+.br
+.BR libnormalform_vnand (),
+.br
+.BR libnormalform_nand_checked (),
+.br
+.BR libnormalform_nandl_checked (),
+.br
+.BR libnormalform_vnand_checked (),
+.br
+.BR libnormalform_vnand2 (),
+.br
+.BR LIBNORMALFORM_NAND ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+The connective is commutative and left-associative. It's truthtable is (1110).
+.PP
+The
+.BR LIBNORMALFORM_NAND ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_nand ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_nand ()
+function.
+.PP
+These functions creates a clause where each term
+is connected with the NAND connective. Unlike what
+the name Alternative denial imply, this does
+.I not
+create a sentence that is true when at most one
+subsentences is true. Nor does it unlike what the
+name Negated AND imply, this does
+.I not
+create a sentence that is true when at least
+one subsentence is false. There is unfortunately
+no generally meaningful interpretation: adding
+false term on the right always create a tautology,
+while adding a true term on the right inverts the
+original clause.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_NAND ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_nand2.3 b/man3/libnormalform_nand2.3
new file mode 120000
index 0000000..0a1c901
--- /dev/null
+++ b/man3/libnormalform_nand2.3
@@ -0,0 +1 @@
+libnormalform_nand_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nand_checked.3 b/man3/libnormalform_nand_checked.3
new file mode 120000
index 0000000..e25f19a
--- /dev/null
+++ b/man3/libnormalform_nand_checked.3
@@ -0,0 +1 @@
+libnormalform_nand.3 \ No newline at end of file
diff --git a/man3/libnormalform_nandl.3 b/man3/libnormalform_nandl.3
new file mode 120000
index 0000000..e25f19a
--- /dev/null
+++ b/man3/libnormalform_nandl.3
@@ -0,0 +1 @@
+libnormalform_nand.3 \ No newline at end of file
diff --git a/man3/libnormalform_nandl_checked.3 b/man3/libnormalform_nandl_checked.3
new file mode 120000
index 0000000..0a1c901
--- /dev/null
+++ b/man3/libnormalform_nandl_checked.3
@@ -0,0 +1 @@
+libnormalform_nand_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nexists.3 b/man3/libnormalform_nexists.3
new file mode 120000
index 0000000..eb792d0
--- /dev/null
+++ b/man3/libnormalform_nexists.3
@@ -0,0 +1 @@
+libnormalform_exists.3 \ No newline at end of file
diff --git a/man3/libnormalform_nif.3 b/man3/libnormalform_nif.3
new file mode 100644
index 0000000..174b9d4
--- /dev/null
+++ b/man3/libnormalform_nif.3
@@ -0,0 +1,239 @@
+.TH LIBNORMALFORM_NIF 3 LIBNORMALFORM
+.SH NAME
+libnormalform_nif \- Converse abjunction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_nif(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nifl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnif(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nif_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nifl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnif_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nif2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_NIF(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_nif ()
+function creates a sentence that is logically
+equivalent to the converse abjunction of the
+arguments.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_nifl ()
+function is a variant of
+.BR libnormalform_nif ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vnif ()
+function is a variant of
+.BR libnormalform_nifl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_nif_checked (),
+.BR libnormalform_nifl_checked (),
+and
+.BR libnormalform_vnif_checked ()
+functions are variants of the
+.BR libnormalform_nif (),
+.BR libnormalform_nifl (),
+and
+.BR libnormalform_vnif ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_nif2(p, q)"
+is equivalent to
+.IR "libnormalform_nifl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_NIF
+macro is a wrapper for the
+.BR libnormalform_nif_checked ()
+but is more similar to
+.BR libnormalform_nifl ().
+Unlike
+.BR libnormalform_nifl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_nif ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_nif_checked (),
+.BR libnormalform_nifl_checked (),
+and
+.BR libnormalform_vnif_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_nif2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_nif (),
+.br
+.BR libnormalform_nif_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_nifl (),
+.br
+.BR libnormalform_nifl_checked (),
+.br
+.BR libnormalform_nif2 (),
+.br
+.BR LIBNORMALFORM_NIF ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vnif (),
+.br
+.BR libnormalform_vnif_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_nif (),
+.br
+.BR libnormalform_nifl (),
+.br
+.BR libnormalform_vnif (),
+.br
+.BR libnormalform_nif_checked (),
+.br
+.BR libnormalform_nifl_checked (),
+.br
+.BR libnormalform_vnif_checked (),
+.br
+.BR libnormalform_vnif2 (),
+.br
+.BR LIBNORMALFORM_NIF ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_nif (),
+.br
+.BR libnormalform_nifl (),
+.br
+.BR libnormalform_vnif (),
+.br
+.BR libnormalform_nif_checked (),
+.br
+.BR libnormalform_nifl_checked (),
+.br
+.BR libnormalform_vnif_checked (),
+.br
+.BR libnormalform_vnif2 (),
+.br
+.BR LIBNORMALFORM_NIF ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a contradiction.
+.PP
+The connective is non-commutative and left-associative. It's truthtable is (0010).
+.PP
+The
+.BR LIBNORMALFORM_NIF ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_nif ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_nif ()
+function.
+.PP
+These functions creates a clause where each term is
+connected with the NIF connective. There is
+unfortunately no generally meaningful interpretation:
+adding a false term on the left does not change the
+result but adding a true term on the left inverts the
+result only if all terms are true, whereas adding false
+term on the right creates a contradiction, while adding
+a true term on the right inverts the original clause.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_NIF ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_nif2.3 b/man3/libnormalform_nif2.3
new file mode 120000
index 0000000..5949db4
--- /dev/null
+++ b/man3/libnormalform_nif2.3
@@ -0,0 +1 @@
+libnormalform_nif_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nif_checked.3 b/man3/libnormalform_nif_checked.3
new file mode 120000
index 0000000..d269483
--- /dev/null
+++ b/man3/libnormalform_nif_checked.3
@@ -0,0 +1 @@
+libnormalform_nif.3 \ No newline at end of file
diff --git a/man3/libnormalform_nifl.3 b/man3/libnormalform_nifl.3
new file mode 120000
index 0000000..d269483
--- /dev/null
+++ b/man3/libnormalform_nifl.3
@@ -0,0 +1 @@
+libnormalform_nif.3 \ No newline at end of file
diff --git a/man3/libnormalform_nifl_checked.3 b/man3/libnormalform_nifl_checked.3
new file mode 120000
index 0000000..5949db4
--- /dev/null
+++ b/man3/libnormalform_nifl_checked.3
@@ -0,0 +1 @@
+libnormalform_nif_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nimply.3 b/man3/libnormalform_nimply.3
new file mode 100644
index 0000000..ca7a0e9
--- /dev/null
+++ b/man3/libnormalform_nimply.3
@@ -0,0 +1,241 @@
+.TH LIBNORMALFORM_NIMPLY 3 LIBNORMALFORM
+.SH NAME
+libnormalform_nimply \- Material abjunction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_nimply(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nimplyl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnimply(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nimply_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nimplyl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnimply_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nimply2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_NIMPLY(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_nimply ()
+function creates a sentence that is logically
+equivalent to the material abjunction of the
+arguments.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_nimplyl ()
+function is a variant of
+.BR libnormalform_nimply ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vnimply ()
+function is a variant of
+.BR libnormalform_nimplyl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_nimply_checked (),
+.BR libnormalform_nimplyl_checked (),
+and
+.BR libnormalform_vnimply_checked ()
+functions are variants of the
+.BR libnormalform_nimply (),
+.BR libnormalform_nimplyl (),
+and
+.BR libnormalform_vnimply ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_nimply2(p, q)"
+is equivalent to
+.IR "libnormalform_nimplyl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_NIMPLY
+macro is a wrapper for the
+.BR libnormalform_nimply_checked ()
+but is more similar to
+.BR libnormalform_nimplyl ().
+Unlike
+.BR libnormalform_nimplyl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_nimply ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EDOM
+The function was not given any subsentences.
+.PP
+The
+.BR libnormalform_nimply_checked (),
+.BR libnormalform_nimplyl_checked (),
+and
+.BR libnormalform_vnimply_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_nimply2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_nimply (),
+.br
+.BR libnormalform_nimply_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_nimplyl (),
+.br
+.BR libnormalform_nimplyl_checked (),
+.br
+.BR libnormalform_nimply2 (),
+.br
+.BR LIBNORMALFORM_NIMPLY ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vnimply (),
+.br
+.BR libnormalform_vnimply_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_nimply (),
+.br
+.BR libnormalform_nimplyl (),
+.br
+.BR libnormalform_vnimply (),
+.br
+.BR libnormalform_nimply_checked (),
+.br
+.BR libnormalform_nimplyl_checked (),
+.br
+.BR libnormalform_vnimply_checked (),
+.br
+.BR libnormalform_vnimply2 (),
+.br
+.BR LIBNORMALFORM_NIMPLY ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_nimply (),
+.br
+.BR libnormalform_nimplyl (),
+.br
+.BR libnormalform_vnimply (),
+.br
+.BR libnormalform_nimply_checked (),
+.br
+.BR libnormalform_nimplyl_checked (),
+.br
+.BR libnormalform_vnimply_checked (),
+.br
+.BR libnormalform_vnimply2 (),
+.br
+.BR LIBNORMALFORM_NIMPLY ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+The connective is non-commutative and right-associative. It's truthtable is (0100).
+.PP
+The
+.BR LIBNORMALFORM_NIMPLY ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_nimply ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_nimply ()
+function.
+.PP
+These functions creates a clause where each term is
+connected with the NIMPLY connective. There is
+unfortunately no generally meaningful interpretation:
+adding false term on the left creates a contradiction,
+while adding a true term on the left inverts the
+original clause, whereas adding a false term on the
+right does not change the result but adding a true
+term on the right inverts the result only if all terms
+are true.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_NIMPLY ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_nimply2.3 b/man3/libnormalform_nimply2.3
new file mode 120000
index 0000000..adb482f
--- /dev/null
+++ b/man3/libnormalform_nimply2.3
@@ -0,0 +1 @@
+libnormalform_nimply_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nimply_checked.3 b/man3/libnormalform_nimply_checked.3
new file mode 120000
index 0000000..47711fc
--- /dev/null
+++ b/man3/libnormalform_nimply_checked.3
@@ -0,0 +1 @@
+libnormalform_nimply.3 \ No newline at end of file
diff --git a/man3/libnormalform_nimplyl.3 b/man3/libnormalform_nimplyl.3
new file mode 120000
index 0000000..47711fc
--- /dev/null
+++ b/man3/libnormalform_nimplyl.3
@@ -0,0 +1 @@
+libnormalform_nimply.3 \ No newline at end of file
diff --git a/man3/libnormalform_nimplyl_checked.3 b/man3/libnormalform_nimplyl_checked.3
new file mode 120000
index 0000000..adb482f
--- /dev/null
+++ b/man3/libnormalform_nimplyl_checked.3
@@ -0,0 +1 @@
+libnormalform_nimply_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nonempty.3 b/man3/libnormalform_nonempty.3
new file mode 120000
index 0000000..899f515
--- /dev/null
+++ b/man3/libnormalform_nonempty.3
@@ -0,0 +1 @@
+libnormalform_any.3 \ No newline at end of file
diff --git a/man3/libnormalform_nor.3 b/man3/libnormalform_nor.3
new file mode 100644
index 0000000..164e8c5
--- /dev/null
+++ b/man3/libnormalform_nor.3
@@ -0,0 +1,245 @@
+.TH LIBNORMALFORM_NOR 3 LIBNORMALFORM
+.SH NAME
+libnormalform_nor \- Alternative denial
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_nor(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_norl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnor(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_norl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vnor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_nor2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_NOR(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_nor ()
+function creates a sentence where each subsentence
+is connected with a NOR (alternative denial) operator.
+In the case that exactly two subsentences are given,
+this creates a clause that is true when and only when
+at least one is false. There is no generally meaningful
+interpretation if a NOR-clause containing more than
+two terms.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_norl ()
+function is a variant of
+.BR libnormalform_nor ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vnor ()
+function is a variant of
+.BR libnormalform_norl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_nor_checked (),
+.BR libnormalform_norl_checked (),
+and
+.BR libnormalform_vnor_checked ()
+functions are variants of the
+.BR libnormalform_nor (),
+.BR libnormalform_norl (),
+and
+.BR libnormalform_vnor ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_nor2(p, q)"
+is equivalent to
+.IR "libnormalform_norl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_NOR
+macro is a wrapper for the
+.BR libnormalform_nor_checked ()
+but is more similar to
+.BR libnormalform_norl ().
+Unlike
+.BR libnormalform_norl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_nor ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EDOM
+The function was not given any subsentences.
+.PP
+The
+.BR libnormalform_nor_checked (),
+.BR libnormalform_norl_checked (),
+and
+.BR libnormalform_vnor_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_nor2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_nor (),
+.br
+.BR libnormalform_nor_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_norl (),
+.br
+.BR libnormalform_norl_checked (),
+.br
+.BR libnormalform_nor2 (),
+.br
+.BR LIBNORMALFORM_NOR ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vnor (),
+.br
+.BR libnormalform_vnor_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_nor (),
+.br
+.BR libnormalform_norl (),
+.br
+.BR libnormalform_vnor (),
+.br
+.BR libnormalform_nor_checked (),
+.br
+.BR libnormalform_norl_checked (),
+.br
+.BR libnormalform_vnor_checked (),
+.br
+.BR libnormalform_vnor2 (),
+.br
+.BR LIBNORMALFORM_NOR ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_nor (),
+.br
+.BR libnormalform_norl (),
+.br
+.BR libnormalform_vnor (),
+.br
+.BR libnormalform_nor_checked (),
+.br
+.BR libnormalform_norl_checked (),
+.br
+.BR libnormalform_vnor_checked (),
+.br
+.BR libnormalform_vnor2 (),
+.br
+.BR LIBNORMALFORM_NOR ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+The connective is commutative and left-associative. It's truthtable is (1000).
+.PP
+The
+.BR LIBNORMALFORM_NOR ()
+macro requires ISO C23 support.
+.PP
+The
+.BR libnormalform_nor ()
+is primary function, the other functions are
+just wrappers for the
+.BR libnormalform_nor ()
+function.
+.PP
+These functions creates a clause where each term is
+connected with the NOR connective. Unlike what the
+names Joint denial and Negated OR imply, this does
+.I not
+create a sentence that is true when all subsentences
+are false. There is unfortunately no generally
+meaningful interpretation: adding true term on the
+right always create a contradiction, while adding a
+false term on the right inverts the original clause.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_NOR ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_nor2.3 b/man3/libnormalform_nor2.3
new file mode 120000
index 0000000..acea879
--- /dev/null
+++ b/man3/libnormalform_nor2.3
@@ -0,0 +1 @@
+libnormalform_nor_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_nor_checked.3 b/man3/libnormalform_nor_checked.3
new file mode 120000
index 0000000..dd76dbe
--- /dev/null
+++ b/man3/libnormalform_nor_checked.3
@@ -0,0 +1 @@
+libnormalform_nor.3 \ No newline at end of file
diff --git a/man3/libnormalform_norl.3 b/man3/libnormalform_norl.3
new file mode 120000
index 0000000..dd76dbe
--- /dev/null
+++ b/man3/libnormalform_norl.3
@@ -0,0 +1 @@
+libnormalform_nor.3 \ No newline at end of file
diff --git a/man3/libnormalform_norl_checked.3 b/man3/libnormalform_norl_checked.3
new file mode 120000
index 0000000..acea879
--- /dev/null
+++ b/man3/libnormalform_norl_checked.3
@@ -0,0 +1 @@
+libnormalform_nor_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_not.3 b/man3/libnormalform_not.3
new file mode 100644
index 0000000..0ebe548
--- /dev/null
+++ b/man3/libnormalform_not.3
@@ -0,0 +1,92 @@
+.TH LIBNORMALFORM_NOT 3 LIBNORMALFORM
+.SH NAME
+libnormalform_not \- Negation
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_not(LIBNORMALFORM_SENTENCE *\fIx\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_not ()
+function creates a sentence whose value is
+always the inverse of the value the sentence
+.I x
+takes.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+The
+.BR libnormalform_not ()
+function adopt the ownership of
+.IR x .
+Therefore, the user shall not attempt to
+deallocate
+.IR x .
+This holds even on failure: if the function
+fails,
+.I x
+is deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_not ()
+function returns an object representing
+the sentence; otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_not ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_not ()
+function also fails without setting
+.I errno
+if
+.I x
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_not ()
+T} Thread safety MT-Safe race:\fIx\fP
+T{
+.BR libnormalform_not ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_not ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_one.3 b/man3/libnormalform_one.3
new file mode 100644
index 0000000..bf79161
--- /dev/null
+++ b/man3/libnormalform_one.3
@@ -0,0 +1,253 @@
+.TH LIBNORMALFORM_ONE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_one \- Unique existential qualifier
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+struct libnormalform_mapping {
+ void *\fIkey\fP;
+ void *\fIvalue\fP;
+};
+
+struct libnormalform_map {
+ struct libnormalform_mapping *\fImappings\fP;
+ size_t \fInmappings\fP;
+ void *\fIuser_data\fP;
+ const char *\fIidentifier\fP;
+ struct libnormalform_map *\fIcopy_for_clone\fP;
+};
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_one(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_unique(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIk\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_uniquely(struct libnormalform_map *\fId\fP, LIBNORMALFORM_SENTENCE *\fIv\fP);
+
+LIBNORMALFORM_SENTENCE *
+libnormalform_singleton(struct libnormalform_map *\fId\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_one ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+for exactly one element in
+.I d
+for which
+.I k
+is also true for the
+.I .key
+of the element.
+.PP
+The
+.BR libnormalform_unique ()
+function creates a sentence that is true
+when and only when the sentence
+.I k
+is true for the
+.I .key
+of exactly one element in
+.IR d .
+.PP
+The
+.BR libnormalform_uniquely ()
+function creates a sentence that is true
+when and only when the sentence
+.I v
+is true for the
+.I .value
+of exactly one element in
+.IR d .
+.PP
+The
+.BR libnormalform_singleton ()
+function creates a sentence that is true
+when and only when
+.IR d
+contains exactly one element.
+.PP
+.I d->mappings
+and
+.I d->nmappings
+is used only be the
+.BR libnormalform_evaluate (3)
+function and must be set before
+.BR libnormalform_evaluate (3)
+is called, but need not be set
+earlier.
+.I d->nmappings
+shall be set to number of elements in
+the qualifers domain of interest, and
+.I d->mappings
+shall be set to the list of elements
+in the domain. Each element shall have its
+.I .key
+set to the value the antecedent formula
+.RI ( k )
+is tested on, and
+.I .value
+set to the value the predicate formula
+.RI ( v )
+is tested on; these fields may be
+.IR NULL ,
+and are ultimately evaluated via
+arguments passed to the
+.BR libnormalform_function (3)
+function but may undergo transformation
+via arguments passed to the
+.BR libnormalform_transformation (3)
+function on the way.
+.PP
+The values
+.I d->mappings
+and
+.I d->nmappings
+may be set differently every time the
+.BR libnormalform_evaluate (3)
+is called.
+.PP
+Although unused, the
+.BR libnormalform_singleton ()
+function requires that
+.I d->mappings
+is properly set up (although the values
+in it can all be
+.IR NULL )
+before the
+.BR libnormalform_evaluate (3)
+function is called.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR d->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR d->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+The application can set
+.I d->user_data
+set freely, and can opt to leave it
+unset. It is never used or reference
+by the library, but it could be used
+by the application to identify the
+domain.
+.PP
+.I d
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_one (),
+.BR libnormalform_unique (),
+.BR libnormalform_uniquely (),
+and
+.BR libnormalform_singleton ()
+functions return an object representing
+the sentence; otherwise, the functions
+return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_one (),
+.BR libnormalform_unique (),
+.BR libnormalform_uniquely (),
+and
+.BR libnormalform_singleton ()
+functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+These functions will also fail without setting
+.I errno
+if
+.I k
+or
+.I v
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_one (),
+.br
+.BR libnormalform_unique (),
+.br
+.BR libnormalform_uniquely ()
+T} Thread safety MT-Safe race:\fIk\fP,\fIv\fP
+T{
+.BR libnormalform_singleton ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_one (),
+.br
+.BR libnormalform_unique (),
+.br
+.BR libnormalform_uniquely (),
+.br
+.BR libnormalform_singleton ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_one (),
+.br
+.BR libnormalform_unique (),
+.br
+.BR libnormalform_uniquely (),
+.br
+.BR libnormalform_singleton ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_function (3)
diff --git a/man3/libnormalform_or.3 b/man3/libnormalform_or.3
new file mode 100644
index 0000000..9d57ee8
--- /dev/null
+++ b/man3/libnormalform_or.3
@@ -0,0 +1,233 @@
+.TH LIBNORMALFORM_OR 3 LIBNORMALFORM
+.SH NAME
+libnormalform_or \- Inclusive disjunction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_or(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_orl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vor(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_or_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_orl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_or2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_OR(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_or ()
+function creates a sentence that is logically
+equivalent to the inclusive disjunction of the
+arguments, that is, a sentence that is true when
+and only when at least one subsentence is true.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_orl ()
+function is a variant of
+.BR libnormalform_or ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vor ()
+function is a variant of
+.BR libnormalform_orl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_or_checked (),
+.BR libnormalform_orl_checked (),
+and
+.BR libnormalform_vor_checked ()
+functions are variants of the
+.BR libnormalform_or (),
+.BR libnormalform_orl (),
+and
+.BR libnormalform_vor ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_or2(p, q)"
+is equivalent to
+.IR "libnormalform_orl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_OR
+macro is a wrapper for the
+.BR libnormalform_or_checked ()
+but is more similar to
+.BR libnormalform_orl ().
+Unlike
+.BR libnormalform_orl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_or ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_or_checked (),
+.BR libnormalform_orl_checked (),
+and
+.BR libnormalform_vor_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_or2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_or (),
+.br
+.BR libnormalform_or_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_orl (),
+.br
+.BR libnormalform_orl_checked (),
+.br
+.BR libnormalform_or2 (),
+.br
+.BR LIBNORMALFORM_OR ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vor (),
+.br
+.BR libnormalform_vor_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_or (),
+.br
+.BR libnormalform_orl (),
+.br
+.BR libnormalform_vor (),
+.br
+.BR libnormalform_or_checked (),
+.br
+.BR libnormalform_orl_checked (),
+.br
+.BR libnormalform_vor_checked (),
+.br
+.BR libnormalform_vor2 (),
+.br
+.BR LIBNORMALFORM_OR ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_or (),
+.br
+.BR libnormalform_orl (),
+.br
+.BR libnormalform_vor (),
+.br
+.BR libnormalform_or_checked (),
+.br
+.BR libnormalform_orl_checked (),
+.br
+.BR libnormalform_vor_checked (),
+.br
+.BR libnormalform_vor2 (),
+.br
+.BR LIBNORMALFORM_OR ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a contradiction.
+.PP
+The connective is commutative and associative. It's truthtable is (0111).
+.PP
+The
+.BR LIBNORMALFORM_OR ()
+macro requires ISO C23 support.
+.PP
+Using
+.BR libnormalform_or2 (),
+has greatest performance, however
+.BR libnormalform_or ()
+is better at simplifying the sentence.
+The other functions are just wrappers for the
+.BR libnormalform_or ()
+function.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_OR ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_or2.3 b/man3/libnormalform_or2.3
new file mode 120000
index 0000000..3d2cdc4
--- /dev/null
+++ b/man3/libnormalform_or2.3
@@ -0,0 +1 @@
+libnormalform_or_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_or_checked.3 b/man3/libnormalform_or_checked.3
new file mode 120000
index 0000000..25bea64
--- /dev/null
+++ b/man3/libnormalform_or_checked.3
@@ -0,0 +1 @@
+libnormalform_or.3 \ No newline at end of file
diff --git a/man3/libnormalform_orl.3 b/man3/libnormalform_orl.3
new file mode 120000
index 0000000..25bea64
--- /dev/null
+++ b/man3/libnormalform_orl.3
@@ -0,0 +1 @@
+libnormalform_or.3 \ No newline at end of file
diff --git a/man3/libnormalform_orl_checked.3 b/man3/libnormalform_orl_checked.3
new file mode 120000
index 0000000..3d2cdc4
--- /dev/null
+++ b/man3/libnormalform_orl_checked.3
@@ -0,0 +1 @@
+libnormalform_or_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_ref.3 b/man3/libnormalform_ref.3
new file mode 100644
index 0000000..5592e48
--- /dev/null
+++ b/man3/libnormalform_ref.3
@@ -0,0 +1,111 @@
+.TH LIBNORMALFORM_REF 3 LIBNORMALFORM
+.SH NAME
+libnormalform_ref \- Acquire new reference
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_ref(LIBNORMALFORM_SENTENCE *\fIx\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_ref ()
+function increases the reference count of
+.I x
+by one and returns
+.I x
+(it is advisable to treat the return pointer
+as unique but that it must be used in the
+same thread as the input pointer),
+letting the application input the sentence
+object to multiple sentences or multiple
+times in the same sentence.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_ref ()
+function returns
+.IR x ;
+otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_ref ()
+function fails if:
+.TP
+.I ENOMEM
+The reference count of
+.I x
+was already maximised (at
+.IR SIZE_MAX );
+this would imply that something is wrong in the
+application, and it might as well abort.
+.PP
+The
+.BR libnormalform_ref ()
+function also fails without setting
+.I errno
+if
+.I x
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_ref ()
+T} Thread safety MT-Safe race:\fIx\fP
+T{
+.BR libnormalform_ref ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_ref ()
+T} Async-cancel safety AC-Unsafe heap
+.TE
+
+.SH APPLICATION USAGE
+The
+.B LIBNORMALFORM_SENTENCE
+type is not thread-safe. If you want to use an
+instance of it from two or more threads concurrently,
+you need to use the
+.BR libnormalform_clone (3)
+function to create a deep clone of the instance.
+Additionally applications shall assume that any
+.B LIBNORMALFORM_SENTENCE
+constructed using an instance of this type contains
+that instance and cannot be used concurrently, from
+another thread, with that instance or any other
+instance created with it, appart from instances
+created with the
+.BR libnormalform_clone (3)
+function.
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_clone (3),
+.BR libnormalform_free (3)
diff --git a/man3/libnormalform_representation_spec.3 b/man3/libnormalform_representation_spec.3
new file mode 120000
index 0000000..a996c52
--- /dev/null
+++ b/man3/libnormalform_representation_spec.3
@@ -0,0 +1 @@
+struct_libnormalform_representation_spec.3 \ No newline at end of file
diff --git a/man3/libnormalform_sentence.3 b/man3/libnormalform_sentence.3
new file mode 120000
index 0000000..3f5af8c
--- /dev/null
+++ b/man3/libnormalform_sentence.3
@@ -0,0 +1 @@
+struct_libnormalform_sentence.3 \ No newline at end of file
diff --git a/man3/libnormalform_singleton.3 b/man3/libnormalform_singleton.3
new file mode 120000
index 0000000..5e3df78
--- /dev/null
+++ b/man3/libnormalform_singleton.3
@@ -0,0 +1 @@
+libnormalform_one.3 \ No newline at end of file
diff --git a/man3/libnormalform_to_string.3 b/man3/libnormalform_to_string.3
new file mode 100644
index 0000000..7008990
--- /dev/null
+++ b/man3/libnormalform_to_string.3
@@ -0,0 +1,105 @@
+.TH LIBNORMALFORM_TO_STRING 3 LIBNORMALFORM
+.SH NAME
+libnormalform_to_string \- Serialise into a string
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+char *libnormalform_to_string(LIBNORMALFORM_SENTENCE *\fIx\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_to_string ()
+function creates a string representation if
+.I x
+designed for machine parsing, but can also be
+read by sufficiently literate humans.
+.PP
+Before calling the
+.BR libnormalform_to_string ()
+function, application provided objects in
+.I x
+must be configured for serialisation:
+.I .identifier
+in each
+.IR "struct libnormalform_variable" ,
+.IR "struct libnormalform_function" ,
+.IR "struct libnormalform_map" ,
+and
+.IR "struct libnormalform_transformer" ,
+shall either be set to the string representation,
+of the object, that can be parsed by the application
+without it being terminated by a null byte.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR free (3)
+function.
+.PP
+.I x
+must not be
+.IR NULL .
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_to_string ()
+function returns a null-byte terminated
+non-empty, ASCII string representation of
+.IR x ;
+otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_to_string ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+serialise the sentence object.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_to_string ()
+T} Thread safety MT-Safe race:\fIx\fP
+T{
+.BR libnormalform_to_string ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_to_string ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+When a
+.I LIBNORMALFORM_SENTENCE
+is created, it is optimised (this includes eliminiation
+of redundant information and reordering) and reduced to
+into fewer types of connetives (it's reducted into negation
+normal form, except with XOR allowed, but not necessarily
+to any canonical form) during construction, so the
+.BR libnormalform_to_string ()
+function will not necessarily reproduce the sentence
+as it was specified when it was constructed.
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_from_string (3)
diff --git a/man3/libnormalform_transformation.3 b/man3/libnormalform_transformation.3
new file mode 100644
index 0000000..7bb129e
--- /dev/null
+++ b/man3/libnormalform_transformation.3
@@ -0,0 +1,245 @@
+.TH LIBNORMALFORM_TRANSFORMATION 3 LIBNORMALFORM
+.SH PROLOGUE
+This document describes the function
+.BR libnormalform_transformation ,
+refer to
+.BR struct_libnormalform_transformation (3)
+for the type
+.IR "struct libnormalform_transformation" .
+
+.SH NAME
+libnormalform_transformation \- Function morphism
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+enum libnormalform_builtin_transformer {
+ \fILIBNORMALFORM_NOT_BUILT_IN\fP = 0,
+ \fILIBNORMALFORM_DOMAIN_VIEW\fP,
+ \fILIBNORMALFORM_IMAGE_VIEW\fP
+};
+
+struct libnormalform_transformer {
+ void *(*\fItransform\fP)(void *user_data, void *input);
+ void (*\fIdeallocate\fP)(void *user_data, void *output);
+ void *\fIuser_data\fP;
+ const char *\fIidentifier\fP;
+ struct libnormalform_transformer *\fIcopy_for_clone\fP;
+ int \fIrequires_elimination\fP;
+ enum libnormalform_builtin_transformer \fIbuiltin\fP;
+};
+
+LIBNORMALFORM_SENTENCE *libnormalform_transformation(struct libnormalform_transformer *\fIfunction\fP,
+ LIBNORMALFORM_SENTENCE *\fIsentence\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_transformation ()
+function creates a sentence output
+the transformation with the application-defined
+.I function
+over a
+.IR sentence.
+The pointer
+.I function
+is application-managed, and must exists while
+the returned sentece exists.
+.PP
+.I function->transform
+is used only be the
+.BR libnormalform_evaluate (3)
+function to modify input to other
+functions (both
+.I struct libnormalform_function
+and
+.IR "struct libnormalform_transformer" ).
+.I function->user_data is passed to
+.I *function->transform
+as its first argument, and the function
+input is passed as its second argument.
+The function is expected to return a
+.RI non- NULL
+pointer upon successful completion and
+.I NULL
+on failure; on failure the function may
+optionally set
+.IR errno .
+If
+.I function->deallocate
+is
+.IR non -NULL ,
+but unless
+.I *function->transform
+returns
+.IR NULL ,
+the library will call
+.I *function->deallocate
+when the pointer returned by
+.I *function->transform
+is no longer needed.
+.I function->user_data is passed to
+.I *function->deallocate
+as its first argument, and the returned
+pointer is passed as its second argument.
+.I function->user_data
+is only used by the application for
+application-defined purposes.
+.I function->user_data
+and
+.I function->deallocate
+may, unlike
+.IR function->transform ,
+be
+.IR NULL ;
+however
+.IR function->transform ,
+.IR function->deallocate ,
+and
+.I function->user_data
+need not be set before the
+.BR libnormalform_evaluate (3)
+function is called.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR function->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR function->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+See
+.BR libnormalform_express (3)
+for the purpose of
+.IR function->requires_elimination ,
+it need not be set before any of the
+.BR libnormalform_express (3),
+.BR libnormalform_dnf (3),
+and
+.BR libnormalform_cnf (3)
+functions are called.
+.PP
+See
+.BR libnormalform_express (3)
+for the purpose of
+.IR function->builtin ,
+the
+.BR libnormalform_transformation ()
+function always sets it to
+.IR LIBNORMALFORM_NOT_BUILT_IN .
+.PP
+.I function
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+The
+.BR libnormalform_transformation ()
+function adopt the ownership of
+.IR sentence .
+Therefore, the user shall not attempt to
+deallocate
+.IR sentence .
+This holds even on failure: if the function
+fails,
+.I sentence
+is deallocated.
+.PP
+.I *function->transform
+may be called multiple types for the same
+input even if it is only specified in a sentence
+once, therefore calling it multiple times must
+not have undesirable side effects. The function
+shall return equivalent output given equivalent
+input. Additionally for any functions F, G,
+T(F # G) must be equivalent to T(F) # T(G) for
+any operator #, where T is
+.IR *function->transform .
+Transformations over constants and variables
+are removed as these do not take any input.
+Transformations over qualifiers are illegal.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_transformation ()
+function returns an sentence object of
+the transformation; otherwise, the function
+returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+.PP
+The
+.BR libnormalform_transformation ()
+function also fails without setting
+.I errno
+if
+.I sentence
+is
+.IR NULL .
+
+.SH ERRORS
+The
+.BR libnormalform_transformation ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.TP
+.I EDOM
+.I sentence
+contains a qualifier.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_transformation ()
+T} Thread safety MT-Safe race:\fIsentence\fP
+T{
+.BR libnormalform_transformation ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_transformation ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+Sentences created with the
+.BR libnormalform_empty (),
+.BR libnormalform_nonempty (),
+and
+.BR libnormalform_singleton ()
+functions count as qualifiers and
+cannot be used in a transformation.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_transformer.3 b/man3/libnormalform_transformer.3
new file mode 120000
index 0000000..9bb1a97
--- /dev/null
+++ b/man3/libnormalform_transformer.3
@@ -0,0 +1 @@
+struct_libnormalform_transformer.3 \ No newline at end of file
diff --git a/man3/libnormalform_true.3 b/man3/libnormalform_true.3
new file mode 100644
index 0000000..a5da907
--- /dev/null
+++ b/man3/libnormalform_true.3
@@ -0,0 +1,70 @@
+.TH LIBNORMALFORM_TRUE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_true \- Tautology
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_true(void);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_true ()
+function creates a tautological sentence:
+a sentence that is always true, regardless
+of the its containing formula's input.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_true ()
+function returns an object representing
+the sentence; otherwise, the function returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_true ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_true ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_true ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_true ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_unique.3 b/man3/libnormalform_unique.3
new file mode 120000
index 0000000..5e3df78
--- /dev/null
+++ b/man3/libnormalform_unique.3
@@ -0,0 +1 @@
+libnormalform_one.3 \ No newline at end of file
diff --git a/man3/libnormalform_uniquely.3 b/man3/libnormalform_uniquely.3
new file mode 120000
index 0000000..5e3df78
--- /dev/null
+++ b/man3/libnormalform_uniquely.3
@@ -0,0 +1 @@
+libnormalform_one.3 \ No newline at end of file
diff --git a/man3/libnormalform_universally.3 b/man3/libnormalform_universally.3
new file mode 120000
index 0000000..e479a8c
--- /dev/null
+++ b/man3/libnormalform_universally.3
@@ -0,0 +1 @@
+libnormalform_all.3 \ No newline at end of file
diff --git a/man3/libnormalform_value.3 b/man3/libnormalform_value.3
new file mode 120000
index 0000000..9602858
--- /dev/null
+++ b/man3/libnormalform_value.3
@@ -0,0 +1 @@
+enum_libnormalform_value.3 \ No newline at end of file
diff --git a/man3/libnormalform_vand.3 b/man3/libnormalform_vand.3
new file mode 120000
index 0000000..32c5aa9
--- /dev/null
+++ b/man3/libnormalform_vand.3
@@ -0,0 +1 @@
+libnormalform_andl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vand_checked.3 b/man3/libnormalform_vand_checked.3
new file mode 120000
index 0000000..b4cbf43
--- /dev/null
+++ b/man3/libnormalform_vand_checked.3
@@ -0,0 +1 @@
+libnormalform_andl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_variable.3 b/man3/libnormalform_variable.3
new file mode 100644
index 0000000..ad30a37
--- /dev/null
+++ b/man3/libnormalform_variable.3
@@ -0,0 +1,127 @@
+.TH LIBNORMALFORM_VARIABLE 3 LIBNORMALFORM
+.SH NAME
+libnormalform_variable \- Boolean variable
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+enum libnormalform_value {
+ \fILIBNORMALFORM_FALSE\fP = 0,
+ \fILIBNORMALFORM_TRUE\fP = 1
+};
+
+struct libnormalform_variable {
+ enum libnormalform_value \fIvalue\fP;
+ void *\fIuser_data\fP;
+ const char *\fIidentifier;
+ struct libnormalform_variable *\fIcopy_for_clone\fP;
+};
+
+LIBNORMALFORM_SENTENCE *libnormalform_variable(struct libnormalform_variable *\fIvariable\fP);
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_variable ()
+function creates a sentence object out
+of an application-managed variable.
+.PP
+.I variable->value
+is used only be the
+.BR libnormalform_evaluate (3)
+function and must be set to either
+.I LIBNORMALFORM_FALSE
+or
+.I LIBNORMALFORM_TRUE
+before the
+.BR libnormalform_evaluate (3)
+function is called, to let the
+function know what value the
+variable has for that specific
+call to the
+.BR libnormalform_evaluate (3)
+function.
+.PP
+See
+.BR libnormalform_to_string (3)
+for the purpose of
+.IR variable->identifier ,
+it need not be set before the
+.BR libnormalform_to_string (3)
+function is called.
+.PP
+See
+.BR libnormalform_clone (3)
+for the purpose of
+.IR variable->copy_for_clone ,
+it need not be set before the
+.BR libnormalform_clone (3)
+function is called.
+.PP
+The application can set
+.I variable->user_data
+set freely, and can opt to leave it
+unset. It is never used or reference
+by the library, but it could be used
+by the application to identify the
+variable.
+.PP
+.I variable
+must not be
+.IR NULL .
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_variable ()
+function returns an sentence object of
+the variable; otherwise, the function
+returns
+.I NULL
+and sets
+.I errno
+to indicate the error.
+
+.SH ERRORS
+The
+.BR libnormalform_variable ()
+function fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_variable ()
+T} Thread safety MT-Safe
+T{
+.BR libnormalform_variable ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_variable ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH SEE ALSO
+.BR libnormalform (7),
+.BR libnormalform_function (3)
diff --git a/man3/libnormalform_vif.3 b/man3/libnormalform_vif.3
new file mode 120000
index 0000000..8c9a9cd
--- /dev/null
+++ b/man3/libnormalform_vif.3
@@ -0,0 +1 @@
+libnormalform_ifl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vif_checked.3 b/man3/libnormalform_vif_checked.3
new file mode 120000
index 0000000..6b5aa3c
--- /dev/null
+++ b/man3/libnormalform_vif_checked.3
@@ -0,0 +1 @@
+libnormalform_ifl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vimply.3 b/man3/libnormalform_vimply.3
new file mode 120000
index 0000000..b973027
--- /dev/null
+++ b/man3/libnormalform_vimply.3
@@ -0,0 +1 @@
+libnormalform_implyl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vimply_checked.3 b/man3/libnormalform_vimply_checked.3
new file mode 120000
index 0000000..777fe30
--- /dev/null
+++ b/man3/libnormalform_vimply_checked.3
@@ -0,0 +1 @@
+libnormalform_implyl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnand.3 b/man3/libnormalform_vnand.3
new file mode 120000
index 0000000..a6f43be
--- /dev/null
+++ b/man3/libnormalform_vnand.3
@@ -0,0 +1 @@
+libnormalform_nandl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnand_checked.3 b/man3/libnormalform_vnand_checked.3
new file mode 120000
index 0000000..c7005ad
--- /dev/null
+++ b/man3/libnormalform_vnand_checked.3
@@ -0,0 +1 @@
+libnormalform_nandl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnif.3 b/man3/libnormalform_vnif.3
new file mode 120000
index 0000000..fecbc72
--- /dev/null
+++ b/man3/libnormalform_vnif.3
@@ -0,0 +1 @@
+libnormalform_nifl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnif_checked.3 b/man3/libnormalform_vnif_checked.3
new file mode 120000
index 0000000..52f1181
--- /dev/null
+++ b/man3/libnormalform_vnif_checked.3
@@ -0,0 +1 @@
+libnormalform_nifl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnimply.3 b/man3/libnormalform_vnimply.3
new file mode 120000
index 0000000..c4fb4a5
--- /dev/null
+++ b/man3/libnormalform_vnimply.3
@@ -0,0 +1 @@
+libnormalform_nimplyl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnimply_checked.3 b/man3/libnormalform_vnimply_checked.3
new file mode 120000
index 0000000..f768f8c
--- /dev/null
+++ b/man3/libnormalform_vnimply_checked.3
@@ -0,0 +1 @@
+libnormalform_nimplyl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnor.3 b/man3/libnormalform_vnor.3
new file mode 120000
index 0000000..7247725
--- /dev/null
+++ b/man3/libnormalform_vnor.3
@@ -0,0 +1 @@
+libnormalform_norl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vnor_checked.3 b/man3/libnormalform_vnor_checked.3
new file mode 120000
index 0000000..b11849c
--- /dev/null
+++ b/man3/libnormalform_vnor_checked.3
@@ -0,0 +1 @@
+libnormalform_norl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vor.3 b/man3/libnormalform_vor.3
new file mode 120000
index 0000000..27a0be5
--- /dev/null
+++ b/man3/libnormalform_vor.3
@@ -0,0 +1 @@
+libnormalform_orl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vor_checked.3 b/man3/libnormalform_vor_checked.3
new file mode 120000
index 0000000..da75167
--- /dev/null
+++ b/man3/libnormalform_vor_checked.3
@@ -0,0 +1 @@
+libnormalform_orl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vxnor.3 b/man3/libnormalform_vxnor.3
new file mode 120000
index 0000000..6f2ae2d
--- /dev/null
+++ b/man3/libnormalform_vxnor.3
@@ -0,0 +1 @@
+libnormalform_xnorl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vxnor_checked.3 b/man3/libnormalform_vxnor_checked.3
new file mode 120000
index 0000000..2f6251f
--- /dev/null
+++ b/man3/libnormalform_vxnor_checked.3
@@ -0,0 +1 @@
+libnormalform_xnorl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_vxor.3 b/man3/libnormalform_vxor.3
new file mode 120000
index 0000000..08ad00c
--- /dev/null
+++ b/man3/libnormalform_vxor.3
@@ -0,0 +1 @@
+libnormalform_xorl.3 \ No newline at end of file
diff --git a/man3/libnormalform_vxor_checked.3 b/man3/libnormalform_vxor_checked.3
new file mode 120000
index 0000000..89c3126
--- /dev/null
+++ b/man3/libnormalform_vxor_checked.3
@@ -0,0 +1 @@
+libnormalform_xorl_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_xnor.3 b/man3/libnormalform_xnor.3
new file mode 100644
index 0000000..56a3cab
--- /dev/null
+++ b/man3/libnormalform_xnor.3
@@ -0,0 +1,245 @@
+.TH LIBNORMALFORM_XNOR 3 LIBNORMALFORM
+.SH NAME
+libnormalform_xnor \- Logical equivalence
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_xnor(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xnorl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vxnor(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xnor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xnorl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vxnor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xnor2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_XNOR(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_xnor ()
+function creates a sentence that is logically
+equivalent to the logical equivalecne of the
+arguments, that is, a sentence that is true
+exactly when the parity of the subsentences
+equal to whether the number of subsentences is odd.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_xnorl ()
+function is a variant of
+.BR libnormalform_xnor ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vxnor ()
+function is a variant of
+.BR libnormalform_xnorl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_xnor_checked (),
+.BR libnormalform_xnorl_checked (),
+and
+.BR libnormalform_vxnor_checked ()
+functions are variants of the
+.BR libnormalform_xnor (),
+.BR libnormalform_xnorl (),
+and
+.BR libnormalform_vxnor ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_xnor2(p, q)"
+is equivalent to
+.IR "libnormalform_xnorl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_XNOR
+macro is a wrapper for the
+.BR libnormalform_xnor_checked ()
+but is more similar to
+.BR libnormalform_xnorl ().
+Unlike
+.BR libnormalform_xnorl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_xnor ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_xnor_checked (),
+.BR libnormalform_xnorl_checked (),
+and
+.BR libnormalform_vxnor_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_xnor2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_xnor (),
+.br
+.BR libnormalform_xnor_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_xnorl (),
+.br
+.BR libnormalform_xnorl_checked (),
+.br
+.BR libnormalform_xnor2 (),
+.br
+.BR LIBNORMALFORM_XNOR ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vxnor (),
+.br
+.BR libnormalform_vxnor_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_xnor (),
+.br
+.BR libnormalform_xnorl (),
+.br
+.BR libnormalform_vxnor (),
+.br
+.BR libnormalform_xnor_checked (),
+.br
+.BR libnormalform_xnorl_checked (),
+.br
+.BR libnormalform_vxnor_checked (),
+.br
+.BR libnormalform_vxnor2 (),
+.br
+.BR LIBNORMALFORM_XNOR ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_xnor (),
+.br
+.BR libnormalform_xnorl (),
+.br
+.BR libnormalform_vxnor (),
+.br
+.BR libnormalform_xnor_checked (),
+.br
+.BR libnormalform_xnorl_checked (),
+.br
+.BR libnormalform_vxnor_checked (),
+.br
+.BR libnormalform_vxnor2 (),
+.br
+.BR LIBNORMALFORM_XNOR ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a tautology.
+.PP
+The connective is commutative and associative. It's truthtable is (1001).
+.PP
+The
+.BR LIBNORMALFORM_XNOR ()
+macro requires ISO C23 support.
+.PP
+Using
+.BR libnormalform_xnor2 (),
+has greatest performance, however
+.BR libnormalform_xnor ()
+is better at simplifying the sentence.
+The other functions are just wrappers for the
+.BR libnormalform_xnor ()
+function.
+.PP
+These functions creates a clause where each term
+is connected with the XNOR connective. Unlike what
+the name Logical equivalence, this does
+.I not
+create a sentence that is true when all subsentences
+have the same value (this is only the case when there
+are 2 or 0 subsentences). Rather, when the number
+of terms is odd, it is equivalent to the parity (XOR)
+of the terms, but when the number of terms is odd, it
+is the inverse of the parity.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_XNOR ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_xnor2.3 b/man3/libnormalform_xnor2.3
new file mode 120000
index 0000000..2c62e7a
--- /dev/null
+++ b/man3/libnormalform_xnor2.3
@@ -0,0 +1 @@
+libnormalform_xnor_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_xnor_checked.3 b/man3/libnormalform_xnor_checked.3
new file mode 120000
index 0000000..49b2b6c
--- /dev/null
+++ b/man3/libnormalform_xnor_checked.3
@@ -0,0 +1 @@
+libnormalform_xnor.3 \ No newline at end of file
diff --git a/man3/libnormalform_xnorl.3 b/man3/libnormalform_xnorl.3
new file mode 120000
index 0000000..49b2b6c
--- /dev/null
+++ b/man3/libnormalform_xnorl.3
@@ -0,0 +1 @@
+libnormalform_xnor.3 \ No newline at end of file
diff --git a/man3/libnormalform_xnorl_checked.3 b/man3/libnormalform_xnorl_checked.3
new file mode 120000
index 0000000..2c62e7a
--- /dev/null
+++ b/man3/libnormalform_xnorl_checked.3
@@ -0,0 +1 @@
+libnormalform_xnor_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_xor.3 b/man3/libnormalform_xor.3
new file mode 100644
index 0000000..70e9584
--- /dev/null
+++ b/man3/libnormalform_xor.3
@@ -0,0 +1,242 @@
+.TH LIBNORMALFORM_XOR 3 LIBNORMALFORM
+.SH NAME
+libnormalform_xor \- Exclusive disjunction
+
+.SH SYNOPSIS
+.nf
+#include <libnormalform.h>
+
+LIBNORMALFORM_SENTENCE *libnormalform_xor(LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xorl(LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vxor(LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE **\fIxs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xorl_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, ... /*, NULL */);
+LIBNORMALFORM_SENTENCE *libnormalform_vxor_checked(size_t \fIn\fP, LIBNORMALFORM_SENTENCE *\fIa\fP, va_list \fPargs\fP);
+LIBNORMALFORM_SENTENCE *libnormalform_xor2(LIBNORMALFORM_SENTENCE *\fIp\fP, LIBNORMALFORM_SENTENCE *\fIq\fP);
+#define LIBNORMALFORM_XOR(...) /* ... */
+.fi
+.PP
+Link with
+.IR -lnormalform .
+
+.SH DESCRIPTION
+The
+.BR libnormalform_xor ()
+function creates a sentence that is logically
+equivalent to the exclusive disjunction of the
+arguments, that is, a sentence that is true when
+and only when an odd number of subsentence is true.
+.PP
+The value of the
+.I xs
+parameter shall be a null-pointer terminated list
+of subsentences.
+.PP
+The
+.BR libnormalform_xorl ()
+function is a variant of
+.BR libnormalform_xor ()
+which uses variadic arguments instead of an array.
+.PP
+The
+.BR libnormalform_vxor ()
+function is a variant of
+.BR libnormalform_xorl ()
+which takes an
+.I va_list
+in place of variadic arguments.
+.PP
+The
+.BR libnormalform_xor_checked (),
+.BR libnormalform_xorl_checked (),
+and
+.BR libnormalform_vxor_checked ()
+functions are variants of the
+.BR libnormalform_xor (),
+.BR libnormalform_xorl (),
+and
+.BR libnormalform_vxor ()
+functions respectively which assumes
+that it is given
+.I n
+subsentances, and checks that all are
+.RI non- NULL .
+However, these functions still require
+the list of subsentences to be terminated
+using a null-pointer.
+.PP
+.I "libnormalform_xor2(p, q)"
+is equivalent to
+.IR "libnormalform_xorl_checked(2, p, q, NULL)" .
+.PP
+The
+.BR LIBNORMALFORM_XOR
+macro is a wrapper for the
+.BR libnormalform_xor_checked ()
+but is more similar to
+.BR libnormalform_xorl ().
+Unlike
+.BR libnormalform_xorl (),
+the argument list
+.I must not
+be terminated by a null-pointer, instead
+the macro automatically as the null-pointer.
+The macro will count the number of arguments
+it is given, therefore it will fail it the
+argument list is has an extra null-pointer.
+.PP
+The returned pointer shall either be
+deallocated with the
+.BR libnormalform_free (3)
+function or be relinquished by being
+used as part of another sentence.
+.PP
+These functions adopt the ownership of any
+.I LIBNORMALFORM_SENTENCE *
+passed into it. Therefore, the user shall
+not attempt to deallocate input sentences.
+This holds even on failure: if the function
+fails, input sentences are deallocated.
+
+.SH RETURN VALUE
+Upon successful completion, the
+.BR libnormalform_xor ()
+function and its variants return an object
+representing the sentence; otherwise, the
+functions return
+.I NULL
+and set
+.I errno
+to indicate the error.
+
+.SH ERRORS
+These functions fails if:
+.TP
+.I ENOMEM
+Insufficient memory was available to
+create the sentence object.
+.PP
+The
+.BR libnormalform_xor_checked (),
+.BR libnormalform_xorl_checked (),
+and
+.BR libnormalform_vxor_checked ()
+functions will also fail without setting
+.I errno
+if any of the first
+.I n
+.IR "LIBNORMALFORM_SENTENCE *" 's
+are
+.IR NULL .
+Likewise, the
+.BR libnormalform_xor2 ()
+function will fail without setting
+.I errno
+if
+.I p
+or
+.I q
+is
+.IR NULL .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this
+section, see
+.BR attributes (7)
+and
+.IR "info \(dq(libc)POSIX Safety Concepts\(dq" .
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR libnormalform_xor (),
+.br
+.BR libnormalform_xor_checked ()
+T} Thread safety MT-Safe race:\fIxs\fP and elements in \fIxs\fP
+T{
+.BR libnormalform_xorl (),
+.br
+.BR libnormalform_xorl_checked (),
+.br
+.BR libnormalform_xor2 (),
+.br
+.BR LIBNORMALFORM_XOR ()
+T} Thread safety MT-Safe race:parameters
+T{
+.BR libnormalform_vxor (),
+.br
+.BR libnormalform_vxor_checked ()
+T} Thread safety MT-Safe race:parameters and arguments in \fIargs\fP
+T{
+.BR libnormalform_xor (),
+.br
+.BR libnormalform_xorl (),
+.br
+.BR libnormalform_vxor (),
+.br
+.BR libnormalform_xor_checked (),
+.br
+.BR libnormalform_xorl_checked (),
+.br
+.BR libnormalform_vxor_checked (),
+.br
+.BR libnormalform_vxor2 (),
+.br
+.BR LIBNORMALFORM_XOR ()
+T} Async-signal safety AS-Unsafe heap
+T{
+.BR libnormalform_xor (),
+.br
+.BR libnormalform_xorl (),
+.br
+.BR libnormalform_vxor (),
+.br
+.BR libnormalform_xor_checked (),
+.br
+.BR libnormalform_xorl_checked (),
+.br
+.BR libnormalform_vxor_checked (),
+.br
+.BR libnormalform_vxor2 (),
+.br
+.BR LIBNORMALFORM_XOR ()
+T} Async-cancel safety AC-Safe mem, AC-Unsafe heap
+.TE
+
+.SH NOTES
+If there are no subsentences, the resulting sentence is a contradiction.
+.PP
+The connective is commutative and associative. It's truthtable is (0110).
+.PP
+The
+.BR LIBNORMALFORM_XOR ()
+macro requires ISO C23 support.
+.PP
+Using
+.BR libnormalform_xor2 (),
+has greatest performance, however
+.BR libnormalform_xor ()
+is better at simplifying the sentence.
+The other functions are just wrappers for the
+.BR libnormalform_xor ()
+function.
+.PP
+These functions creates a clause where each term
+is connected with the XOR connective. Unlike what
+the name Exclusive disjunction imply, this does
+.I not
+create a sentence that is true when exactly one
+subsentence is true. Rather this creates the parity
+of the terms: the sentece is when an odd number of
+subsentences are true.
+.PP
+Side-effects in the arguments of the macro
+.BR LIBNORMALFORM_XOR ()
+are guaranteed to be applied exactly once.
+The side-effects in each macro are applied
+in no particular order.
+
+.SH SEE ALSO
+.BR libnormalform (7)
diff --git a/man3/libnormalform_xor2.3 b/man3/libnormalform_xor2.3
new file mode 120000
index 0000000..cd0eb39
--- /dev/null
+++ b/man3/libnormalform_xor2.3
@@ -0,0 +1 @@
+libnormalform_xor_checked.3 \ No newline at end of file
diff --git a/man3/libnormalform_xor_checked.3 b/man3/libnormalform_xor_checked.3
new file mode 120000
index 0000000..7da4f32
--- /dev/null
+++ b/man3/libnormalform_xor_checked.3
@@ -0,0 +1 @@
+libnormalform_xor.3 \ No newline at end of file
diff --git a/man3/libnormalform_xorl.3 b/man3/libnormalform_xorl.3
new file mode 120000
index 0000000..7da4f32
--- /dev/null
+++ b/man3/libnormalform_xorl.3
@@ -0,0 +1 @@
+libnormalform_xor.3 \ No newline at end of file
diff --git a/man3/libnormalform_xorl_checked.3 b/man3/libnormalform_xorl_checked.3
new file mode 120000
index 0000000..cd0eb39
--- /dev/null
+++ b/man3/libnormalform_xorl_checked.3
@@ -0,0 +1 @@
+libnormalform_xor_checked.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_function.3 b/man3/struct_libnormalform_function.3
new file mode 120000
index 0000000..57241ab
--- /dev/null
+++ b/man3/struct_libnormalform_function.3
@@ -0,0 +1 @@
+libnormalform_function.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_map.3 b/man3/struct_libnormalform_map.3
new file mode 120000
index 0000000..899f515
--- /dev/null
+++ b/man3/struct_libnormalform_map.3
@@ -0,0 +1 @@
+libnormalform_any.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_mapping.3 b/man3/struct_libnormalform_mapping.3
new file mode 120000
index 0000000..907aac0
--- /dev/null
+++ b/man3/struct_libnormalform_mapping.3
@@ -0,0 +1 @@
+struct_libnormalform_map.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_representation_spec.3 b/man3/struct_libnormalform_representation_spec.3
new file mode 120000
index 0000000..19a7285
--- /dev/null
+++ b/man3/struct_libnormalform_representation_spec.3
@@ -0,0 +1 @@
+libnormalform_from_string.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_sentence.3 b/man3/struct_libnormalform_sentence.3
new file mode 120000
index 0000000..8d3cfbb
--- /dev/null
+++ b/man3/struct_libnormalform_sentence.3
@@ -0,0 +1 @@
+LIBNORMALFORM_SENTENCE.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_transformer.3 b/man3/struct_libnormalform_transformer.3
new file mode 120000
index 0000000..86848de
--- /dev/null
+++ b/man3/struct_libnormalform_transformer.3
@@ -0,0 +1 @@
+libnormalform_transformation.3 \ No newline at end of file
diff --git a/man3/struct_libnormalform_variable.3 b/man3/struct_libnormalform_variable.3
new file mode 120000
index 0000000..3bf0ee7
--- /dev/null
+++ b/man3/struct_libnormalform_variable.3
@@ -0,0 +1 @@
+libnormalform_variable.3 \ No newline at end of file
diff --git a/memcheck.c b/memcheck.c
new file mode 100644
index 0000000..b119fad
--- /dev/null
+++ b/memcheck.c
@@ -0,0 +1,638 @@
+/* See LICENSE file for copyright and license details. */
+#include "memcheck.h"
+
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <elfutils/libdwfl.h>
+
+
+enum alloctype {
+ ALLOCTYPE_REALLOC,
+ ALLOCTYPE_MMAP
+};
+
+#ifdef PRINT_BACKTRACES
+struct backtrace {
+ size_t alloc_size;
+ size_t n;
+ uintptr_t rips[];
+};
+#endif
+
+struct allocinfo {
+ void *real;
+ void *ptr;
+ size_t real_size;
+ enum alloctype type;
+ int flags;
+ int dont_track;
+#ifdef PRINT_BACKTRACES
+ struct backtrace *backtrace;
+#endif
+};
+
+
+static struct allocinfo *allocs = NULL;
+static size_t nallocs = 0;
+static size_t allocs_size = 0;
+static size_t alloc_fail_exclusion = 0;
+static size_t memleak_exclusion = 1;
+static size_t alloc_fail_in = 0;
+static int alloc_fail_all = 0;
+static int have_custom_malloc = 0;
+
+
+static void *
+memcheck_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off)
+{
+#ifdef SYS_mmap2
+ unsigned long pgoff = (unsigned long)(off / 4096);
+ long int r = syscall(SYS_mmap2, addr, len, (unsigned long)prot, (unsigned long)flags, fd, pgoff);
+#else
+ long int r = syscall(SYS_mmap, addr, len, (unsigned long)prot, (unsigned long)flags, fd, off);
+#endif
+ return (void *)r;
+}
+
+
+static void *
+memcheck_mremap(void *old, size_t old_size, size_t new_size, int flags, void *new_address)
+{
+ long int r = syscall(SYS_mremap, old, old_size, new_size, flags, new_address);
+ return (void *)r;
+}
+
+
+static int
+memcheck_munmap(void *ptr, size_t n)
+{
+ return (int)syscall(SYS_munmap, ptr, n);
+}
+
+
+static void *
+memcheck_mmap_ram(size_t n)
+{
+ return memcheck_mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+
+#ifdef PRINT_BACKTRACES
+static struct backtrace *
+memcheck_create_backtrace(void)
+{
+ struct backtrace *ret = NULL, *new;
+ unw_word_t rip;
+ unw_cursor_t cursor;
+ unw_context_t context;
+ size_t n = 0, size = 0;
+ int saved_errno = errno;
+
+ memleak_exclusion += 1;
+
+ if (unw_getcontext(&context))
+ goto fail;
+ if (unw_init_local(&cursor, &context))
+ goto fail;
+
+ n = 8;
+ size = offsetof(struct backtrace, rips) + n * sizeof(*ret->rips);
+ ret = memcheck_mmap_ram(size);
+ if (!ret)
+ goto fail;
+ ret->alloc_size = size;
+
+ ret->n = 0;
+ while (unw_step(&cursor) > 0) {
+ if (unw_get_reg(&cursor, UNW_REG_IP, &rip))
+ goto fail;
+ if (ret->n == n) {
+ n += 8;
+ size = offsetof(struct backtrace, rips) + n * sizeof(*ret->rips);
+ new = memcheck_mmap_ram(size);
+ if (!new)
+ goto fail;
+ new->alloc_size = size;
+ new->n = ret->n;
+ memcpy(new->rips, ret->rips, n * sizeof(*ret->rips));
+ memcheck_munmap(ret, ret->alloc_size);
+ ret = new;
+ }
+ ret->rips[ret->n++] = (uintptr_t)rip;
+ }
+
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+ return ret;
+
+fail:
+ if (ret)
+ memcheck_munmap(ret, ret->alloc_size);
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+ return NULL;
+}
+#endif
+
+
+#ifdef PRINT_BACKTRACES
+static void
+memcheck_print_backtrace(struct backtrace *backtrace, const char *indent)
+{
+ int saved_errno = errno, lineno;
+ char *debuginfo_path = NULL;
+ Dwarf_Addr ip;
+ Dwfl_Callbacks callbacks;
+ Dwfl *dwfl = NULL;
+ Dwfl_Line *line = NULL;
+ Dwfl_Module *module = NULL;
+ const char *filename = NULL;
+ const char *funcname = NULL;
+ size_t i;
+
+ if (!backtrace)
+ return;
+
+ memleak_exclusion += 1;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.find_elf = dwfl_linux_proc_find_elf;
+ callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
+ callbacks.debuginfo_path = &debuginfo_path;
+
+ if (dwfl) {
+ if (dwfl_linux_proc_report(dwfl, getpid()) ||
+ dwfl_report_end(dwfl, NULL, NULL)) {
+ perror("");
+ dwfl_end(dwfl);
+ dwfl = NULL;
+ }
+ }
+
+ for (i = 0; i < backtrace->n; i++) {
+ ip = (Dwarf_Addr)backtrace->rips[i];
+
+ if (dwfl) {
+ module = dwfl_addrmodule(dwfl, ip);
+ funcname = module ? dwfl_module_addrname(module, ip) : NULL;
+ line = dwfl_getsrc(dwfl, ip);
+ if (line)
+ filename = dwfl_lineinfo(line, &(Dwarf_Addr){0}, &lineno, NULL, NULL, NULL);
+ }
+
+ dprintf(STDERR_FILENO, "%s%s 0x%016"PRIxPTR": %s",
+ indent, !i ? "at" : "by",
+ (uintptr_t)ip,
+ funcname ? funcname : "???");
+ if (line)
+ dprintf(STDERR_FILENO, " (%s:%i)\n", filename, lineno);
+ else
+ dprintf(STDERR_FILENO, "\n");
+ }
+
+ if (dwfl)
+ dwfl_end(dwfl);
+
+ errno = saved_errno;
+ memleak_exclusion -= 1;
+}
+#endif
+
+
+#if defined(__GNUC__)
+__attribute__((__format__(__gnu_printf__, 1, 2)))
+#endif
+static void
+memcheck_errorf(const char *fmt, ...)
+{
+ va_list ap;
+ memleak_exclusion = 1;
+ va_start(ap, fmt);
+ vdprintf(STDERR_FILENO, fmt, ap);
+ dprintf(STDERR_FILENO, ", aborting...\n");
+ va_end(ap);
+#ifdef PRINT_BACKTRACES
+ memcheck_print_backtrace(memcheck_create_backtrace(), "\t");
+#endif
+ abort();
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static struct allocinfo *
+memcheck_getalloc(void *ptr)
+{
+ size_t i;
+ for (i = 0; i < nallocs; i++)
+ if (allocs[i].ptr == ptr)
+ return &allocs[i];
+ return NULL;
+}
+
+
+static int
+memcheck_fake_fail(void)
+{
+ have_custom_malloc = 1;
+
+ if (!alloc_fail_exclusion) {
+ if (alloc_fail_all || (alloc_fail_in && !--alloc_fail_in)) {
+ alloc_fail_all = 1;
+ errno = ENOMEM;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+memcheck_putalloc(struct allocinfo *info)
+{
+ if (nallocs == allocs_size) {
+ size_t old_allocs_size = allocs_size;
+ void *new;
+ if (allocs_size > SIZE_MAX / sizeof(*allocs) - 64)
+ memcheck_errorf("size of memory bookkeeping will exceed SIZE_MAX");
+ allocs_size += 64;
+ new = memcheck_mmap_ram(allocs_size * sizeof(*allocs));
+ if (!new)
+ memcheck_errorf("failed to allocate enought memory for bookkeeping");
+ if (allocs) {
+ memcpy(new, allocs, nallocs * sizeof(*allocs));
+ memcheck_munmap(allocs, old_allocs_size * sizeof(*allocs));
+ }
+ allocs = new;
+ }
+ allocs[nallocs++] = *info;
+}
+
+
+static int
+memcheck_free(void *ptr, size_t size, enum alloctype type)
+{
+ int ret;
+ struct allocinfo *alloc;
+ alloc = memcheck_getalloc(ptr);
+ if (!alloc)
+ memcheck_errorf("attempting to deallocate unknown pointer %p", ptr);
+ if (alloc->type != type)
+ memcheck_errorf("attempting to deallocating with incompatible function");
+ if (type == ALLOCTYPE_MMAP && size != alloc->real_size)
+ memcheck_errorf("attempting to munmap(2) pointer %p with wrong size", ptr);
+ ret = memcheck_munmap(alloc->real, alloc->real_size);
+#ifdef PRINT_BACKTRACES
+ if (alloc->backtrace)
+ memcheck_munmap(alloc->backtrace, alloc->backtrace->alloc_size);
+#endif
+ *alloc = allocs[--nallocs];
+ if (!nallocs) {
+ memcheck_munmap(allocs, allocs_size * sizeof(*allocs));
+ allocs = NULL;
+ nallocs = 0;
+ allocs_size = 0;
+ }
+ return ret;
+}
+
+
+static void *
+memcheck_realloc(void *old, size_t n, size_t m, size_t alignment, int initbyte)
+{
+ struct allocinfo info;
+ uintptr_t addr;
+ void *ret;
+
+ if (memcheck_fake_fail())
+ return NULL;
+
+ if (!n || !m)
+ memcheck_errorf("attempting to allocate with zero size which is implementation-defined behaviour");
+ if (n > SIZE_MAX / m)
+ memcheck_errorf("attempting allocate more than SIZE_MAX");
+
+ if (!alignment)
+ alignment = _Alignof(max_align_t);
+
+ n *= m;
+ if (n > SIZE_MAX - (alignment - 1U))
+ memcheck_errorf("attempting allocate more than SIZE_MAX after alignment padding");
+
+#ifdef PRINT_BACKTRACES
+ info.backtrace = memleak_exclusion ? NULL : memcheck_create_backtrace();
+#endif
+ info.dont_track = memleak_exclusion > 0;
+ info.flags = 0;
+ info.type = ALLOCTYPE_REALLOC;
+ info.real_size = n + (alignment - 1U);
+ info.real = memcheck_mmap_ram(info.real_size);
+ if (!info.real)
+ memcheck_errorf("failed to allocate enough memory");
+ memset(info.real, initbyte, info.real_size);
+
+ addr = (uintptr_t)info.real;
+ if (addr % alignment)
+ addr += alignment - addr % alignment;
+ ret = info.ptr = (void *)addr;
+
+ memcheck_putalloc(&info);
+
+ if (old) {
+ struct allocinfo *oldinfo;
+ oldinfo = memcheck_getalloc(old);
+ if (!oldinfo)
+ memcheck_errorf("attempting to reallocate unknown pointer %p", old);
+ memcpy(info.real, oldinfo->real, info.real_size < oldinfo->real_size ? info.real_size : oldinfo->real_size);
+ memcheck_free(old, 0, ALLOCTYPE_REALLOC);
+ }
+
+ return ret;
+}
+
+
+
+/* Implementation of checked functions */
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+
+void *
+mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off)
+{
+ struct allocinfo info;
+ void *ret;
+
+ if (off % 4096)
+ memcheck_errorf("attempting to mmap(2) with offset %ju which is not a multiple of 4096", (uintmax_t)off);
+
+#ifdef PRINT_BACKTRACES
+ info.backtrace = memleak_exclusion ? NULL : memcheck_create_backtrace();
+#endif
+ info.dont_track = memleak_exclusion > 0;
+ info.flags = flags;
+ info.type = ALLOCTYPE_MMAP;
+ info.real_size = len;
+ info.real = memcheck_mmap(addr, len, prot, flags, fd, off);
+ if (!info.real)
+ memcheck_errorf("failed to mmap(2)");
+ ret = info.ptr = info.real;
+
+ memcheck_putalloc(&info);
+ return ret;
+}
+
+
+int
+munmap(void *ptr, size_t n)
+{
+ return memcheck_free(ptr, n, ALLOCTYPE_MMAP);
+}
+
+
+void *
+mremap(void *old, size_t old_size, size_t new_size, int flags, ...)
+{
+ struct allocinfo *oldinfo;
+ void *new_address = NULL;
+ void *ret;
+
+ if (memcheck_fake_fail())
+ return NULL;
+
+ oldinfo = memcheck_getalloc(old);
+ if (!oldinfo)
+ memcheck_errorf("attempting to mremap(2) unknown pointer %p", old);
+ if (oldinfo->type != ALLOCTYPE_MMAP)
+ memcheck_errorf("attempting to mremap(2) pointer %p that was not allocated with mmap(2) or mremap(2)", old);
+
+ if (flags & MREMAP_FIXED) {
+ va_list ap;
+ va_start(ap, flags);
+ new_address = va_arg(ap, void *);
+ va_end(ap);
+ }
+
+ ret = memcheck_mremap(old, old_size, new_size, flags, new_address);
+ if (!ret)
+ return NULL;
+
+ if (ret == old || !(flags & MREMAP_DONTUNMAP)) {
+ oldinfo->ptr = oldinfo->real = ret;
+ oldinfo->real_size = new_size;
+ } else {
+ struct allocinfo info;
+ info = *oldinfo;
+ info.ptr = info.real = ret;
+ info.real_size = new_size;
+ memcheck_putalloc(&info);
+ }
+
+ return ret;
+}
+
+
+void
+free(void *ptr)
+{
+ if (!ptr)
+ return;
+ memcheck_free(ptr, 0, ALLOCTYPE_REALLOC);
+}
+
+
+void *
+malloc(size_t n)
+{
+ return memcheck_realloc(NULL, 1, n, 0, 'x');
+}
+
+
+void *
+calloc(size_t n, size_t m)
+{
+ return memcheck_realloc(NULL, n, m, 0, 0);
+}
+
+
+void *
+realloc(void *old, size_t n)
+{
+ return memcheck_realloc(old, 1, n, 0, 'x');
+}
+
+
+void *
+reallocarray(void *old, size_t n, size_t m)
+{
+ return memcheck_realloc(old, n, m, 0, 'x');
+}
+
+
+void *
+memdup(const void *s, size_t n)
+{
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+char *
+strdup(const char *s)
+{
+ size_t n = strlen(s) + 1U;
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+char *
+strndup(const char *s, size_t max)
+{
+ size_t n = strnlen(s, max) + 1U;
+ char *r = memcheck_realloc(NULL, 1, n, 0, 'x');
+ if (n > max)
+ n = max;
+ if (r)
+ memcpy(r, s, n);
+ return r;
+}
+
+
+int
+posix_memalign(void **ret_out, size_t align, size_t n)
+{
+ int saved_errno, r;
+ saved_errno = errno, errno = 0;
+ if (!align || (align & (align - 1U)) || align % sizeof(void *))
+ memcheck_errorf("invalid alignment for posix_memalign: %zu", align);
+ *ret_out = memcheck_realloc(NULL, 1, n, align, 'x');
+ return r = errno, errno = saved_errno, r;
+}
+
+
+void *
+memalign(size_t align, size_t n)
+{
+ if (!align)
+ memcheck_errorf("invalid alignment for memalign: %zu", align);
+ return memcheck_realloc(NULL, 1, n, align, 'x');
+}
+
+
+void *
+aligned_alloc(size_t align, size_t n)
+{
+ if (!align || (align & (align - 1U)))
+ memcheck_errorf("invalid alignment for aligned_alloc: %zu", align);
+ return memcheck_realloc(NULL, 1, n, align, 'x');
+}
+
+
+void *
+valloc(size_t n)
+{
+ return memcheck_realloc(NULL, 1, n, 4096U, 'x');
+}
+
+
+void *
+pvalloc(size_t n)
+{
+ if (n % 4096U) {
+ n |= 4096U - 1U;
+ if (n == SIZE_MAX)
+ memcheck_errorf("attempting to allocate beyond SIZE_MAX");
+ n += 1U;
+ }
+ return memcheck_realloc(NULL, 1, n, 4096U, 'x');
+}
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/* Test functions */
+
+
+int
+memcheck_have_custom_malloc(void)
+{
+ static void *volatile test_have_custom_malloc_ptr = NULL;
+ int saved_errno = errno;
+ alloc_fail_exclusion++;
+ memleak_exclusion++;
+ test_have_custom_malloc_ptr = malloc(1);
+ free(test_have_custom_malloc_ptr);
+ memleak_exclusion--;
+ alloc_fail_exclusion--;
+ errno = saved_errno;
+ return have_custom_malloc;
+}
+
+size_t
+memcheck_alloc_fail_in(size_t n)
+{
+ size_t ret = alloc_fail_in;
+ alloc_fail_in = n;
+ alloc_fail_all = 0;
+ return ret;
+}
+
+
+int
+memcheck_check_memleaks(void)
+{
+ struct allocinfo *leak;
+ size_t i, count = 0;
+ memleak_exclusion = 1;
+ for (i = 0; i < nallocs; i++) {
+ leak = &allocs[i];
+ if (leak->dont_track)
+ continue;
+ count += 1;
+ }
+ if (!count)
+ return 1;
+ dprintf(STDERR_FILENO, "%zu %s!\n", count, count == 1 ? "memory leak" : "memory leaks");
+#ifdef PRINT_BACKTRACES
+ for (i = 0; i < nallocs; i++) {
+ leak = &allocs[i];
+ if (leak->dont_track)
+ continue;
+ memcheck_print_backtrace(leak->backtrace, "\t");
+ }
+#endif
+ return 0;
+}
+
+void memcheck_alloc_fail_exclusion_begin(void) { alloc_fail_exclusion++; }
+void memcheck_alloc_fail_exclusion_end(void) { alloc_fail_exclusion--; }
+void memcheck_exclusion_begin(void) { memleak_exclusion++; }
+void memcheck_exclusion_end(void) { memleak_exclusion--; }
+void memcheck_begin(void) { memleak_exclusion = 0; }
diff --git a/memcheck.h b/memcheck.h
new file mode 100644
index 0000000..66625df
--- /dev/null
+++ b/memcheck.h
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include <stddef.h>
+
+int memcheck_have_custom_malloc(void);
+size_t memcheck_alloc_fail_in(size_t);
+int memcheck_check_memleaks(void);
+void memcheck_alloc_fail_exclusion_begin(void);
+void memcheck_alloc_fail_exclusion_end(void);
+void memcheck_exclusion_begin(void);
+void memcheck_exclusion_end(void);
+void memcheck_begin(void);
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..f7c4977
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libnormalform.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :