// The parser of format strings, a port of `fmt_ebb_of_string` in OCaml's
// `camlinternalFormat.ml`, in legacy mode (the default of the compiler).
//
// The structure of the OCaml parser is kept, down to the order of the
// recursive calls: the rest of a format is parsed before some checks of a
// conversion, which determines the error reported for a format with several
// errors.
//
// Copyright 1996 Institut National de Recherche en Informatique et en
// Automatique (OCaml), distributed under the terms of the GNU Lesser
// General Public License version 2.1, with the special exception on
// linking described in the file LICENSE.
///|
priv suberror FormatError {
FormatError(String)
}
///|
priv struct FormatParser {
str : String
}
///|
/// OCaml's `Sys.max_string_length` on 64-bit platforms: the limit of
/// widths and precisions.
let max_string_length : Int64 = 144115188075855863L
///|
fn FormatParser::char_at(self : FormatParser, i : Int) -> Char {
self.str[i].unsafe_to_char()
}
///|
/// The position of an index in error messages: like in OCaml, it counts
/// the bytes of the UTF-8 encoding.
fn FormatParser::pos(self : FormatParser, i : Int) -> Int {
utf8_length(self.str.view(end_offset=i))
}
///|
fn FormatParser::quoted(self : FormatParser) -> String {
string_to_caml_string(self.str)
}
///|
fn FormatParser::invalid_format_message(
self : FormatParser,
str_ind : Int,
msg : String,
) -> FormatError {
FormatError(
"invalid format \{self.quoted()}: at character number \{self.pos(str_ind)}, \{msg}",
)
}
///|
fn FormatParser::unexpected_end_of_format(
self : FormatParser,
end_ind : Int,
) -> FormatError {
self.invalid_format_message(end_ind, "unexpected end of format")
}
///|
fn FormatParser::expected_character(
self : FormatParser,
str_ind : Int,
expected : String,
read : Char,
) -> FormatError {
self.invalid_format_message(
str_ind,
"\{expected} expected, read \{caml_char(read)}",
)
}
///|
fn FormatParser::incompatible_flag(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
symb : Char,
option : String,
) -> FormatError {
let subfmt = self.str
.view(start_offset=pct_ind, end_offset=str_ind)
.to_owned()
FormatError(
"invalid format \{self.quoted()}: at character number \{self.pos(pct_ind)}, \{option} is incompatible with '\{symb}' in sub-format \{string_to_caml_string(subfmt)}",
)
}
///|
/// Parse the format from `beg_ind` (included) to `end_ind` (excluded).
fn FormatParser::parse(
self : FormatParser,
beg_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
self.parse_literal(beg_ind, beg_ind, end_ind)
}
///|
/// Read literal characters up to '%' or '@' special characters.
fn FormatParser::parse_literal(
self : FormatParser,
lit_start : Int,
str_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
let mut str_ind = str_ind
while str_ind < end_ind && !(self.char_at(str_ind) is ('%' | '@')) {
str_ind += 1
}
if str_ind == end_ind {
return self.add_literal(lit_start, str_ind, @list.empty())
}
let fmt_rest = if self.char_at(str_ind) == '%' {
self.parse_format(str_ind, end_ind)
} else {
self.parse_after_at(str_ind + 1, end_ind)
}
self.add_literal(lit_start, str_ind, fmt_rest)
}
///|
/// Add a literal to a format from a literal character sub-sequence.
fn FormatParser::add_literal(
self : FormatParser,
lit_start : Int,
str_ind : Int,
fmt : @list.List[Item],
) -> @list.List[Item] {
if str_ind == lit_start {
fmt
} else {
fmt.prepend(
Lit(self.str.view(start_offset=lit_start, end_offset=str_ind).to_owned()),
)
}
}
///|
/// Parse a format after '%'.
fn FormatParser::parse_format(
self : FormatParser,
pct_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
let str_ind = pct_ind + 1
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
if self.char_at(str_ind) == '_' {
self.parse_flags(pct_ind, str_ind + 1, end_ind, true)
} else {
self.parse_flags(pct_ind, str_ind, end_ind, false)
}
}
///|
fn FormatParser::parse_flags(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
ign : Bool,
) -> @list.List[Item] raise FormatError {
// in legacy mode, duplicate flags are accepted
let mut zero = false
let mut minus = false
let mut plus = false
let mut space = false
let mut hash = false
let mut str_ind = str_ind
for ;; {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind) {
'0' => zero = true
'-' => minus = true
'+' => plus = true
'#' => hash = true
' ' => space = true
_ => break
}
str_ind += 1
}
self.parse_padding(
pct_ind, str_ind, end_ind, zero, minus, plus, hash, space, ign,
)
}
///|
/// Try to read a digital or a '*' padding.
fn FormatParser::parse_padding(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
zero : Bool,
minus : Bool,
plus : Bool,
hash : Bool,
space : Bool,
ign : Bool,
) -> @list.List[Item] raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
let padty : PadTy = match (zero, minus) {
(false, false) => Right
(false, true) => Left
(true, false) => Zeros
(true, true) => Left // legacy
}
match self.char_at(str_ind) {
'0'..='9' => {
let (new_ind, width) = self.parse_positive(str_ind, end_ind)
self.parse_after_padding(
pct_ind,
new_ind,
end_ind,
minus,
plus,
hash,
space,
ign,
LitPad(padty, width),
)
}
'*' =>
self.parse_after_padding(
pct_ind,
str_ind + 1,
end_ind,
minus,
plus,
hash,
space,
ign,
ArgPad(padty),
)
_ => {
let pad = match padty {
Left => NoPad // legacy
// a '0' padding indication not followed by anything is a Right
// padding of width 0 (used by the scanning conversions %0s, %0c)
Zeros => LitPad(Right, 0)
Right => NoPad
}
self.parse_after_padding(
pct_ind, str_ind, end_ind, minus, plus, hash, space, ign, pad,
)
}
}
}
///|
/// Is precision defined?
fn FormatParser::parse_after_padding(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
minus : Bool,
plus : Bool,
hash : Bool,
space : Bool,
ign : Bool,
pad : Pad,
) -> @list.List[Item] raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind) {
'.' =>
self.parse_precision(
pct_ind,
str_ind + 1,
end_ind,
minus,
plus,
hash,
space,
ign,
pad,
)
symb =>
self.parse_conversion(
pct_ind,
str_ind + 1,
end_ind,
plus,
hash,
space,
ign,
pad,
NoPrec,
pad,
symb,
)
}
}
///|
/// Read the digital or '*' precision.
fn FormatParser::parse_precision(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
minus : Bool,
plus : Bool,
hash : Bool,
space : Bool,
ign : Bool,
pad : Pad,
) -> @list.List[Item] raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
let parse_literal = (minus : Bool, str_ind : Int) => {
let (new_ind, prec) = self.parse_positive(str_ind, end_ind)
self.parse_after_precision(
pct_ind,
new_ind,
end_ind,
minus,
plus,
hash,
space,
ign,
pad,
LitPrec(prec),
)
}
match self.char_at(str_ind) {
'0'..='9' => parse_literal(minus, str_ind)
// legacy: '+' or '-' before the precision is accepted and ignored
'+' | '-' as symb => parse_literal(minus || symb == '-', str_ind + 1)
'*' =>
self.parse_after_precision(
pct_ind,
str_ind + 1,
end_ind,
minus,
plus,
hash,
space,
ign,
pad,
ArgPrec,
)
// legacy: '.' without a number means '.0'
_ =>
self.parse_after_precision(
pct_ind,
str_ind,
end_ind,
minus,
plus,
hash,
space,
ign,
pad,
LitPrec(0),
)
}
}
///|
/// Try to read the conversion.
fn FormatParser::parse_after_precision(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
minus : Bool,
plus : Bool,
hash : Bool,
space : Bool,
ign : Bool,
pad : Pad,
prec : Prec,
) -> @list.List[Item] raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
// in legacy mode, %s and %S accept a mix of padding and precision, merged
// as a single padding: %.10s is %10s, and %-.3s and %.-3s are %-3s
let padprec = match pad {
NoPad =>
match (minus, prec) {
(_, NoPrec) => NoPad
(false, LitPrec(n)) => LitPad(Right, n)
(true, LitPrec(n)) => LitPad(Left, n)
(false, ArgPrec) => ArgPad(Right)
(true, ArgPrec) => ArgPad(Left)
}
pad => pad
}
self.parse_conversion(
pct_ind,
str_ind + 1,
end_ind,
plus,
hash,
space,
ign,
pad,
prec,
padprec,
self.char_at(str_ind),
)
}
///|
/// Case analysis on conversion.
fn FormatParser::parse_conversion(
self : FormatParser,
pct_ind : Int,
str_ind : Int,
end_ind : Int,
plus : Bool,
hash : Bool,
space : Bool,
ign : Bool,
pad : Pad,
prec : Prec,
padprec : Pad,
symb : Char,
) -> @list.List[Item] raise FormatError {
// the '_' flag must be used by the conversion
let mut ign_used = false
let get_ign = () => {
ign_used = true
ign
}
// padding as an option: '*' is not accepted
let opt_of_pad = (c : Char, pad : Pad) => {
match pad {
NoPad => None
LitPad(_, width) => Some(width)
ArgPad(_) => raise self.incompatible_flag(pct_ind, str_ind, c, "'*'")
}
}
let get_pad_opt = (c : Char) => opt_of_pad(c, pad)
let get_padprec_opt = (c : Char) => opt_of_pad(c, padprec)
let get_prec_opt = () => {
match prec {
NoPrec => None
LitPrec(ndec) => Some(ndec)
ArgPrec => raise self.incompatible_flag(pct_ind, str_ind, '_', "'*'")
}
}
// legacy: a '0' padding is a right padding for integers with a
// precision, and for strings and booleans
let check_no_0 = (pad : Pad) => {
match pad {
LitPad(Zeros, width) => LitPad(Right, width)
ArgPad(Zeros) => ArgPad(Right)
pad => pad
}
}
let get_int_pad = () => {
match (pad, prec) {
(pad, NoPrec) => pad
(NoPad, _) => NoPad
(pad, _) => check_no_0(pad)
}
}
// the item of an ignored conversion: an error when printing
let ignored = (sig : String) => {
Item::Invalid("Printf: bad conversion %_", sig)
}
let fmt_result = match symb {
',' => self.parse(str_ind, end_ind)
'c' => {
let fmt_rest = self.parse(str_ind, end_ind)
// %0c (which reads the next character in scanf) and the widths of
// %c (ignored in legacy mode) print a character
ignore(get_pad_opt('c'))
fmt_rest.prepend(
if get_ign() {
ignored("")
} else {
Conversion(CChar, NoPad, NoPrec)
},
)
}
'C' => {
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
ignored("")
} else {
Conversion(CCamlChar, NoPad, NoPrec)
},
)
}
's' | 'S' => {
let pad = check_no_0(padprec)
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
ignore(get_padprec_opt('_'))
ignored("")
} else {
let conv = if symb == 's' { CString } else { CCamlString }
Conversion(conv, pad, NoPrec)
},
)
}
'd' | 'i' | 'x' | 'X' | 'o' | 'u' => {
let iconv = int_conv(plus, hash, space, symb)
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
ignore(get_pad_opt('_'))
ignored("")
} else {
Conversion(make_int_conv(OInt, symb, iconv), get_int_pad(), prec)
},
)
}
'N' => {
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(if get_ign() { ignored("") } else { ScanCounter })
}
'l' | 'n' | 'L' if str_ind == end_ind || !is_int_base(self.char_at(str_ind)) => {
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(if get_ign() { ignored("") } else { ScanCounter })
}
'l' | 'n' | 'L' => {
let c = self.char_at(str_ind)
let iconv = int_conv(plus, hash, space, c)
let fmt_rest = self.parse(str_ind + 1, end_ind)
let size = match symb {
'l' => I32
'n' => Native
_ => I64
}
fmt_rest.prepend(
if get_ign() {
ignore(get_pad_opt('_'))
ignored("")
} else {
Conversion(make_int_conv(size, c, iconv), get_int_pad(), prec)
},
)
}
'f' | 'e' | 'E' | 'g' | 'G' | 'F' | 'h' | 'H' => {
// legacy: '+' wins over ' '
let flag = if plus { '+' } else if space { ' ' } else { '-' }
let kind = match symb {
'F' => if hash { FCF } else { FF }
'h' => FH
'H' => FHU
c => FC(c)
}
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
// the arguments of a constructor are evaluated from right to left
ignore(get_prec_opt())
ignore(get_pad_opt('_'))
ignored("")
} else {
Conversion(CFloat(flag, kind), pad, prec)
},
)
}
'b' | 'B' => {
let pad = check_no_0(padprec)
let fmt_rest = self.parse(str_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
ignore(get_padprec_opt('_'))
ignored("")
} else {
Conversion(CBool, pad, NoPrec)
},
)
}
'a' =>
self.parse(str_ind, end_ind).prepend(Conversion(CAlpha, NoPad, NoPrec))
't' =>
self.parse(str_ind, end_ind).prepend(Conversion(CTheta, NoPad, NoPrec))
'r' => {
let fmt_rest = self.parse(str_ind, end_ind)
// %r (a reader, for scanf) is impossible with printf by typing
fmt_rest.prepend(
if get_ign() {
Invalid(
"Printf: %_r is not supported by printf-like functions", "%_r",
)
} else {
Invalid("Printf: %r is not supported by printf-like functions", "%r")
},
)
}
'!' => self.parse(str_ind, end_ind).prepend(Flush)
'%' | '@' => self.parse(str_ind, end_ind).prepend(Lit(symb.to_string()))
'{' => {
let sub_end = self.search_subformat_end(str_ind, end_ind, '}')
let sub_fmt = self.parse(str_ind, sub_end)
let fmt_rest = self.parse(sub_end + 2, end_ind)
let sub_fmtty = signature(sub_fmt)
fmt_rest.prepend(
if get_ign() {
ignore(get_pad_opt('_'))
ignored("")
} else {
ignore(get_pad_opt('{'))
FormatArg(sub_fmtty)
},
)
}
'(' => {
let sub_end = self.search_subformat_end(str_ind, end_ind, ')')
let fmt_rest = self.parse(sub_end + 2, end_ind)
let sub_fmt = self.parse(str_ind, sub_end)
let sub_fmtty = signature(sub_fmt)
fmt_rest.prepend(
if get_ign() {
ignore(get_pad_opt('_'))
// `%_( fmt %)` takes the arguments of `fmt`
ignored(sub_fmtty)
} else {
ignore(get_pad_opt('('))
FormatSubst(sub_fmtty)
},
)
}
'[' => {
let next_ind = self.parse_char_set(str_ind, end_ind)
let fmt_rest = self.parse(next_ind, end_ind)
fmt_rest.prepend(
if get_ign() {
ignore(get_pad_opt('_'))
ignored("")
} else {
ignore(get_pad_opt('['))
Invalid("Printf: bad conversion %[", "%s")
},
)
}
'-' | '+' | '#' | ' ' | '_' =>
raise FormatError(
"invalid format \{self.quoted()}: at character number \{self.pos(pct_ind)}, flag \{caml_char(symb)} is only allowed after the '%', before padding and precision",
)
_ =>
raise self.invalid_format_message(
str_ind - 1,
"invalid conversion \"%\{symb}\"",
)
}
// this check is not disabled in legacy mode, since ignoring it would
// result in a different typing (argument-less formats can be ignored)
if !ign_used && ign && !(symb is ('@' | '%' | '!' | ',')) {
raise self.incompatible_flag(pct_ind, str_ind, symb, "'_'")
}
fmt_result
}
///|
/// Parse formatting information (after '@').
fn FormatParser::parse_after_at(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
if str_ind == end_ind {
return @list.singleton(Lit("@"))
}
let lit = (lit : FmtLit) => {
self.parse(str_ind + 1, end_ind).prepend(Formatting(lit))
}
match self.char_at(str_ind) {
'[' => self.parse_tag(false, str_ind + 1, end_ind)
']' => lit(CloseBox)
'{' => self.parse_tag(true, str_ind + 1, end_ind)
'}' => lit(CloseTag)
',' => lit(BreakHint(0, 0))
' ' => lit(BreakHint(1, 0))
';' => self.parse_good_break(str_ind + 1, end_ind)
'?' => lit(FFlush)
'\n' => lit(ForceNewline)
'.' => lit(FlushNewline)
'<' => self.parse_magic_size(str_ind + 1, end_ind)
'@' => lit(EscapedAt)
'%' if str_ind + 1 < end_ind && self.char_at(str_ind + 1) == '%' =>
self.parse(str_ind + 2, end_ind).prepend(Formatting(EscapedPercent))
'%' => self.parse(str_ind, end_ind).prepend(Lit("@"))
c => lit(ScanIndic(c))
}
}
///|
/// Try to read the optional after "@{" or "@[".
fn FormatParser::parse_tag(
self : FormatParser,
is_open_tag : Bool,
str_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
let make = (sub : @list.List[Item]) => {
if is_open_tag {
OpenTagGen(sub.to_array())
} else {
OpenBoxGen(sub.to_array())
}
}
if str_ind < end_ind && self.char_at(str_ind) == '<' {
// the closing '>' is searched in the whole string
let mut ind = str_ind + 1
while ind < self.str.length() && self.char_at(ind) != '>' {
ind += 1
}
if ind < end_ind {
let fmt_rest = self.parse(ind + 1, end_ind)
let sub_fmt = self.parse(str_ind, ind + 1)
return fmt_rest.prepend(make(sub_fmt))
}
}
self.parse(str_ind, end_ind).prepend(make(@list.empty()))
}
///|
/// Try to read the optional after "@;".
fn FormatParser::parse_good_break(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
let attempt = () => {
if str_ind == end_ind || self.char_at(str_ind) != '<' {
return None
}
let str_ind_1 = self.parse_spaces(str_ind + 1, end_ind)
match self.char_at(str_ind_1) {
'0'..='9' | '-' => {
let (str_ind_2, width) = self.parse_integer(str_ind_1, end_ind)
let str_ind_3 = self.parse_spaces(str_ind_2, end_ind)
match self.char_at(str_ind_3) {
'>' => Some((str_ind_3 + 1, BreakHint(width, 0)))
'0'..='9' | '-' => {
let (str_ind_4, offset) = self.parse_integer(str_ind_3, end_ind)
let str_ind_5 = self.parse_spaces(str_ind_4, end_ind)
if self.char_at(str_ind_5) != '>' {
None
} else {
Some((str_ind_5 + 1, BreakHint(width, offset)))
}
}
_ => None
}
}
_ => None
}
}
let (next_ind, formatting_lit) = match (attempt() catch { _ => None }) {
Some(res) => res
None => (str_ind, BreakHint(1, 0))
}
self.parse(next_ind, end_ind).prepend(Formatting(formatting_lit))
}
///|
/// Parse the size in a .
fn FormatParser::parse_magic_size(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> @list.List[Item] raise FormatError {
let attempt = () => {
let str_ind_1 = self.parse_spaces(str_ind, end_ind)
match self.char_at(str_ind_1) {
'0'..='9' | '-' => {
let (str_ind_2, size) = self.parse_integer(str_ind_1, end_ind)
let str_ind_3 = self.parse_spaces(str_ind_2, end_ind)
if self.char_at(str_ind_3) != '>' {
None
} else {
Some((str_ind_3 + 1, size))
}
}
_ => None
}
}
match (attempt() catch { _ => None }) {
Some((next_ind, size)) =>
self.parse(next_ind, end_ind).prepend(Formatting(MagicSize(size)))
None => self.parse(str_ind, end_ind).prepend(Formatting(ScanIndic('<')))
}
}
///|
/// Parse a char set (of scanf's %[...]); return the index after it.
fn FormatParser::parse_char_set(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> Int raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
let fail_single_percent = (str_ind : Int) => {
FormatError(
"invalid format \{self.quoted()}: '%' alone is not accepted in character sets, use %% instead at position \{self.pos(str_ind)}.",
)
}
// the states of the parser of the content: the first character, a
// character, after a character c, after c-
let str_ind = if self.char_at(str_ind) == '^' { str_ind + 1 } else { str_ind }
// parse the first character
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
let mut c = self.char_at(str_ind)
let mut i = str_ind + 1
// after a character `c`
for ;; {
if i == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(i) {
']' => return i + 1
'-' => {
// after `c-`
let i1 = i + 1
if i1 == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(i1) {
']' => return i1 + 1
'%' => {
if i1 + 1 == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
if !(self.char_at(i1 + 1) is ('%' | '@')) {
raise fail_single_percent(i1)
}
i = i1 + 2
}
_ => i = i1 + 1
}
// the content, until the first ']'
for ;; {
if i == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(i) {
']' => return i + 1
'-' => i += 1
ch => {
c = ch
i += 1
break
}
}
}
}
'%' | '@' as c2 if c == '%' => {
ignore(c2)
i += 1
// the content, until the first ']'
for ;; {
if i == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(i) {
']' => return i + 1
'-' => i += 1
ch => {
c = ch
i += 1
break
}
}
}
}
c2 => {
if c == '%' {
raise fail_single_percent(i)
}
c = c2
i += 1
}
}
}
}
///|
/// Consume all next spaces, raise an error if end_ind is reached.
fn FormatParser::parse_spaces(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> Int raise FormatError {
let mut str_ind = str_ind
for ;; {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
if self.char_at(str_ind) != ' ' {
return str_ind
}
str_ind += 1
}
}
///|
/// Read a positive integer, raise an error if end_ind is reached.
fn FormatParser::parse_positive(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> (Int, Int) raise FormatError {
let mut str_ind = str_ind
let mut acc = 0L
for ;; {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind) {
'0'..='9' as c => {
acc = acc * 10 + (c.to_int() - '0'.to_int()).to_int64()
if acc > max_string_length {
raise FormatError(
"invalid format \{self.quoted()}: integer \{acc} is greater than the limit \{max_string_length}",
)
}
str_ind += 1
}
_ => break
}
}
// such widths can't be printed anyway
(str_ind, if acc > 0x7FFFFFFFL { 0x7FFFFFFF } else { acc.to_int() })
}
///|
/// Read a positive or negative integer, raise an error if end_ind is
/// reached.
fn FormatParser::parse_integer(
self : FormatParser,
str_ind : Int,
end_ind : Int,
) -> (Int, Int) raise FormatError {
if str_ind == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind) {
'0'..='9' => self.parse_positive(str_ind, end_ind)
'-' => {
if str_ind + 1 == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind + 1) {
'0'..='9' => {
let (next_ind, n) = self.parse_positive(str_ind + 1, end_ind)
(next_ind, -n)
}
c => raise self.expected_character(str_ind + 1, "digit", c)
}
}
_ => abort("parse_integer")
}
}
///|
/// Search the end of the current sub-format (the corresponding "%}" or
/// "%)").
fn FormatParser::search_subformat_end(
self : FormatParser,
str_ind : Int,
end_ind : Int,
c : Char,
) -> Int raise FormatError {
let mut str_ind = str_ind
for ;; {
if str_ind == end_ind {
raise FormatError(
"invalid format \{self.quoted()}: unclosed sub-format, expected \"%\{c}\" at character number \{self.pos(end_ind)}",
)
}
if self.char_at(str_ind) != '%' {
str_ind += 1
continue
}
if str_ind + 1 == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
let c1 = self.char_at(str_ind + 1)
if c1 == c {
// end of format found
return str_ind
}
match c1 {
'_' => {
// search for "%_(" or "%_{"
if str_ind + 2 == end_ind {
raise self.unexpected_end_of_format(end_ind)
}
match self.char_at(str_ind + 2) {
'{' =>
str_ind = self.search_subformat_end(str_ind + 3, end_ind, '}') + 2
'(' =>
str_ind = self.search_subformat_end(str_ind + 3, end_ind, ')') + 2
_ => str_ind += 3
}
}
'{' => str_ind = self.search_subformat_end(str_ind + 2, end_ind, '}') + 2
'(' => str_ind = self.search_subformat_end(str_ind + 2, end_ind, ')') + 2
'}' => raise self.expected_character(str_ind + 1, "character ')'", '}')
')' => raise self.expected_character(str_ind + 1, "character '}'", ')')
_ => str_ind += 2
}
}
}
///|
/// Check if symb is a valid int conversion after "%l", "%n" or "%L".
fn is_int_base(c : Char) -> Bool {
c is ('d' | 'i' | 'x' | 'X' | 'o' | 'u')
}
///|
/// Compute the flags of an integer conversion like OCaml's
/// `compute_int_conv` in legacy mode: (plus, space, alt, group).
fn int_conv(
plus : Bool,
hash : Bool,
space : Bool,
symb : Char,
) -> (Bool, Bool, Bool, Bool) {
match (plus, hash, space, symb) {
(false, false, false, _) => (false, false, false, false)
(false, false, true, 'd' | 'i') => (false, true, false, false)
(true, false, false, 'd' | 'i') => (true, false, false, false)
(false, true, false, 'd' | 'i' | 'u') => (false, false, false, true)
(false, true, false, 'x' | 'X' | 'o') => (false, false, true, false)
// legacy: '#' wins for 'x', 'X' and 'o'
(_, true, _, 'x' | 'X' | 'o') => (false, false, true, false)
// legacy: '#' is ignored
(_, true, _, 'd' | 'i' | 'u') => int_conv(plus, false, space, symb)
// legacy: '+' wins over ' '
(true, _, true, _) => int_conv(plus, hash, false, symb)
// legacy: ' ' is ignored
(false, _, true, _) => int_conv(plus, hash, false, symb)
// legacy: '+' is ignored
(true, _, false, _) => int_conv(false, hash, space, symb)
// other conversions don't take flags
(false, true, false, _) => (false, false, false, false)
}
}
///|
fn make_int_conv(
size : IntSize,
symb : Char,
flags : (Bool, Bool, Bool, Bool),
) -> Conv {
let (plus, space, alt, group) = flags
CInt(size, symb, plus, space, alt, group)
}
///|
/// The signature of a format: a canonical representation of its
/// conversions, like OCaml's `string_of_fmtty (fmtty_of_fmt fmt)`.
fn signature(items : @list.List[Item]) -> String {
let buf = StringBuilder()
fn pad_arg(p : Pad) -> Unit {
if p is ArgPad(_) {
buf.write_string("%i")
}
}
fn prec_arg(p : Prec) -> Unit {
if p is ArgPrec {
buf.write_string("%i")
}
}
fn go(items : Iter[Item]) -> Unit {
for item in items {
match item {
Conversion(conv, pad, prec) => {
match conv {
CChar | CCamlChar | CAlpha | CTheta => ()
CString | CCamlString | CBool => pad_arg(pad)
CInt(_, _, _, _, _, _) | CFloat(_, _) => {
pad_arg(pad)
prec_arg(prec)
}
}
buf.write_string(
match conv {
CChar | CCamlChar => "%c"
CString | CCamlString => "%s"
CInt(OInt, _, _, _, _, _) => "%i"
CInt(I32, _, _, _, _, _) => "%li"
CInt(I64, _, _, _, _, _) => "%Li"
CInt(Native, _, _, _, _, _) => "%ni"
CFloat(_, _) => "%f"
CBool => "%B"
CAlpha => "%a"
CTheta => "%t"
},
)
}
OpenBoxGen(sub) | OpenTagGen(sub) => go(sub.iter())
FormatArg(s) => buf.write_string("%{" + s + "%}")
FormatSubst(s) => buf.write_string("%(" + s + "%)")
ScanCounter => buf.write_string("%i")
Invalid(_, s) => buf.write_string(s)
Lit(_) | Flush | Formatting(_) => ()
}
}
}
go(items.iter())
buf.to_string()
}