///|
priv struct Parser {
source : String
mut pos : Int
}
///|
fn Parser::peek(self : Parser) -> Int {
self.peek_at(self.pos)
}
///|
fn Parser::peek_at(self : Parser, offset : Int) -> Int {
if offset >= self.source.length() {
-1
} else {
self.source[offset].to_int()
}
}
///|
fn is_digit(c : Int) -> Bool {
c >= 48 && c <= 57
}
///|
fn parse_int_digits(digits : String) -> Int {
let mut value = 0
for i = 0; i < digits.length(); i = i + 1 {
value = value * 10 + (digits[i].to_int() - 48)
}
value
}
///|
fn Parser::read_two_digits(
self : Parser,
code : String,
) -> String raise EdtfError {
let start = self.pos
if self.pos + 2 > self.source.length() {
raise Syntax(code, self.pos)
}
if !is_digit(self.peek()) || !is_digit(self.peek_at(self.pos + 1)) {
raise Syntax(code, self.pos)
}
self.pos += 2
self.source[start:self.pos].to_owned()
}
///|
fn Parser::parse_qualifier(self : Parser) -> Qualifier {
match self.peek() {
63 => {
self.pos += 1
{ uncertain: true, approximate: false, }
}
126 => {
self.pos += 1
{ uncertain: false, approximate: true, }
}
37 => {
self.pos += 1
{ uncertain: true, approximate: true, }
}
_ => Qualifier::none()
}
}
///|
fn Parser::parse_year(self : Parser) -> (YearSpec, Qualifier) raise EdtfError {
let start = self.pos
let mut negative = false
let extended = self.peek() == 89
if extended {
self.pos += 1
}
if self.peek() == 45 {
negative = true
self.pos += 1
}
let digits_start = self.pos
while is_digit(self.peek()) {
self.pos += 1
}
let digits_end = self.pos
let x_start = self.pos
while self.peek() == 88 {
self.pos += 1
}
let x_count = self.pos - x_start
if digits_end - digits_start == 0 {
raise Syntax("EXPECTED_YEAR", start)
}
if x_count > 0 && is_digit(self.peek()) {
raise Syntax("INVALID_YEAR", self.pos)
}
let digits = self.source[digits_start:digits_end].to_owned()
let qualifier = self.parse_qualifier()
if x_count > 0 {
if negative || extended {
raise Syntax("INVALID_YEAR", start)
}
if digits.length() + x_count != 4 || x_count > 2 {
raise Syntax("INVALID_YEAR", start)
}
return (Masked(digits, x_count), qualifier)
}
if digits.length() < 4 {
raise Syntax("INVALID_YEAR", start)
}
if digits.length() > 9 {
raise Syntax("YEAR_LIMIT", start)
}
if (extended && digits.length() <= 4) || (!extended && digits.length() != 4) {
raise Syntax("INVALID_YEAR", start)
}
let value = parse_int_digits(digits)
if extended && value < 10000 {
raise Syntax("INVALID_YEAR", start)
}
if negative {
if value == 0 {
raise Syntax("INVALID_YEAR", start)
}
return (Exact(-value), qualifier)
}
(Exact(value), qualifier)
}
///|
fn Parser::parse_month(self : Parser) -> (MonthSpec, Qualifier) raise EdtfError {
let start = self.pos
self.pos += 1
let digits = self.read_two_digits("EXPECTED_MONTH")
let value = parse_int_digits(digits)
let qualifier = self.parse_qualifier()
if value >= 1 && value <= 12 {
return (Exact(value), qualifier)
}
if value >= 21 && value <= 24 {
return (Season(value), qualifier)
}
raise Syntax("INVALID_MONTH", start)
}
///|
fn Parser::parse_day(self : Parser) -> (DaySpec, Qualifier) raise EdtfError {
let start = self.pos
self.pos += 1
let digits = self.read_two_digits("EXPECTED_DAY")
let value = parse_int_digits(digits)
let qualifier = self.parse_qualifier()
if value < 1 || value > 31 {
raise Syntax("INVALID_DAY", start)
}
(Exact(value), qualifier)
}
///|
fn common_month_days(month : Int) -> Int {
if month == 2 {
28
} else if month == 4 || month == 6 || month == 9 || month == 11 {
30
} else {
31
}
}
///|
fn is_leap_year(year : Int) -> Bool {
let y = if year < 0 { -year } else { year }
y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)
}
///|
fn month_days(year : Int, month : Int) -> Int {
if month == 2 && is_leap_year(year) {
29
} else {
common_month_days(month)
}
}
///|
fn validate_day(
year : YearSpec,
month : MonthSpec,
day : DaySpec,
day_start : Int,
) -> Unit raise EdtfError {
match (month, day) {
(Unspecified, Exact(_)) => raise Syntax("INVALID_DATE", day_start)
(Season(_), Exact(_)) => raise Syntax("INVALID_DATE", day_start)
(Exact(m), Exact(d)) => {
let limit = match year {
Exact(y) => month_days(y, m)
_ => if m == 2 { 29 } else { common_month_days(m) }
}
if d > limit {
raise Syntax("INVALID_DAY", day_start)
}
}
_ => ()
}
}
///|
fn Parser::parse_date(self : Parser) -> EdtfDate raise EdtfError {
let (year, year_qualifier) = self.parse_year()
let mut month : MonthSpec = Unspecified
let mut month_qualifier = Qualifier::none()
let mut day : DaySpec = Unspecified
let mut day_qualifier = Qualifier::none()
if self.peek() == 45 {
let (m, mq) = self.parse_month()
month = m
month_qualifier = mq
if self.peek() == 45 {
match month {
Season(_) => raise Syntax("INVALID_DATE", self.pos)
_ => ()
}
let day_start = self.pos
let (d, dq) = self.parse_day()
day = d
day_qualifier = dq
validate_day(year, month, day, day_start)
}
}
// A suffix qualifies its component and every component to its left.
month_qualifier = {
uncertain: month_qualifier.uncertain || day_qualifier.uncertain,
approximate: month_qualifier.approximate || day_qualifier.approximate,
}
let year_qualifier = {
uncertain: year_qualifier.uncertain || month_qualifier.uncertain,
approximate: year_qualifier.approximate || month_qualifier.approximate,
}
{ year, year_qualifier, month, month_qualifier, day, day_qualifier, }
}
///|
fn Parser::parse_endpoint(self : Parser) -> Endpoint raise EdtfError {
if self.peek() == 46 && self.peek_at(self.pos + 1) == 46 {
self.pos += 2
return Open
}
Known(self.parse_date())
}
///|
fn Parser::parse_interval_end(self : Parser) -> Endpoint raise EdtfError {
if self.peek() == -1 {
return Unknown
}
self.parse_endpoint()
}
///|
fn Parser::parse_set(self : Parser) -> EdtfValue raise EdtfError {
let start = self.pos
self.pos += 1
if self.peek() == 93 {
raise Syntax("INVALID_SET", start)
}
let items : Array[EdtfValue] = []
while true {
if self.peek() == 91 {
raise Syntax("INVALID_SET", self.pos)
}
if self.peek() == -1 {
raise Syntax("INVALID_SET", self.pos)
}
let first = self.parse_date()
let item = if self.peek() == 46 && self.peek_at(self.pos + 1) == 46 {
self.pos += 2
Range(first, self.parse_date())
} else {
Date(first)
}
items.push(item)
match self.peek() {
44 => {
self.pos += 1
if self.peek() == 93 {
raise Syntax("INVALID_SET", self.pos)
}
}
93 => {
self.pos += 1
return Set(items)
}
_ => raise Syntax("INVALID_SET", self.pos)
}
}
raise Syntax("INVALID_SET", start)
}
///|
fn Parser::parse_value(self : Parser) -> EdtfValue raise EdtfError {
if self.peek() == 91 {
return self.parse_set()
}
let first = if self.peek() == 47 { Unknown } else { self.parse_endpoint() }
if self.peek() == 47 {
self.pos += 1
let second = self.parse_interval_end()
return Interval({ start: first, end: second, })
}
match first {
Known(d) => Date(d)
Open | Unknown => raise Syntax("EXPECTED_INTERVAL", self.pos)
}
}
///|
/// Parse a strict EDTF subset. Raises `EdtfError::Syntax(code, offset)` on
/// invalid input and never guesses missing components or uncertain markers.
pub fn parse(input : String) -> EdtfValue raise EdtfError {
if input.length() == 0 {
raise Syntax("EMPTY_INPUT", 0)
}
let p = Parser::{ source: input, pos: 0, }
let value = p.parse_value()
if p.pos != input.length() {
raise Syntax("EXTRA_CONTENT", p.pos)
}
value
}