///|
pub(all) suberror ParseError {
InvalidLine(line~ : Int, text~ : String)
} derive(Debug, Eq)
///|
fn token(parts : Array[StringView], index : Int) -> String? {
match parts.get(index) {
Some(part) => Some(part.to_owned())
None => None
}
}
///|
fn parse_bool_token(text : String) -> Bool? {
match text {
"true" => Some(true)
"false" => Some(false)
_ => None
}
}
///|
fn[T] raise_invalid(line : Int, text : String) -> T raise ParseError {
raise InvalidLine(line~, text~)
}
///|
fn parse_non_negative_int(text : String) -> Int? {
if text == "" {
None
} else {
let mut value = 0
let mut ok = true
for char in text {
if char >= '0' && char <= '9' {
value = value * 10 + char.to_int() - '0'.to_int()
} else {
ok = false
}
}
if ok {
Some(value)
} else {
None
}
}
}
///|
fn parse_int_token(text : String) -> Int? {
if text == "" {
None
} else {
let negative = text.has_prefix("-")
let digits = if negative { text[1:] } else { text[:] }
match parse_non_negative_int(digits.to_owned()) {
Some(value) => Some(if negative { -value } else { value })
None => None
}
}
}
///|
fn parse_double_token(text : String) -> Double? {
try {
match @json.parse(text) {
Number(value, ..) => Some(value)
_ => None
}
} catch {
_ => None
}
}
///|
fn parse_flag_value(kind : String, text : String) -> FlagValue? {
match kind {
"bool" => parse_bool_token(text).map(value => BoolValue(value))
"string" => if text == "" { None } else { Some(StringValue(text)) }
"int" => parse_int_token(text).map(value => IntValue(value))
"double" => parse_double_token(text).map(value => DoubleValue(value))
_ => None
}
}
///|
fn parse_modifier(
provider : Provider,
key : String,
value : FlagValue,
parts : Array[StringView],
line_no : Int,
text : String,
) -> Provider raise ParseError {
let disabled = token(parts, parts.length() - 1) is Some("disabled")
let length = if disabled { parts.length() - 1 } else { parts.length() }
match length {
4 => provider.with_value(key, value, enabled=!disabled)
6 =>
if token(parts, 4) is Some("rollout") {
match token(parts, 5).bind(parse_non_negative_int) {
Some(pct) =>
provider.with_value_rollout(
key,
value,
percentage=pct,
enabled=!disabled,
)
None => raise_invalid(line_no, text)
}
} else {
raise_invalid(line_no, text)
}
7 =>
if token(parts, 4) is Some("target") {
let attr = token(parts, 5).unwrap()
let expected = token(parts, 6).unwrap()
if attr == "" || expected == "" {
raise_invalid(line_no, text)
} else {
provider.with_value_target(
key,
value,
attr~,
equals=StringValue(expected),
enabled=!disabled,
)
}
} else {
raise_invalid(line_no, text)
}
_ => raise_invalid(line_no, text)
}
}
///|
fn parse_line(
provider : Provider,
line_no : Int,
text : String,
) -> Provider raise ParseError {
let parts = text.split(" ").to_array()
guard token(parts, 0) is Some("flag") else { raise_invalid(line_no, text) }
let key = match token(parts, 1) {
Some(value) if value != "" => value
None => raise_invalid(line_no, text)
_ => raise_invalid(line_no, text)
}
let kind = match token(parts, 2) {
Some(value) => value
None => raise_invalid(line_no, text)
}
let raw_value = match token(parts, 3) {
Some(value) => value
None => raise_invalid(line_no, text)
}
let value = match parse_flag_value(kind, raw_value) {
Some(value) => value
None => raise_invalid(line_no, text)
}
parse_modifier(provider, key, value, parts, line_no, text)
}
///|
pub fn parse_provider(text : String) -> Provider raise ParseError {
let mut provider = empty_provider()
let mut line_no = 0
for raw in text.split("\n") {
line_no += 1
let line = raw.trim().to_owned()
if line == "" || line.has_prefix("#") {
()
} else {
provider = parse_line(provider, line_no, line)
}
}
provider
}