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);
}
}