///|
fn make_function_name(reference : String) -> String? {
let chars = reference.iter().collect()
let mut end = 0
while end < chars.length() &&
!(chars[end] is (' ' | '\t' | '\r' | '\n' | '\u000b' | '\u000c')) {
end += 1
}
if end == chars.length() {
None
} else {
Some(String::from_array(chars[:end]))
}
}
///|
fn reject_unsupported_function(reference : String) -> Unit raise MakeError {
guard make_function_name(reference) is Some(name) else { return }
// GNU make 4.4.1 function_table_init; detect lazy functions before touching
// their arguments. Unknown names retain ordinary variable-name expansion.
if [
"abspath", "addprefix", "addsuffix", "basename", "dir", "notdir", "subst",
"suffix", "filter", "filter-out", "findstring", "firstword", "flavor", "join",
"lastword", "patsubst", "realpath", "sort", "strip", "wildcard", "word", "wordlist",
"words", "origin", "foreach", "let", "call", "info", "error", "warning", "intcmp",
"if", "or", "and", "value", "eval", "file",
].contains(name) {
raise MakeError(
"GNU function '\{name}' is outside the supported make profile",
)
}
}
///|
fn default_shell_profile(literal : String) -> Bool {
literal == "/bin/sh"
}
///|
fn validate_special_assignment(
name : String,
operator : String,
value : String,
) -> Unit raise MakeError {
if name == "SHELL" || name == ".SHELLFLAGS" {
let literal = value.trim().to_owned()
let accepted = operator is ("=" | ":=" | "::=" | "?=") &&
(if name == "SHELL" {
literal == "sh" || default_shell_profile(literal)
} else {
literal == "-c"
})
if !accepted {
raise MakeError(
"\{name} requires the literal built-in sh profile (SHELL=sh or /bin/sh, .SHELLFLAGS=-c); custom interpreters and flags are unsupported",
)
}
}
if [
".RECIPEPREFIX", ".DEFAULT_GOAL", ".EXTRA_PREREQS", "VPATH", "GPATH", "MAKEOVERRIDES",
"GNUMAKEFLAGS", "MAKEFILES",
].contains(name) {
raise MakeError(
"control variable '\{name}' is outside the supported make profile",
)
}
}
///|
fn validate_rule_words(
targets : Array[String],
dependencies : Array[String],
) -> Unit raise MakeError {
for target in targets {
if target == "&" {
raise MakeError("grouped targets are outside the supported make profile")
}
if [
".ONESHELL", ".POSIX", ".DEFAULT", ".SUFFIXES", ".IGNORE", ".SILENT", ".NOTPARALLEL",
".DELETE_ON_ERROR", ".PRECIOUS", ".SECONDARY", ".INTERMEDIATE", ".NOTINTERMEDIATE",
".LOW_RESOLUTION_TIME", ".WAIT",
].contains(target) {
raise MakeError(
"special target '\{target}' is outside the supported make profile",
)
}
}
for word in targets + dependencies {
if !word.contains("$(") && word.contains("(") && word.has_suffix(")") {
raise MakeError("archive members are outside the supported make profile")
}
}
if dependencies.any(word => word.contains("|") || word == ".WAIT") {
raise MakeError(
"order-only and .WAIT prerequisites are outside the supported make profile",
)
}
}
///|
fn split_rule_separator(text : String, separator : Char) -> (String, String)? {
let chars = text.iter().collect()
let closes : Array[Char] = []
let mut index = 0
while index < chars.length() {
if chars[index] == '\\' {
index += 2
continue
}
if chars[index] == '#' && closes.is_empty() {
return None
}
if chars[index] == '$' && chars.get(index + 1) is Some('(' | '{' as open) {
closes.push(if open == '(' { ')' } else { '}' })
index += 2
continue
}
if closes.last() == Some(')') && chars[index] == '(' {
closes.push(')')
} else if closes.last() == Some('}') && chars[index] == '{' {
closes.push('}')
} else if closes.last() == Some(chars[index]) {
ignore(closes.pop())
} else if chars[index] == separator && closes.is_empty() {
return Some(
(
String::from_array(chars[:index]),
String::from_array(chars[index + 1:]),
),
)
}
index += 1
}
None
}
///|
fn split_inline_recipe(text : String) -> (String, String?) {
match split_rule_separator(text, ';') {
Some((rule, recipe)) => (rule, Some(recipe))
None => (text, None)
}
}
///|
fn add_recipe(rule : Rule, group : Prerequisites, recipe : String) -> Unit {
if !group.has_recipe {
rule.recipes.clear()
for previous in rule.groups {
previous.has_recipe = false
}
group.has_recipe = true
ignore(rule.groups.pop())
rule.groups.insert(0, group)
}
rule.recipes.push(recipe)
}