Last active
July 9, 2021 05:00
-
-
Save gromnitsky/e39a90811ee478c4ae9b38c490e80d78 to your computer and use it in GitHub Desktop.
An extract of patsubst function from GNU Make
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
An extract of patsubst function from GNU Make 4.2.90 | |
(6f339b22eb87dc80d1037ccb04c787d156db0e8f) | |
$ cc gmake-patsubst.c -o gmake-patsubst | |
$ ./gmake-patsubst %.c %.o 'foo.c bar.c' | |
foo.o bar.o | |
*/ | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#include <err.h> | |
#include <ctype.h> | |
#include <getopt.h> | |
/* not demo related */ | |
int conf_verbose = 0; | |
extern const char *__progname; /* glibc */ | |
void | |
msg(const char *fmt, ...) | |
{ | |
if (!conf_verbose) return; | |
va_list ap; | |
va_start(ap, fmt); | |
printf("%s: ", __progname); | |
vprintf(fmt, ap); | |
va_end(ap); | |
} | |
/* gmake code */ | |
#define UCHAR_MAX 255 | |
#define MAP_NUL 0x0001 | |
#define MAP_BLANK 0x0002 | |
#define MAP_NEWLINE 0x0004 | |
#define MAP_COMMENT 0x0008 | |
#define MAP_SEMI 0x0010 | |
#define MAP_EQUALS 0x0020 | |
#define MAP_COLON 0x0040 | |
#define MAP_PERCENT 0x0080 | |
#define MAP_PIPE 0x0100 | |
#define MAP_DOT 0x0200 | |
#define MAP_COMMA 0x0400 | |
/* These are the valid characters for a user-defined function. */ | |
#define MAP_USERFUNC 0x2000 | |
/* This means not only a '$', but skip the variable reference. */ | |
#define MAP_VARIABLE 0x4000 | |
/* The set of characters which are directory separators is OS-specific. */ | |
#define MAP_DIRSEP 0x8000 | |
#define MAP_SPACE (MAP_BLANK|MAP_NEWLINE) | |
unsigned short stopchar_map[UCHAR_MAX + 1] = {0}; | |
#define ANY_SET(_v,_m) (((_v)&(_m)) != 0) | |
#define STOP_SET(_v,_m) ANY_SET(stopchar_map[(unsigned char)(_v)],(_m)) | |
#define NONE_SET(_v,_m) (! ANY_SET ((_v),(_m))) | |
#define ISBLANK(c) STOP_SET((c),MAP_BLANK) | |
#define ISSPACE(c) STOP_SET((c),MAP_SPACE) | |
#define NEXT_TOKEN(s) while (ISSPACE (*(s))) ++(s) | |
#define END_OF_TOKEN(s) while (! STOP_SET (*(s), MAP_SPACE|MAP_NUL)) ++(s) | |
#define strneq(a, b, l) (strncmp ((a), (b), (l)) == 0) | |
/* Search STRING for an unquoted STOPCHAR or blank (if BLANK is nonzero). | |
Backslashes quote STOPCHAR, blanks if BLANK is nonzero, and backslash. | |
Quoting backslashes are removed from STRING by compacting it into | |
itself. Returns a pointer to the first unquoted STOPCHAR if there is | |
one, or nil if there are none. STOPCHARs inside variable references are | |
ignored if IGNOREVARS is true. | |
STOPCHAR _cannot_ be '$' if IGNOREVARS is true. */ | |
static char * | |
find_char_unquote (char *string, int map) | |
{ | |
unsigned int string_len = 0; | |
char *p = string; | |
/* Always stop on NUL. */ | |
map |= MAP_NUL; | |
while (1) | |
{ | |
while (! STOP_SET (*p, map)) | |
++p; | |
if (*p == '\0') | |
break; | |
/* If we stopped due to a variable reference, skip over its contents. */ | |
if (STOP_SET (*p, MAP_VARIABLE)) | |
{ | |
char openparen = p[1]; | |
/* Check if '$' is the last character in the string. */ | |
if (openparen == '\0') | |
break; | |
p += 2; | |
/* Skip the contents of a non-quoted, multi-char variable ref. */ | |
if (openparen == '(' || openparen == '{') | |
{ | |
unsigned int pcount = 1; | |
char closeparen = (openparen == '(' ? ')' : '}'); | |
while (*p) | |
{ | |
if (*p == openparen) | |
++pcount; | |
else if (*p == closeparen) | |
if (--pcount == 0) | |
{ | |
++p; | |
break; | |
} | |
++p; | |
} | |
} | |
/* Skipped the variable reference: look for STOPCHARS again. */ | |
continue; | |
} | |
if (p > string && p[-1] == '\\') | |
{ | |
/* Search for more backslashes. */ | |
int i = -2; | |
while (&p[i] >= string && p[i] == '\\') | |
--i; | |
++i; | |
/* Only compute the length if really needed. */ | |
if (string_len == 0) | |
string_len = strlen (string); | |
/* The number of backslashes is now -I. | |
Copy P over itself to swallow half of them. */ | |
memmove (&p[i], &p[i/2], (string_len - (p - string)) - (i/2) + 1); | |
p += i/2; | |
if (i % 2 == 0) | |
/* All the backslashes quoted each other; the STOPCHAR was | |
unquoted. */ | |
return p; | |
/* The STOPCHAR was quoted by a backslash. Look for another. */ | |
} | |
else | |
/* No backslash in sight. */ | |
return p; | |
} | |
/* Never hit a STOPCHAR or blank (with BLANK nonzero). */ | |
return 0; | |
} | |
/* Search PATTERN for an unquoted % and handle quoting. */ | |
char * | |
find_percent (char *pattern) | |
{ | |
return find_char_unquote (pattern, MAP_PERCENT); | |
} | |
/* Return the address of the first whitespace or null in the string S. */ | |
char * | |
end_of_token (const char *s) | |
{ | |
END_OF_TOKEN (s); | |
return (char *)s; | |
} | |
/* Return the address of the first nonwhitespace or null in the string S. */ | |
char * | |
next_token (const char *s) | |
{ | |
NEXT_TOKEN (s); | |
return (char *)s; | |
} | |
/* Find the next token in PTR; return the address of it, and store the length | |
of the token into *LENGTHPTR if LENGTHPTR is not nil. Set *PTR to the end | |
of the token, so this function can be called repeatedly in a loop. */ | |
char * | |
find_next_token (const char **ptr, unsigned int *lengthptr) | |
{ | |
const char *p = next_token (*ptr); | |
if (*p == '\0') | |
return 0; | |
*ptr = end_of_token (p); | |
if (lengthptr != 0) | |
*lengthptr = *ptr - p; | |
return (char *)p; | |
} | |
void * | |
xrealloc (void *ptr, unsigned int size) | |
{ | |
void *result; | |
/* Some older implementations of realloc() don't conform to ISO. */ | |
if (! size) | |
size = 1; | |
result = ptr ? realloc (ptr, size) : malloc (size); | |
if (result == 0) | |
errx(1, "no memory"); | |
return result; | |
} | |
void * | |
xmalloc (unsigned int size) | |
{ | |
/* Make sure we don't allocate 0, for pre-ISO implementations. */ | |
void *result = malloc (size ? size : 1); | |
if (result == 0) | |
errx(1, "no memory"); | |
return result; | |
} | |
/* The next two describe the variable output buffer. | |
This buffer is used to hold the variable-expansion of a line of the | |
makefile. It is made bigger with realloc whenever it is too small. | |
variable_buffer_length is the size currently allocated. | |
variable_buffer is the address of the buffer. | |
For efficiency, it's guaranteed that the buffer will always have | |
VARIABLE_BUFFER_ZONE extra bytes allocated. This allows you to add a few | |
extra chars without having to call a function. Note you should never use | |
these bytes unless you're _sure_ you have room (you know when the buffer | |
length was last checked. */ | |
#define VARIABLE_BUFFER_ZONE 5 | |
static unsigned int variable_buffer_length; | |
char *variable_buffer; | |
/* Subroutine of variable_expand and friends: | |
The text to add is LENGTH chars starting at STRING to the variable_buffer. | |
The text is added to the buffer at PTR, and the updated pointer into | |
the buffer is returned as the value. Thus, the value returned by | |
each call to variable_buffer_output should be the first argument to | |
the following call. */ | |
char * | |
variable_buffer_output (char *ptr, const char *string, unsigned int length) | |
{ | |
register unsigned int newlen = length + (ptr - variable_buffer); | |
if ((newlen + VARIABLE_BUFFER_ZONE) > variable_buffer_length) | |
{ | |
unsigned int offset = ptr - variable_buffer; | |
variable_buffer_length = (newlen + 100 > 2 * variable_buffer_length | |
? newlen + 100 | |
: 2 * variable_buffer_length); | |
variable_buffer = xrealloc (variable_buffer, variable_buffer_length); | |
ptr = variable_buffer + offset; | |
} | |
memcpy (ptr, string, length); | |
return ptr + length; | |
} | |
/* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing | |
each occurrence of SUBST with REPLACE. TEXT is null-terminated. SLEN is | |
the length of SUBST and RLEN is the length of REPLACE. If BY_WORD is | |
nonzero, substitutions are done only on matches which are complete | |
whitespace-delimited words. */ | |
char * | |
subst_expand (char *o, const char *text, const char *subst, const char *replace, | |
unsigned int slen, unsigned int rlen, int by_word) | |
{ | |
const char *t = text; | |
const char *p; | |
if (slen == 0 && !by_word) | |
{ | |
/* The first occurrence of "" in any string is its end. */ | |
o = variable_buffer_output (o, t, strlen (t)); | |
if (rlen > 0) | |
o = variable_buffer_output (o, replace, rlen); | |
return o; | |
} | |
do | |
{ | |
if (by_word && slen == 0) | |
/* When matching by words, the empty string should match | |
the end of each word, rather than the end of the whole text. */ | |
p = end_of_token (next_token (t)); | |
else | |
{ | |
p = strstr (t, subst); | |
if (p == 0) | |
{ | |
/* No more matches. Output everything left on the end. */ | |
o = variable_buffer_output (o, t, strlen (t)); | |
return o; | |
} | |
} | |
/* Output everything before this occurrence of the string to replace. */ | |
if (p > t) | |
o = variable_buffer_output (o, t, p - t); | |
/* If we're substituting only by fully matched words, | |
or only at the ends of words, check that this case qualifies. */ | |
if (by_word | |
&& ((p > text && !ISSPACE (p[-1])) | |
|| ! STOP_SET (p[slen], MAP_SPACE|MAP_NUL))) | |
/* Struck out. Output the rest of the string that is | |
no longer to be replaced. */ | |
o = variable_buffer_output (o, subst, slen); | |
else if (rlen > 0) | |
/* Output the replacement string. */ | |
o = variable_buffer_output (o, replace, rlen); | |
/* Advance T past the string to be replaced. */ | |
t = p + slen; | |
} while (*t != '\0'); | |
return o; | |
} | |
/* Store into VARIABLE_BUFFER at O the result of scanning TEXT | |
and replacing strings matching PATTERN with REPLACE. | |
If PATTERN_PERCENT is not nil, PATTERN has already been | |
run through find_percent, and PATTERN_PERCENT is the result. | |
If REPLACE_PERCENT is not nil, REPLACE has already been | |
run through find_percent, and REPLACE_PERCENT is the result. | |
Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the | |
character _AFTER_ the %, not to the % itself. | |
*/ | |
char * | |
patsubst_expand_pat (char *o, const char *text, | |
const char *pattern, const char *replace, | |
const char *pattern_percent, const char *replace_percent) | |
{ | |
unsigned int pattern_prepercent_len, pattern_postpercent_len; | |
unsigned int replace_prepercent_len, replace_postpercent_len; | |
const char *t; | |
unsigned int len; | |
int doneany = 0; | |
/* Record the length of REPLACE before and after the % so we don't have to | |
compute these lengths more than once. */ | |
if (replace_percent) | |
{ | |
replace_prepercent_len = replace_percent - replace - 1; | |
replace_postpercent_len = strlen (replace_percent); | |
} | |
else | |
{ | |
replace_prepercent_len = strlen (replace); | |
replace_postpercent_len = 0; | |
} | |
if (!pattern_percent) | |
/* With no % in the pattern, this is just a simple substitution. */ | |
return subst_expand (o, text, pattern, replace, | |
strlen (pattern), strlen (replace), 1); | |
/* Record the length of PATTERN before and after the % | |
so we don't have to compute it more than once. */ | |
pattern_prepercent_len = pattern_percent - pattern - 1; | |
pattern_postpercent_len = strlen (pattern_percent); | |
while ((t = find_next_token (&text, &len)) != 0) | |
{ | |
int fail = 0; | |
/* Is it big enough to match? */ | |
if (len < pattern_prepercent_len + pattern_postpercent_len) | |
fail = 1; | |
/* Does the prefix match? */ | |
if (!fail && pattern_prepercent_len > 0 | |
&& (*t != *pattern | |
|| t[pattern_prepercent_len - 1] != pattern_percent[-2] | |
|| !strneq (t + 1, pattern + 1, pattern_prepercent_len - 1))) | |
fail = 1; | |
/* Does the suffix match? */ | |
if (!fail && pattern_postpercent_len > 0 | |
&& (t[len - 1] != pattern_percent[pattern_postpercent_len - 1] | |
|| t[len - pattern_postpercent_len] != *pattern_percent | |
|| !strneq (&t[len - pattern_postpercent_len], | |
pattern_percent, pattern_postpercent_len - 1))) | |
fail = 1; | |
if (fail) | |
/* It didn't match. Output the string. */ | |
o = variable_buffer_output (o, t, len); | |
else | |
{ | |
/* It matched. Output the replacement. */ | |
/* Output the part of the replacement before the %. */ | |
o = variable_buffer_output (o, replace, replace_prepercent_len); | |
if (replace_percent != 0) | |
{ | |
/* Output the part of the matched string that | |
matched the % in the pattern. */ | |
o = variable_buffer_output (o, t + pattern_prepercent_len, | |
len - (pattern_prepercent_len | |
+ pattern_postpercent_len)); | |
/* Output the part of the replacement after the %. */ | |
o = variable_buffer_output (o, replace_percent, | |
replace_postpercent_len); | |
} | |
} | |
/* Output a space, but not if the replacement is "". */ | |
if (fail || replace_prepercent_len > 0 | |
|| (replace_percent != 0 && len + replace_postpercent_len > 0)) | |
{ | |
o = variable_buffer_output (o, " ", 1); | |
doneany = 1; | |
} | |
} | |
if (doneany) | |
/* Kill the last space. */ | |
--o; | |
return o; | |
} | |
/* Store into VARIABLE_BUFFER at O the result of scanning TEXT | |
and replacing strings matching PATTERN with REPLACE. | |
If PATTERN_PERCENT is not nil, PATTERN has already been | |
run through find_percent, and PATTERN_PERCENT is the result. | |
If REPLACE_PERCENT is not nil, REPLACE has already been | |
run through find_percent, and REPLACE_PERCENT is the result. | |
Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the | |
character _AFTER_ the %, not to the % itself. | |
*/ | |
char * | |
patsubst_expand (char *o, const char *text, char *pattern, char *replace) | |
{ | |
const char *pattern_percent = find_percent (pattern); | |
const char *replace_percent = find_percent (replace); | |
/* If there's a percent in the pattern or replacement skip it. */ | |
if (replace_percent) | |
++replace_percent; | |
if (pattern_percent) | |
++pattern_percent; | |
msg("pattern_percent=`%s`\n", pattern_percent); | |
msg("replace_percent=`%s`\n", replace_percent); | |
return patsubst_expand_pat (o, text, pattern, replace, | |
pattern_percent, replace_percent); | |
} | |
/* Return a pointer to the beginning of the variable buffer. */ | |
static char * | |
initialize_variable_output (void) | |
{ | |
/* If we don't have a variable output buffer yet, get one. */ | |
if (variable_buffer == 0) | |
{ | |
variable_buffer_length = 200; | |
variable_buffer = xmalloc (variable_buffer_length); | |
variable_buffer[0] = '\0'; | |
} | |
return variable_buffer; | |
} | |
/* This character map locate stop chars when parsing GNU makefiles. | |
Each element is true if we should stop parsing on that character. */ | |
static void | |
initialize_stopchar_map (void) | |
{ | |
int i; | |
stopchar_map[(int)'\0'] = MAP_NUL; | |
stopchar_map[(int)'#'] = MAP_COMMENT; | |
stopchar_map[(int)';'] = MAP_SEMI; | |
stopchar_map[(int)'='] = MAP_EQUALS; | |
stopchar_map[(int)':'] = MAP_COLON; | |
stopchar_map[(int)'%'] = MAP_PERCENT; | |
stopchar_map[(int)'|'] = MAP_PIPE; | |
stopchar_map[(int)'.'] = MAP_DOT | MAP_USERFUNC; | |
stopchar_map[(int)','] = MAP_COMMA; | |
stopchar_map[(int)'$'] = MAP_VARIABLE; | |
stopchar_map[(int)'-'] = MAP_USERFUNC; | |
stopchar_map[(int)'_'] = MAP_USERFUNC; | |
stopchar_map[(int)' '] = MAP_BLANK; | |
stopchar_map[(int)'\t'] = MAP_BLANK; | |
stopchar_map[(int)'/'] = MAP_DIRSEP; | |
#if defined(VMS) | |
stopchar_map[(int)':'] |= MAP_DIRSEP; | |
stopchar_map[(int)']'] |= MAP_DIRSEP; | |
stopchar_map[(int)'>'] |= MAP_DIRSEP; | |
#elif defined(HAVE_DOS_PATHS) | |
stopchar_map[(int)'\\'] |= MAP_DIRSEP; | |
#endif | |
for (i = 1; i <= UCHAR_MAX; ++i) | |
{ | |
if (isspace (i) && NONE_SET (stopchar_map[i], MAP_BLANK)) | |
/* Don't mark blank characters as newline characters. */ | |
stopchar_map[i] |= MAP_NEWLINE; | |
else if (isalnum (i)) | |
stopchar_map[i] |= MAP_USERFUNC; | |
} | |
} | |
void | |
usage() { | |
errx(1, "Usage: gmake_patsubst [-v] pattern replacement text"); | |
} | |
int main(int argc, char **argv) { | |
int c; | |
while ((c = getopt (argc, argv, "v")) != -1) { | |
c == 'v' ? conf_verbose++ : usage(); | |
} | |
argc -= optind; | |
argv += optind; | |
if (argc != 3) usage(); | |
initialize_stopchar_map(); | |
char *buf = initialize_variable_output(); | |
char *bufstart = buf; | |
buf = patsubst_expand(buf, argv[2], argv[0], argv[1]); | |
char *result = xmalloc(variable_buffer_length); | |
printf(conf_verbose ? "result: `%s`\n" : "%s\n", | |
strncpy(result, bufstart, buf-bufstart)); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/* | |
A "port" of GNU Make's patsubst function to nodejs-6.6. | |
$ ./gmake-patsubst.js %.c %.o 'foo.c bar.c' | |
foo.o bar.o | |
*/ | |
let transform = function(ps, pattern, replacement, chunk) { | |
if (pattern.indexOf('%') === -1) | |
return simple(pattern, replacement, chunk) | |
// does the prefix & the suffix match? | |
if ( (chunk.slice(0, ps.pattern_prefix.length) === ps.pattern_prefix) | |
&& (chunk.slice(-ps.pattern_suffix.length) === ps.pattern_suffix) ) { | |
if (replacement.indexOf('%') === -1) return replacement | |
let stem = chunk.slice(ps.pattern_prefix.length, | |
-ps.pattern_suffix.length) | |
return ps.replacement_prefix + stem + ps.replacement_suffix | |
} | |
return chunk | |
} | |
let simple = function(pattern, replacement, chunk) { | |
return pattern === chunk ? replacement : chunk | |
} | |
let affixes = function(pattern, replacement) { | |
let p = pattern.split('%') | |
let r = replacement.split('%') | |
return { | |
pattern_prefix: p[0], | |
pattern_suffix: p.slice(1).join('%'), | |
replacement_prefix: r[0], | |
replacement_suffix: r.slice(1).join('%'), | |
} | |
} | |
if (process.argv.length !== 2+3) { | |
console.error('Usage: gmake-patsubst.js PATTERN REPLACEMENT TEXT') | |
process.exit(1) | |
} | |
let [pattern, replacement, text] = process.argv.slice(2) | |
let ps = affixes(pattern, replacement) | |
//console.log(ps) | |
console.log(text | |
.trim() | |
.split(/\s+/) | |
.map( (chunk) => transform(ps, pattern, replacement, chunk) ) | |
.join(' ')) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment