Files
chapter-organizer/Core/Parsers/EventOccurrenceGrammar.cs
T

154 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Sprache;
using Core.Entities;
using Core.Models;
namespace Core.Parsers;
/// <summary>
/// Grammar definitions for parsing event occurrence DSL using parser combinators.
/// Provides composable parsers for each grammar rule.
/// </summary>
public static class EventOccurrenceGrammar
{
// Months - all 12 months supported
private static readonly Parser<string> January = Parse.String("January").Text().Token();
private static readonly Parser<string> February = Parse.String("February").Text().Token();
private static readonly Parser<string> March = Parse.String("March").Text().Token();
private static readonly Parser<string> April = Parse.String("April").Text().Token();
private static readonly Parser<string> May = Parse.String("May").Text().Token();
private static readonly Parser<string> June = Parse.String("June").Text().Token();
private static readonly Parser<string> July = Parse.String("July").Text().Token();
private static readonly Parser<string> August = Parse.String("August").Text().Token();
private static readonly Parser<string> September = Parse.String("September").Text().Token();
private static readonly Parser<string> October = Parse.String("October").Text().Token();
private static readonly Parser<string> November = Parse.String("November").Text().Token();
private static readonly Parser<string> December = Parse.String("December").Text().Token();
/// <summary>
/// Parser for month names (January through December).
/// </summary>
public static readonly Parser<string> Month = January
.Or(February)
.Or(March)
.Or(April)
.Or(May)
.Or(June)
.Or(July)
.Or(August)
.Or(September)
.Or(October)
.Or(November)
.Or(December);
/// <summary>
/// Parser for day of month (1-31, optional semicolon).
/// </summary>
public static readonly Parser<int> DayOfMonth =
from day in Parse.Number
from semicolon in Parse.Char(';').Optional()
select int.Parse(day);
// Time parsing components
private static readonly Parser<string> Noon = Parse.String("NOON").Text().Token();
private static readonly Parser<string> Tbd = Parse.String("TBD").Text().Token();
private static readonly Parser<string> 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<string> 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}";
/// <summary>
/// Parser for hyphen characters (en-dash, hyphen, em-dash).
/// </summary>
public static readonly Parser<char> Hyphen = Parse.Char('').Or(Parse.Char('-')).Or(Parse.Char('—'));
/// <summary>
/// Parser for time values, including ranges and special values (NOON, TBD).
/// </summary>
public static readonly Parser<string> 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
);
/// <summary>
/// Parser for section headers: EventName [-—] (MS|HS).
/// </summary>
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);
/// <summary>
/// Parser for General Schedule/Session headers.
/// </summary>
public static readonly Parser<string> GeneralSchedule =
Parse.String("General Schedule").Or(Parse.String("General Session")).Text().Token();
/// <summary>
/// Parser for comment lines (starting with #).
/// </summary>
public static readonly Parser<string> CommentLine =
from hash in Parse.Char('#')
from rest in Parse.AnyChar.Many().Text()
select rest;
/// <summary>
/// Attempts to parse a section header from the given line.
/// Returns null if not a section header.
/// </summary>
public static (string EventName, string SchoolLevel)? TryParseSectionHeader(string line)
{
try
{
var result = SectionHeader.Parse(line);
return result;
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to parse a General Schedule/Session header from the given line.
/// Returns null if not a General Schedule header.
/// </summary>
public static bool IsGeneralSchedule(string line)
{
try
{
GeneralSchedule.Parse(line);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to parse a comment line from the given line.
/// Returns true if the line is a comment.
/// </summary>
public static bool IsCommentLine(string line)
{
return line.TrimStart().StartsWith("#", StringComparison.Ordinal);
}
}