%start TopLevelOptions
%avoid_insert "INT" "STRING"
%expect-unused Unknown "UNKNOWN"

%%

TopLevelOptions -> Result<Vec<TopLevelOption>, ()>:
    TopLevelOptions TopLevelOption { flattenr($1, $2) }
  | { Ok(vec![]) }
  ;

TopLevelOption -> Result<TopLevelOption, ()>:
    "GITHUB" "{" OptionsOrMatches "}" {
        let (options, matches) = $3?;
        Ok(TopLevelOption::GitHub($1.unwrap_or_else(|x| x).span(), options, matches))
    }
  | "LISTEN" "=" "STRING" ";" { Ok(TopLevelOption::Listen(map_err($3)?)) }
  | "MAXJOBS" "=" "INT" ";" { Ok(TopLevelOption::MaxJobs(map_err($3)?)) }
  | "USER" "=" "STRING" ";" { Ok(TopLevelOption::User(map_err($3)?)) }
  ;

OptionsOrMatches -> Result<(Vec<ProviderOption>, Vec<Match>), ()>:
    OptionsOrMatches ProviderOption {
        let (mut options, matches) = $1?;
        options.push($2?);
        Ok((options, matches))
    }
  | OptionsOrMatches Match {
        let (options, mut matches) = $1?;
        matches.push($2?);
        Ok((options, matches))
    }
  | { Ok((vec![], vec![])) }
  ;

ProviderOption -> Result<ProviderOption, ()>:
    "REPOSDIR" "=" "STRING" ";" { Ok(ProviderOption::ReposDir(map_err($3)?)) }
  ;

Match -> Result<Match, ()>:
    "MATCH" "STRING" "{" PerRepoOptions "}" { Ok(Match{re: map_err($2)?, options: $4? }) }
  ;

PerRepoOptions -> Result<Vec<PerRepoOption>, ()>:
    PerRepoOptions PerRepoOption { flattenr($1, $2) }
  | { Ok(vec![]) }
  ;

PerRepoOption -> Result<PerRepoOption, ()>:
    "CMD" "=" "STRING" ";" { Ok(PerRepoOption::Cmd(map_err($3)?)) }
  | "EMAIL" "=" "STRING" ";" { Ok(PerRepoOption::Email(map_err($3)?)) }
  | "ERRORCMD" "=" "STRING" ";" { Ok(PerRepoOption::ErrorCmd(map_err($3)?)) }
  | "QUEUE" "=" QueueKind ";" {
        let (span, qkind) = $3?;
        Ok(PerRepoOption::Queue(span, qkind))
    }
  | "SECRET" "=" "STRING" ";" { Ok(PerRepoOption::Secret(map_err($3)?)) }
  | "TIMEOUT" "=" "INT" ";" { Ok(PerRepoOption::Timeout(map_err($3)?)) }
  ;

QueueKind -> Result<(Span, QueueKind), ()>:
    "EVICT" { Ok((map_err($1)?, QueueKind::Evict)) }
  | "PARALLEL" { Ok((map_err($1)?, QueueKind::Parallel)) }
  | "SEQUENTIAL" { Ok((map_err($1)?, QueueKind::Sequential)) }
  ;

// This rule helps turn lexing errors into parsing errors.
Unknown -> ():
    "UNKNOWN" { }
  ;

%%

use lrlex::DefaultLexeme;
use lrpar::Span;

type StorageT = u8;

use crate::config_ast::{TopLevelOption, Match, PerRepoOption, ProviderOption, QueueKind};

fn map_err(r: Result<DefaultLexeme<StorageT>, DefaultLexeme<StorageT>>)
    -> Result<Span, ()>
{
    r.map(|x| x.span()).map_err(|_| ())
}

/// Flatten `rhs` into `lhs`.
fn flattenr<T>(lhs: Result<Vec<T>, ()>, rhs: Result<T, ()>) -> Result<Vec<T>, ()> {
    let mut flt = lhs?;
    flt.push(rhs?);
    Ok(flt)
}
