import ENGLISH , { Language } from './i18n' ; import RRule from '../index' ; import { Options } from '../types' ; import { WeekdayStr } from '../weekday' ; class Parser { private readonly rules : { [ k ] : RegExp ; } ; public text : string ; public symbol : string | null ; public value : RegExpExecArray | null ; private done = true ; constructor ( rules ) { this . rules = rules ; } start ( text ) { this . text = text ; this . done = false ; return this . nextSymbol ( ) ; } isDone ( ) { return this . done && this . symbol === null ; } nextSymbol ( ) { let best ; let bestSymbol ; const p = this ; this . symbol = null ; this . value = null ; do { if ( this . done ) return false ; let rule ; best = null ; for ( let name in this . rules ) { rule = this . rules [ name ] ; const match = rule . exec ( p . text ) ; if ( match ) { if ( best === null || match [ 0 ] . length > best [ 0 ] . length ) { best = match ; bestSymbol = name ; } } } if ( best != null ) { this . text = this . text . substr ( best [ 0 ] . length ) ; if ( this . text === '' ) this . done = true ; } if ( best == null ) { this . done = true ; this . symbol = null ; this . value = null ; return ; } } while ( bestSymbol === 'SKIP' ) ; this . symbol = bestSymbol ; this . value = best ; return true ; } accept ( name ) { if ( this . symbol === name ) { if ( this . value ) { const v = this . value ; this . nextSymbol ( ) ; return v ; } this . nextSymbol ( ) ; return true ; } return false ; } acceptNumber ( ) { return this . accept ( 'number' ) as RegExpExecArray ; } expect ( name ) { if ( this . accept ( name ) ) return true ; throw new Error ( 'expected ' + name + ' but found ' + this . symbol ) ; } } export default function parseText ( text , language = ENGLISH ) { const options = { } ; const ttr = new Parser ( language . tokens ) ; if ( ! ttr . start ( text ) ) return null ; S ( ) ; return options ; function S ( ) { ttr . expect ( 'every' ) ; let n = ttr . acceptNumber ( ) ; if ( n ) options . interval = parseInt ( n [ 0 ] , 10 ) ; if ( ttr . isDone ( ) ) throw new Error ( 'Unexpected end' ) ; switch ( ttr . symbol ) { case 'day(s)' : options . freq = RRule . DAILY ; if ( ttr . nextSymbol ( ) ) { AT ( ) ; F ( ) ; } break ; case 'weekday(s)' : options . freq = RRule . WEEKLY ; options . byweekday = [ RRule . MO , RRule . TU , RRule . WE , RRule . TH , RRule . FR ] ; ttr . nextSymbol ( ) ; F ( ) ; break ; case 'week(s)' : options . freq = RRule . WEEKLY ; if ( ttr . nextSymbol ( ) ) { ON ( ) ; F ( ) ; } break ; case 'hour(s)' : options . freq = RRule . HOURLY ; if ( ttr . nextSymbol ( ) ) { ON ( ) ; F ( ) ; } break ; case 'minute(s)' : options . freq = RRule . MINUTELY ; if ( ttr . nextSymbol ( ) ) { ON ( ) ; F ( ) ; } break ; case 'month(s)' : options . freq = RRule . MONTHLY ; if ( ttr . nextSymbol ( ) ) { ON ( ) ; F ( ) ; } break ; case 'year(s)' : options . freq = RRule . YEARLY ; if ( ttr . nextSymbol ( ) ) { ON ( ) ; F ( ) ; } break ; case 'monday' : case 'tuesday' : case 'wednesday' : case 'thursday' : case 'friday' : case 'saturday' : case 'sunday' : options . freq = RRule . WEEKLY ; const key = ttr . symbol . substr ( 0 , 2 ) . toUpperCase ( ) as WeekdayStr ; options . byweekday = [ RRule [ key ] ] ; if ( ! ttr . nextSymbol ( ) ) return ; while ( ttr . accept ( 'comma' ) ) { if ( ttr . isDone ( ) ) throw new Error ( 'Unexpected end' ) ; let wkd = decodeWKD ( ) as keyof typeof RRule ; if ( ! wkd ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + ', expected weekday' ) ; } options . byweekday . push ( RRule [ wkd ] ) ; ttr . nextSymbol ( ) ; } MDAYs ( ) ; F ( ) ; break ; case 'january' : case 'february' : case 'march' : case 'april' : case 'may' : case 'june' : case 'july' : case 'august' : case 'september' : case 'october' : case 'november' : case 'december' : options . freq = RRule . YEARLY ; options . bymonth = [ decodeM ( ) as number ] ; if ( ! ttr . nextSymbol ( ) ) return ; while ( ttr . accept ( 'comma' ) ) { if ( ttr . isDone ( ) ) throw new Error ( 'Unexpected end' ) ; let m = decodeM ( ) ; if ( ! m ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + ', expected month' ) ; } options . bymonth . push ( m ) ; ttr . nextSymbol ( ) ; } ON ( ) ; F ( ) ; break ; default : throw new Error ( 'Unknown symbol' ) ; } } function ON ( ) { const on = ttr . accept ( 'on' ) ; const the = ttr . accept ( 'the' ) ; if ( ! ( on || the ) ) return ; do { let nth = decodeNTH ( ) ; let wkd = decodeWKD ( ) ; let m = decodeM ( ) ; if ( nth ) { if ( wkd ) { ttr . nextSymbol ( ) ; if ( ! options . byweekday ) options . byweekday = [ ] ; options . byweekday . push ( RRule [ wkd ] . nth ( nth ) ) ; } else { if ( ! options . bymonthday ) options . bymonthday = [ ] ; options . bymonthday . push ( nth ) ; ttr . accept ( 'day(s)' ) ; } } else if ( wkd ) { ttr . nextSymbol ( ) ; if ( ! options . byweekday ) options . byweekday = [ ] ; options . byweekday . push ( RRule [ wkd ] ) ; } else if ( ttr . symbol === 'weekday(s)' ) { ttr . nextSymbol ( ) ; if ( ! options . byweekday ) { options . byweekday = [ RRule . MO , RRule . TU , RRule . WE , RRule . TH , RRule . FR ] ; } } else if ( ttr . symbol === 'week(s)' ) { ttr . nextSymbol ( ) ; let n = ttr . acceptNumber ( ) ; if ( ! n ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + ', expected week number' ) ; } options . byweekno = [ parseInt ( n [ 0 ] , 10 ) ] ; while ( ttr . accept ( 'comma' ) ) { n = ttr . acceptNumber ( ) ; if ( ! n ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + '; expected monthday' ) ; } options . byweekno . push ( parseInt ( n [ 0 ] , 10 ) ) ; } } else if ( m ) { ttr . nextSymbol ( ) ; if ( ! options . bymonth ) options . bymonth = [ ] ; options . bymonth . push ( m ) ; } else { return ; } } while ( ttr . accept ( 'comma' ) || ttr . accept ( 'the' ) || ttr . accept ( 'on' ) ) ; } function AT ( ) { const at = ttr . accept ( 'at' ) ; if ( ! at ) return ; do { let n = ttr . acceptNumber ( ) ; if ( ! n ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + ', expected hour' ) ; } options . byhour = [ parseInt ( n [ 0 ] , 10 ) ] ; while ( ttr . accept ( 'comma' ) ) { n = ttr . acceptNumber ( ) ; if ( ! n ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + '; expected hour' ) ; } options . byhour . push ( parseInt ( n [ 0 ] , 10 ) ) ; } } while ( ttr . accept ( 'comma' ) || ttr . accept ( 'at' ) ) ; } function decodeM ( ) { switch ( ttr . symbol ) { case 'january' : return 1 ; case 'february' : return 2 ; case 'march' : return 3 ; case 'april' : return 4 ; case 'may' : return 5 ; case 'june' : return 6 ; case 'july' : return 7 ; case 'august' : return 8 ; case 'september' : return 9 ; case 'october' : return 10 ; case 'november' : return 11 ; case 'december' : return 12 ; default : return false ; } } function decodeWKD ( ) { switch ( ttr . symbol ) { case 'monday' : case 'tuesday' : case 'wednesday' : case 'thursday' : case 'friday' : case 'saturday' : case 'sunday' : return ttr . symbol . substr ( 0 , 2 ) . toUpperCase ( ) ; default : return false ; } } function decodeNTH ( ) { switch ( ttr . symbol ) { case 'last' : ttr . nextSymbol ( ) ; return - 1 ; case 'first' : ttr . nextSymbol ( ) ; return 1 ; case 'second' : ttr . nextSymbol ( ) ; return ttr . accept ( 'last' ) ? - 2 : 2 ; case 'third' : ttr . nextSymbol ( ) ; return ttr . accept ( 'last' ) ? - 3 : 3 ; case 'nth' : const v = parseInt ( ttr . value ! [ 1 ] , 10 ) ; if ( v < - 366 || v > 366 ) throw new Error ( 'Nth out of range: ' + v ) ; ttr . nextSymbol ( ) ; return ttr . accept ( 'last' ) ? - v : v ; default : return false ; } } function MDAYs ( ) { ttr . accept ( 'on' ) ; ttr . accept ( 'the' ) ; let nth = decodeNTH ( ) ; if ( ! nth ) return ; options . bymonthday = [ nth ] ; ttr . nextSymbol ( ) ; while ( ttr . accept ( 'comma' ) ) { nth = decodeNTH ( ) ; if ( ! nth ) { throw new Error ( 'Unexpected symbol ' + ttr . symbol + '; expected monthday' ) ; } options . bymonthday . push ( nth ) ; ttr . nextSymbol ( ) ; } } function F ( ) { if ( ttr . symbol === 'until' ) { const date = Date . parse ( ttr . text ) ; if ( ! date ) throw new Error ( 'Cannot parse until date:' + ttr . text ) ; options . until = new Date ( date ) ; } else if ( ttr . accept ( 'for' ) ) { options . count = parseInt ( ttr . value ! [ 0 ] , 10 ) ; ttr . expect ( 'number' ) ; } } }