| 1 | % (c) 2021-2022 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
| 2 | % Heinrich Heine Universitaet Duesseldorf | |
| 3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
| 4 | ||
| 5 | :- module(tools_portability, [ | |
| 6 | check_arithmetic_function/1, | |
| 7 | exists_source/1, | |
| 8 | supported_term_expansion_style/1, | |
| 9 | supported_goal_expansion_style/1 | |
| 10 | ]). | |
| 11 | ||
| 12 | % To make it easier to write code that works with | |
| 13 | % both library(atts) (SICStus, Ciao, etc.) and the *_attr builtins (SWI, hProlog, etc.), | |
| 14 | % declare the attribute operator from library(atts) here. | |
| 15 | % This prevents syntax errors if a library(atts)-style attribute declaration directive | |
| 16 | % appears in code that is conditionally disabled via if/endif. | |
| 17 | :- op(1150, fx, (attribute)). | |
| 18 | ||
| 19 | :- use_module(module_information). | |
| 20 | ||
| 21 | :- module_info(group, infrastructure). | |
| 22 | :- module_info(description, 'Utilities for inspecting the features of the current Prolog system, to simplify writing portable code that can run on different Prolog systems.'). | |
| 23 | ||
| 24 | %! check_arithmetic_function(+FuncCall) | |
| 25 | % | |
| 26 | % True if the term FuncCall is a valid arithmetic function/operator. | |
| 27 | % FuncCall must be a valid arithmetic expression that evaluates without errors | |
| 28 | % (assuming the function in question exists). | |
| 29 | % It is *not* enough to pass a skeletal term (i. e. with variable arguments), | |
| 30 | % because the implementation may try to actually evaluate the expression. | |
| 31 | % | |
| 32 | % For example: check_arithmetic_function(log(2, 4)) and NOT check_arithmetic_function(log(_, _)) | |
| 33 | ||
| 34 | :- if(predicate_property(current_arithmetic_function(_), _)). | |
| 35 | % Use current_arithmetic_function/1 if possible (supported by SWI). | |
| 36 | check_arithmetic_function(FuncCall) :- | |
| 37 | current_arithmetic_function(FuncCall). | |
| 38 | :- else. | |
| 39 | % Otherwise try to call the function and see if it works or not. | |
| 40 | check_arithmetic_function(FuncCall) :- | |
| 41 | catch(_Res is FuncCall, _Error, false). | |
| 42 | :- endif. | |
| 43 | ||
| 44 | %! exists_source(+Source). | |
| 45 | % | |
| 46 | % True if Source refers to a Prolog source file that exists. | |
| 47 | % Source must be a Prolog source specifier as accepted by use_module, consult, etc. | |
| 48 | % | |
| 49 | % This predicate is natively supported on some Prolog systems (at least SWI and YAP). | |
| 50 | % Otherwise this module provides a fallback implementation based on absolute_file_name/2. | |
| 51 | ||
| 52 | :- if(predicate_property(exists_source(_), _)). | |
| 53 | % Spider will assume if to be false | |
| 54 | :- else. | |
| 55 | exists_source(Source) :- | |
| 56 | absolute_file_name(Source, _, [access(exist), file_type(source), file_errors(fail)]). | |
| 57 | :- endif. | |
| 58 | ||
| 59 | % Define all known variants of term_expansion to detect which ones are supported by the system. | |
| 60 | ||
| 61 | :- multifile user:term_expansion/2. | |
| 62 | user:term_expansion(Term1, Term2) :- | |
| 63 | Term1 == unsupported_term_expansion_style(traditional), | |
| 64 | !, | |
| 65 | Term2 = supported_term_expansion_style(traditional). | |
| 66 | ||
| 67 | :- multifile user:term_expansion/4. | |
| 68 | user:term_expansion(Term1, Layout1, Term2, Layout2) :- | |
| 69 | Term1 == unsupported_term_expansion_style(swi_layout), | |
| 70 | !, | |
| 71 | Term2 = supported_term_expansion_style(swi_layout), | |
| 72 | Layout2 = Layout1. | |
| 73 | ||
| 74 | :- multifile user:term_expansion/6. | |
| 75 | user:term_expansion(Term1, Layout1, Tokens1, Term2, Layout2, Tokens2) :- | |
| 76 | nonmember(tools_portability, Tokens1), | |
| 77 | Term1 == unsupported_term_expansion_style(sicstus), | |
| 78 | !, | |
| 79 | Term2 = supported_term_expansion_style(sicstus), | |
| 80 | Layout2 = Layout1, | |
| 81 | Tokens2 = [tools_portability|Tokens1]. | |
| 82 | ||
| 83 | %! supported_term_expansion_style(+Style). | |
| 84 | % | |
| 85 | % True if the Prolog system supports the given style of term_expansion. | |
| 86 | % Style must be an atom. | |
| 87 | % The following styles are recognized by this predicate: | |
| 88 | % | |
| 89 | % * `traditional`: The traditional style, with no extra parameters - `term_expansion(Term1, Term2)`. | |
| 90 | % * `swi_layout`: The SWI-Prolog style, with extra parameters for source layout information - `term_expansion(Term1, Layout1, Term2, Layout2)`. | |
| 91 | % * `sicstus`: The SICStus Prolog style, with extra parameters for source layout information and avoiding duplicate expansion - `term_expansion(Term1, Layout1, Tokens1, Term2, Layout2, Tokens2)`. | |
| 92 | % | |
| 93 | % All styles should be defined globally, | |
| 94 | % i. e. with an explicit `user:` module prefix. | |
| 95 | % Module-local term expansion is not supported consistently across Prolog systems - | |
| 96 | % at least SICStus (as of version 4.6) only supports global term expansion. | |
| 97 | % Some module-local expansions can be implemented using goal_expansion instead. | |
| 98 | ||
| 99 | :- discontiguous supported_term_expansion_style/1. | |
| 100 | supported_term_expansion_style(_) :- fail. | |
| 101 | ||
| 102 | :- public unsupported_term_expansion_style/1. % for Spider, processed only be term expansions below | |
| 103 | ||
| 104 | unsupported_term_expansion_style(traditional). | |
| 105 | unsupported_term_expansion_style(swi_layout). | |
| 106 | unsupported_term_expansion_style(sicstus). | |
| 107 | ||
| 108 | % Define all known variants of goal_expansion to detect which ones are supported by the system. | |
| 109 | ||
| 110 | supported_goal_expansion_style_inner(_Style) :- fail. | |
| 111 | ||
| 112 | goal_expansion(Goal1, Goal2) :- | |
| 113 | Goal1 == supported_goal_expansion_style_inner(traditional), | |
| 114 | !, | |
| 115 | Goal2 = true. | |
| 116 | ||
| 117 | goal_expansion(Goal1, Layout1, Goal2, Layout2) :- | |
| 118 | Goal1 == supported_goal_expansion_style_inner(swi_layout), | |
| 119 | !, | |
| 120 | Goal2 = true, | |
| 121 | Layout2 = Layout1. | |
| 122 | ||
| 123 | goal_expansion(Goal1, Layout1, _Module, Goal2, Layout2) :- | |
| 124 | Goal1 == supported_goal_expansion_style_inner(sicstus), | |
| 125 | !, | |
| 126 | Goal2 = true, | |
| 127 | Layout2 = Layout1. | |
| 128 | ||
| 129 | %! supported_goal_expansion_style(+Style). | |
| 130 | % | |
| 131 | % True if the Prolog system supports the given style of goal_expansion. | |
| 132 | % Style must be an atom. | |
| 133 | % The following styles are recognized by this predicate: | |
| 134 | % | |
| 135 | % * `traditional`: The traditional style, with no extra parameters - `goal_expansion(Goal1, Goal2)`. | |
| 136 | % * `swi_layout`: The SWI-Prolog style, with extra parameters for source layout information - `goal_expansion(Goal1, Layout1, Goal2, Layout2)`. | |
| 137 | % * `sicstus`: The SICStus Prolog style, with extra parameters for source layout and module information - `goal_expansion(Goal1, Layout1, Module, Goal2, Layout2)`. | |
| 138 | % | |
| 139 | % All styles should be defined locally in the module they belong to, | |
| 140 | % i. e. without an explicit module prefix. | |
| 141 | % Global goal expansion is not supported consistently across Prolog systems - | |
| 142 | % at least on SICStus 4.6.0, defining user:goal_expansion/5 has no effect. | |
| 143 | % Try to avoid unscoped global expansions if possible, | |
| 144 | % but if really necessary they can be implemented using term_expansion. | |
| 145 | ||
| 146 | supported_goal_expansion_style(traditional) :- supported_goal_expansion_style_inner(traditional). | |
| 147 | supported_goal_expansion_style(swi_layout) :- supported_goal_expansion_style_inner(swi_layout). | |
| 148 | supported_goal_expansion_style(sicstus) :- supported_goal_expansion_style_inner(sicstus). | |
| 149 |