///|
/// A whitespace-delimited token with source position. METAR/TAF syntax is mostly
/// token based, so the first lexer keeps raw groups while preserving spans for
/// diagnostics.
pub(all) struct Token {
lexeme : String
span : Span
} derive(Eq, Debug)
///|
pub fn Token::to_string(self : Token) -> String {
self.lexeme
}
///|
pub fn tokenize(source : String) -> Array[Token] {
let tokens : Array[Token] = []
source
.split(" ")
.filter(part => !part.is_empty())
.each(part => {
let start = part.start_offset()
let text = part.to_owned()
tokens.push({
lexeme: text,
span: Span::new(start, start + text.length(), line=1, column=start + 1),
})
})
tokens
}
///|
fn is_ascii_upper(ch : Char) -> Bool {
ch >= 'A' && ch <= 'Z'
}
///|
fn is_ascii_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
fn is_station_char(ch : Char) -> Bool {
is_ascii_upper(ch) || is_ascii_digit(ch)
}
///|
fn is_station_code(text : String) -> Bool {
text.length() == 4 && text.all(is_station_char)
}
///|
fn is_digits(text : StringView) -> Bool {
!text.is_empty() && text.all(is_ascii_digit)
}
///|
fn parse_unsigned(text : StringView) -> Int? {
if text.is_empty() {
return None
}
let mut value = 0
let mut valid = true
text
.iter()
.each(ch => {
if is_ascii_digit(ch) {
value = value * 10 + (ch.to_int() - '0'.to_int())
} else {
valid = false
}
})
if valid {
Some(value)
} else {
None
}
}
///|
fn parse_signed_metar_int(text : StringView) -> Int? {
if text.is_empty() {
return None
}
match text.strip_prefix("M") {
Some(rest) =>
match parse_unsigned(rest) {
Some(value) => Some(-value)
None => None
}
None => parse_unsigned(text)
}
}