use crate::ast::*;
use crate::lexer::*;

grammar<'input>(ast: &mut ASTArena);

extern {
    type Location = usize;
    type Error = LexerError;

    enum Token<'input> {
        "<" => Token::LAngle,
        ">" => Token::RAngle,
        "(" => Token::LPar,
        ")" => Token::RPar,
        "{" => Token::LCurl,
        "}" => Token::RCurl,
        "|" => Token::Pipe,
        ":" => Token::Colon,
        "," => Token::Comma,
        ";" => Token::Semi,
        "." => Token::Dot,
        ".." => Token::DotDot,
        "=>" => Token::Arrow,
        ":=" => Token::Assn,
        "::=" => Token::LitAssn,
        "puts" => Token::Puts,
        "case" => Token::Case,
        "let" => Token::Let,
        "in" => Token::In,
        "fix" => Token::Fix,
        "var" => Token::Var(<&'input str>),
        "atom" => Token::Atom(<&'input str>),
        "num" => Token::Num(<i64>),
        "str" => Token::Str(<String>)
    }
}

pub Stmts: Vec<Stmt> = {
    <mut stmts:(<Stmt> ";")*> <stmt:Stmt?> => match stmt {
        None => stmts,
        Some(stmt) => {
            stmts.push(stmt);
            stmts
        }
    },
};

pub Stmt: Stmt = {
    "puts" <Expr> => Stmt::Puts(<>),
    <fixed:"fix"?> <name:Name> ":=" <expr:Expr> => Stmt::Assn(fixed.is_some(), name, expr),
    <fixed:"fix"?> <name:Name> "::=" <strs:(<"var">)*> =>
        Stmt::LitAssn(fixed.is_some(), name, strs.iter().map(|x| x.to_string()).collect()),
    "fix" <Name> => Stmt::Fix(<>),
};

pub Name: Name = {
    "var" => ast.add_string(<>),
};

pub Expr: Expr = {
    <mut ts:(<Choice> "|")*> <t:Choice> => {
        if ts.len() == 0 {
            t.value
        } else {
            ts.push(t);
            Expr::Chc(ts)
        }
    }
};

pub Choice: Choice = {
    <weight:"num"> ":" <value:Term> => Choice {
        weight: Some(weight),
        value
    },
    <value:Term> => Choice {
        weight: None,
        value
    }
};

pub Term: Expr = {
    <mut bs:(<Branch>)*> => {
        if bs.len() == 1 {
            bs.pop().unwrap()
        } else {
            Expr::Cat(<>)
        }
    }
};

pub Branch: Expr = {
    <l:Branch> "." <r:Subbranch> => Expr::Ap(Box::new(l), Box::new(r)),
    <Subbranch> => <>,
};

pub Subbranch: Expr = {
    <l:Subbranch> ".." <r:Leaf> => Expr::Range(Box::new(l), Box::new(r)),
    <Leaf> => <>,
}

pub Leaf: Expr = {
    <Literal> => Expr::Lit(<>),
    <Name> => Expr::Var(<>),
    "<" <mut es:(<Expr> ",")*> <e:Expr> ">" => {
        es.push(e);
        Expr::Tup(es)
    },
    "let" <name:Name> ":=" <e1:Expr> "in" "{" <e2:Expr> "}" =>
        Expr::Let(name, Box::new(e1), Box::new(e2)),
    "{" <mut cs:(<Case> ";")*> <c:Case> "}" => {
        cs.push(c);
        Expr::Fun(cs)
    },
    "(" <e:Expr> ")" => e,
};

pub Case: Case = {
    <pat:Pat> "=>" <expr:Expr> => Case { pat, expr },
};

pub Pat: Pat = {
    <Literal> => Pat::Lit(<>),
    <Name> => Pat::Var(<>),
    "<" <mut ps:(<Pat> ",")*> <p:Pat> ">" => {
        ps.push(p);
        Pat::Tup(ps)
    },
};

pub Literal: Literal = {
    "num" => Literal::Num(<>),
    "str" => Literal::Str(<>),
    "atom" => Literal::Atom(ast.add_string(<>)),
};