mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
133 lines
4.0 KiB
Prolog
133 lines
4.0 KiB
Prolog
:- module(interpolate, ['$interpolate_macro_sentinel'/0]).
|
|
:- use_module(library(dcg/basics), [prolog_var_name//1, string//1]).
|
|
:- use_module(library(function_expansion)).
|
|
:- use_module(library(error), [type_error/2]).
|
|
|
|
%% Template(+Vars:pairs, -Formats:list, -Args:list)//
|
|
%
|
|
% Parse a list of codes as if it were an interpolated string. Formats
|
|
% is a list of atoms that can be joined together to
|
|
% create the first argument of format/2. Args are values for the
|
|
% second.
|
|
template(Vars, [Static,_|Formats], [Arg|Args]) -->
|
|
string(Codes),
|
|
variable(VarName),
|
|
{ memberchk(VarName=Arg, Vars) },
|
|
{ atom_codes(Static, Codes) },
|
|
template(Vars, Formats, Args).
|
|
template(_, [Format], []) -->
|
|
string(Codes),
|
|
{ atom_codes(Format, Codes) }.
|
|
|
|
|
|
%% variable(-VarName:atom)//
|
|
%
|
|
% Parse a $-prefixed variable name. Like `$Message`.
|
|
% For now this is a thin wrapper around prolog_var_name//1 but may
|
|
% eventuall grow to support other kinds of variables so I want the
|
|
% details abstracted away.
|
|
variable(VarName) -->
|
|
"$",
|
|
prolog_var_name(VarName).
|
|
|
|
|
|
% true if the module whose terms are being read has specifically
|
|
% requested string interpolation.
|
|
wants_interpolation :-
|
|
prolog_load_context(module, Module),
|
|
Module \== interpolate, % we don't want string interpolation ourselves
|
|
predicate_property(
|
|
Module:'$interpolate_macro_sentinel',
|
|
imported_from(interpolate)
|
|
).
|
|
|
|
|
|
%% textual(-Type:atom, +Text, -Codes)
|
|
%
|
|
% True if Text is of type Type and representable as Codes.
|
|
textual(_,Var,_) :-
|
|
var(Var),
|
|
!,
|
|
fail.
|
|
textual(atom, Atom, Codes) :-
|
|
atom(Atom),
|
|
!,
|
|
atom_codes(Atom,Codes).
|
|
textual(Type, Text, Codes) :-
|
|
is_list(Text),
|
|
!,
|
|
Text = [H|_], % empty lists are not text for our purposes
|
|
( atom(H) ->
|
|
Type = chars,
|
|
catch(atom_chars(Atom,Text),_,fail),
|
|
atom_codes(Atom,Codes)
|
|
; integer(H) ->
|
|
Type = codes,
|
|
Codes = Text
|
|
). % fail on all other lists
|
|
textual(string, String, Codes) :-
|
|
string(String),
|
|
string_to_list(String,Codes).
|
|
|
|
|
|
%% build_text(?Output, ?Formats:list, ?Args:list)
|
|
%
|
|
% Like format/3 but dynamically chooses tilde sequences to match the
|
|
% values in Args.
|
|
build_text(Output, Formats0, Args) :-
|
|
instantiate_formats(Formats0, Args, Formats),
|
|
atomic_list_concat(Formats, Format),
|
|
format(Output, Format, Args).
|
|
|
|
|
|
% choose format tilde sequences for a list of values
|
|
instantiate_formats([], _, []).
|
|
instantiate_formats([Static|Formats0],Args,[Static|Formats]) :-
|
|
atom(Static),
|
|
!,
|
|
instantiate_formats(Formats0,Args,Formats).
|
|
instantiate_formats([Var|Formats0],[Arg|Args],[Format|Formats]) :-
|
|
var(Var),
|
|
!,
|
|
preferred_tilde(Arg,Format),
|
|
instantiate_formats(Formats0,Args,Formats).
|
|
instantiate_formats([X|_],_,[_|_]) :-
|
|
type_error(atom_or_var,X).
|
|
|
|
|
|
% Which format/2 tilde sequence does a value prefer?
|
|
preferred_tilde(X,'~s') :-
|
|
textual(Type, X, _),
|
|
( Type = codes; Type = chars ),
|
|
!.
|
|
preferred_tilde(_,'~p').
|
|
|
|
|
|
:- multifile user:function_expansion/3.
|
|
user:function_expansion(Term,Replacement,Guard) :-
|
|
wants_interpolation,
|
|
prolog_load_context(variable_names, Vars),
|
|
Vars \== [], % need variables to interpolate
|
|
|
|
% is this a string in need of interpolation?
|
|
textual(Type, Term, TextCodes),
|
|
phrase(template(Vars, Formats, Args), TextCodes),
|
|
Args \== [], % no args means no interpolation
|
|
|
|
% yup, so perform the expansion
|
|
Output =.. [Type, Replacement],
|
|
Guard = interpolate:build_text(Output, Formats, Args).
|
|
|
|
|
|
%% '$interpolate_macro_sentinel'.
|
|
%
|
|
% Nothing to see here. This is an implementation detail to avoid
|
|
% unwanted string interpolation. Because string interpolation is
|
|
% implemented as a macro and macro expansion is done through a
|
|
% predicate with gloabl reach (user:term_expansion/2), we must take
|
|
% precautions to avoid performing string interpolation on code that
|
|
% doesn't want it. Importing this predicate opts-in to string
|
|
% interpolation. Writing `use_module(library(interpolate))` does it
|
|
% for you.
|
|
'$interpolate_macro_sentinel'.
|