///|
/// Python percent formatting uses mapping names or sequential arguments.
pub fn python_percent_contract(text : String) -> Result[FormatContract, String] {
let chars = text.to_array()
let arguments : Array[FormatArgument] = []
let kinds : Map[String, String] = Map([])
let mut i = 0
let mut next = 1
let mut mapping = false
let mut sequential = false
while i < chars.length() {
if chars[i] != '%' {
i += 1
continue
}
let offset = i
i += 1
if i == chars.length() {
return Err("Incomplete Python percent format")
}
if chars[i] == '%' {
i += 1
continue
}
let mut name : String? = None
if chars[i] == '(' {
mapping = true
i += 1
let start = i
let mut depth = 1
while i < chars.length() && depth > 0 {
if chars[i] == '(' {
depth += 1
} else if chars[i] == ')' {
depth -= 1
}
if depth > 0 {
i += 1
}
}
if i == chars.length() {
return Err("Python mapping key has no closing parenthesis")
}
name = Some(char_slice(chars, start, i))
i += 1
} else {
sequential = true
}
if mapping && sequential {
return Err("Cannot mix mapping and sequential Python percent fields")
}
while i < chars.length() && ['#', '0', '-', ' ', '+'].contains(chars[i]) {
i += 1
}
if i < chars.length() && chars[i] == '*' {
if mapping {
return Err("Mapping percent fields cannot use dynamic width")
}
match
add_format_argument(
arguments,
kinds,
next.to_string(),
"integer",
offset,
) {
Err(error) => return Err(error)
Ok(_) => ()
}
next += 1
i += 1
} else {
while i < chars.length() && chars[i].is_ascii_digit() {
i += 1
}
}
if i < chars.length() && chars[i] == '.' {
i += 1
if i < chars.length() && chars[i] == '*' {
if mapping {
return Err("Mapping percent fields cannot use dynamic precision")
}
match
add_format_argument(
arguments,
kinds,
next.to_string(),
"integer",
offset,
) {
Err(error) => return Err(error)
Ok(_) => ()
}
next += 1
i += 1
} else {
while i < chars.length() && chars[i].is_ascii_digit() {
i += 1
}
}
}
// Python accepts but ignores a single C length modifier.
if i < chars.length() && ['h', 'l', 'L'].contains(chars[i]) {
i += 1
}
if i >= chars.length() {
return Err("Python percent conversion is incomplete")
}
let kind = match chars[i] {
'd' | 'i' | 'u' | 'o' | 'x' | 'X' => "integer"
'e' | 'E' | 'f' | 'F' | 'g' | 'G' => "numeric"
'c' => "character"
's' => "string-conversion"
'r' => "repr-conversion"
'a' => "ascii-conversion"
_ =>
return Err(
"Unsupported Python percent conversion: " + chars[i].to_string(),
)
}
i += 1
let argument = match name {
Some(name) => "mapping:" + name
None => {
let name = next.to_string()
next += 1
name
}
}
match add_python_argument(arguments, kinds, argument, kind, offset) {
Err(error) => return Err(error)
Ok(_) => ()
}
if arguments.length() > 4096 {
return Err("Format contract exceeds 4096 distinct arguments")
}
}
Ok({ arguments, style: "python-format", })
}
///|
pub fn format_contract(
style : String,
text : String,
) -> Result[FormatContract, String] {
match style {
"c-format" => printf_contract(text)
"python-brace-format" => brace_contract(text)
"python-format" => python_percent_contract(text)
_ => Err("Unsupported format style: " + style)
}
}