From b65375b8ae983098ec4ed4d6d4e0242bcb1a3fb1 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 15:26:04 -0800 Subject: [PATCH 01/24] tweaked Prolog test --- README.md | 2 +- t/ref/prolog/Makespec.pro | 5 +++-- t/target/prolog/Makespec.pro | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2cae027..28fa7f7 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ We can create a top-level target that generates all solutions: sp(human). sp(zebrafish). - % rule for generating a pair of (non-identical) species (asymetric) + % rule for generating a pair of (non-identical) species (asymmetric) pair(X,Y) :- sp(X),sp(Y),X@ Date: Fri, 9 Dec 2016 17:07:59 -0800 Subject: [PATCH 02/24] style edits --- README.md | 2 +- t/target/prolog/Makespec.pro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28fa7f7..27b3d69 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ We can create a top-level target that generates all solutions: all <-- Deps, {findall( t(['align-',X,-,Y,'.tbl']), pair(X,Y), - Deps)}. + Deps )}. % biomake rule 'align-$X-$Y.tbl' <-- ['$X.obo', '$Y.obo'], diff --git a/t/target/prolog/Makespec.pro b/t/target/prolog/Makespec.pro index 1b36c8a..10393af 100644 --- a/t/target/prolog/Makespec.pro +++ b/t/target/prolog/Makespec.pro @@ -14,7 +14,7 @@ all <-- Deps, {findall( t([X,-,Y,'.pair']), pair(X,Y), Deps), -format("Deps=~w~n",[Deps])}. + format("Deps=~w~n",[Deps])}. % biomake rules '$X.single' <-- [], From 0d96b1b42919714ec80a1fb41810822e24b488eb Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 17:12:20 -0800 Subject: [PATCH 03/24] fixed test-9 --- t/ref/prolog/Makespec.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/ref/prolog/Makespec.pro b/t/ref/prolog/Makespec.pro index 1b36c8a..10393af 100644 --- a/t/ref/prolog/Makespec.pro +++ b/t/ref/prolog/Makespec.pro @@ -14,7 +14,7 @@ all <-- Deps, {findall( t([X,-,Y,'.pair']), pair(X,Y), Deps), -format("Deps=~w~n",[Deps])}. + format("Deps=~w~n",[Deps])}. % biomake rules '$X.single' <-- [], From 965382451873ab4a609c2f7019883178bae67ae3 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 17:20:07 -0800 Subject: [PATCH 04/24] Updated README with some installation instructions --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27b3d69..88e2d4e 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,11 @@ Getting Started 1. Install SWI-Prolog from http://www.swi-prolog.org 2. Get the latest biomake source from github. No installation steps are -required. Add it to your path (changing the directory if necessary): +required. Just add it to your path (changing the directory if necessary): `export PATH=$PATH:$HOME/biomake/bin` +Alternatively, if you want to install it in `/usr/local/bin`, type `make install`. +You can also try `make test` to run the test suite. 3. Get (minimal) help from the command line: @@ -320,14 +322,18 @@ treats variable expansion as a post-processing step (part of the language) rathe In Biomake, variable expansions must be aligned with the overall syntactic structure; they cannot span multiple syntactic elements. As a concrete example, GNU Make allows this sort of thing: + ~~~~ RULE = target: dep1 dep2 $(RULE) dep3 ~~~~ + which (in GNU Make, but not biomake) expands to + ~~~~ target: dep1 dep2 dep3 ~~~~ + That is, the expansion of the `RULE` variable spans both the target list and the start of the dependency list. To emulate this behavior faithfully, Biomake would have to do the variable expansion in a separate preprocessing pass - which would mean we couldn't translate variables directly into Prolog. We think it's worth sacrificing this edge case in order to maintain the semantic parallel between Makefile variables and Prolog variables, which allows for some powerful constructs. @@ -359,6 +365,7 @@ using the `-Q` option (long form `--queue-engine`). Note that, unlike with GNU M simply by specifying the number of threads with `-j`; you need `-Q` as well. There are several queueing engines currently supported: + - `-Q poolq` uses an internal thread pool for running jobs in parallel on the same machine that `biomake` is running on - `-Q sge` uses [Sun Grid Engine](https://en.wikipedia.org/wiki/Oracle_Grid_Engine) - `-Q pbs` uses [PBS](https://en.wikipedia.org/wiki/Portable_Batch_System) From 5b453b511929719f9d4671217168f491e1ff5ef4 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:50:29 -0800 Subject: [PATCH 05/24] Arithmetic functions --- README.md | 11 +++++++++ prolog/biomake/functions.pl | 44 +++++++++++++++++++++++++++++----- prolog/test/test.pl | 48 +++++++++++++++++++++---------------- t/ref/add | 1 + t/ref/divide | 1 + t/ref/iota | 1 + t/ref/iota2 | 1 + t/ref/iota_add_multiply | 1 + t/ref/multiply | 1 + t/target/Makefile | 18 ++++++++++++++ 10 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 t/ref/add create mode 100644 t/ref/divide create mode 100644 t/ref/iota create mode 100644 t/ref/iota2 create mode 100644 t/ref/iota_add_multiply create mode 100644 t/ref/multiply diff --git a/README.md b/README.md index 88e2d4e..9b7be5d 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,17 @@ at a point where a variable assignment, recipe, or `include` directive could go Unlike GNU Make, Biomake does not offer domain-specific language extensions in [Scheme](https://www.gnu.org/software/guile/) (even though this is one of the cooler aspects of GNU Make), but you can program it in Prolog instead - it's quite hackable. +Arithmetic functions +-------------------- + +Biomake provides a few extra functions for arithmetic on lists: + +- `$(iota N)` returns a space-separated list of numbers from `1` to `N` +- `$(iota S,E)` returns a space-separated list of numbers from `S` to `E` +- `$(add X,L)` adds `X` to every element of the space-separated list `L` +- `$(multiply Y,L)` multiplies every element of the space-separated list `L` by `Y` +- `$(divide Z,L)` divides every element of the space-separated list `L` by `Z` + MD5 hashes ---------- diff --git a/prolog/biomake/functions.pl b/prolog/biomake/functions.pl index 6151cf6..c4fa995 100644 --- a/prolog/biomake/functions.pl +++ b/prolog/biomake/functions.pl @@ -51,10 +51,10 @@ remove_dups(S,R), concat_string_list_spaced(R,Result) }. -makefile_function(Result,V) --> lb("word"), num_arg(N), comma, xlst_arg(L,V), rb, !, +makefile_function(Result,V) --> lb("word"), int_arg(N), comma, xlst_arg(L,V), rb, !, { nth_element(N,L,Result) }. -makefile_function(Result,V) --> lb("wordlist"), num_arg(S), comma, num_arg(E), comma, xlst_arg(L,V), rb, !, +makefile_function(Result,V) --> lb("wordlist"), int_arg(S), comma, int_arg(E), comma, xlst_arg(L,V), rb, !, { slice(S,E,L,Sliced), concat_string_list(Sliced,Result," ") }. @@ -133,6 +133,29 @@ makefile_function(Result,V) --> lb("value"), opt_whitespace, var_arg(Var), rb, !, { bindvar(Var,V,Result) }. +makefile_function(Result,V) --> lb("iota"), opt_whitespace, xstr_arg(Na,V), rb, !, + { atom_number(Na,N), + iota(N,L), + concat_string_list_spaced(L,Result) }. + +makefile_function(Result,V) --> lb("iota"), opt_whitespace, xstr_arg(Sa,V), comma, opt_whitespace, xstr_arg(Ea,V), rb, !, + { atom_number(Sa,S), + atom_number(Ea,E), + iota(S,E,L), + concat_string_list_spaced(L,Result) }. + +makefile_function(Result,V) --> lb("add"), opt_whitespace, xstr_arg(Na,V), comma, opt_whitespace, xlst_arg(List,V), rb, !, + { maplist(add(Na),List,ResultList), + concat_string_list_spaced(ResultList,Result) }. + +makefile_function(Result,V) --> lb("multiply"), opt_whitespace, xstr_arg(Na,V), comma, opt_whitespace, xlst_arg(List,V), rb, !, + { maplist(multiply(Na),List,ResultList), + concat_string_list_spaced(ResultList,Result) }. + +makefile_function(Result,V) --> lb("divide"), opt_whitespace, xstr_arg(Na,V), comma, opt_whitespace, xlst_arg(List,V), rb, !, + { maplist(divide(Na),List,ResultList), + concat_string_list_spaced(ResultList,Result) }. + makefile_function("",_V) --> ['('], str_arg(S), [')'], !, {format("Warning: unknown function $(~w)~n",[S])}. makefile_subst_ref(Result) --> makefile_subst_ref(Result,v(null,null,null,[])). @@ -212,10 +235,10 @@ makefile_and([C|Cs],Result,V) :- expand_vars(C,X,V), X \= "", !, makefile_and(Cs,Result,V). makefile_and(_,"",_). -num_arg(N) --> opt_whitespace, num_chars(C), {C\=[],number_chars(N,C)}. -num_chars([]) --> []. -num_chars([C|Cs]) --> num_char(C), num_chars(Cs). -num_char(X) --> [X],{X@>='0',X@=<'9'},!. % foo('0') % +int_arg(N) --> opt_whitespace, int_chars(C), {C\=[],number_chars(N,C)}. +int_chars([]) --> []. +int_chars([C|Cs]) --> int_char(C), int_chars(Cs). +int_char(X) --> [X],{X@>='0',X@=<'9'},!. % foo('0') % subst(Cs,Ds,Result) --> Cs, !, subst(Cs,Ds,Rest), {append(Ds,Rest,Result)}. subst(Cs,Ds,[C|Rest]) --> [C], !, subst(Cs,Ds,Rest). @@ -282,3 +305,12 @@ addprefix(_,[],[]). addprefix(P,[N|Ns],[R|Rs]) :- string_concat(P,N,R), addprefix(P,Ns,Rs). + +iota(N,L) :- iota(1,N,L). +iota(S,E,[]) :- S > E, !. +iota(S,E,[S|L]) :- Snext is S + 1, iota(Snext,E,L). + +% these arithmetic functions are highly idiosyncratic to this module - do not re-use! +multiply(Aa,Bs,C) :- atom_string(Aa,As), number_string(A,As), number_string(B,Bs), C is A * B. +divide(Aa,Bs,C) :- atom_string(Aa,As), number_string(A,As), number_string(B,Bs), C is B / A. +add(Aa,Bs,C) :- atom_string(Aa,As), number_string(A,As), number_string(B,Bs), C is A + B. diff --git a/prolog/test/test.pl b/prolog/test/test.pl index b044cc0..e5aaff0 100644 --- a/prolog/test/test.pl +++ b/prolog/test/test.pl @@ -85,6 +85,26 @@ run_test("computed_var3"), run_test("two_lines"), run_test("shell_assign"), + + announce("CONDITIONAL SYNTAX"), + + run_test("-f Makefile.cond","ifdef_true"), + run_test("-f Makefile.cond","ifdef_false"), + run_test("-f Makefile.cond","ifeq_true"), + run_test("-f Makefile.cond","ifeq_false"), + run_test("-f Makefile.cond","ifndef_true"), + run_test("-f Makefile.cond","ifndef_false"), + run_test("-f Makefile.cond","ifneq_true"), + run_test("-f Makefile.cond","ifneq_false"), + run_test("-f Makefile.cond","ifeq_true_ifneq_false"), + run_test("-f Makefile.cond","ifeq_false_ifneq_true"), + run_test("-f Makefile.cond","nested_ifeq_ifneq"), + run_test("-f Makefile.cond","nested_ifeq_include"), + run_test("-f Makefile.cond","ifeq_space1"), + run_test("-f Makefile.cond","ifeq_space2"), + run_test("-f Makefile.cond","ifeq_space3"), + run_test("-f Makefile.cond","ifeq_quote"), + run_test("-f Makefile.cond","ifeq_dblquote"), announce("TEXT FUNCTIONS"), run_test("subst"), @@ -132,6 +152,14 @@ run_test("value"), run_test("bad_function_syntax"), + announce("ARITHMETIC FUNCTIONS"), + run_test("iota"), + run_test("iota2"), + run_test("add"), + run_test("multiply"), + run_test("divide"), + run_test("iota_add_multiply"), + announce("MD5 CHECKSUMS"), % this is a test of the MD5 checksums @@ -179,26 +207,6 @@ run_test("CMDLINE_VAR=average --eval EVAL_VAR=worthy","cmdline_eval1"), run_test("CMDLINE_VAR=mediocre. --eval-prolog EVAL_VAR=deserving.","cmdline_eval2"), - announce("CONDITIONAL SYNTAX"), - - run_test("-f Makefile.cond","ifdef_true"), - run_test("-f Makefile.cond","ifdef_false"), - run_test("-f Makefile.cond","ifeq_true"), - run_test("-f Makefile.cond","ifeq_false"), - run_test("-f Makefile.cond","ifndef_true"), - run_test("-f Makefile.cond","ifndef_false"), - run_test("-f Makefile.cond","ifneq_true"), - run_test("-f Makefile.cond","ifneq_false"), - run_test("-f Makefile.cond","ifeq_true_ifneq_false"), - run_test("-f Makefile.cond","ifeq_false_ifneq_true"), - run_test("-f Makefile.cond","nested_ifeq_ifneq"), - run_test("-f Makefile.cond","nested_ifeq_include"), - run_test("-f Makefile.cond","ifeq_space1"), - run_test("-f Makefile.cond","ifeq_space2"), - run_test("-f Makefile.cond","ifeq_space3"), - run_test("-f Makefile.cond","ifeq_quote"), - run_test("-f Makefile.cond","ifeq_dblquote"), - % All done report_counts, ( failed_test(_,_) diff --git a/t/ref/add b/t/ref/add new file mode 100644 index 0000000..db498d8 --- /dev/null +++ b/t/ref/add @@ -0,0 +1 @@ +11 12 13 diff --git a/t/ref/divide b/t/ref/divide new file mode 100644 index 0000000..8395753 --- /dev/null +++ b/t/ref/divide @@ -0,0 +1 @@ +0.5 1.5 4 diff --git a/t/ref/iota b/t/ref/iota new file mode 100644 index 0000000..db5114f --- /dev/null +++ b/t/ref/iota @@ -0,0 +1 @@ +1 2 3 4 5 6 7 8 9 10 diff --git a/t/ref/iota2 b/t/ref/iota2 new file mode 100644 index 0000000..3c2bb02 --- /dev/null +++ b/t/ref/iota2 @@ -0,0 +1 @@ +3 4 5 6 diff --git a/t/ref/iota_add_multiply b/t/ref/iota_add_multiply new file mode 100644 index 0000000..627a249 --- /dev/null +++ b/t/ref/iota_add_multiply @@ -0,0 +1 @@ +26 28 30 32 diff --git a/t/ref/multiply b/t/ref/multiply new file mode 100644 index 0000000..af217af --- /dev/null +++ b/t/ref/multiply @@ -0,0 +1 @@ +6 12 18 diff --git a/t/target/Makefile b/t/target/Makefile index 9644141..3dea2af 100644 --- a/t/target/Makefile +++ b/t/target/Makefile @@ -290,3 +290,21 @@ cmdline_eval%: echo Value of NON_EVAL_VAR is $(NON_EVAL_VAR) >$@ echo Value of CMDLINE_VAR is $(CMDLINE_VAR) >>$@ echo Value of EVAL_VAR is $(EVAL_VAR) >>$@ + +iota: + echo $(iota 10) >$@ + +iota2: + echo $(iota 3,6) >$@ + +add: + echo $(add 10,1 2 3) >$@ + +multiply: + echo $(multiply 3,2 4 6) >$@ + +divide: + echo $(divide 2,1 3 8) >$@ + +iota_add_multiply: + echo $(multiply 2,$(add 10,$(iota 3,6))) >$@ From 9ca51b8a157570b136e554f920f9c7a1d6be2d06 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:51:39 -0800 Subject: [PATCH 06/24] Added a note re installation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9b7be5d..9e0e352 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ required. Just add it to your path (changing the directory if necessary): `export PATH=$PATH:$HOME/biomake/bin` Alternatively, if you want to install it in `/usr/local/bin`, type `make install`. +(This just creates a symlink, so don't remove the `biomake` directory after installation.) You can also try `make test` to run the test suite. 3. Get (minimal) help from the command line: From 4b5491fbd77edd840ce90b3038c510966916bd77 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:52:39 -0800 Subject: [PATCH 07/24] Reformatted install instructions in README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e0e352..c3b7af4 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,16 @@ Getting Started required. Just add it to your path (changing the directory if necessary): `export PATH=$PATH:$HOME/biomake/bin` -Alternatively, if you want to install it in `/usr/local/bin`, type `make install`. + +3. Alternatively, if you want to install it in `/usr/local/bin`, type `make install`. (This just creates a symlink, so don't remove the `biomake` directory after installation.) You can also try `make test` to run the test suite. -3. Get (minimal) help from the command line: +4. Get (minimal) help from the command line: `biomake -h` -4. Create a 'Makespec.pro' or a 'Makefile' (see below) +5. Create a 'Makespec.pro' or a 'Makefile' (see below) Alternate installation instructions ----------------------------------- From b3c0c74fff230d14826a9768b21266d0c7449dc0 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:54:00 -0800 Subject: [PATCH 08/24] Reformatted install instructions in README.md (again) --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c3b7af4..c0e2df2 100644 --- a/README.md +++ b/README.md @@ -24,21 +24,21 @@ required. Just add it to your path (changing the directory if necessary): `export PATH=$PATH:$HOME/biomake/bin` -3. Alternatively, if you want to install it in `/usr/local/bin`, type `make install`. -(This just creates a symlink, so don't remove the `biomake` directory after installation.) -You can also try `make test` to run the test suite. - -4. Get (minimal) help from the command line: +3. Get (minimal) help from the command line: `biomake -h` -5. Create a 'Makespec.pro' or a 'Makefile' (see below) +4. Create a 'Makespec.pro' or a 'Makefile' (see below) Alternate installation instructions ----------------------------------- -This can also be installed via the SWI-Prolog pack system +If you want to install biomake in `/usr/local/bin` instead of adding it to your path, type `make install` in the top level directory of the repository. +(This just creates a symlink, so don't remove the `biomake` directory after installation.) + +You can also try `make test` to run the test suite. +The program can also be installed via the SWI-Prolog pack system. Just start SWI and type: ?- pack_install('biomake'). From 96b7d86ab625b7b2182de05ef17d6f04d0c0f4bb Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:54:50 -0800 Subject: [PATCH 09/24] Reformatted install instructions in README.md (still more) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0e2df2..22f48ae 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Alternate installation instructions ----------------------------------- If you want to install biomake in `/usr/local/bin` instead of adding it to your path, type `make install` in the top level directory of the repository. -(This just creates a symlink, so don't remove the `biomake` directory after installation.) +(This just creates a symlink, so be sure to put the repository somewhere safe, and don't remove it after installation.) You can also try `make test` to run the test suite. From c263061e1cbdb7b9b44afd5858a271f70116facf Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:55:30 -0800 Subject: [PATCH 10/24] Reformatted install instructions in README.md (STILL more) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22f48ae..357f687 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Alternate installation instructions ----------------------------------- If you want to install biomake in `/usr/local/bin` instead of adding it to your path, type `make install` in the top level directory of the repository. -(This just creates a symlink, so be sure to put the repository somewhere safe, and don't remove it after installation.) +(This just creates a symlink, so be sure to put the repository somewhere safe beforehand, and don't remove it after installation.) You can also try `make test` to run the test suite. From 7564046071a8d7cd386118e1a7b9a3ddbb18f218 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:56:21 -0800 Subject: [PATCH 11/24] Simplified usage line in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 357f687..39cc338 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Just start SWI and type: Command-line ------------ - biomake [-h] [-p MAKEPROG] [-f GNUMAKEFILE] [-l DIR] [-n|--dry-run] [-B|--always-make] [TARGETS...] + biomake [OPTIONS] [TARGETS] Options ------- From 67ba619e66ce8e23dd2ef44c3892f8c4ebb3ff32 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Fri, 9 Dec 2016 18:57:48 -0800 Subject: [PATCH 12/24] fixed README typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39cc338..3b48803 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,7 @@ MD5 hashes Instead of using file timestamps, which are fragile (especially on networked filesystems), Biomake can optionally use MD5 checksums to decide when to rebuild files. -Turn on this behavior with the `-H` options (long form `--md5-hash`). +Turn on this behavior with the `-H` option (long form `--md5-hash`). Biomake uses the external program `md5` to do checksums (available on OS X), or `md5sum` (available on Linux). If neither of these are found, Biomake falls back to using the SWI-Prolog md5 implementation; From 005ef23d5f76d139656ea81552aaa0b3ffd7b77a Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 11:33:10 -0800 Subject: [PATCH 13/24] $(bagof...) function --- prolog/biomake/biomake.pl | 274 ++++++++++++++++--------------- prolog/biomake/cli.pl | 22 +-- prolog/biomake/functions.pl | 16 +- prolog/biomake/gnumake_parser.pl | 46 ++++++ prolog/biomake/utils.pl | 6 +- prolog/test/test.pl | 4 + t/ref/bagof1 | 1 + t/ref/bagof2 | 1 + t/target/Makefile.bagof | 15 ++ 9 files changed, 225 insertions(+), 160 deletions(-) create mode 100644 t/ref/bagof1 create mode 100644 t/ref/bagof2 create mode 100644 t/target/Makefile.bagof diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index 413a98d..4446d73 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -2,7 +2,10 @@ :- module(biomake, [ - build_default/0, + disable_backtrace/0, + call_without_backtrace/1, + + build_default/0, build_default/1, halt_success/0, @@ -16,20 +19,23 @@ report/3, report/4, - - consult_gnu_makefile/3, + + consult_gnu_makefile/3, consult_makeprog/3, - eval_string_as_makeprog_term/3, - eval_atom_as_makeprog_term/3, add_spec_clause/3, add_spec_clause/4, add_cmdline_assignment/1, add_gnumake_clause/3, + global_binding/2, + target_bindrule/2, rebuild_required/4, + normalize_pattern/3, + unwrap_t/2, + rule_target/3, rule_dependencies/3, rule_execs/3, @@ -39,7 +45,6 @@ report_run_exec/3, update_hash/3, - global_binding/2, bindvar/3, expand_vars/2, expand_vars/3 @@ -47,6 +52,44 @@ :- use_module(library(biomake/utils)). :- use_module(library(biomake/functions)). +:- use_module(library(biomake/vars)). + +:- user:op(1100,xfy,<--). +:- user:op(1101,xfy,?=). +:- user:op(1102,xfy,:=). +:- user:op(1103,xfy,+=). +:- user:op(1104,xfy,=*). + +% ---------------------------------------- +% EXCEPTIONS +% ---------------------------------------- + +% use no_backtrace to permanently disable backtrace on exception, +% and suppress_backtrace to temporarily disable it. +:- dynamic no_backtrace/0. +:- dynamic suppress_backtrace/0. +:- multifile no_backtrace/0. + +% Intercept a couple of exceptions that are thrown by the threadpool library +% This is kind of yucky, but only seems to affect our exception-handling code +:- dynamic prolog_exception_hook/4. + +user:prolog_exception_hook(error(existence_error(thread,_),context(system:thread_property/2,_)),_,_,_) :- !, fail. +user:prolog_exception_hook('$aborted',_,_,_) :- !, fail. + +% Default exception handler: show backtrace +user:prolog_exception_hook(E,_,_,_) :- + format("Exception: ~w~n",[E]), + (no_backtrace; (suppress_backtrace; backtrace(99))), + !, + fail. + +call_without_backtrace(Term) :- + assert(suppress_backtrace), + catch(call(Term),_,fail), + retract(suppress_backtrace). + +disable_backtrace :- assert(no_backtrace). /** Prolog implementation of Makefile-inspired build system @@ -99,8 +142,6 @@ halt_error. build(T,SL,Opts) :- - %show_global_bindings, - %report('Target: ~w',[T]), debug_report(build,' Target: ~w',[T],SL), target_bindrule(T,Rule), debug_report(build,' Bindrule: ~w',[Rule],SL), @@ -279,6 +320,7 @@ % internal tracking of build order +% a bit hacky to use global assertions/retractions for this :- dynamic build_count/2. :- dynamic build_counter/1. @@ -431,87 +473,9 @@ rule_target(Rule,T,Opts), report('Building target ~w',[T],SL,Opts). -% ---------------------------------------- -% RULES AND PATTERN MATCHING -% ---------------------------------------- - - - -target_bindrule(T,rb(T,Ds,Execs)) :- - mkrule_default(TP1,DP1,Exec1,Goal,Bindings), - append(Bindings,_,Bindings_Open), - V=v(_Base,T,Ds,Bindings_Open), - normalize_patterns(TP1,TPs,V), - - % we allow multiple heads; - % only one of the specified targets has to match - member(TP,TPs), - uniq_pattern_match(TP,T), - Goal, - - % Two-pass expansion of dependency list. - % This is ultra-hacky but allows for variable-expanded dependency lists that contain % wildcards - % (the variables are expanded on the first pass, and the %'s on the second pass). - % A more rigorous solution would be a two-pass expansion of the entire GNU Makefile, - % which would allow currently impossible things like variable-expanded rules, e.g. - % RULE = target: dep1 dep2 - % $(RULE) dep3 - % which (in GNU make, but not here) expands to - % target: dep1 dep2 dep3 - % However, this would fragment the current homology between the Prolog syntax and GNU Make syntax, - % making it harder to translate GNU Makefiles into Prolog. - % Consequently, we currently sacrifice perfect GNU make compatibility for a simpler translation. - expand_deps(DP1,DP2,V), - expand_deps(DP2,Ds,V), - - % expansion of executables - expand_execs(Exec1,Execs,V). - -% semidet -uniq_pattern_match(TL,A) :- - debug(bindrule,'Matching: ~w to ~w',[TL,A]), - pattern_match(TL,A), - debug(bindrule,' Matched: ~w to ~w',[TL,A]), - !. -uniq_pattern_match(TL,A) :- - debug(bindrule,' NO_MATCH: ~w to ~w',[TL,A]), - fail. - -pattern_match(A,B) :- var(A),!,B=A. -pattern_match(t(TL),A) :- !, pattern_match(TL,A). -pattern_match([],''). -pattern_match([Tok|PatternToks],Atom) :- - nonvar(Tok), - !, - atom_concat(Tok,Rest,Atom), - pattern_match(PatternToks,Rest). -pattern_match([Tok|PatternToks],Atom) :- - var(Tok), - !, - atom_concat(Tok,Rest,Atom), - Tok\='', - pattern_match(PatternToks,Rest). - - -pattern_match_list([],[]). -pattern_match_list([P|Ps],[M|Ms]) :- - pattern_match(P,M), - pattern_match_list(Ps,Ms). - -expand_deps(Deps,Result,V) :- - normalize_patterns(Deps,NormDeps,V), - maplist(unwrap_t,NormDeps,ExpandedDeps), - maplist(split_spaces,ExpandedDeps,DepLists), - flatten(DepLists,Result). - -expand_execs(Execs,Result,V) :- - normalize_patterns(Execs,NormExecs,V), - maplist(unwrap_t,NormExecs,ExpandedExecs), - maplist(split_newlines,ExpandedExecs,ExecLists), - flatten(ExecLists,Result). % ---------------------------------------- -% READING +% READING AND WRITING MAKEPROGS % ---------------------------------------- :- dynamic global_cmdline_binding/2. @@ -520,54 +484,23 @@ :- dynamic default_target/1. -:- user:op(1100,xfy,<--). - -:- user:op(1101,xfy,?=). -:- user:op(1102,xfy,:=). -:- user:op(1103,xfy,+=). -:- user:op(1104,xfy,=*). - is_assignment_op(=). is_assignment_op(?=). is_assignment_op(:=). is_assignment_op(+=). is_assignment_op(=*). -consult_gnu_makefile(F,AllOpts,Opts) :- - ensure_loaded(library(biomake/gnumake_parser)), - parse_gnu_makefile(F,M,AllOpts,Opts), - (member(translate_gnu_makefile(P),AllOpts) - -> translate_gnu_makefile(M,P); true). - consult_makeprog(F,AllOpts,Opts) :- debug(makeprog,'reading: ~w',[F]), open(F,read,IO,[]), - read_makeprog_stream(IO,AllOpts,Opts), + read_makeprog_stream(IO,AllOpts,Opts,_), debug(makeprog,'read: ~w',[F]). -read_makeprog_stream(IO,Opts,Opts) :- - at_end_of_stream(IO), - !, - close(IO). - -read_makeprog_stream(IO,OptsOut,OptsIn) :- - read_term(IO,Term,[variable_names(VNs), - syntax_errors(error), - module(biomake)]), - debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), - add_spec_clause(Term,VNs,Opts,OptsIn), - read_makeprog_stream(IO,OptsOut,Opts). - -eval_string_as_makeprog_term(String,OptsOut,OptsIn) :- - atom_string(Atom,String), - eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn). - -eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn) :- - read_term_from_atom(Atom,Term,[variable_names(VNs), - syntax_errors(error), - module(biomake)]), - debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), - add_spec_clause(Term,VNs,OptsOut,OptsIn). +consult_gnu_makefile(F,AllOpts,Opts) :- + ensure_loaded(library(biomake/gnumake_parser)), + parse_gnu_makefile(F,M,AllOpts,Opts), + (member(translate_gnu_makefile(P),AllOpts) + -> translate_gnu_makefile(M,P); true). translate_gnu_makefile(M,P) :- debug(makeprog,"Writing translated makefile to ~w",[P]), @@ -585,6 +518,7 @@ translate_gnumake_clause(assignment(Var,":=",Val), (Var := Val)). translate_gnumake_clause(assignment(Var,"+=",Val), (Var += Val)). translate_gnumake_clause(assignment(Var,"!=",Val), (Var =* Val)). +translate_gnumake_clause(prolog(Term), Term). translate_gnumake_clause(C,_) :- format("Error translating ~w~n",[C]), backtrace(20), @@ -600,13 +534,17 @@ write_clause(_,assignment(Var,_,_)) :- atom_codes(Var,[V|_]), - V @>= 97, V @=< 122, % a through z + V @>= 0'a, V @=< 0'z, % a through z format("Prolog will not recognize `~w' as a variable, as it does not begin with an upper-case letter.~nStubbornly refusing to translate unless you fix this outrageous affront!~n",[Var]), halt_error. write_clause(IO,assignment(Var,Op,Val)) :- format(IO,"~w ~w ~q.~n",[Var,Op,Val]). +write_clause(IO,prolog(Term)) :- + !, + write_term(IO,Term,[]). + add_cmdline_assignment((Var = X)) :- global_unbind(Var), assert(global_cmdline_binding(Var,X)), @@ -618,11 +556,10 @@ !, add_spec_clause(Ass, [Var=Var], Opts, Opts). -add_spec_clause( (Head <-- Deps, Exec), Opts, Opts ) :- - !, - add_spec_clause( (Head <-- Deps, Exec), [], Opts, Opts ). +add_spec_clause( Term, Opts, Opts ) :- + add_spec_clause( Term, [], Opts, Opts ). -add_spec_clause( option(Opts), OptsOut, OptsIn ) :- +add_spec_clause( option(Opts), _VNs, OptsOut, OptsIn ) :- !, append(Opts,OptsIn,OptsOut). @@ -647,7 +584,7 @@ Ass =.. [Op,Var,_], is_assignment_op(Op), atom_codes(Var,[V|_]), - V @>= 97, V @=< 122, % a through z + V @>= 0'a, V @=< 0'z, % a through z debug(makeprog,"Warning: Prolog will not recognize ~w as a variable as it does not begin with an upper-case letter. Use at your own peril!~n",[Var]), fail. @@ -741,6 +678,83 @@ global_binding(Var,Val) :- global_simple_binding(Var,Val). global_binding(Var,Val) :- global_lazy_binding(Var,Val). +% ---------------------------------------- +% RULES AND PATTERN MATCHING +% ---------------------------------------- + +target_bindrule(T,rb(T,Ds,Execs)) :- + mkrule_default(TP1,DP1,Exec1,Goal,Bindings), + append(Bindings,_,Bindings_Open), + V=v(_Base,T,Ds,Bindings_Open), + normalize_patterns(TP1,TPs,V), + + % we allow multiple heads; + % only one of the specified targets has to match + member(TP,TPs), + uniq_pattern_match(TP,T), + Goal, + + % Two-pass expansion of dependency list. + % This is ultra-hacky but allows for variable-expanded dependency lists that contain % wildcards + % (the variables are expanded on the first pass, and the %'s on the second pass). + % A more rigorous solution would be a two-pass expansion of the entire GNU Makefile, + % which would allow currently impossible things like variable-expanded rules, e.g. + % RULE = target: dep1 dep2 + % $(RULE) dep3 + % which (in GNU make, but not here) expands to + % target: dep1 dep2 dep3 + % However, this would fragment the current homology between the Prolog syntax and GNU Make syntax, + % making it harder to translate GNU Makefiles into Prolog. + % Consequently, we currently sacrifice perfect GNU make compatibility for a simpler translation. + expand_deps(DP1,DP2,V), + expand_deps(DP2,Ds,V), + + % expansion of executables + expand_execs(Exec1,Execs,V). + +% semidet +uniq_pattern_match(TL,A) :- + debug(bindrule,'Matching: ~w to ~w',[TL,A]), + pattern_match(TL,A), + debug(bindrule,' Matched: ~w to ~w',[TL,A]), + !. +uniq_pattern_match(TL,A) :- + debug(bindrule,' NO_MATCH: ~w to ~w',[TL,A]), + fail. + +pattern_match(A,B) :- var(A),!,B=A. +pattern_match(t(TL),A) :- !, pattern_match(TL,A). +pattern_match([],''). +pattern_match([Tok|PatternToks],Atom) :- + nonvar(Tok), + !, + atom_concat(Tok,Rest,Atom), + pattern_match(PatternToks,Rest). +pattern_match([Tok|PatternToks],Atom) :- + var(Tok), + !, + atom_concat(Tok,Rest,Atom), + Tok\='', + pattern_match(PatternToks,Rest). + + +pattern_match_list([],[]). +pattern_match_list([P|Ps],[M|Ms]) :- + pattern_match(P,M), + pattern_match_list(Ps,Ms). + +expand_deps(Deps,Result,V) :- + normalize_patterns(Deps,NormDeps,V), + maplist(unwrap_t,NormDeps,ExpandedDeps), + maplist(split_spaces,ExpandedDeps,DepLists), + flatten(DepLists,Result). + +expand_execs(Execs,Result,V) :- + normalize_patterns(Execs,NormExecs,V), + maplist(unwrap_t,NormExecs,ExpandedExecs), + maplist(split_newlines,ExpandedExecs,ExecLists), + flatten(ExecLists,Result). + % ---------------------------------------- % PATTERN SYNTAX AND API % ---------------------------------------- diff --git a/prolog/biomake/cli.pl b/prolog/biomake/cli.pl index f275aba..35bcd86 100644 --- a/prolog/biomake/cli.pl +++ b/prolog/biomake/cli.pl @@ -2,25 +2,7 @@ :- use_module(library(biomake/biomake)). :- use_module(library(biomake/utils)). - -% ---------------------------------------- -% EXCEPTIONS -% ---------------------------------------- - -:- dynamic no_backtrace/0. -:- dynamic prolog_exception_hook/4. - -% Intercept a couple of exceptions that are thrown by the threadpool library -% This is kind of yucky, but only seems to affect our exception-handling code -user:prolog_exception_hook(error(existence_error(thread,_),context(system:thread_property/2,_)),_,_,_) :- !, fail. -user:prolog_exception_hook('$aborted',_,_,_) :- !, fail. - -% Default exception handler: show backtrace -user:prolog_exception_hook(E,_,_,_) :- - format("Exception: ~w~n",[E]), - (no_backtrace; backtrace(99)), - !, - fail. +:- use_module(library(biomake/vars)). % ---------------------------------------- % MAIN PROGRAM @@ -366,5 +348,5 @@ parse_arg(['--trace',Pred|L],L,null) :- trace(Pred), !. arg_info('--trace','predicate','[developers] Print debugging trace for given predicate'). -parse_arg(['--no-backtrace'|L],L,null) :- assert(no_backtrace), !. +parse_arg(['--no-backtrace'|L],L,null) :- disable_backtrace, !. arg_info('--no-backtrace','','[developers] Do not print a backtrace on error'). diff --git a/prolog/biomake/functions.pl b/prolog/biomake/functions.pl index c4fa995..85f75ce 100644 --- a/prolog/biomake/functions.pl +++ b/prolog/biomake/functions.pl @@ -15,6 +15,7 @@ :- use_module(library(readutil)). :- use_module(library(biomake/utils)). +:- use_module(library(biomake/vars)). :- use_module(library(biomake/biomake)). :- use_module(library(biomake/gnumake_parser)). @@ -133,15 +134,12 @@ makefile_function(Result,V) --> lb("value"), opt_whitespace, var_arg(Var), rb, !, { bindvar(Var,V,Result) }. -makefile_function(Result,V) --> lb("iota"), opt_whitespace, xstr_arg(Na,V), rb, !, - { atom_number(Na,N), - iota(N,L), +makefile_function(Result,V) --> lb("iota"), opt_whitespace, xnum_arg(N,V), rb, !, + { iota(N,L), concat_string_list_spaced(L,Result) }. -makefile_function(Result,V) --> lb("iota"), opt_whitespace, xstr_arg(Sa,V), comma, opt_whitespace, xstr_arg(Ea,V), rb, !, - { atom_number(Sa,S), - atom_number(Ea,E), - iota(S,E,L), +makefile_function(Result,V) --> lb("iota"), opt_whitespace, xnum_arg(S,V), comma, opt_whitespace, xnum_arg(E,V), rb, !, + { iota(S,E,L), concat_string_list_spaced(L,Result) }. makefile_function(Result,V) --> lb("add"), opt_whitespace, xstr_arg(Na,V), comma, opt_whitespace, xlst_arg(List,V), rb, !, @@ -156,6 +154,9 @@ { maplist(divide(Na),List,ResultList), concat_string_list_spaced(ResultList,Result) }. +makefile_function(Result,_V) --> lb("bagof"), str_arg(Template), comma, str_arg(Goal), rb, !, + { eval_bagof(Template,Goal,Result) }. + makefile_function("",_V) --> ['('], str_arg(S), [')'], !, {format("Warning: unknown function $(~w)~n",[S])}. makefile_subst_ref(Result) --> makefile_subst_ref(Result,v(null,null,null,[])). @@ -179,6 +180,7 @@ comma --> opt_whitespace, [',']. xlst_arg(L,V) --> xstr_arg(S,V), !, {split_spaces(S,L)}. xchr_arg(C,V) --> xstr_arg(S,V), !, {string_chars(S,C)}. +xnum_arg(N,V) --> xstr_arg(S,V), !, {atom_number(S,N)}. xstr_arg(Sx,V) --> str_arg(S), !, {expand_vars(S,Sx,V)}. chr_arg(C) --> str_arg(S), !, {string_chars(S,C)}. str_arg(S) --> opt_whitespace, str_arg_outer(S). diff --git a/prolog/biomake/gnumake_parser.pl b/prolog/biomake/gnumake_parser.pl index 54df7c7..e0af2f2 100644 --- a/prolog/biomake/gnumake_parser.pl +++ b/prolog/biomake/gnumake_parser.pl @@ -45,6 +45,7 @@ makefile_block([],Opts,Opts,_,_,1) --> info_line, !. makefile_block([],Opts,Opts,_,_,1) --> warning_line, !. makefile_block([],Opts,Opts,_,_,1) --> error_line, !. +makefile_block(Rules,OptsOut,OptsIn,Line,File,Lines) --> prolog_block(true,Rules,OptsOut,OptsIn,Line,File,Lines). makefile_block(Rules,OptsOut,OptsIn,Line,File,Lines) --> makefile_conditional(true,Rules,OptsOut,OptsIn,Line,File,Lines), !. makefile_block(Rules,OptsOut,OptsIn,_,File,1) --> include_line(true,File,Rules,OptsOut,OptsIn), !. makefile_block([Assignment],Opts,Opts,_,_,Lines) --> makefile_assignment(Assignment,Lines), !, @@ -61,6 +62,7 @@ {format(string(Err),"GNU makefile parse error at line ~d of file ~w: ~w",[Line,File,L]), syntax_error(Err)}. +ignore_makefile_block(Opts,Opts,Line,File,Lines) --> prolog_block(false,_,_,Opts,Line,File,Lines). ignore_makefile_block(Opts,Line,File,Lines) --> makefile_conditional(false,_,_,Opts,Line,File,Lines), !. ignore_makefile_block(Opts,_,_,1) --> include_line(false,null,_,Opts,Opts), !. ignore_makefile_block(_Opts,_,_,Lines) --> makefile_assignment(_,Lines), !. @@ -68,6 +70,50 @@ ignore_makefile_block(_Opts,_,_,Lines) --> makefile_recipe(_,Lines), !. ignore_makefile_block(Opts,Line,File,Lines) --> makefile_block([],Opts,Opts,Line,File,Lines). +prolog_block(Active,Rules,OptsOut,OptsIn,Line,File,Lines) --> + opt_space, + "prolog", + opt_period, + opt_whitespace, + "\n", + { Lnext is Line + 1 }, + prolog_block_body(RawLines,Lnext,File,Lbody), + { Lines is Lbody + 1, + read_prolog_from_string(Active,Rules,OptsOut,OptsIn,RawLines) }, + !. + +prolog_block_body(_,_,File,_) --> + call(eos), + { format(string(Err),"GNU makefile parse error (expected endprolog) at end of file ~w",[File]), + syntax_error(Err) }. + +prolog_block_body([],_,_,1) --> + opt_space, + "endprolog", + opt_period, + opt_whitespace, + "\n", + !. + +prolog_block_body([RawLine|RawLines],Line,File,Lines) --> + line_as_string(RawLine), + { Lnext is Line + 1 }, + prolog_block_body(RawLines,Lnext,File,Lbody), + { Lines is Lbody + 1 }, + !. + +opt_period --> ".". +opt_period --> []. + +read_prolog_from_string(false,[],Opts,Opts,_). +read_prolog_from_string(true,Rules,OptsOut,OptsIn,RawLines) :- + concat_string_list(RawLines,Raw,"\n"), + open_string(Raw,IOS), + read_makeprog_stream(IOS,OptsOut,OptsIn,Terms), + maplist(wrap_prolog,Terms,Rules). + +wrap_prolog(Term,prolog(Term)). + error_line --> opt_space, "$(error", diff --git a/prolog/biomake/utils.pl b/prolog/biomake/utils.pl index 531699f..be62673 100644 --- a/prolog/biomake/utils.pl +++ b/prolog/biomake/utils.pl @@ -90,10 +90,10 @@ alphanum_char(X) --> parse_num_char(X),!. parse_num_char(X) --> [X],{X@>='0',X@=<'9'}. -alphanum_code(X) --> [X],{X@>=65,X@=<90},!. % A through Z -alphanum_code(X) --> [X],{X@>=97,X@=<122},!. % a through z +alphanum_code(X) --> [X],{X@>=0'A,X@=<0'Z},!. % A through Z +alphanum_code(X) --> [X],{X@>=0'a,X@=<0'z},!. % a through z alphanum_code(X) --> parse_num_code(X),!. -parse_num_code(X) --> [X],{X@>=48,X@=<57}. % 0 through 9 +parse_num_code(X) --> [X],{X@>=0'0,X@=<0'9}. % 0 through 9 n_chars(N,_,[]) :- N =< 0, !. n_chars(N,C,[C|Ls]) :- Ndec is N - 1, n_chars(Ndec,C,Ls), !. diff --git a/prolog/test/test.pl b/prolog/test/test.pl index e5aaff0..ced65f7 100644 --- a/prolog/test/test.pl +++ b/prolog/test/test.pl @@ -207,6 +207,10 @@ run_test("CMDLINE_VAR=average --eval EVAL_VAR=worthy","cmdline_eval1"), run_test("CMDLINE_VAR=mediocre. --eval-prolog EVAL_VAR=deserving.","cmdline_eval2"), + announce("EMBEDDED PROLOG SYNTAX"), + run_test("-f Makefile.bagof","bagof1"), + run_test("-f Makefile.bagof","bagof2"), + % All done report_counts, ( failed_test(_,_) diff --git a/t/ref/bagof1 b/t/ref/bagof1 new file mode 100644 index 0000000..3774da6 --- /dev/null +++ b/t/ref/bagof1 @@ -0,0 +1 @@ +a b c diff --git a/t/ref/bagof2 b/t/ref/bagof2 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/t/ref/bagof2 @@ -0,0 +1 @@ + diff --git a/t/target/Makefile.bagof b/t/target/Makefile.bagof new file mode 100644 index 0000000..6b36abd --- /dev/null +++ b/t/target/Makefile.bagof @@ -0,0 +1,15 @@ + +prolog +sp(a). +sp(b). +sp(c). +endprolog + +ABC = $(bagof X,sp(X)) +DEF = $(bagof X,nonexistent_predicate(X)) + +bagof1: + echo $(ABC) >$@ + +bagof2: + echo $(DEF) >$@ From 604a074222124a1cf613c4abc81b2f52ba1a983d Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 11:38:39 -0800 Subject: [PATCH 14/24] refactored code for prolog variables into separate namespace --- prolog/biomake/vars.pl | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 prolog/biomake/vars.pl diff --git a/prolog/biomake/vars.pl b/prolog/biomake/vars.pl new file mode 100644 index 0000000..973a201 --- /dev/null +++ b/prolog/biomake/vars.pl @@ -0,0 +1,55 @@ +% * -*- Mode: Prolog -*- */ + +:- module(vars, + [ + read_makeprog_stream/4, + + eval_string_as_makeprog_term/3, + eval_atom_as_makeprog_term/3, + + eval_bagof/3 + ]). + +:- use_module(library(biomake/biomake)). + +% we have to duplicate these op declarations, ugh +:- user:op(1100,xfy,<--). +:- user:op(1101,xfy,?=). +:- user:op(1102,xfy,:=). +:- user:op(1103,xfy,+=). +:- user:op(1104,xfy,=*). + +% although it is called from another module, eval_bagof has to be in this module +% because term assertions from the makeprog go in this namespace. +eval_bagof(TemplateStr,GoalStr,Result) :- + format(atom(BagofAtom),"bagof(~w,~w,List)",[TemplateStr,GoalStr]), + atom_to_term(BagofAtom,BagofTerm,Bindings), + call_without_backtrace(BagofTerm), + member(('List' = List), Bindings), + atomic_list_concat(List," ",Result), + !. +eval_bagof(_,_,""). + +read_makeprog_stream(IO,Opts,Opts,[]) :- + at_end_of_stream(IO), + !, + close(IO). + +read_makeprog_stream(IO,OptsOut,OptsIn,[Term|Terms]) :- + read_term(IO,Term,[variable_names(VNs), + syntax_errors(error), + module(vars)]), + debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), + add_spec_clause(Term,VNs,Opts,OptsIn), + read_makeprog_stream(IO,OptsOut,Opts,Terms). + +eval_string_as_makeprog_term(String,OptsOut,OptsIn) :- + atom_string(Atom,String), + eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn). + +eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn) :- + read_term_from_atom(Atom,Term,[variable_names(VNs), + syntax_errors(error), + module(vars)]), + debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), + add_spec_clause(Term,VNs,OptsOut,OptsIn). From d76fa035f587e1d8fbc8e9e780dcfe6cd651682a Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 12:54:30 -0800 Subject: [PATCH 15/24] Embedded Prolog goals attached to Makefile rules --- README.md | 49 +++++++++++++++--- prolog/biomake/biomake.pl | 78 ++++++++++++++++++++++------- prolog/biomake/gnumake_parser.pl | 19 +++++-- prolog/biomake/vars.pl | 37 +++----------- prolog/test/test.pl | 3 ++ t/ref/embedded/Makefile | 22 ++++++++ t/ref/embedded/human-mouse.pair | 3 ++ t/ref/embedded/human-zebrafish.pair | 3 ++ t/ref/embedded/human.single | 1 + t/ref/embedded/mouse-zebrafish.pair | 3 ++ t/ref/embedded/mouse.single | 1 + t/ref/embedded/zebrafish.single | 1 + t/ref/goal_a | 1 + t/ref/goal_x | 1 + t/ref/prolog/Makespec.pro | 2 +- t/target/Makefile.goal | 12 +++++ t/target/embedded/Makefile | 22 ++++++++ t/target/prolog/Makespec.pro | 2 +- 18 files changed, 199 insertions(+), 61 deletions(-) create mode 100644 t/ref/embedded/Makefile create mode 100644 t/ref/embedded/human-mouse.pair create mode 100644 t/ref/embedded/human-zebrafish.pair create mode 100644 t/ref/embedded/human.single create mode 100644 t/ref/embedded/mouse-zebrafish.pair create mode 100644 t/ref/embedded/mouse.single create mode 100644 t/ref/embedded/zebrafish.single create mode 100644 t/ref/goal_a create mode 100644 t/ref/goal_x create mode 100644 t/target/Makefile.goal create mode 100644 t/target/embedded/Makefile diff --git a/README.md b/README.md index 3b48803..78ee05f 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ files producing some output (e.g. biological sequence alignment, or ontology alignment). Assume our file convention is to suffix ".fa" on the inputs. We can write a `Makespec.pro` with the following: - 'align-$X-$Y.tbl' <-- ['$X.fa', '$Y.fa'], + 'align-$X-$Y' <-- ['$X.fa', '$Y.fa'], 'align $X.fa $Y.fa > $@'. (note that if we have multiple dependecies, these must be separated by @@ -225,11 +225,11 @@ commas and enclodes in square brackets - i.e. a Prolog list) Now if we have files `x.fa` and `y.fa` we can type: - biomake align-x-y.tbl + biomake align-x-y We could achieve the same thing with the following GNU `Makefile`: - align-$X-$Y.tbl: $X.fa $Y.fa + align-$X-$Y: $X.fa $Y.fa align $X.fa $Y.fa > $@ This is already an improvement over GNU Make, which only allows a single wildcard. @@ -243,7 +243,7 @@ program when the inputs match a certain table in our database: sp(human). sp(zebrafish). - 'align-$X-$Y.tbl' <-- ['$X.fa', '$Y.fa'], + 'align-$X-$Y' <-- ['$X.fa', '$Y.fa'], {sp(X),sp(Y)}, 'align $X.fa $Y.fa > $@'. @@ -257,7 +257,7 @@ Note that here the rule consists of 4 parts: In this case, the Prolog goal succeeds with 9 solutions, with 3 different values for X and Y. If we type: - biomake align-platypus-coelocanth.tbl + biomake align-platypus-coelocanth It will not succeed, even if the .fa files are on the filesystem. This is because the goal cannot be satisfied for these two values. @@ -274,14 +274,16 @@ We can create a top-level target that generates all solutions: % top level target all <-- Deps, - {findall( t(['align-',X,-,Y,'.tbl']), + {findall( t(['align-',X,-,Y]), pair(X,Y), Deps )}. % biomake rule - 'align-$X-$Y.tbl' <-- ['$X.obo', '$Y.obo'], + 'align-$X-$Y' <-- ['$X.obo', '$Y.obo'], 'align $X.obo $Y.obo > $@'. +(The `t(...)` wrapper around `['align-',X,-,Y]` is used to concatenate a list of tokens.) + Now if we type: biomake all @@ -359,6 +361,39 @@ Biomake provides a few extra functions for arithmetic on lists: - `$(multiply Y,L)` multiplies every element of the space-separated list `L` by `Y` - `$(divide Z,L)` divides every element of the space-separated list `L` by `Z` +Embedding Prolog in Makefiles +----------------------------- + +- Prolog can be embedded within `prolog` and `endprolog` directives +- `$(bagof Template,Goal)` expands to the space-separated `List` from the Prolog `bagof(Template,Goal,List)` +- Following the dependent list with `{Goal}` causes the rule to match only if `Goal` is satisfied + +e.g. + +~~~~ +prolog +mammal(mouse). +mammal(human). +sp(zebrafish). +sp(X) :- mammal(X). + +pair(X,Y) :- sp(X),sp(Y),X@ $@ + +$X-$Y.pair: $X.single $Y.single { pair(X,Y) } + cat $X.single $Y.single > $@ + echo Files: $X.single $Y.single >> $@ +~~~~ + MD5 hashes ---------- diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index 4446d73..f967c21 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -22,6 +22,11 @@ consult_gnu_makefile/3, consult_makeprog/3, + read_makeprog_stream/4, + + read_string_as_makeprog_term/3, + read_atom_as_makeprog_term/3, + eval_atom_as_makeprog_term/3, add_spec_clause/3, add_spec_clause/4, @@ -60,15 +65,20 @@ :- user:op(1103,xfy,+=). :- user:op(1104,xfy,=*). +/** Prolog implementation of Makefile-inspired build system + + See the README + + */ + % ---------------------------------------- % EXCEPTIONS % ---------------------------------------- -% use no_backtrace to permanently disable backtrace on exception, -% and suppress_backtrace to temporarily disable it. +% use disable_backtrace to permanently disable backtrace on exception, +% and call_without_backtrace to temporarily disable it. :- dynamic no_backtrace/0. :- dynamic suppress_backtrace/0. -:- multifile no_backtrace/0. % Intercept a couple of exceptions that are thrown by the threadpool library % This is kind of yucky, but only seems to affect our exception-handling code @@ -91,13 +101,6 @@ disable_backtrace :- assert(no_backtrace). -/** Prolog implementation of Makefile-inspired build system - - See the README - - */ - - % ---------------------------------------- % TOP-LEVEL @@ -502,16 +505,49 @@ (member(translate_gnu_makefile(P),AllOpts) -> translate_gnu_makefile(M,P); true). +read_makeprog_stream(IO,Opts,Opts,[]) :- + at_end_of_stream(IO), + !, + close(IO). + +read_makeprog_stream(IO,OptsOut,OptsIn,[Term|Terms]) :- + read_term(IO,Term,[variable_names(VNs), + syntax_errors(error), + module(vars)]), + debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), + add_spec_clause(Term,VNs,Opts,OptsIn), + read_makeprog_stream(IO,OptsOut,Opts,Terms). + +eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn) :- + read_atom_as_makeprog_term(Atom,Term,VNs), + debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), + add_spec_clause(Term,VNs,OptsOut,OptsIn). + +read_atom_as_makeprog_term(Atom,Term,VNs) :- + read_term_from_atom(Atom,Term,[variable_names(VNs), + syntax_errors(error), + module(vars)]). + +read_string_as_makeprog_term(String,Term,VNs) :- + atom_string(Atom,String), + read_atom_as_makeprog_term(Atom,Term,VNs). + translate_gnu_makefile(M,P) :- debug(makeprog,"Writing translated makefile to ~w",[P]), open(P,write,IO,[]), forall(member(G,M), write_clause(IO,G)), close(IO). +add_gnumake_clause(G,OptsOut,OptsIn) :- + translate_gnumake_clause(G,P,VNs), + !, + add_spec_clause(P,VNs,OptsOut,OptsIn). + add_gnumake_clause(G,OptsOut,OptsIn) :- translate_gnumake_clause(G,P), add_spec_clause(P,OptsOut,OptsIn). - + +translate_gnumake_clause(rule(Ts,Ds,Es,{Goal},VNs), (Ts <-- Ds,{Goal},Es), VNs). translate_gnumake_clause(rule(Ts,Ds,Es), (Ts <-- Ds,Es)). translate_gnumake_clause(assignment(Var,"=",Val), (Var = Val)). translate_gnumake_clause(assignment(Var,"?=",Val), (Var ?= Val)). @@ -692,7 +728,7 @@ % only one of the specified targets has to match member(TP,TPs), uniq_pattern_match(TP,T), - Goal, + call_without_backtrace(Goal), % Two-pass expansion of dependency list. % This is ultra-hacky but allows for variable-expanded dependency lists that contain % wildcards @@ -747,13 +783,19 @@ normalize_patterns(Deps,NormDeps,V), maplist(unwrap_t,NormDeps,ExpandedDeps), maplist(split_spaces,ExpandedDeps,DepLists), - flatten(DepLists,Result). + flatten_trim(DepLists,Result). expand_execs(Execs,Result,V) :- normalize_patterns(Execs,NormExecs,V), maplist(unwrap_t,NormExecs,ExpandedExecs), maplist(split_newlines,ExpandedExecs,ExecLists), - flatten(ExecLists,Result). + flatten_trim(ExecLists,Result). + +flatten_trim(Lumpy,Trimmed) :- + flatten(Lumpy,Untrimmed), + include(not_empty,Untrimmed,Trimmed). + +not_empty(X) :- X \= "", X \= ''. % ---------------------------------------- % PATTERN SYNTAX AND API @@ -791,11 +833,10 @@ % this is a bit hacky - parsing is too eager to add t(...) wrapper (original comment by cmungall) % Comment by ihh: not entirely sure what all this wrapping evaluated patterns in t(...) is about. -% Mostly it is a royal pain in the butt, but it seems to be some kind of a marker for pattern evaluation. +% It seems to be some kind of a marker for pattern evaluation. % Anyway... % wrap_t is a construct from cmungall's original code, abstracted into a separate term by me (ihh). -% Beyond the definition here, I really don't know the original intent here. -% unwrap_t flattens a list into a string, removing any t(...) wrappers in the process, +% unwrap_t flattens a list into an atom, removing any t(...) wrappers in the process, % and evaluating any postponed functions wrapped with a call(...) compound clause. wrap_t(t([L]),L) :- member(t(_),L), !. wrap_t(X,[X]). @@ -807,8 +848,7 @@ unwrap_t([],"") :- !. unwrap_t([L|Ls],Flat) :- unwrap_t(L,F), unwrap_t(Ls,Fs), atom_concat(F,Fs,Flat), !. unwrap_t(N,A) :- number(A), atom_number(A,N), !. -unwrap_t(A,F) :- atom(A), atom_string(A,F), !. -unwrap_t(S,S) :- string(S), !. +unwrap_t(S,A) :- string(S), atom_string(A,S), !. unwrap_t(S,S) :- ground(S), !. unwrap_t(X,_) :- type_of(X,T), format("Can't unwrap ~w ~w~n",[T,X]), fail. diff --git a/prolog/biomake/gnumake_parser.pl b/prolog/biomake/gnumake_parser.pl index e0af2f2..f3e9ce3 100644 --- a/prolog/biomake/gnumake_parser.pl +++ b/prolog/biomake/gnumake_parser.pl @@ -304,7 +304,8 @@ {format(string(Err),"GNU makefile parse error (expected endif) at line ~d of file ~w: ~w",[Line,File,L]), syntax_error(Err)}. -xbracket(Sx) --> {char_code('(',L),char_code(')',R),char_code(',',C)}, xdelim(Sx,L,R,[C]). +xbracket(Sx) --> xdelim(Sx,0'(,0'),[0',]). +xbrace(Sx) --> xdelim(Sx,0'{,0'},[]). xdelim(Sx,L,R,X) --> delim(S,L,R,X), !, {expand_vars(S,Sx)}. delim(S,L,R,X) --> delim_outer(Sc,L,R,X), {string_codes(S,Sc)}. delim_outer(S,L,R,X) --> [L], !, delim_inner(I,L,R), [R], delim_outer(Rest,L,R,X), @@ -319,10 +320,22 @@ xdblquote(Sx) --> code_list(C,['"']), {string_codes(S,C), expand_vars(S,Sx)}. xvar(Sx) --> makefile_var_string_from_codes(S), opt_whitespace, "\n", {eval_var(S,Sx)}. - makefile_special_target(queue(none),Lines) --> makefile_recipe(rule([".NOTPARALLEL"],_,_),Lines). +makefile_recipe(rule(Head,Deps,Exec,{Goal},VNs),Lines) --> + makefile_targets(Head), + ":", + opt_makefile_targets(Deps), + "{", + xbrace(GoalAtom), + "}", + "\n", + !, + makefile_execs(Exec,Lexecs), + { Lines is 1 + Lexecs, + read_atom_as_makeprog_term(GoalAtom,Goal,VNs) }. + makefile_recipe(rule(Head,Deps,Exec),Lines) --> makefile_targets(Head), ":", @@ -350,7 +363,7 @@ makefile_warning_text(S) --> string_from_codes(S,")"). makefile_filename_string(S) --> string_from_codes(S," \t\n"). -makefile_target_string(S) --> string_from_codes(S,":; \t\n"). +makefile_target_string(S) --> delim(S,0'(,0'),[0':,0';,0'\s,0'\t,0'\n]), {S \= ""}, !. op_string("=") --> "=". op_string(":=") --> ":=". diff --git a/prolog/biomake/vars.pl b/prolog/biomake/vars.pl index 973a201..ff4addd 100644 --- a/prolog/biomake/vars.pl +++ b/prolog/biomake/vars.pl @@ -2,17 +2,18 @@ :- module(vars, [ - read_makeprog_stream/4, - - eval_string_as_makeprog_term/3, - eval_atom_as_makeprog_term/3, - eval_bagof/3 ]). +/** + + A minimally-cluttered namespace for user variable bindings in Makeprogs & Makefiles. + + */ + :- use_module(library(biomake/biomake)). -% we have to duplicate these op declarations, ugh +% We have to redefine these operators, ugh :- user:op(1100,xfy,<--). :- user:op(1101,xfy,?=). :- user:op(1102,xfy,:=). @@ -29,27 +30,3 @@ atomic_list_concat(List," ",Result), !. eval_bagof(_,_,""). - -read_makeprog_stream(IO,Opts,Opts,[]) :- - at_end_of_stream(IO), - !, - close(IO). - -read_makeprog_stream(IO,OptsOut,OptsIn,[Term|Terms]) :- - read_term(IO,Term,[variable_names(VNs), - syntax_errors(error), - module(vars)]), - debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), - add_spec_clause(Term,VNs,Opts,OptsIn), - read_makeprog_stream(IO,OptsOut,Opts,Terms). - -eval_string_as_makeprog_term(String,OptsOut,OptsIn) :- - atom_string(Atom,String), - eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn). - -eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn) :- - read_term_from_atom(Atom,Term,[variable_names(VNs), - syntax_errors(error), - module(vars)]), - debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), - add_spec_clause(Term,VNs,OptsOut,OptsIn). diff --git a/prolog/test/test.pl b/prolog/test/test.pl index ced65f7..5e4e333 100644 --- a/prolog/test/test.pl +++ b/prolog/test/test.pl @@ -210,6 +210,9 @@ announce("EMBEDDED PROLOG SYNTAX"), run_test("-f Makefile.bagof","bagof1"), run_test("-f Makefile.bagof","bagof2"), + run_test("-f Makefile.goal","goal_a"), + run_test("-f Makefile.goal","goal_x"), + run_test("ref/embedded","target/embedded",["rm [hmz]*"],"",""), % All done report_counts, diff --git a/t/ref/embedded/Makefile b/t/ref/embedded/Makefile new file mode 100644 index 0000000..99dff93 --- /dev/null +++ b/t/ref/embedded/Makefile @@ -0,0 +1,22 @@ + +prolog +mammal(mouse). +mammal(human). +sp(zebrafish). +sp(X) :- mammal(X). + +pair(X,Y) :- sp(X),sp(Y),X@ $@ + +$X-$Y.pair: $X.single $Y.single { pair(X,Y) } + cat $X.single $Y.single > $@ + echo Files: $X.single $Y.single >> $@ diff --git a/t/ref/embedded/human-mouse.pair b/t/ref/embedded/human-mouse.pair new file mode 100644 index 0000000..b081d2d --- /dev/null +++ b/t/ref/embedded/human-mouse.pair @@ -0,0 +1,3 @@ +Single: human +Single: mouse +Files: human.single mouse.single diff --git a/t/ref/embedded/human-zebrafish.pair b/t/ref/embedded/human-zebrafish.pair new file mode 100644 index 0000000..a905730 --- /dev/null +++ b/t/ref/embedded/human-zebrafish.pair @@ -0,0 +1,3 @@ +Single: human +Single: zebrafish +Files: human.single zebrafish.single diff --git a/t/ref/embedded/human.single b/t/ref/embedded/human.single new file mode 100644 index 0000000..9678141 --- /dev/null +++ b/t/ref/embedded/human.single @@ -0,0 +1 @@ +Single: human diff --git a/t/ref/embedded/mouse-zebrafish.pair b/t/ref/embedded/mouse-zebrafish.pair new file mode 100644 index 0000000..2e78f72 --- /dev/null +++ b/t/ref/embedded/mouse-zebrafish.pair @@ -0,0 +1,3 @@ +Single: mouse +Single: zebrafish +Files: mouse.single zebrafish.single diff --git a/t/ref/embedded/mouse.single b/t/ref/embedded/mouse.single new file mode 100644 index 0000000..6c6e034 --- /dev/null +++ b/t/ref/embedded/mouse.single @@ -0,0 +1 @@ +Single: mouse diff --git a/t/ref/embedded/zebrafish.single b/t/ref/embedded/zebrafish.single new file mode 100644 index 0000000..3220c1b --- /dev/null +++ b/t/ref/embedded/zebrafish.single @@ -0,0 +1 @@ +Single: zebrafish diff --git a/t/ref/goal_a b/t/ref/goal_a new file mode 100644 index 0000000..b371017 --- /dev/null +++ b/t/ref/goal_a @@ -0,0 +1 @@ +a is valid. diff --git a/t/ref/goal_x b/t/ref/goal_x new file mode 100644 index 0000000..84ee32f --- /dev/null +++ b/t/ref/goal_x @@ -0,0 +1 @@ +x is invalid. diff --git a/t/ref/prolog/Makespec.pro b/t/ref/prolog/Makespec.pro index 10393af..10407e7 100644 --- a/t/ref/prolog/Makespec.pro +++ b/t/ref/prolog/Makespec.pro @@ -6,7 +6,7 @@ mammal(human). sp(zebrafish). sp(X) :- mammal(X). -% rule for generating a pair of (non-identical) species (asymetric) +% rule for generating a pair of (non-identical) species (asymmetric) pair(X,Y) :- sp(X),sp(Y),X@$@ + +goal_$X: { \+ valid(X) } + echo $X is invalid. >$@ diff --git a/t/target/embedded/Makefile b/t/target/embedded/Makefile new file mode 100644 index 0000000..99dff93 --- /dev/null +++ b/t/target/embedded/Makefile @@ -0,0 +1,22 @@ + +prolog +mammal(mouse). +mammal(human). +sp(zebrafish). +sp(X) :- mammal(X). + +pair(X,Y) :- sp(X),sp(Y),X@ $@ + +$X-$Y.pair: $X.single $Y.single { pair(X,Y) } + cat $X.single $Y.single > $@ + echo Files: $X.single $Y.single >> $@ diff --git a/t/target/prolog/Makespec.pro b/t/target/prolog/Makespec.pro index 10393af..10407e7 100644 --- a/t/target/prolog/Makespec.pro +++ b/t/target/prolog/Makespec.pro @@ -6,7 +6,7 @@ mammal(human). sp(zebrafish). sp(X) :- mammal(X). -% rule for generating a pair of (non-identical) species (asymetric) +% rule for generating a pair of (non-identical) species (asymmetric) pair(X,Y) :- sp(X),sp(Y),X@ Date: Sat, 10 Dec 2016 17:22:02 -0800 Subject: [PATCH 16/24] Added automatic variable TARGET to recipe goals. Reorganized README around Prolog-extended Makefiles --- README.md | 253 ++++++++++++++------------- prolog/biomake/biomake.pl | 47 +++-- prolog/biomake/cli.pl | 2 +- prolog/biomake/{vars.pl => embed.pl} | 4 +- prolog/biomake/functions.pl | 6 +- 5 files changed, 165 insertions(+), 147 deletions(-) rename prolog/biomake/{vars.pl => embed.pl} (87%) diff --git a/README.md b/README.md index 78ee05f..177df58 100644 --- a/README.md +++ b/README.md @@ -118,16 +118,113 @@ Var=Val [developers] Do not print a backtrace on error ``` +Embedding Prolog in Makefiles +----------------------------- + +Brief overview: + +- Prolog can be embedded within `prolog` and `endprolog` directives +- `$(bagof Template,Goal)` expands to the space-separated `List` from the Prolog `bagof(Template,Goal,List)` +- Following the dependent list with `{Goal}` causes the rule to match only if `Goal` is satisfied. The special variable `TARGET`, if used, will be bound to `$@` + Examples -------- -(this assumes some knowledge of GNU Make and [Makefiles](https://www.gnu.org/software/make/manual/html_node/index.html)) +This assumes some knowledge of GNU Make and [Makefiles](https://www.gnu.org/software/make/manual/html_node/index.html). + +Unlike makefiles, biomake allows multiple variables in pattern +matching. Let's say we have a program called `align` that compares two +files producing some output (e.g. biological sequence alignment, or +ontology alignment). Assume our file convention is to suffix ".fa" on +the inputs. We can write a `Makefile` with the following: + + align-$X-$Y: $X.fa $Y.fa + align $X.fa $Y.fa > $@ + +Now if we have files `x.fa` and `y.fa` we can type: + + biomake align-x-y + +Prolog extensions allow us to do even fancier things with logic. +Specifically, we can embed arbitrary Prolog, including both database facts and +rules. We can use these rules to control flow in a way that is more +powerful than makefiles. + +Let's say we only want to run a certain program when the inputs match a certain table in our database. +We can embed Prolog in our Makefile as follows: + + prolog + sp(mouse). + sp(human). + sp(zebrafish). + endprolog + + align-$X-$Y: $X.fa $Y.fa {sp(X),sp(Y)} + align $X.fa $Y.fa > $@ -biomake looks for a Prolog file called `Makespec.pro` (or `Makeprog`) in your -current directory. If it's not there, it will try looking for a +The lines beginning `sp` between `prolog` and `endprolog` define the set of species that we want the rule to apply to. +The rule itself consists of 4 parts: + + * the target (`align-$X-$Y`) + * the dependencies (`$X.fa` and `$Y.fa`) + * a Prolog goal, enclosed in braces (`{sp(X),sp(Y)}`), that is used as an additional logic test of whether the rule can be applied + * the command (`align ...`) + +In this case, the Prolog goal succeeds with 9 solutions, with 3 +different values for `X` and `Y`. If we type... + + biomake align-platypus-coelacanth + +...it will not succeed, even if the .fa files are on the filesystem. This +is because the goal `{sp(X),sp(Y)}` cannot be satisfied for these two values of `X` and `Y`. + +To get a list of all matching targets, +we can use the special BioMake function `$(bagof...)` +which wraps the Prolog predicate [bagof/3](http://www.swi-prolog.org/pldoc/man?predicate=bagof/3). +The following example also uses the Prolog predicates +[format/2](http://www.swi-prolog.org/pldoc/man?predicate=format/2) +and +[format/3](http://www.swi-prolog.org/pldoc/man?predicate=format/3), +for formatted output: + +~~~~ +prolog + +sp(mouse). +sp(human). +sp(zebrafish). + +ordered_pair(X,Y) :- sp(X),sp(Y),X@ $@ +~~~~ + +Now if we type... + + biomake all + +...then all non-identical ordered pairs will be compared +(since we have required them to be _ordered_ pairs, we get e.g. "mouse-zebrafish" but not "zebrafish-mouse"; +the motivation here is that the `align` program is symmetric, and so only needs to be run once per pair). + +Programming directly in Prolog +------------------------------ + +If you are a Prolog wizard who finds embedding Prolog in Makefiles too cumbersome, you can use a native Prolog-like syntax. +Biomake looks for a Prolog file called `Makespec.pro` (or `Makeprog`) in your +current directory. (If it's not there, it will try looking for a `Makefile` in GNU Make format. The following examples describe the -Prolog syntax; GNU Make syntax is described elsewhere, -e.g. [here](https://www.gnu.org/software/make/manual/html_node/index.html). +Prolog syntax.) Assume you have two file formats, ".foo" and ".bar", and a `foo2bar` converter. @@ -157,12 +254,12 @@ converter. We can add an additional rule: '%.baz' <-- '%.bar', 'bar2baz $< > $@'. -Now if we type: +Now if we type... touch x.foo biomake x.baz -The output shows the tree structure of the dependencies: +...we get the following output, showing the tree structure of the dependencies: Checking dependencies: test.baz <-- [test.bar] Checking dependencies: test.bar <-- [test.foo] @@ -191,7 +288,7 @@ The equivalent `Makefile` would be this... ...although this isn't _strictly_ equivalent, since unbound variables don't work the same way in GNU Make as they do in Biomake -(Biomake will try to use them as wildcards for [pattern-matching](#PatternMatching), +(Biomake will try to use them as wildcards for pattern-matching, whereas GNU Make will just replace them with the empty string - which is also the default behavior for Biomake if they occur outside of a pattern-matching context). @@ -207,95 +304,35 @@ You can also use GNU Makefile constructs, like automatic variables (`$<`, `$@`, Following the GNU Make convention, variable names must be enclosed in parentheses unless they are single letters. - -Pattern-matching ----------------- - -Unlike makefiles, biomake allows multiple variables in pattern -matching. Let's say we have a program called `align` that compares two -files producing some output (e.g. biological sequence alignment, or -ontology alignment). Assume our file convention is to suffix ".fa" on -the inputs. We can write a `Makespec.pro` with the following: - - 'align-$X-$Y' <-- ['$X.fa', '$Y.fa'], - 'align $X.fa $Y.fa > $@'. - -(note that if we have multiple dependecies, these must be separated by -commas and enclodes in square brackets - i.e. a Prolog list) - -Now if we have files `x.fa` and `y.fa` we can type: - - biomake align-x-y - -We could achieve the same thing with the following GNU `Makefile`: - - align-$X-$Y: $X.fa $Y.fa - align $X.fa $Y.fa > $@ - -This is already an improvement over GNU Make, which only allows a single wildcard. -However, the Prolog version allows us to do even fancier things with logic. -Specifically, we can add arbitrary Prolog, including both database facts and -rules. We can use these rules to control flow in a way that is more -powerful than makefiles. Let's say we only want to run a certain -program when the inputs match a certain table in our database: - - sp(mouse). - sp(human). - sp(zebrafish). - - 'align-$X-$Y' <-- ['$X.fa', '$Y.fa'], - {sp(X),sp(Y)}, - 'align $X.fa $Y.fa > $@'. - -Note that here the rule consists of 4 parts: - - * the target/output - * dependencies - * a Prolog goal, enclosed in `{}`s, that is called to determine values - * the command +Automatic translation to Prolog +------------------------------- -In this case, the Prolog goal succeeds with 9 solutions, with 3 -different values for X and Y. If we type: - - biomake align-platypus-coelocanth - -It will not succeed, even if the .fa files are on the filesystem. This -is because the goal cannot be satisfied for these two values. - -We can create a top-level target that generates all solutions: - - % Database of species - sp(mouse). - sp(human). - sp(zebrafish). - - % rule for generating a pair of (non-identical) species (asymmetric) - pair(X,Y) :- sp(X),sp(Y),X@ $@'. - -(The `t(...)` wrapper around `['align-',X,-,Y]` is used to concatenate a list of tokens.) +You can parse a GNU Makefile (including Biomake-specific extensions, if any) +and save the corresponding Prolog syntax using the `-T` option +(long-form `--translate`). -Now if we type: +Here is the translation of the Makefile from the previous section: +~~~ +sp(mouse). +sp(human). +sp(zebrafish). - biomake all +ordered_pair(X,Y):- + sp(X), + sp(Y), + X@ $@"]. +~~~ Make-like features ------------------ @@ -361,39 +398,6 @@ Biomake provides a few extra functions for arithmetic on lists: - `$(multiply Y,L)` multiplies every element of the space-separated list `L` by `Y` - `$(divide Z,L)` divides every element of the space-separated list `L` by `Z` -Embedding Prolog in Makefiles ------------------------------ - -- Prolog can be embedded within `prolog` and `endprolog` directives -- `$(bagof Template,Goal)` expands to the space-separated `List` from the Prolog `bagof(Template,Goal,List)` -- Following the dependent list with `{Goal}` causes the rule to match only if `Goal` is satisfied - -e.g. - -~~~~ -prolog -mammal(mouse). -mammal(human). -sp(zebrafish). -sp(X) :- mammal(X). - -pair(X,Y) :- sp(X),sp(Y),X@ $@ - -$X-$Y.pair: $X.single $Y.single { pair(X,Y) } - cat $X.single $Y.single > $@ - echo Files: $X.single $Y.single >> $@ -~~~~ - MD5 hashes ---------- @@ -431,3 +435,4 @@ Ideas for future development: * semantic web enhancement (using NEPOMUK file ontology) * using other back ends and target sources (sqlite db, REST services) * cloud-based computing +* metadata diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index f967c21..c07208f 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -57,7 +57,7 @@ :- use_module(library(biomake/utils)). :- use_module(library(biomake/functions)). -:- use_module(library(biomake/vars)). +:- use_module(library(biomake/embed)). :- user:op(1100,xfy,<--). :- user:op(1101,xfy,?=). @@ -510,13 +510,16 @@ !, close(IO). -read_makeprog_stream(IO,OptsOut,OptsIn,[Term|Terms]) :- +read_makeprog_stream(IO,OptsOut,OptsIn,Terms) :- read_term(IO,Term,[variable_names(VNs), syntax_errors(error), - module(vars)]), - debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), - add_spec_clause(Term,VNs,Opts,OptsIn), - read_makeprog_stream(IO,OptsOut,Opts,Terms). + module(embed)]), + (Term = 'end_of_file' + -> (Terms = [], OptsOut = OptsIn) + ; (Terms = [(Term,VNs)|Rest], + debug(makeprog,'adding: ~w (variables: ~w)',[Term,VNs]), + add_spec_clause(Term,VNs,Opts,OptsIn), + read_makeprog_stream(IO,OptsOut,Opts,Rest))). eval_atom_as_makeprog_term(Atom,OptsOut,OptsIn) :- read_atom_as_makeprog_term(Atom,Term,VNs), @@ -526,7 +529,7 @@ read_atom_as_makeprog_term(Atom,Term,VNs) :- read_term_from_atom(Atom,Term,[variable_names(VNs), syntax_errors(error), - module(vars)]). + module(embed)]). read_string_as_makeprog_term(String,Term,VNs) :- atom_string(Atom,String), @@ -547,14 +550,14 @@ translate_gnumake_clause(G,P), add_spec_clause(P,OptsOut,OptsIn). -translate_gnumake_clause(rule(Ts,Ds,Es,{Goal},VNs), (Ts <-- Ds,{Goal},Es), VNs). -translate_gnumake_clause(rule(Ts,Ds,Es), (Ts <-- Ds,Es)). -translate_gnumake_clause(assignment(Var,"=",Val), (Var = Val)). -translate_gnumake_clause(assignment(Var,"?=",Val), (Var ?= Val)). -translate_gnumake_clause(assignment(Var,":=",Val), (Var := Val)). -translate_gnumake_clause(assignment(Var,"+=",Val), (Var += Val)). -translate_gnumake_clause(assignment(Var,"!=",Val), (Var =* Val)). -translate_gnumake_clause(prolog(Term), Term). +translate_gnumake_clause(rule(Ts,Ds,Es,{Goal},VNs), (Ts <-- Ds,{Goal},Es), VNs):- !. +translate_gnumake_clause(prolog(Term,VNs), Term, VNs):- !. +translate_gnumake_clause(rule(Ts,Ds,Es), (Ts <-- Ds,Es)):- !. +translate_gnumake_clause(assignment(Var,"=",Val), (Var = Val)):- !. +translate_gnumake_clause(assignment(Var,"?=",Val), (Var ?= Val)):- !. +translate_gnumake_clause(assignment(Var,":=",Val), (Var := Val)):- !. +translate_gnumake_clause(assignment(Var,"+=",Val), (Var += Val)):- !. +translate_gnumake_clause(assignment(Var,"!=",Val), (Var =* Val)):- !. translate_gnumake_clause(C,_) :- format("Error translating ~w~n",[C]), backtrace(20), @@ -568,6 +571,12 @@ !, format(IO,"~q <-- ~q, ~q.~n",[Ts,Ds,Es]). +write_clause(IO,rule(Ts,Ds,Es,{Goal},VNs)) :- + !, + format(IO,"~q <-- ~q, {",[Ts,Ds]), + write_term(IO,Goal,[variable_names(VNs),quoted(true)]), + format(IO,"}, ~q.~n",[Es]). + write_clause(_,assignment(Var,_,_)) :- atom_codes(Var,[V|_]), V @>= 0'a, V @=< 0'z, % a through z @@ -577,9 +586,12 @@ write_clause(IO,assignment(Var,Op,Val)) :- format(IO,"~w ~w ~q.~n",[Var,Op,Val]). -write_clause(IO,prolog(Term)) :- +write_clause(IO,prolog( (Term,VNs) )) :- !, - write_term(IO,Term,[]). + write_term(IO,Term,[variable_names(VNs),quoted(true)]), + write(IO,'.\n'). + +write_clause(_,X) :- format("Don't know how to write ~w~n",[X]). add_cmdline_assignment((Var = X)) :- global_unbind(Var), @@ -728,6 +740,7 @@ % only one of the specified targets has to match member(TP,TPs), uniq_pattern_match(TP,T), + (member(('TARGET' = T), Bindings) ; true), % make $@ available to the Goal as variable TARGET call_without_backtrace(Goal), % Two-pass expansion of dependency list. diff --git a/prolog/biomake/cli.pl b/prolog/biomake/cli.pl index 35bcd86..6979592 100644 --- a/prolog/biomake/cli.pl +++ b/prolog/biomake/cli.pl @@ -2,7 +2,7 @@ :- use_module(library(biomake/biomake)). :- use_module(library(biomake/utils)). -:- use_module(library(biomake/vars)). +:- use_module(library(biomake/embed)). % ---------------------------------------- % MAIN PROGRAM diff --git a/prolog/biomake/vars.pl b/prolog/biomake/embed.pl similarity index 87% rename from prolog/biomake/vars.pl rename to prolog/biomake/embed.pl index ff4addd..84311cd 100644 --- a/prolog/biomake/vars.pl +++ b/prolog/biomake/embed.pl @@ -1,13 +1,13 @@ % * -*- Mode: Prolog -*- */ -:- module(vars, +:- module(embed, [ eval_bagof/3 ]). /** - A minimally-cluttered namespace for user variable bindings in Makeprogs & Makefiles. + A minimally-cluttered namespace for embedded Prolog in Makefiles. */ diff --git a/prolog/biomake/functions.pl b/prolog/biomake/functions.pl index 85f75ce..3e3c09f 100644 --- a/prolog/biomake/functions.pl +++ b/prolog/biomake/functions.pl @@ -15,7 +15,7 @@ :- use_module(library(readutil)). :- use_module(library(biomake/utils)). -:- use_module(library(biomake/vars)). +:- use_module(library(biomake/embed)). :- use_module(library(biomake/biomake)). :- use_module(library(biomake/gnumake_parser)). @@ -155,8 +155,8 @@ concat_string_list_spaced(ResultList,Result) }. makefile_function(Result,_V) --> lb("bagof"), str_arg(Template), comma, str_arg(Goal), rb, !, - { eval_bagof(Template,Goal,Result) }. - + { eval_bagof(Template,Goal,Result) }. + makefile_function("",_V) --> ['('], str_arg(S), [')'], !, {format("Warning: unknown function $(~w)~n",[S])}. makefile_subst_ref(Result) --> makefile_subst_ref(Result,v(null,null,null,[])). From cac23c4974e6361fdc57bea60230cabd8936c531 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 17:45:59 -0800 Subject: [PATCH 17/24] Cleaner GNU->Biomake translation, updated README --- README.md | 41 +++++++++++++++++++++++++++++++++++---- prolog/biomake/biomake.pl | 22 ++++++++++++++++++--- t/ref/Makefile.translated | 20 +++++++++---------- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 177df58..6bb4a86 100644 --- a/README.md +++ b/README.md @@ -311,7 +311,39 @@ You can parse a GNU Makefile (including Biomake-specific extensions, if any) and save the corresponding Prolog syntax using the `-T` option (long-form `--translate`). -Here is the translation of the Makefile from the previous section: +Here is the translation of the Makefile from the previous section (lightly formatted for clarity): + +~~~ +sp(mouse). +sp(human). +sp(zebrafish). + +ordered_pair(X,Y):- + sp(X), + sp(Y), + X@ $@". +~~~ + +Note how the list of dependencies in the second rule, which contains more than one dependency (`$X.fa` and `$Y.fa`), is enclosed in square brackets, i.e. a Prolog list (`["$X.fa","$Y.fa"]`). +The same syntax applies to rules which have lists of multiple targets, or multiple executables. + +The rule for target `all` in this translation involves a call to the Biomake function `$(bagof ...)`, +but (as noted) this function is just a wrapper for the Prolog `bagof/3` predicate. +The automatic translation is not smart enough to remove this double layer of wrapping, +but we can do so manually, yielding a clearer program: + ~~~ sp(mouse). sp(human). @@ -326,12 +358,13 @@ make_filename(F):- ordered_pair(X,Y), format(atom(F),"align-~w-~w",[X,Y]). -["all"] <-- ["$(bagof F,make_filename(F))"], []. +"all" <-- Deps, {bagof(F,make_filename(F),Deps)}. -["align-$X-$Y"] <-- ["$X.fa","$Y.fa"], +"align-$X-$Y" <-- + ["$X.fa","$Y.fa"], {ordered_pair(X,Y), format("Matched ~w~n",[TARGET])}, - ["align $X.fa $Y.fa > $@"]. + "align $X.fa $Y.fa > $@". ~~~ Make-like features diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index c07208f..1adb030 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -569,13 +569,26 @@ write_clause(IO,rule(Ts,Ds,Es)) :- !, - format(IO,"~q <-- ~q, ~q.~n",[Ts,Ds,Es]). + write_list(IO,Ts), + write(IO,' <-- '), + write_list(IO,Ds), + (Es = [] + ; (write(IO,', '), + write_list(IO,Es))), + write(IO,'.\n'). write_clause(IO,rule(Ts,Ds,Es,{Goal},VNs)) :- !, - format(IO,"~q <-- ~q, {",[Ts,Ds]), + write_list(IO,Ts), + write(IO,' <-- '), + write_list(IO,Ds), + write(IO,', {'), write_term(IO,Goal,[variable_names(VNs),quoted(true)]), - format(IO,"}, ~q.~n",[Es]). + write(IO,'}'), + (Es = [] + ; (write(IO,', '), + write_list(IO,Es))), + write(IO,'.\n'). write_clause(_,assignment(Var,_,_)) :- atom_codes(Var,[V|_]), @@ -593,6 +606,9 @@ write_clause(_,X) :- format("Don't know how to write ~w~n",[X]). +write_list(IO,[X]) :- format(IO,"~q",[X]), !. +write_list(IO,L) :- format(IO,"~q",[L]). + add_cmdline_assignment((Var = X)) :- global_unbind(Var), assert(global_cmdline_binding(Var,X)), diff --git a/t/ref/Makefile.translated b/t/ref/Makefile.translated index 1e227fa..870e8e5 100644 --- a/t/ref/Makefile.translated +++ b/t/ref/Makefile.translated @@ -1,15 +1,15 @@ MAKEFILE_LIST += 'Makefile.translate'. -["simple"] <-- [], ["echo Simple test >simple"]. -["uptodate"] <-- [], ["echo Not up to date >$@"]. -["target1","target2"] <-- [], ["echo $@ >$@"]. -["%.echo"] <-- [], ["echo $* >$@"]. -["first_dep"] <-- ["a.echo","b.echo","c.echo"], ["echo $< >$@"]. -["all_deps"] <-- ["a.echo","b.echo","c.echo"], ["echo $^ >$@"]. +"simple" <-- [], "echo Simple test >simple". +"uptodate" <-- [], "echo Not up to date >$@". +["target1","target2"] <-- [], "echo $@ >$@". +"%.echo" <-- [], "echo $* >$@". +"first_dep" <-- ["a.echo","b.echo","c.echo"], "echo $< >$@". +"all_deps" <-- ["a.echo","b.echo","c.echo"], "echo $^ >$@". FOO = ".foo". -["%.bar"] <-- ["%$(FOO)"], ["perl -pe 'tr/a-z/A-Z/' $< > $@"]. -["$A.baz"] <-- ["$A.bar"], ["echo \"Baz!\" >$@","cat $< >>$@","echo \"I said Baz!\" >> $@"]. -["$A.$B.foo"] <-- [], ["echo b= $B a= $A >$@"]. +"%.bar" <-- "%$(FOO)", "perl -pe 'tr/a-z/A-Z/' $< > $@". +"$A.baz" <-- "$A.bar", ["echo \"Baz!\" >$@","cat $< >>$@","echo \"I said Baz!\" >> $@"]. +"$A.$B.foo" <-- [], "echo b= $B a= $A >$@". XYZ := "$(ABC) xyz". DEF ?= "$(ABC)". ABC = "abc". -["Makefile.translated"] <-- [], []. +"Makefile.translated" <-- []. From 5f429afb79a758eeaa988dc8e261d6e31da4d252 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 17:47:11 -0800 Subject: [PATCH 18/24] clarified README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bb4a86..c5a1f63 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,8 @@ The equivalent `Makefile` would be this... $(Base).bar: $(Base).foo foo2bar $(Base).foo > $(Base).bar -...although this isn't _strictly_ equivalent, since unbound variables +...although strictly speaking, this is only equivalent if you are using Biomake; +GNU Make's treatment of this Makefile isn't quite equivalent, since unbound variables don't work the same way in GNU Make as they do in Biomake (Biomake will try to use them as wildcards for pattern-matching, whereas GNU Make will just replace them with the empty string - which is also the default behavior From eb6181fed65218ef6b91ef61848aff5f5e241273 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 17:48:26 -0800 Subject: [PATCH 19/24] clarified README some more --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5a1f63..11d06d6 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ The same syntax applies to rules which have lists of multiple targets, or multip The rule for target `all` in this translation involves a call to the Biomake function `$(bagof ...)`, but (as noted) this function is just a wrapper for the Prolog `bagof/3` predicate. -The automatic translation is not smart enough to remove this double layer of wrapping, +The automatic translation is not smart enough to remove this layer of wrapping, but we can do so manually, yielding a clearer program: ~~~ From f1fb6b8534bca7c5ed014258f4e25c51f5e4ed77 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 18:07:44 -0800 Subject: [PATCH 20/24] Added extra automatic variable DEPS for recipe goals, loosely equivalent to $^ --- README.md | 8 ++++---- prolog/biomake/biomake.pl | 10 ++++++++-- t/ref/prolog/Makespec.pro | 6 +++--- t/target/prolog/Makespec.pro | 6 +++--- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 11d06d6..2ffa03f 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Brief overview: - Prolog can be embedded within `prolog` and `endprolog` directives - `$(bagof Template,Goal)` expands to the space-separated `List` from the Prolog `bagof(Template,Goal,List)` -- Following the dependent list with `{Goal}` causes the rule to match only if `Goal` is satisfied. The special variable `TARGET`, if used, will be bound to `$@` +- Following the dependent list with `{Goal}` causes the rule to match only if `Goal` is satisfied. The special variables `TARGET` and `DEPS`, if used, will be bound to the target and dependency-list (i.e. `$@` and `$^`, loosely speaking, except the latter is a list) Examples -------- @@ -333,7 +333,7 @@ make_filename(F):- "align-$X-$Y" <-- ["$X.fa","$Y.fa"], {ordered_pair(X,Y), - format("Matched ~w~n",[TARGET])}, + format("Matched ~w <-- ~n",[TARGET,DEPS])}, "align $X.fa $Y.fa > $@". ~~~ @@ -359,12 +359,12 @@ make_filename(F):- ordered_pair(X,Y), format(atom(F),"align-~w-~w",[X,Y]). -"all" <-- Deps, {bagof(F,make_filename(F),Deps)}. +"all" <-- DepList, {bagof(F,make_filename(F),DepList)}. "align-$X-$Y" <-- ["$X.fa","$Y.fa"], {ordered_pair(X,Y), - format("Matched ~w~n",[TARGET])}, + format("Matched ~w <-- ~n",[TARGET,DEPS])}, "align $X.fa $Y.fa > $@". ~~~ diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index 1adb030..fba27a9 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -757,9 +757,15 @@ member(TP,TPs), uniq_pattern_match(TP,T), (member(('TARGET' = T), Bindings) ; true), % make $@ available to the Goal as variable TARGET - call_without_backtrace(Goal), - % Two-pass expansion of dependency list. + % Do a dummy expansion of the dependency list so that Goal has something to chew on + expand_deps(DP1,DPtmp,V), + (member(('DEPS' = DPtmp), Bindings) ; true), % make $^ available to the Goal as variable DEPS + + % Check the Goal + call_without_backtrace(Goal), + + % Do a two-pass expansion of dependency list. % This is ultra-hacky but allows for variable-expanded dependency lists that contain % wildcards % (the variables are expanded on the first pass, and the %'s on the second pass). % A more rigorous solution would be a two-pass expansion of the entire GNU Makefile, diff --git a/t/ref/prolog/Makespec.pro b/t/ref/prolog/Makespec.pro index 10407e7..65a98fd 100644 --- a/t/ref/prolog/Makespec.pro +++ b/t/ref/prolog/Makespec.pro @@ -10,11 +10,11 @@ sp(X) :- mammal(X). pair(X,Y) :- sp(X),sp(Y),X@ Date: Sat, 10 Dec 2016 18:08:38 -0800 Subject: [PATCH 21/24] modified README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ffa03f..cc7ef45 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ endprolog all: $(bagof F,make_filename(F)) align-$X-$Y: $X.fa $Y.fa { ordered_pair(X,Y), - format("Matched ~w~n",[TARGET]) } + format("Matched ~w <-- ~n",[TARGET,DEPS]) }, align $X.fa $Y.fa > $@ ~~~~ From 6eb2d800ba7c59d47e9366254207b0459c1b99af Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 18:33:25 -0800 Subject: [PATCH 22/24] Allow optional linebreak between dependencies and goal in extended Makefile syntax --- prolog/biomake/gnumake_parser.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prolog/biomake/gnumake_parser.pl b/prolog/biomake/gnumake_parser.pl index f3e9ce3..bdc6068 100644 --- a/prolog/biomake/gnumake_parser.pl +++ b/prolog/biomake/gnumake_parser.pl @@ -327,6 +327,7 @@ makefile_targets(Head), ":", opt_makefile_targets(Deps), + opt_linebreak, "{", xbrace(GoalAtom), "}", @@ -361,6 +362,9 @@ makefile_targets([T|Ts]) --> opt_space, makefile_target_string(T), whitespace, makefile_targets(Ts), opt_whitespace. makefile_targets([T]) --> opt_space, makefile_target_string(T), opt_whitespace. +opt_linebreak --> []. +opt_linebreak --> "\n", opt_whitespace. + makefile_warning_text(S) --> string_from_codes(S,")"). makefile_filename_string(S) --> string_from_codes(S," \t\n"). makefile_target_string(S) --> delim(S,0'(,0'),[0':,0';,0'\s,0'\t,0'\n]), {S \= ""}, !. From 0b18a066d26a4d4aa160e744569efc0c7b0f7bf1 Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sat, 10 Dec 2016 23:15:33 -0800 Subject: [PATCH 23/24] Updated README --- README.md | 12 ------------ prolog/biomake/biomake.pl | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index cc7ef45..433a620 100644 --- a/README.md +++ b/README.md @@ -280,9 +280,6 @@ variables. The following form is functionally equivalent: The equivalent `Makefile` would be this... - $(Base).foo: - echo $(Base) >$@ - $(Base).bar: $(Base).foo foo2bar $(Base).foo > $(Base).bar @@ -293,15 +290,6 @@ don't work the same way in GNU Make as they do in Biomake whereas GNU Make will just replace them with the empty string - which is also the default behavior for Biomake if they occur outside of a pattern-matching context). -If you want variables to work as Prolog variables as well -as GNU Make variables, then they must conform to Prolog syntax: -they must have a leading uppercase, and only alphanumeric characters plus underscore. - -You can also use GNU Makefile constructs, like automatic variables (`$<`, `$@`, `$*`, etc.), if you like: - - '$(Base).bar' <-- '$(Base).foo', - 'foo2bar $< > $@'. - Following the GNU Make convention, variable names must be enclosed in parentheses unless they are single letters. diff --git a/prolog/biomake/biomake.pl b/prolog/biomake/biomake.pl index fba27a9..b011743 100644 --- a/prolog/biomake/biomake.pl +++ b/prolog/biomake/biomake.pl @@ -759,6 +759,7 @@ (member(('TARGET' = T), Bindings) ; true), % make $@ available to the Goal as variable TARGET % Do a dummy expansion of the dependency list so that Goal has something to chew on + % We do not however want to do the real expansion yet - because Goal might affect that expand_deps(DP1,DPtmp,V), (member(('DEPS' = DPtmp), Bindings) ; true), % make $^ available to the Goal as variable DEPS From 681675c31d5db0fce0e753c2537b119e52f00c7c Mon Sep 17 00:00:00 2001 From: Ian Holmes Date: Sun, 11 Dec 2016 11:51:56 -0800 Subject: [PATCH 24/24] Added alternate syntax for defining Makefile vars from cmdline --- prolog/biomake/cli.pl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/prolog/biomake/cli.pl b/prolog/biomake/cli.pl index 6979592..fef4b88 100644 --- a/prolog/biomake/cli.pl +++ b/prolog/biomake/cli.pl @@ -254,14 +254,17 @@ simple_arg('-t',touch_only(true)). arg_alias('-t','--touch'). -arg_info('-t','','Touch files (and update MD5 hashes, if appropriate) instead of running recipes'). +arg_info('-t','','Touch files (& update MD5 hashes, if appropriate) instead of running recipes'). +parse_arg(['-D',Var,Val|L],L,assignment(Var,Val)). +arg_alias('-D','--define'). parse_arg([VarEqualsVal|L],L,assignment(Var,Val)) :- string_codes(VarEqualsVal,C), phrase(makefile_assign(Var,Val),C). recover_arg(VarEqualsVal,assignment(Var,Val)) :- - format(string(VarEqualsVal),"~w=~q",[Var,Val]). -arg_info('Var=Val','','Assign Makefile variables from command line'). + format(string(VarEqualsVal),"--define ~w ~q",[Var,Val]). +arg_info('-D','Var Val','Assign Makefile variables from command line'). +arg_info('Var=Val','','Alternative syntax for \'-D Var Val\''). makefile_assign(Var,Val) --> makefile_var(Var), "=", makefile_val(Val). makefile_var(A) --> atom_from_codes(A,":= \t\n").