Language Specification

use super::ast::*;
use lalrpop_util::ErrorRecovery;
use crate::lexer::{Token,  Location};
use crate::lexer::Word;
use crate::Db;
use super::span::Spanned;
use crate::span;

#[LALR]
grammar<'input, 'err>(errors: &'err mut Vec<ErrorRecovery<Location, Token<'input>, &'static str>>, db: &dyn Db);

extern {
    type Location = Location;

    enum Token<'input> {
        // Operators
        "|" => Token::Pipe,      // |
        "&" => Token::Ampersand, // &
        ";" => Token::Semicolon, // ;
        "=" =>  Token::Equals,    // =
        // Redirections
        "<" => Token::LessThan,    // <
        ">" => Token::GreaterThan, // >
        // Identifiers
        // "param" => Variable::Parameter(<&'input str>), // var
        // "param_default" => Variable::ParameterDefault(<&'input str>, <&'input str>), // var = value
        // "positional_param" => Variable::PositionalParameter(<usize>), // $var
        // Literals
        "true" => Token::Word(Word::True),   // true
        "none" => Token::Word(Word::None),   // none
        "false" => Token::Word(Word::False), // false
        "null" => Token::Word(Word::Null),   // null
        "fn" => Token::Word(Word::Fn),      // fn
        "if" => Token::Word(Word::If),      // if
        "else" => Token::Word(Word::Else),  // else
        "match" => Token::Word(Word::Match), // match
        "let" => Token::Word(Word::Let),    // let
        "import" => Token::Word(Word::Import), // import
        "action" => Token::Word(Word::Action), // action
        "struct" => Token::Word(Word::Struct), // struct
        "enum" => Token::Word(Word::Enum),   // enum
        "effect" => Token::Word(Word::Effect), // trait
        "impl" => Token::Word(Word::Impl),   // impl
        "when" => Token::Word(Word::When),   // when
        "use" => Token::Word(Word::Use),    // use
        "from" => Token::Word(Word::From),   // from
        "where" => Token::Word(Word::Where),  // where
        "self" => Token::Word(Word::Self_),   // self
        "for" => Token::Word(Word::For),    // for
        "pub" => Token::Word(Word::Pub),    // pub
        "priv" => Token::Word(Word::Priv),   // priv
        "#!" => Token::Shebang, // #!
        
        "ident" => Token::Word(Word::Ident(<&'input str>)),     // a-z, A-Z, 0-9, _
        "string" => Token::String(<&'input str>), // "..."
        // Comments
        "comment" => Token::Comment(<&'input str>), // #
        // Numbers
        "int" => Token::Integer(<i64>), // 0-9
        "float" => Token::Float(<f64>),   // [0-9]*.0-9+
        // Special
        "eof" => Token::Eof,        // EOF
        "\n" => Token::NewLine,     // \n
        "(" => Token::LeftParen,    // (
        ")" => Token::RightParen,   // )
        "{" => Token::LeftBrace,    // {
        "}" => Token::RightBrace,   // }
        "[" => Token::LeftBracket,  // [
        "]" => Token::RightBracket, // ]
        "," => Token::Comma,        // ,
        ":" => Token::Colon,        // :
        "." => Token::Dot,          // .
        "-" => Token::Minus,        // -
        "+" => Token::Plus,         // +
        "/" => Token::Divide,       // /
        "*" => Token::Multiply,     // *
        "%" => Token::Percent,      // %
        "$" => Token::Dollar,       // $
        "!" => Token::Exclamation,  // !
        "?" => Token::Question,     // ?
        "~" => Token::Tilde,        // ~
        "@" => Token::At,           // @
        "^" => Token::Caret,        // ^
        "->" => Token::Arrow,       // ->
        "=>" => Token::FatArrow,    // =>
    }
}

#[inline]
Span<T>: Spanned<T> = {
    <@L> <T> <@R> => span!(<>)
};

#[inline]
Lines<T>: Vec<T> = {
    <mut v:(<T> "\n")*> <e:T?> => match e {
        None => v,
        Some(e) => {
            v.push(e);
            v
        }
    }
}

#[inline]
Comma<T>: Vec<T> = { // (0)
    <mut v:(<T> ",")*> <e:T?> => match e { // (1)
        None=> v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};

#[inline]
Plus<T>: Vec<T> = {
    <mut v:(<T> "+")*> <e:T?> => match e { // (1)
        None=> v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};

#[inline]
Block<T>: Block<T> = {
    "{" ("\n"?) <lines:Lines<T>> "}" => Block(lines)
};

// Keywords
None: Spanned<Keyword> = <lo:@L> "none" <hi:@R> => span!(lo, Keyword::None, hi);
Fn: Spanned<Keyword> = <lo:@L> "fn" <hi:@R> => span!(lo, Keyword::Fn, hi);
Effect: Spanned<Keyword> = <lo:@L> "effect" <hi:@R> => span!(lo, Keyword::Effect, hi);
Struct: Spanned<Keyword> = <lo:@L> "struct" <hi:@R> => span!(lo, Keyword::Struct, hi);
If: Spanned<Keyword> = <lo:@L> "if" <hi:@R> => span!(lo, Keyword::If, hi);
Else: Spanned<Keyword> = <lo:@L> "else" <hi:@R> => span!(lo, Keyword::Else, hi);
When: Spanned<Keyword> = <lo:@L> "when" <hi:@R> => span!(lo, Keyword::When, hi);
Use: Spanned<Keyword> = <lo:@L> "use" <hi:@R> => span!(lo, Keyword::Use, hi);
From: Spanned<Keyword> = <lo:@L> "from" <hi:@R> => span!(lo, Keyword::From, hi);
Impl: Spanned<Keyword> = <lo:@L> "impl" <hi:@R> => span!(lo, Keyword::Impl, hi);
Let: Spanned<Keyword> = <lo:@L> "let" <hi:@R> => span!(lo, Keyword::Let, hi);

True: Node = "true" => Node::Bool(true);
False: Node = "false" => Node::Bool(false);

KeywordAndVisibility<T>: Spanned<KeywordAndVisibility> = {
    <lo:@L> <v:Visibility> <k:T> <hi:@R> => span!(lo, KeywordAndVisibility(k, v), hi),
};

Ident: Spanned<Ident> = {
    <l:@L> <i:"ident"> <r:@R> =>  span!(l,Ident(i.to_string(), None),r),
};

IdentWithGenerics: Spanned<Ident> = {
    <l:@L> <i:"ident"> "<" <g:Comma<Ident>> ">" <r:@R> => span!(l,Ident(i.to_string(), Some(g)),r),
    
};

IdentOrIdentWithGenerics: Spanned<Ident> = {
    <i:Ident> => i,
    <i:IdentWithGenerics> => i,
    <l:@L> "self" <r:@R> => span!(l, Ident("self".to_string(), None),r),
};

Punctuated<T, Token>: Vec<T> = {
    <mut v:(<T> <Token>)*> <e:T?> => match e {
        None => v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};

Atom: Spanned<Value> = {
    #[precedence(level="0")]
    <l:@L> <i:"int"> <r:@R> => span!(l, Value::Literal(Literal::Integer(i)),r),
    <l:@L> <f:"float"> <r:@R>=> span!(l, Value::Literal(Literal::Float(f)),r),
    <l:@L> <s:"string"> <r:@R> => {
        let start = 1;
        let end = s.len() - 1;
        span!(l, Value::Literal(Literal::String(s.get(start..end).expect(format!("malformed string {s}, strings must be quoted").as_str()).to_string())), r)
    },    
    #[precedence(level="1")]
    <i:Ident> => span!(i.0, Value::Ident(i.1), i.2),
};


String: Spanned<Node> = {
    <l:@L> <s:"string"> <r:@R> => {
        let start = 1;
        let end = s.len() - 1;
        span!(l, Node::String(s.get(start..end).expect(format!("malformed string {s}, strings must be quoted").as_str()).to_string()),r)
    },
};


Expression: Spanned<Node> = {
    #[precedence(level="1")] 
    Term,

    #[precedence(level="2")] #[assoc(side="left")]
    <lhs:Expression> "*" <rhs:Expression> => {
        let (l, r) = (lhs.2, rhs.0);
        span!(l,Node::BinaryExpression(BinaryOperation {
            lhs: Box::new(lhs),
            op: Operator::Mul,
            rhs: Box::new(rhs)
        }),r)
    },
    <l:@L> <lhs:Expression> "/" <rhs:Expression> <r:@R>  => {
        let (l, r) = (lhs.2, rhs.0);
        span!(l,Node::BinaryExpression(BinaryOperation {
            lhs: Box::new(lhs),
            op: Operator::Div,
            rhs: Box::new(rhs)
        }),r)
    },

    #[precedence(level="3")] #[assoc(side="left")]
    <lhs:Expression> "+" <rhs:Expression> => {
        let (l, r) = (lhs.2, rhs.0);
        span!(l,Node::BinaryExpression(BinaryOperation {
            lhs: Box::new(lhs),
            op: Operator::Add,
            rhs: Box::new(rhs)
        }),r)
    },
    <lhs:Expression> "-" <rhs:Expression> => {
        let (l, r) = (lhs.2, rhs.0);
        span!(l, Node::BinaryExpression(BinaryOperation {
            lhs: Box::new(lhs),
            op: Operator::Sub,
            rhs: Box::new(rhs)
        }),r)
    },
};


FnCall: Spanned<Node> = {
    <name:IdentOrIdentWithGenerics> "(" <args:Comma<Expression>> ")" <r:@R> => span!(name.0, Node::FnCall(FnCall(name, args)) ,r),
};


Term: Spanned<Node> = {
    <s:Span<String>> => s.1,
    <l:@L> <val:"int"> <r:@R> => span!(l, Node::Integer(val),r),
    <i:Ident> => {
        let (l, r) = (i.0, i.2);
        span!(l, Node::Ident(i), r)},
    <f:FnCall> => <>,
    <l:@L> <true_:True> <r:@R> => span!(l,true_, r),
    <l:@L> <false_:False> <r:@R> => span!(l,false_, r),
    "(" <Expression> ")",
};


IdentOrIdentWithGenericsOrFnCall: Spanned<Node> = {
    <i:IdentOrIdentWithGenerics> => {
        let (l, r) = (i.0, i.2);
        span!(l, Node::Ident(i), r)
    },
    <f:FnCall> => f,
};

FieldAccess: Spanned<Node> = {
    <lhs:FieldAccess> "." <rhs:IdentOrIdentWithGenericsOrFnCall> => {
        let (l, r) = (lhs.0, rhs.2);
        span!(l,Node::FieldAccess(FieldAccess(Box::new(lhs), Box::new(rhs))),r)
    },
    <lhs:IdentOrIdentWithGenericsOrFnCall> => lhs,
};


Field: Spanned<FieldDef> = {
    <vis:Visibility> <name:Ident> ":" <ty:IdentOrIdentWithGenerics> => {
        let (l, r) = (name.0, ty.2);
        span!(l, FieldDef(vis, name, ty), r)
    }
};

TypeParameters: Spanned<Vec<Spanned<Ident>>> =
    <l:@L> "<" <is:Comma<Ident>> ">" <r:@R> => span!(l,is ,r);

FnArg: Spanned<FnArg> = {
    <self_:Span<"self">> => span!(self_.0, FnArg::Reciever, self_.2),
    <field:Span<Field>> => {
        let (l, r) = (field.0, field.2);
        span!(l, FnArg::Field(field.1), r)
    },
};

Prototype: Spanned<Prototype> = {
    <l:@L> <name:IdentOrIdentWithGenerics> "("<args:Comma<FnArg>> ")" "[" <effects:Comma<Ident>> "]" <ret:("->" Ident)?> <r:@R> => {
        let ret = match ret {
            None => None,
            Some(r) => Some(r.1),
        };
        span!(l, Prototype{name, args, ret, effects},r)
    }
};


Statement: Spanned<Node> = {
    <l:@L> Let <name:Ident> "=" <value:Expression> <r:@R> => span!(l, Node::Binding(Binding(name, Box::new(value))),r),
    <IfDef> => <>,
    FieldAccess => <>,  
};

Visibility: Spanned<Visibility> = {
    <l:@L> "pub" <r:@R> => span!(l, Visibility::Public, r),
    <l:@L> "priv" <r:@R> => span!(l, Visibility::Private, r),
    // elided
    <l:@L> () <r:@R> => span!(l, Visibility::Private, r),
};

WhenBlock: (Spanned<Ident>,Block<Spanned<Node>>) = {
    <l:@L> When <i:Ident> <lines:Block<Statement>> <r:@R> => (i, lines)
};

FnDef: Spanned<Node> = {
    <l:@L> <kwv:KeywordAndVisibility<Fn>> <proto:Prototype> <block:Block<Statement>> <r:@R> => span!(l, Node::FnDef(FnDef(kwv,proto, block)), r),
};

EffectDef: Spanned<Node> = {
    <l:@L> <kwv:KeywordAndVisibility<Effect>> <i:Ident> ":" <effects:Plus<Ident>> <block:Block<Prototype>> <r:@R> => span!(l,Node::EffectDef(EffectDef(kwv, i, effects,block)), r),
};

StructDef: Spanned<Node> = {
    <l:@L> <kwv:KeywordAndVisibility<Struct>> <i:Ident> <fields:Block<Field>> <r:@R> => span!(l, Node::StructDef(StructDef(kwv, i, fields)),r),
};

IfDef: Spanned<Node> = {
    <l:@L> If <cl:@L><cond:Statement><cr:@R> <if_:Block<Statement>> <r:@R> => {
        let branch = BranchDef (
            Box::new(cond),
            vec![
                (span!(l, Node::Bool(true), cl), if_),
            ]
        );
        span!(l, Node::Branch(branch), r)
    },
    <l:@L> If <cl:@L> <cond:Statement> <cr:@R> <if_:Block<Statement>> <el:@L> Else <er:@R> <else_:Block<Statement>> <r:@R> => {
        let branch = BranchDef (
            Box::new(cond),
            vec![
                (span!(l,Node::Bool(true),cl), if_),
                (span!(el, Node::Bool(false), er), else_),
            ]
        );
        span!(l, Node::Branch(branch), r)
    },
};

UseDef: Spanned<Node> = {
    <l:@L> <kwv:KeywordAndVisibility<Use>> "{" <imports:Comma<Ident>> "}" From <i:Ident> <r:@R> => {
        span!(l, Node::UseDef(UseDef(kwv,imports, i)), r)
    },
};

ImplDef: Spanned<Node> = {
    <l:@L> <kwv:KeywordAndVisibility<Impl>> <i:Ident> <t:("for" Ident)?> <lines:Block<FnDef>> <r:@R> => span!(l,Node::ImplDef(ImplDef(kwv, i, t.map(|t| t.1), lines)),r),
};

TopLevel: Spanned<Node> = {
    <FnDef> => <>,
    <EffectDef> => <>,
    <StructDef> => <>,
    <UseDef> => <>,
    <ImplDef> => <>,
};

pub Source: Module = {
    <expr:("\n"* TopLevel)*> => Module(expr.into_iter().map(|e| e.1).collect()),
    ! => {
        errors.push(<>);
        Module(vec![])
    }
};