///|
fn is_sep(code : Int) -> Bool {
code == 47 || code == 92
}
///|
pub fn split_path(path : String) -> Result[Array[String], FatError] {
if path.length() == 0 {
return Err(InvalidPath(path))
}
let parts : Array[String] = []
let mut current = ""
for i = 0; i < path.length(); i = i + 1 {
let code = path[i].to_int()
if code == 0 {
return Err(InvalidPath(path))
}
if is_sep(code) {
if current.length() > 0 {
parts.push(current)
current = ""
}
} else {
current = current + path[i:i + 1].to_owned()
}
}
if current.length() > 0 {
parts.push(current)
}
for i = 0; i < parts.length(); i = i + 1 {
if parts[i] == "." || parts[i] == ".." {
continue
}
if parts[i].length() > 255 {
return Err(InvalidName(parts[i]))
}
}
Ok(parts)
}
///|
fn ascii_upper_code(code : Int) -> Int {
if code >= 97 && code <= 122 {
code - 32
} else {
code
}
}
///|
fn names_equal(left : String, right : String) -> Bool {
if left.length() != right.length() {
return false
}
for i = 0; i < left.length(); i = i + 1 {
if ascii_upper_code(left[i].to_int()) != ascii_upper_code(right[i].to_int()) {
return false
}
}
true
}
///|
fn is_short_char(code : Int) -> Bool {
if code >= 97 && code <= 122 {
return true
}
if code >= 65 && code <= 90 {
return true
}
if code >= 48 && code <= 57 {
return true
}
code == 36 ||
code == 37 ||
code == 39 ||
code == 45 ||
code == 95 ||
code == 64 ||
code == 126 ||
code == 96 ||
code == 33 ||
code == 40 ||
code == 41 ||
code == 123 ||
code == 125 ||
code == 94 ||
code == 35 ||
code == 38
}
///|
fn split_stem_ext(name : String) -> (String, String) {
let mut dot = -1
for i = 0; i < name.length(); i = i + 1 {
if name[i].to_int() == 46 {
dot = i
}
}
if dot <= 0 || dot == name.length() - 1 {
(name, "")
} else {
(name[0:dot].to_owned(), name[dot + 1:name.length()].to_owned())
}
}
///|
fn sanitize_short_part(text : String, max : Int) -> (String, Bool) {
let mut out = ""
let mut lossy = false
for i = 0; i < text.length(); i = i + 1 {
let mut code = text[i].to_int()
if code == 32 || code == 46 {
lossy = true
continue
}
if code >= 97 && code <= 122 {
code = code - 32
}
if !is_short_char(code) {
code = 95
lossy = true
}
if out.length() < max {
out = out + code.unsafe_to_char().to_string()
} else {
lossy = true
}
}
if out.length() == 0 {
("X", true)
} else {
(out, lossy)
}
}
///|
fn needs_lfn(name : String) -> Bool {
if name == "." || name == ".." {
return false
}
let (stem, ext) = split_stem_ext(name)
if stem.length() == 0 || stem.length() > 8 || ext.length() > 3 {
return true
}
for i = 0; i < name.length(); i = i + 1 {
let code = name[i].to_int()
if code >= 97 && code <= 122 {
return true
}
if code == 46 {
continue
}
if !is_short_char(code) {
return true
}
}
let mut dots = 0
for i = 0; i < name.length(); i = i + 1 {
if name[i].to_int() == 46 {
dots = dots + 1
}
}
dots > 1
}
///|
fn pack_short_name(stem : String, ext : String) -> Bytes {
let out = Array::make(11, b' ')
let mut i = 0
while i < stem.length() && i < 8 {
out[i] = stem[i].to_int().to_byte()
i = i + 1
}
let mut j = 0
while j < ext.length() && j < 3 {
out[8 + j] = ext[j].to_int().to_byte()
j = j + 1
}
Bytes::from_array(out)
}
///|
fn display_short_name(raw : Bytes) -> String {
let name_bytes = Bytes::makei(8, i => raw[i])
let ext_bytes = Bytes::makei(3, i => raw[8 + i])
let mut stem = ascii_from_padded(name_bytes)
let ext = ascii_from_padded(ext_bytes)
if raw.length() > 0 && raw[0] == b'\x05' {
let rest = if stem.length() > 1 {
stem[1:stem.length()].to_owned()
} else {
""
}
stem = "\u{00e5}" + rest
}
if ext.length() == 0 {
stem
} else {
stem + "." + ext
}
}
///|
fn lfn_checksum(short_name : Bytes) -> Int {
let mut sum = 0
for i = 0; i < 11; i = i + 1 {
let rotated = ((sum & 1) << 7) | ((sum & 0xFF) >> 1)
sum = (rotated + short_name[i].to_int()) & 0xFF
}
sum
}
///|
fn lfn_entry_count(name : String) -> Int {
let chars = name.length()
(chars + 12) / 13
}
///|
fn utf16_units(name : String) -> Array[Int] {
let units : Array[Int] = []
for i = 0; i < name.length(); i = i + 1 {
let code = name[i].to_int()
if code <= 0xFFFF {
units.push(code)
} else {
let v = code - 0x10000
units.push(0xD800 + ((v >> 10) & 0x3FF))
units.push(0xDC00 + (v & 0x3FF))
}
}
units
}
///|
fn string_from_utf16(units : Array[Int]) -> String {
let mut text = ""
let mut i = 0
while i < units.length() {
let unit = units[i]
if unit == 0 {
break
}
if unit >= 0xD800 && unit <= 0xDBFF && i + 1 < units.length() {
let low = units[i + 1]
if low >= 0xDC00 && low <= 0xDFFF {
let code = 0x10000 + (((unit - 0xD800) << 10) | (low - 0xDC00))
text = text + code.unsafe_to_char().to_string()
i = i + 2
continue
}
}
if unit != 0xFFFF {
text = text + unit.unsafe_to_char().to_string()
}
i = i + 1
}
text
}
///|
fn short_name_taken(existing : Array[Bytes], candidate : Bytes) -> Bool {
for i = 0; i < existing.length(); i = i + 1 {
let mut same = true
for j = 0; j < 11; j = j + 1 {
if existing[i][j] != candidate[j] {
same = false
break
}
}
if same {
return true
}
}
false
}
///|
fn make_numeric_tail(stem : String, n : Int) -> String {
let tail = "~" + n.to_string()
let keep = 8 - tail.length()
let mut head = stem
if head.length() > keep {
head = head[0:keep].to_owned()
}
if head.length() == 0 {
head = "X"
}
head + tail
}
///|
fn allocate_short_name(
name : String,
existing : Array[Bytes],
) -> Result[Bytes, FatError] {
if name == "." {
return Ok(pack_short_name(".", ""))
}
if name == ".." {
return Ok(pack_short_name("..", ""))
}
let (raw_stem, raw_ext) = split_stem_ext(name)
let (stem, stem_lossy) = sanitize_short_part(raw_stem, 8)
let (ext, ext_lossy) = if raw_ext.length() == 0 {
("", false)
} else {
sanitize_short_part(raw_ext, 3)
}
let lossy = stem_lossy || ext_lossy || needs_lfn(name)
if !lossy {
let candidate = pack_short_name(stem, ext)
if !short_name_taken(existing, candidate) {
return Ok(candidate)
}
}
let mut n = 1
while n < 1000000 {
let numbered = make_numeric_tail(stem, n)
let candidate = pack_short_name(numbered, ext)
if !short_name_taken(existing, candidate) {
return Ok(candidate)
}
n = n + 1
}
Err(InvalidName(name))
}
///|
fn join_path(parent : String, name : String) -> String {
if parent == "/" || parent.length() == 0 {
"/" + name
} else if parent[parent.length() - 1].to_int() == 47 {
parent + name
} else {
parent + "/" + name
}
}