///|
fn clean(input : StringView) -> String {
input.trim().to_owned()
}
///|
fn split_char(input : String, sep : Char) -> Array[String] {
let parts : Array[String] = []
let buf = StringBuilder(size_hint=input.length())
for c in input.iter() {
if c == sep {
parts.push(buf.to_string())
buf.reset()
} else {
buf.write_char(c)
}
}
parts.push(buf.to_string())
parts
}
///|
fn split_quoted(input : String, sep : Char) -> Array[String] {
let parts : Array[String] = []
let buf = StringBuilder(size_hint=input.length())
let mut quoted = false
let mut escaped = false
for c in input.iter() {
if escaped {
buf.write_char(c)
escaped = false
} else if quoted && c == '\\' {
buf.write_char(c)
escaped = true
} else if c == '"' {
quoted = !quoted
buf.write_char(c)
} else if !quoted && c == sep {
parts.push(buf.to_string())
buf.reset()
} else {
buf.write_char(c)
}
}
parts.push(buf.to_string())
parts
}
///|
fn digit_value(c : Char) -> Int? {
let code = c.to_int()
if code >= 48 && code <= 57 {
Some(code - 48)
} else {
None
}
}
///|
fn normalize_newlines(input : String) -> String {
input.replace_all(old="\r\n", new="\n").replace_all(old="\r", new="\n")
}
///|
fn parse_int_text(raw : String) -> Int? {
let text = clean(raw[:])
if text == "" {
return None
}
let mut sign = 1
let mut index = 0
match text.get_char(0) {
Some('-') => {
sign = -1
index = 1
}
Some('+') => index = 1
_ => ()
}
if index >= text.length() {
return None
}
let mut value = 0
let mut digits = 0
for i in index..
match digit_value(c) {
Some(d) => {
value = value * 10 + d
digits += 1
}
None => return None
}
None => return None
}
}
if digits == 0 {
None
} else {
Some(sign * value)
}
}
///|
fn parse_unsigned_int(
raw : String,
line : Int,
field : String,
) -> Result[Int, ParseError] {
match parse_int_text(raw) {
Some(value) =>
if value >= 0 {
Ok(value)
} else {
Err(InvalidNumber(line, field, raw))
}
None => Err(InvalidNumber(line, field, raw))
}
}
///|
fn parse_millis(raw : String, line : Int, field : String) -> Result[Int, ParseError] {
match parse_millis_text(raw) {
Some(value) =>
if value >= 0 {
Ok(value)
} else {
Err(InvalidNumber(line, field, raw))
}
None => Err(InvalidNumber(line, field, raw))
}
}
///|
fn parse_signed_millis(
raw : String,
line : Int,
field : String,
) -> Result[Int, ParseError] {
match parse_millis_text(raw) {
Some(value) => Ok(value)
None => Err(InvalidNumber(line, field, raw))
}
}
///|
fn parse_millis_text(raw : String) -> Int? {
let text = clean(raw[:])
if text == "" {
return None
}
let mut sign = 1
let mut index = 0
match text.get_char(0) {
Some('-') => {
sign = -1
index = 1
}
Some('+') => index = 1
_ => ()
}
let mut whole = 0
let mut whole_digits = 0
while index < text.length() {
match text.get_char(index) {
Some('.') => break
Some(c) =>
match digit_value(c) {
Some(d) => {
whole = whole * 10 + d
whole_digits += 1
index += 1
}
None => return None
}
None => return None
}
}
if whole_digits == 0 {
return None
}
let mut frac = 0
let mut frac_digits = 0
if index < text.length() {
if text.get_char(index) != Some('.') {
return None
}
index += 1
while index < text.length() {
match text.get_char(index) {
Some(c) =>
match digit_value(c) {
Some(d) => {
if frac_digits < 3 {
frac = frac * 10 + d
frac_digits += 1
}
index += 1
}
None => return None
}
None => return None
}
}
}
while frac_digits < 3 {
frac = frac * 10
frac_digits += 1
}
Some(sign * (whole * 1000 + frac))
}
///|
fn pad3(value : Int) -> String {
let v = if value < 0 { -value } else { value }
let buf = StringBuilder(size_hint=3)
if v < 100 {
buf.write_char('0')
}
if v < 10 {
buf.write_char('0')
}
buf.write_string(v.to_string())
buf.to_string()
}
///|
pub fn millis_to_string(ms : Int) -> String {
let sign = if ms < 0 { "-" } else { "" }
let value = if ms < 0 { -ms } else { ms }
let whole = value / 1000
let frac = value % 1000
if frac == 0 {
sign + whole.to_string() + ".000"
} else {
let mut text = pad3(frac)
while text.length() > 1 && text.get_char(text.length() - 1) == Some('0') {
text = text[0:text.length() - 1].to_owned()
}
sign + whole.to_string() + "." + text
}
}
///|
fn yes_no(value : Bool) -> String {
if value {
"YES"
} else {
"NO"
}
}
///|
fn parse_yes_no(value : String) -> Bool {
value.to_upper() == "YES"
}
///|
fn maybe_attr(attrs : Array[HlsAttribute], name : String) -> String? {
let wanted = name.to_upper()
for attr in attrs {
if attr.name == wanted {
return Some(attr.value)
}
}
None
}
///|
fn maybe_int_attr(
attrs : Array[HlsAttribute],
name : String,
line : Int,
) -> Result[Int?, ParseError] {
match maybe_attr(attrs, name) {
Some(value) =>
match parse_unsigned_int(value, line, name) {
Ok(parsed) => Ok(Some(parsed))
Err(e) => Err(e)
}
None => Ok(None)
}
}
///|
fn required_attr(
attrs : Array[HlsAttribute],
name : String,
line : Int,
) -> Result[String, ParseError] {
match maybe_attr(attrs, name) {
Some(value) =>
if value == "" {
Err(InvalidAttribute(line, "attribute \{name} must not be empty"))
} else {
Ok(value)
}
None => Err(InvalidAttribute(line, "missing required attribute \{name}"))
}
}
///|
fn push_unknown(unknown : Array[String], line : Int, text : String) -> Unit {
unknown.push("\{line}: \{text}")
}