using Sprache; using Core.Entities; using Core.Models; namespace Core.Parsers; /// /// Grammar definitions for parsing event occurrence DSL using parser combinators. /// Provides composable parsers for each grammar rule. /// public static class EventOccurrenceGrammar { // Months - all 12 months supported private static readonly Parser January = Parse.String("January").Text().Token(); private static readonly Parser February = Parse.String("February").Text().Token(); private static readonly Parser March = Parse.String("March").Text().Token(); private static readonly Parser April = Parse.String("April").Text().Token(); private static readonly Parser May = Parse.String("May").Text().Token(); private static readonly Parser June = Parse.String("June").Text().Token(); private static readonly Parser July = Parse.String("July").Text().Token(); private static readonly Parser August = Parse.String("August").Text().Token(); private static readonly Parser September = Parse.String("September").Text().Token(); private static readonly Parser October = Parse.String("October").Text().Token(); private static readonly Parser November = Parse.String("November").Text().Token(); private static readonly Parser December = Parse.String("December").Text().Token(); /// /// Parser for month names (January through December). /// public static readonly Parser Month = January .Or(February) .Or(March) .Or(April) .Or(May) .Or(June) .Or(July) .Or(August) .Or(September) .Or(October) .Or(November) .Or(December); /// /// Parser for day of month (1-31, optional semicolon). /// public static readonly Parser DayOfMonth = from day in Parse.Number from semicolon in Parse.Char(';').Optional() select int.Parse(day); // Time parsing components private static readonly Parser Noon = Parse.String("NOON").Text().Token(); private static readonly Parser Tbd = Parse.String("TBD").Text().Token(); private static readonly Parser AmPm = Parse.String("a.m.").Or(Parse.String("am")).Or(Parse.String("A.M.")).Or(Parse.String("AM")) .Or(Parse.String("p.m.")).Or(Parse.String("pm")).Or(Parse.String("P.M.")).Or(Parse.String("PM")) .Text().Token(); private static readonly Parser TimeValue = from hour in Parse.Number from colon in Parse.Char(':').Optional() from minute in Parse.Number.Optional() from ws in Parse.WhiteSpace.Many() from ampm in AmPm select $"{hour}:{(minute.IsDefined ? minute.Get() : "00")} {ampm}"; /// /// Parser for hyphen characters (en-dash, hyphen, em-dash). /// public static readonly Parser Hyphen = Parse.Char('–').Or(Parse.Char('-')).Or(Parse.Char('—')); /// /// Parser for time values, including ranges and special values (NOON, TBD). /// public static readonly Parser Time = Noon.Or(Tbd) .Or( from start in TimeValue.Or(Noon) from dash in Hyphen.Then(_ => Parse.WhiteSpace.Many()).Optional() from end in TimeValue.Or(Noon).Optional() select end.IsDefined ? $"{start} – {end.Get()}" : start ); /// /// Parser for section headers: EventName [–-—] (MS|HS). /// public static readonly Parser<(string EventName, string SchoolLevel)> SectionHeader = from eventName in Parse.AnyChar.Except(Hyphen).Many().Text().Token() from hyphen in Hyphen.Token() from schoolLevel in Parse.String("MS").Or(Parse.String("HS")).Text().Token() select (eventName.Trim(), schoolLevel); /// /// Parser for General Schedule/Session headers. /// public static readonly Parser GeneralSchedule = Parse.String("General Schedule").Or(Parse.String("General Session")).Text().Token(); /// /// Parser for comment lines (starting with #). /// public static readonly Parser CommentLine = from hash in Parse.Char('#') from rest in Parse.AnyChar.Many().Text() select rest; /// /// Attempts to parse a section header from the given line. /// Returns null if not a section header. /// public static (string EventName, string SchoolLevel)? TryParseSectionHeader(string line) { try { var result = SectionHeader.Parse(line); return result; } catch { return null; } } /// /// Attempts to parse a General Schedule/Session header from the given line. /// Returns null if not a General Schedule header. /// public static bool IsGeneralSchedule(string line) { try { GeneralSchedule.Parse(line); return true; } catch { return false; } } /// /// Attempts to parse a comment line from the given line. /// Returns true if the line is a comment. /// public static bool IsCommentLine(string line) { return line.TrimStart().StartsWith("#", StringComparison.Ordinal); } }