use lalrpop_util::ParseError;
use super::pt::*;
use super::lexer::{Token, LexicalError};
use lalrpop_util::ErrorRecovery;
grammar<'input, 'err>(input: &'input str, file_no: usize , parser_errors: &'err mut Vec<ErrorRecovery<usize, Token<'input>, LexicalError>> );

pub SourceUnit: SourceUnit = {
    SourceUnitPart* => SourceUnit(<>)
}

SourceUnitPart: SourceUnitPart = {
    ContractDefinition => SourceUnitPart::ContractDefinition(<>),
    PragmaDirective => <>,
    ImportDirective => <>,
    EnumDefinition => SourceUnitPart::EnumDefinition(<>),
    StructDefinition => SourceUnitPart::StructDefinition(<>),
    EventDefinition => SourceUnitPart::EventDefinition(<>),
    ErrorDefinition => SourceUnitPart::ErrorDefinition(<>),
    FunctionDefinition => SourceUnitPart::FunctionDefinition(<>),
    VariableDefinition => SourceUnitPart::VariableDefinition(<>),
    TypeDefinition => SourceUnitPart::TypeDefinition(<>),
    Using => SourceUnitPart::Using(<>),
    <l:@L> ";" <r:@R> => SourceUnitPart::StraySemicolon(Loc::File(file_no, l, r)),
}

ImportDirective: SourceUnitPart = {
    <l:@L> "import" <s:StringLiteral> <r:@R> ";" => SourceUnitPart::ImportDirective(Import::Plain(s, Loc::File(file_no, l, r))),
    <l:@L> "import" <s:StringLiteral> "as" <id:SolIdentifier> <r:@R> ";" => SourceUnitPart::ImportDirective(Import::GlobalSymbol(s, id, Loc::File(file_no, l, r))),
    <l:@L> "import" "*" "as" <id:SolIdentifier> <from:SolIdentifier> <s:StringLiteral> <r:@R> ";" => {
        if from.name != "from" {
            let error = ErrorRecovery {
                error: ParseError::User {
                    error: LexicalError::ExpectedFrom(from.loc, from.name)
                },
                dropped_tokens: Vec::new(),
            };
            parser_errors.push(error);   
        }
        SourceUnitPart::ImportDirective(Import::GlobalSymbol(s, id, Loc::File(file_no, l, r)))   
    },
    <l:@L> "import" "{" <rename:CommaOne<ImportRename>> "}" <from:SolIdentifier> <s:StringLiteral> <r:@R> ";" =>? {
        if from.name != "from" {
            let error = ErrorRecovery {
                error: ParseError::User {
                    error: LexicalError::ExpectedFrom(from.loc, from.name),
                },
                dropped_tokens: Vec::new(),
            };
            parser_errors.push(error);
        }
        Ok(SourceUnitPart::ImportDirective(Import::Rename(s,rename,Loc::File(file_no, l, r))))
    },
}

ImportRename: (Identifier, Option<Identifier>) = {
    <SolIdentifier> => (<>, None),
    <from:SolIdentifier> "as" <to:SolIdentifier> => (from, Some(to)),
}

PragmaDirective: SourceUnitPart = {
    // The lexer does special parsing for String literal; it isn't really a string literal
    <l:@L> "pragma" <i:SolIdentifier> <s:StringLiteral> <r:@R> ";" => SourceUnitPart::PragmaDirective(Loc::File(file_no, l, r), Some(i), Some(s)) ,
    <l:@L> "pragma" <false_token:!> <r:@R> ";" => {
        parser_errors.push(false_token);     
        SourceUnitPart::PragmaDirective(Loc::File(file_no, l, r), None, None)
    },
}

Type: Type = {
    NoFunctionType,
    FunctionType
}

NoFunctionType: Type = {
    "bool" => Type::Bool,
    "address" => Type::Address,
    "address" "payable" => Type::AddressPayable,
    // payable is only used as a cast in solc
    "payable" => Type::Payable,
    "string" => Type::String,
    "bytes" => Type::DynamicBytes,
    Uint => Type::Uint(<>),
    Int => Type::Int(<>),
    Bytes => Type::Bytes(<>),
    // prior to 0.8.0 `byte` used to be an alias for `bytes1`
    "byte" => Type::Bytes(1),
    <l:@L> "mapping" "(" <k:AnyType> "=>" <v:AnyType> ")" <r:@R> => {
        Type::Mapping(Loc::File(file_no, l, r), Box::new(k), Box::new(v))
    },
}

FunctionType: Type = {
    "function" <params:ParameterList>
    <attributes:FunctionTypeAttribute*>
    <returns:("returns" <ParameterList> <FunctionTypeAttribute*>)?> => {
        Type::Function {
            params,
            attributes,
            returns,
        }
    }
}

FunctionTypeAttribute: FunctionAttribute = {
    <l:@L> "immutable" <r:@R> => FunctionAttribute::Immutable(Loc::File(file_no, l, r)),
    Mutability => FunctionAttribute::Mutability(<>),
    Visibility => FunctionAttribute::Visibility(<>),
}

ArrayDimension: Option<Expression> = {
    "[" "]" => None,
    "[" <Expression> "]" => Some(<>)
}

StorageLocation: StorageLocation = {
    <l:@L> "memory" <r:@R> => StorageLocation::Memory(Loc::File(file_no, l, r)),
    <l:@L> "storage" <r:@R> => StorageLocation::Storage(Loc::File(file_no, l, r)),
    <l:@L> "calldata" <r:@R> => StorageLocation::Calldata(Loc::File(file_no, l, r)),
}

Identifier: Identifier = {
    <l:@L> <n:identifier> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: n.to_string()}
}

SolIdentifier: Identifier = {
    <l:@L> <n:identifier> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: n.to_string()},
    <l:@L> "switch" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "switch".to_string()},
    <l:@L> "leave" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "leave".to_string()},
    <l:@L> "case" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "case".to_string()},
    <l:@L> "default" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "default".to_string()},
    <l:@L> "error" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "error".to_string()},
    <l:@L> "revert" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()},
}

SolIdentifierOrError: Option<Identifier> = {
    SolIdentifier => Some(<>),
    ! => {
        parser_errors.push(<>);
        None
    }
}

SolNoErrorIdentifier: Identifier = {
    <l:@L> <n:identifier> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: n.to_string()},
    <l:@L> "switch" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "switch".to_string()},
    <l:@L> "leave" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "leave".to_string()},
    <l:@L> "case" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "case".to_string()},
    <l:@L> "default" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "default".to_string()},
}

SolIdentifierPath: IdentifierPath = {
    <l:@L> <e:SolIdentifier> <v:("." <SolIdentifier>)*> <r:@R> => {
        let mut v = v;
        v.insert(0, e);
        IdentifierPath { loc: Loc::File(file_no, l, r), identifiers: v }
    }
}

VariableDeclaration: VariableDeclaration = {
    <l:@L> <ty:AnyType> <storage:StorageLocation?> <name:SolIdentifierOrError> <r:@R> => VariableDeclaration {
        loc: Loc::File(file_no, l, r), ty, storage, name
    },
}

StructDefinition: Box<StructDefinition> = {
    <l:@L> "struct" <name:SolIdentifierOrError> "{" <fields:(<VariableDeclaration> ";")*> "}" <r:@R> => {
        Box::new(StructDefinition{loc: Loc::File(file_no, l, r), name, fields})
    }
}

ContractTy: ContractTy = {
    <l:@L> "abstract" "contract" <r:@R> => ContractTy::Abstract(Loc::File(file_no, l, r)),
    <l:@L> "contract" <r:@R> => ContractTy::Contract(Loc::File(file_no, l, r)),
    <l:@L> "interface" <r:@R> => ContractTy::Interface(Loc::File(file_no, l, r)),
    <l:@L> "library" <r:@R> => ContractTy::Library(Loc::File(file_no, l, r)),
}

ContractPart: ContractPart = {
    StructDefinition => ContractPart::StructDefinition(<>),
    EventDefinition => ContractPart::EventDefinition(<>),
    ErrorDefinition => ContractPart::ErrorDefinition(<>),
    EnumDefinition => ContractPart::EnumDefinition(<>),
    VariableDefinition => ContractPart::VariableDefinition(<>),
    FunctionDefinition => ContractPart::FunctionDefinition(<>),
    ModifierDefinition => ContractPart::FunctionDefinition(<>),
    ConstructorDefinition => ContractPart::FunctionDefinition(<>),
    TypeDefinition => ContractPart::TypeDefinition(<>),
    <l:@L> ";" <r:@R> => ContractPart::StraySemicolon(Loc::File(file_no, l, r)),
    Using => ContractPart::Using(<>),
}

Bases: Vec<Base> = {
    => Vec::new(),
    "is" <CommaOne<Base>> => <>,
}

Base: Base = {
    <l:@L> <name:SolIdentifierPath> <args:("(" <Comma<Expression>> ")")?> <r:@R> => Base {
        loc: Loc::File(file_no, l, r),
        name,
        args
    }
}

ContractDefinition: Box<ContractDefinition> = {
    <l:@L> <ty:ContractTy> <name:SolIdentifierOrError> <base:Bases>
    "{" <parts:(<ContractPart>)*> "}" <r:@R> => {
        Box::new(ContractDefinition{loc: Loc::File(file_no, l, r), ty, name, base, parts})
    }
}

EventParameter: EventParameter = {
    <l:@L> <ty:AnyType> <i:"indexed"?> <name:SolIdentifier?> <r:@R> => EventParameter{
        loc: Loc::File(file_no, l, r), ty, indexed: i.is_some(), name
    }
}

ErrorParameter: ErrorParameter = {
    <l:@L> <ty:AnyType> <name:SolIdentifier?> <r:@R> => ErrorParameter{
        loc: Loc::File(file_no, l, r), ty, name
    }
}

EventDefinition: Box<EventDefinition> = {
    <l:@L> "event" <name:SolIdentifierOrError> "(" <v:Comma<EventParameter>> ")" <a:"anonymous"?> <r:@R> ";" => {
        Box::new(EventDefinition{
            loc: Loc::File(file_no, l, r), name, fields: v, anonymous: a.is_some()
        })
    },
}

ErrorDefinition: Box<ErrorDefinition> = {
    <l:@L> "error" <name:SolIdentifierOrError> "(" <v:Comma<ErrorParameter>> ")" <r:@R> ";" => {
        Box::new(ErrorDefinition{
            loc: Loc::File(file_no, l, r), name, fields: v
        })
    },
}

EnumDefinition: Box<EnumDefinition> = {
    <l:@L> "enum" <name:SolIdentifierOrError> "{" <values:Comma<SolIdentifierOrError>> "}" <r:@R> => {
        Box::new(EnumDefinition{loc: Loc::File(file_no, l, r), name, values})
    }
}

VariableDefinition: Box<VariableDefinition> = {
    <l:@L> <ty:NoFunctionTyPrecedence0> <attrs:VariableAttribute*> <name:SolIdentifierOrError> <e:("=" <Expression>)?> <r:@R> ";" => {
        Box::new(VariableDefinition{
            loc: Loc::File(file_no, l, r), ty, attrs, name, initializer: e,
        })
    },
    <l:@L> <ty:NoFunctionTyPrecedence0> <attrs:VariableAttribute*> <name:SolIdentifier> <false_token:!> <r:@R> ";" => {
        parser_errors.push (false_token);
        Box::new(VariableDefinition{
            loc: Loc::File(file_no, l, r), ty, attrs, name: Some(name), initializer: None,
        })
    },
    
    <l:@L> "error" <er:@R> <attrs:VariableAttribute*> <name:SolIdentifier> <e:("=" <Expression>)?> <r:@R> ";" => {
        let ty = Expression::Variable(Identifier{loc: Loc::File(file_no, l, r), name: "error".to_string()});

        Box::new(VariableDefinition{
            loc: Loc::File(file_no, l, r), ty, attrs, name: Some(name), initializer: e,
        })
    },
    // attributes cause shift-reduce errors with function type, since a function type contract variable can be declare as
    // contract foo {
    //     // f is a variable with internal visibility referencing an external function
    //     function() external internal f;
    // }
    <l:@L> <ty:FunctionTyPrecedence0> <name:SolIdentifier> <e:("=" <Expression>)?> <r:@R> ";" => {
        Box::new(VariableDefinition{
            loc: Loc::File(file_no, l, r), ty, attrs: Vec::new(), name: Some(name), initializer: e,
        })
    }
}

TypeDefinition: Box<TypeDefinition> = {
    <l:@L> "type" <name:SolIdentifier> "is" <ty:AnyType> <r:@R> ";" => {
        Box::new(TypeDefinition{
            loc: Loc::File(file_no, l, r), name, ty
        })
    },
}

Visibility: Visibility = {
    <l:@L> "public" <r:@R> => Visibility::Public(Some(Loc::File(file_no, l, r))),
    <l:@L> "external" <r:@R> => Visibility::External(Some(Loc::File(file_no, l, r))),
    <l:@L> "internal" <r:@R> => Visibility::Internal(Some(Loc::File(file_no, l, r))),
    <l:@L> "private" <r:@R> => Visibility::Private(Some(Loc::File(file_no, l, r))),
}

VariableAttribute: VariableAttribute = {
    Visibility => VariableAttribute::Visibility(<>),
    <l:@L> "constant" <r:@R> => VariableAttribute::Constant(Loc::File(file_no, l, r)),
    <l:@L> "immutable" <r:@R> => VariableAttribute::Immutable(Loc::File(file_no, l, r)),
    <l:@L> "override" <r:@R> => VariableAttribute::Override(Loc::File(file_no, l, r), Vec::new()),
    <l:@L> "override" "(" <list:CommaOne<SolIdentifierPath>> ")" <r:@R> => {
        VariableAttribute::Override(Loc::File(file_no, l, r), list)
    }
}

Expression: Expression = {
    Precedence14,
}

Precedence14: Expression = {
    <a:@L> <l:Precedence13> "=" <r:Precedence14> <b:@R> => Expression::Assign(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "|=" <r:Precedence14> <b:@R> => Expression::AssignOr(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "^=" <r:Precedence14> <b:@R> => Expression::AssignXor(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "&=" <r:Precedence14> <b:@R> => Expression::AssignAnd(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "<<=" <r:Precedence14> <b:@R> => Expression::AssignShiftLeft(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> ">>=" <r:Precedence14> <b:@R> => Expression::AssignShiftRight(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "+=" <r:Precedence14> <b:@R> => Expression::AssignAdd(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "-=" <r:Precedence14> <b:@R> => Expression::AssignSubtract(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "*=" <r:Precedence14> <b:@R> => Expression::AssignMultiply(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "/=" <r:Precedence14> <b:@R> => Expression::AssignDivide(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence13> "%=" <r:Precedence14> <b:@R> => Expression::AssignModulo(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <c:Precedence13> "?" <l:Precedence14> ":" <r:Precedence14> <b:@R> => {
        Expression::ConditionalOperator(Loc::File(file_no, a, b), Box::new(c), Box::new(l), Box::new(r))
    },
    Precedence13,
}

Precedence13: Expression = {
    <a:@L> <l:Precedence13> "||" <r:Precedence12> <b:@R> => Expression::Or(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence12,
}

Precedence12: Expression = {
    <a:@L> <l:Precedence12> "&&" <r:Precedence11> <b:@R> => Expression::And(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence11,
}

Precedence11: Expression = {
    <a:@L> <l:Precedence11> "==" <r:Precedence10> <b:@R> => Expression::Equal(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence11> "!=" <r:Precedence10> <b:@R> => Expression::NotEqual(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence10,
}

Precedence10: Expression = {
    <a:@L> <l:Precedence10> "<" <r:Precedence9> <b:@R> => Expression::Less(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence10> ">" <r:Precedence9> <b:@R> => Expression::More(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence10> "<=" <r:Precedence9> <b:@R> => Expression::LessEqual(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence10> ">=" <r:Precedence9> <b:@R> => Expression::MoreEqual(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence9,
}

Precedence9: Expression = {
    <a:@L> <l:Precedence9> "|" <r:Precedence8> <b:@R> => Expression::BitwiseOr(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence8,
}

Precedence8: Expression = {
    <a:@L> <l:Precedence8> "^" <r:Precedence7> <b:@R> => Expression::BitwiseXor(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence7,
}

Precedence7: Expression = {
    <a:@L> <l:Precedence7> "&" <r:Precedence6> <b:@R> => Expression::BitwiseAnd(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence6,
}

Precedence6: Expression = {
    <a:@L> <l:Precedence6> "<<" <r:Precedence5> <b:@R> => Expression::ShiftLeft(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence6> ">>" <r:Precedence5> <b:@R> => Expression::ShiftRight(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence5,
}

Precedence5: Expression = {
    <a:@L> <l:Precedence5> "+" <r:Precedence4> <b:@R> => Expression::Add(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence5> "-" <r:Precedence4> <b:@R> => Expression::Subtract(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence4,
}

Precedence4: Expression = {
    <a:@L> <l:Precedence4> "*" <r:Precedence3> <b:@R> => Expression::Multiply(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence4> "/" <r:Precedence3> <b:@R> => Expression::Divide(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    <a:@L> <l:Precedence4> "%" <r:Precedence3> <b:@R> => Expression::Modulo(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence3,
}

Precedence3: Expression = {
    <a:@L> <l:Precedence2> "**" <r:Precedence3> <b:@R> => Expression::Power(Loc::File(file_no, a, b), Box::new(l), Box::new(r)),
    Precedence2,
}

Precedence2: Expression = {
    <a:@L> "!" <e:Precedence2> <b:@R> => Expression::Not(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "~" <e:Precedence2> <b:@R> => Expression::Complement(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "delete" <e:Precedence2> <b:@R>  => Expression::Delete(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "new" <call:Precedence2> <b:@R> => Expression::New(Loc::File(file_no, a, b), Box::new(call)),
    <a:@L> "++" <e:Precedence2> <b:@R> => Expression::PreIncrement(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "--" <e:Precedence2> <b:@R> => Expression::PreDecrement(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "+" <e:Precedence2> <b:@R>  => Expression::UnaryPlus(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> "-" <e:Precedence2> <b:@R> => Expression::UnaryMinus(Loc::File(file_no, a, b), Box::new(e)),
    <l:@L> "error" <r:@R> => Expression::Variable(Identifier{loc: Loc::File(file_no, l, r), name: "error".to_string()}),
    <l:@L> "revert" <r:@R> => Expression::Variable(Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()}),
    Precedence0,
}

NamedArgument: NamedArgument = {
    <l:@L> <name:SolIdentifier> ":" <expr:Expression> <r:@R> => {
        NamedArgument{ loc: Loc::File(file_no, l, r), name, expr }
    }
}

FunctionCall: Expression = {
    <a:@L> <i:FunctionCallPrecedence> "(" <v:Comma<Expression>> ")" <b:@R> => {
        Expression::FunctionCall(Loc::File(file_no, a, b), Box::new(i), v)
    },
    <l:@L> <i:FunctionCallPrecedence> <block:BlockStatement> <r:@R> => {
        Expression::FunctionCallBlock(Loc::File(file_no, l, r), Box::new(i), Box::new(block))
    },
    <a:@L> <i:FunctionCallPrecedence> "(" "{" <v:Comma<NamedArgument>> "}" ")" <b:@R> => {
        Expression::NamedFunctionCall(Loc::File(file_no, a, b), Box::new(i), v)
    },
}

FunctionCallPrecedence: Expression = {
    NoFunctionTyPrecedence0,
    <l:@L> "type" <r:@R> => Expression::Variable(Identifier{loc: Loc::File(file_no, l, r), name: "type".to_string()}),
}

AnyType: Expression = {
    <l:@L> "error" <r:@R> => Expression::Variable(Identifier{loc: Loc::File(file_no, l, r), name: "error".to_string()}),
    Precedence0
}

Precedence0: Expression = {
    NoFunctionTyPrecedence0,
    FunctionTyPrecedence0
}

FunctionTyPrecedence0: Expression = {
    <l:@L> <ty:FunctionType> <r:@R> => Expression::Type(Loc::File(file_no, l, r), ty),
}

NoFunctionTyPrecedence0: Expression = {
    <a:@L> <e:Precedence0> "++" <b:@R> => Expression::PostIncrement(Loc::File(file_no, a, b), Box::new(e)),
    <a:@L> <e:Precedence0> "--" <b:@R> => Expression::PostDecrement(Loc::File(file_no, a, b), Box::new(e)),
    <FunctionCall> => <>,
    <a:@L> <e:Precedence0> "[" <i:Expression?> "]" <b:@R> => Expression::ArraySubscript(Loc::File(file_no, a, b), Box::new(e), i.map(Box::new)),
    <a:@L> <e:Precedence0> "[" <l:Expression?> ":" <r:Expression?> "]" <b:@R> => Expression::ArraySlice(Loc::File(file_no, a, b), Box::new(e), l.map(Box::new), r.map(Box::new)),
    <a:@L> <e:Precedence0> "." <i:SolIdentifier> <b:@R> => Expression::MemberAccess(Loc::File(file_no, a, b), Box::new(e), i),
    // Solidity has ".address" members on external function types. Address is a keyword, so special casing needed
    <a:@L> <e:Precedence0> "." <al:@L> "address" <b:@R> => {
        Expression::MemberAccess(Loc::File(file_no, a, b), Box::new(e),
            Identifier { loc: Loc::File(file_no, al, b), name: "address".to_string() })
    },
    <l:@L> <ty:NoFunctionType> <r:@R> => Expression::Type(Loc::File(file_no, l, r), ty),
    <a:@L> "[" <v:CommaOne<Expression>> "]" <b:@R> => {
        Expression::ArrayLiteral(Loc::File(file_no, a, b), v)
    },
    <SolNoErrorIdentifier> => Expression::Variable(<>),
    <l:@L> <e:Precedence0> <u:Unit> <r:@R> => Expression::Unit(Loc::File(file_no, l, r), Box::new(e), u),
    <l:@L> <a:ParameterList> <r:@R> => {
        if a.len() == 1 {
            if let Some(Parameter{ ty, storage: None, name: None, .. }) = &a[0].1 {
                // this means "(" Expression ")"
                return Expression::Parenthesis(ty.loc(), Box::new(ty.clone()));
            }
        }

        Expression::List(Loc::File(file_no, l, r), a)
    },
    <@L> "this" <@R> => Expression::This(Loc::File(file_no, <>)),
    LiteralExpression
}

LiteralExpression: Expression = {
    <a:@L> "true" <b:@R> => Expression::BoolLiteral(Loc::File(file_no, a, b), true),
    <a:@L> "false" <b:@R> => Expression::BoolLiteral(Loc::File(file_no, a, b), false),
    <StringLiteral+> => Expression::StringLiteral(<>),
    <HexLiteral+> => Expression::HexLiteral(<>),
    <l:@L> <a:address> <r:@R> => {
        let v = a.to_string();
        let address_len = a.len() - 9;

        Expression::AddressLiteral(Loc::File(file_no, l, r), a.chars().skip(8).filter(|c|  *c != '"' && *c != '\'').collect())
    },
    <l:@L> <n:number> <r:@R> => {
        let integer: String = n.0.chars().filter(|v| *v != '_').collect();
        let exp: String = n.1.chars().filter(|v| *v != '_').collect();

        Expression::NumberLiteral(Loc::File(file_no, l, r), integer, exp)
    },
    <l:@L> <n:rational> <r:@R> => {
        let integer: String = n.0.chars().filter(|v| *v != '_').collect();
        let fraction: String = n.1.chars().filter(|v| *v != '_').collect();
        let exp: String = n.2.chars().filter(|v| *v != '_').collect();

        Expression::RationalNumberLiteral(Loc::File(file_no, l, r), integer, fraction, exp)
    },
    <l:@L> <n:hexnumber> <r:@R> => {
        Expression::HexNumberLiteral(Loc::File(file_no, l, r), n.to_owned())
    }
}

Unit: Unit = {
    <@L> "seconds" <@R> => Unit::Seconds(Loc::File(file_no, <>)),
    <@L> "minutes" <@R> => Unit::Minutes(Loc::File(file_no, <>)),
    <@L> "hours" <@R> => Unit::Hours(Loc::File(file_no, <>)),
    <@L> "days" <@R> => Unit::Days(Loc::File(file_no, <>)),
    <@L> "weeks" <@R> => Unit::Weeks(Loc::File(file_no, <>)),
    <@L> "wei" <@R> => Unit::Wei(Loc::File(file_no, <>)),
    <@L> "gwei" <@R> => Unit::Gwei(Loc::File(file_no, <>)),
    <@L> "ether" <@R> => Unit::Ether(Loc::File(file_no, <>)),
}

StringLiteral: StringLiteral = {
    <l:@L> <s:string> <r:@R> => {
        StringLiteral{ loc: Loc::File(file_no, l, r), unicode: s.0, string: s.1.to_string() }
    }
}

HexLiteral: HexLiteral = {
    <l:@L> <s:hexstring> <r:@R> => {
        let v = s.to_string();
        let hex_len = v.len() - 5;

        HexLiteral{ loc: Loc::File(file_no, l, r), hex: v.chars().skip(4).filter(|c| *c != '_' && *c != '"' && *c != '\'').collect() }
    }
}

// A parameter list is used for function arguments, returns, and destructuring statements.
// In destructuring statements, parameters can be optional. So, we make parameters optional
// and as an added bonus we can generate error messages about missing parameters/returns
// to functions
Parameter: Parameter = {
    <l:@L> <ty:Expression> <storage:StorageLocation?> <name:SolIdentifier?> <r:@R> => {
        let loc = Loc::File(file_no, l, r);
        Parameter{loc, ty, storage, name}
    }
}

OptParameter: (Loc, Option<Parameter>) = {
    <l:@L> <p:Parameter?> <r:@R> => (Loc::File(file_no, l, r), p),
}

ParameterList: Vec<(Loc, Option<Parameter>)> = {
    "(" ")" => Vec::new(),
    "(" <l:@L> <p:Parameter> <r:@R> ")" => vec!((Loc::File(file_no, l, r), Some(p))),
    "(" <CommaTwo<OptParameter>> ")" => <>,
    "(" <false_token:!> ")"=> {
        parser_errors.push(<>);
        Vec::new()
    }
}

Mutability: Mutability = {
    <l:@L> "pure" <r:@R> => Mutability::Pure(Loc::File(file_no, l, r)),
    <l:@L> "constant" <r:@R> => Mutability::Constant(Loc::File(file_no, l, r)),
    <l:@L> "view" <r:@R> => Mutability::View(Loc::File(file_no, l, r)),
    <l:@L> "payable" <r:@R> => Mutability::Payable(Loc::File(file_no, l, r)),
}

FunctionAttribute: FunctionAttribute = {
    Mutability => FunctionAttribute::Mutability(<>),
    Visibility => FunctionAttribute::Visibility(<>),
    <l:@L> "immutable" <r:@R> => FunctionAttribute::Immutable(Loc::File(file_no, l, r)),
    <l:@L> "virtual" <r:@R> => FunctionAttribute::Virtual(Loc::File(file_no, <>)),
    <l:@L> "override" <r:@R> => FunctionAttribute::Override(Loc::File(file_no, <>), Vec::new()),
    <l:@L> "override" "(" <list:CommaOne<SolIdentifierPath>> ")" <r:@R> => {
        FunctionAttribute::Override(Loc::File(file_no, l, r), list)
    },
    <l:@L> <base:Base> <r:@R> => FunctionAttribute::BaseOrModifier(Loc::File(file_no, l, r), base),
    <l:@L> <name:SolIdentifier> "=" <value:LiteralExpression> <r:@R> => {
        FunctionAttribute::NameValue(Loc::File(file_no, l, r), name, value)
    },
    <l:@L> <false_token:!> <r:@R> => {
        parser_errors.push(false_token); 
        FunctionAttribute::Error(Loc::File(file_no, l ,r))
    },
}

// Modifier and function have special case
FunctionTy: FunctionTy = {
    "fallback" => FunctionTy::Fallback,
    "receive" => FunctionTy::Receive,
    "constructor" => FunctionTy::Constructor,
}

BlockStatementOrSemiColon: Option<Statement> = {
    <@L> <@R> ";" => None,
    BlockStatement => Some(<>),
}

returns: Option<Loc> = {
    "returns" => None,
    <l:@L> "return" <r:@R> => Some(Loc::File(file_no, l, r)),
}

// Modifiers can't have attributes or return values, but we parse them anyway so we can give nice
// error messages. The parameter list is optional
ModifierDefinition: Box<FunctionDefinition> = {
    <l:@L> "modifier" <nl:@L> <name:SolIdentifier> <nr:@R> <params:ParameterList?>
    <attributes:FunctionAttribute*>
    <returns:(returns ParameterList)?> <r:@R> <body:BlockStatementOrSemiColon> => {
        let params = params.unwrap_or(Vec::new());
        let (return_not_returns, returns) = returns.unwrap_or((None, Vec::new()));

        Box::new(FunctionDefinition{
            loc: Loc::File(file_no, l, r),
            ty: FunctionTy::Modifier,
            name: Some(name),
            name_loc: Loc::File(file_no, nl, nr),
            params,
            attributes,
            return_not_returns,
            returns,
            body,
        })
    },
}

ConstructorDefinition: Box<FunctionDefinition> = {
    <l:@L> <ty:FunctionTy> <nl:@L> <name:SolIdentifier?> <nr:@R> <params:ParameterList>
    <attributes:FunctionAttribute*>
    <returns:(returns ParameterList)?> <r:@R> <body:BlockStatementOrSemiColon> => {
        let (return_not_returns, returns) = returns.unwrap_or((None, Vec::new()));

        Box::new(FunctionDefinition{
            loc: Loc::File(file_no, l, r),
            ty,
            name,
            name_loc: Loc::File(file_no, nl, nr),
            params,
            attributes,
            return_not_returns,
            returns,
            body,
        })
    },
}

FunctionDefinition: Box<FunctionDefinition> = {
    <l:@L> "function" <nl:@L> <name:SolIdentifierOrError> <nr:@R> <params:ParameterList>
    <attributes:FunctionAttribute*>
    <returns:(returns ParameterList)?> <r:@R> <body:BlockStatementOrSemiColon> => {
        let (return_not_returns, returns) = returns.unwrap_or((None, Vec::new()));

        Box::new(FunctionDefinition{
            loc: Loc::File(file_no, l, r),
            ty: FunctionTy::Function,
            name,
            name_loc: Loc::File(file_no, nl, nr),
            params,
            attributes,
            return_not_returns,
            returns,
            body,
        })
    },
    // Old-style fallback function without name. Sema will give a nice error message
    // with some instructions how to update your syntax
    <l:@L> <ft:FunctionType> <r:@R> <body:BlockStatementOrSemiColon> => {
        match ft {
            // we're dropping the trailing attributes, but this production is for
            // generating an error messages
            Type::Function { params, attributes, returns, .. } => {
                Box::new(FunctionDefinition{
                    loc: Loc::File(file_no, l, r),
                    ty: FunctionTy::Function,
                    name: None,
                    name_loc: Loc::File(file_no, l, r),
                    params,
                    attributes,
                    return_not_returns: None,
                    returns: match returns {
                        Some((returns, _)) => returns,
                        None => Vec::new(),
                    },
                    body,
                })
            },
            _ => unreachable!(),
        }
    }
}

Using: Box<Using> = {
    <l:@L> "using" <list:UsingList> "for" <ty:AnyType> <global:SolIdentifier?> <r:@R> ";" => Box::new(Using {
        loc: Loc::File(file_no, l, r),
        list,
        ty: Some(ty),
        global,
    }),
    <l:@L> "using" <list:UsingList> "for" "*" <global:SolIdentifier?> <r:@R> ";" => Box::new(Using {
        loc: Loc::File(file_no, l, r),
        list,
        ty: None,
        global,
    }),
  <l:@L> "using" <false_token:!> <r:@R> ";" => {
        parser_errors.push(false_token);
        let list = UsingList::Error();
        let global = None;
        Box::new(Using {
            loc: Loc::File(file_no, l, r),
            list,
            ty: None,
            global,
        })
    },
}

UsingList: UsingList = {
    SolIdentifierPath => UsingList::Library(<>),
    "{" <Comma<SolIdentifierPath>> "}" => UsingList::Functions(<>),
}

BlockStatement: Statement = {
    <l:@L> "{" <statements:Statement*> "}" <r:@R> => {
        Statement::Block { loc: Loc::File(file_no, l, r), unchecked: false, statements }
    },
    <l:@L> "{" <v:CommaOne<NamedArgument>> "}" <r:@R> => Statement::Args(Loc::File(file_no, l, r), v),
}

OpenStatement: Statement = {
    <l:@L> "if" "(" <cond:Expression> ")" <body:Statement> <r:@R> => {
        Statement::If(Loc::File(file_no, l, r), cond, Box::new(body), None)
    },
    <l:@L> "if" "(" <cond:Expression> ")" <body:ClosedStatement> "else" <o:OpenStatement> <r:@R> => {
        Statement::If(Loc::File(file_no, l, r), cond, Box::new(body), Some(Box::new(o)))
    },
    <l:@L> "for" "(" <b:SimpleStatement?> ";" <c:Expression?> ";" <n:SimpleStatement?> ")" <block:OpenStatement> <r:@R> => {
        Statement::For(Loc::File(file_no, l, r), b.map(Box::new), c.map(Box::new), n.map(Box::new), Some(Box::new(block)))
    },
    <l:@L> "while" "(" <e:Expression> ")" <b:OpenStatement> <r:@R> => {
        Statement::While(Loc::File(file_no, l, r), e, Box::new(b))
    }
}

ClosedStatement: Statement = {
    NonIfStatement,
    <l:@L> "if" "(" <cond:Expression> ")" <body:ClosedStatement> "else" <o:ClosedStatement> <r:@R> => {
        Statement::If(Loc::File(file_no, l, r), cond, Box::new(body), Some(Box::new(o)))
    },
    <l:@L> "while" "(" <e:Expression> ")" <b:ClosedStatement> <r:@R> => {
        Statement::While(Loc::File(file_no, l, r), e, Box::new(b))
    },
    <l:@L> "for" "(" <b:SimpleStatement?> ";" <c:Expression?> ";" <n:SimpleStatement?> ")" <block:ClosedStatement> <r:@R> => {
        Statement::For(Loc::File(file_no, l, r), b.map(Box::new), c.map(Box::new), n.map(Box::new), Some(Box::new(block)))
    },
    <l:@L> "for" "(" <b:SimpleStatement?> ";" <c:Expression?> ";" <n:SimpleStatement?> ")" <r:@R> ";" => {
        Statement::For(Loc::File(file_no, l, r), b.map(Box::new), c.map(Box::new), n.map(Box::new), None)
    }
}

Statement: Statement = {
    OpenStatement,
    ClosedStatement,
    <l:@L> <false_token:!> <r:@R> => {
        parser_errors.push(false_token);
        Statement::Error(Loc::File(file_no, l, r))
    },
}

SimpleStatement: Statement = {
    <l:@L> <v:VariableDeclaration> <e:("=" <Expression>)?> <r:@R> => {
        Statement::VariableDefinition(Loc::File(file_no, l, r), v, e)
    },
    <l:@L> <e:Expression> <r:@R> => {
        Statement::Expression(Loc::File(file_no, l, r), e)
    }
}

CatchClause: CatchClause = {
    <l:@L> "catch" <param:("(" <Parameter> ")")?> <block:BlockStatement> <r:@R> => {
        CatchClause::Simple(Loc::File(file_no, l, r), param, block)
    },
    <l:@L> "catch" <id:SolIdentifier> "(" <param:Parameter> ")" <block:BlockStatement> <r:@R> => {
        CatchClause::Named(Loc::File(file_no, l, r), id, param, block)
    }
}

TryReturns: (Vec<(Loc, Option<Parameter>)>, Box<Statement>) = {
    "returns" <list:ParameterList> <ok:BlockStatement> => (list, Box::new(ok))
}

TryExpression: Expression = {
    <a:@L> "new" <call:FunctionCall> <b:@R> => Expression::New(Loc::File(file_no, a, b), Box::new(call)),
    FunctionCall
}

NonIfStatement: Statement = {
    BlockStatement => <>,
    <l:@L> "unchecked" "{" <statements:Statement*> "}" <r:@R> => {
        Statement::Block { loc: Loc::File(file_no, l, r), unchecked: true, statements }
    },
    <l:@L> "assembly" <dialect:StringLiteral?> <flags:AssemblyFlags?> <block:YulBlock> <r:@R> => {
        Statement::Assembly {
            loc: Loc::File(file_no, l, r),
            dialect: dialect.map(|dialect| dialect.to_owned()),
            flags: flags.map(|flag| flag.to_owned()),
            block
        }
    },
    <SimpleStatement> ";" => <>,
    <l:@L> "do" <b:Statement> "while" "(" <e:Expression> ")" <r:@R> ";" => {
        Statement::DoWhile(Loc::File(file_no, l, r), Box::new(b), e)
    },
    <l:@L> "continue" <r:@R> ";" => {
        Statement::Continue(Loc::File(file_no, l, r))
    },
    <l:@L> "break" <r:@R> ";" => {
        Statement::Break(Loc::File(file_no, l, r))
    },
    <l:@L> "return" <r:@R> ";" => {
        Statement::Return(Loc::File(file_no, l, r), None)
    },
    <l:@L> "return" <e:Expression> <r:@R> ";" => {
        Statement::Return(Loc::File(file_no, l, r), Some(e))
    },
    <l:@L> "try" <e:TryExpression> <returns:TryReturns?> <clauses:CatchClause+> <r:@R> => {
        Statement::Try(Loc::File(file_no, l, r), e, returns, clauses)
    },
    <l:@L> "emit" <ty:FunctionCall> <r:@R> ";" => {
        Statement::Emit(Loc::File(file_no, l, r), ty)
    },
    <l:@L> "revert" <error:SolIdentifierPath?> "(" <v:Comma<Expression>> ")" <r:@R> ";" => {
        Statement::Revert(Loc::File(file_no, l, r), error, v)
    },
    <l:@L> "revert" <error:SolIdentifierPath?> "(" "{" <v:Comma<NamedArgument>> "}" ")" <r:@R> ";" => {
        Statement::RevertNamedArgs(Loc::File(file_no, l, r), error, v)
    },
}

AssemblyFlags: Vec<StringLiteral> = {
    "(" <CommaOne<StringLiteral>> ")" => <>,
}

YulIdentifier: Identifier = {
    <l:@L> <n:identifier> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: n.to_string()},
    <l:@L> "return" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "return".to_string()},
    <l:@L> "revert" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()},
    <l:@L> "bool" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "bool".to_string()},
    <l:@L> "address" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "address".to_string()},
    <l:@L> "byte" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "byte".to_string()},
}

YulStatement: YulStatement = {
    <YulBlock> => YulStatement::Block(<>),
    <YulVariableDeclaration> => <>,
    <YulAssignment> => <>,
    <YulFunctionCall> => YulStatement::FunctionCall(Box::new(<>)),
    <YulIf> => <>,
    <YulFor> => <>,
    <YulSwitch> => <>,
    <YulFunctionDefinition> => <>,
    <l:@L> "leave" <r:@R> => {
        YulStatement::Leave(Loc::File(file_no, l, r))
    },
    <l:@L> "break" <r:@R> => {
        YulStatement::Break(Loc::File(file_no, l, r))
    },
    <l:@L> "continue" <r:@R> => {
        YulStatement::Continue(Loc::File(file_no, l, r))
    },
    <l:@L><false_token:!><r:@R> => {
        parser_errors.push(false_token);
        YulStatement::Error(Loc::File(file_no, l ,r))
    },
}

YulBlock: YulBlock = {
    <l:@L> "{" <statements:YulStatement*> "}" <r:@R> => YulBlock{loc: Loc::File(file_no, l, r), statements},
}

YulLiteral: YulExpression = {
    <a:@L> "true" <t_type:(":" <YulIdentifier>)?> <b:@R> => YulExpression::BoolLiteral(Loc::File(file_no, a, b), true, t_type),
    <a:@L> "false" <t_type:(":" <YulIdentifier>)?> <b:@R> => YulExpression::BoolLiteral(Loc::File(file_no, a, b), false, t_type),
    <l:@L> <n:number> <t_type:(":" <YulIdentifier>)?> <r:@R> => {
        let integer: String = n.0.chars().filter(|v| *v != '_').collect();
        let exp: String = n.1.chars().filter(|v| *v != '_').collect();

        YulExpression::NumberLiteral(Loc::File(file_no, l, r), integer, exp, t_type)
    },
    <l:@L> <n:hexnumber> <t_type:(":" <YulIdentifier>)?> <r:@R> => {
        YulExpression::HexNumberLiteral(Loc::File(file_no, l, r), n.to_owned(), t_type)
    },
    <l:@L> <s:hexstring> <t_type:(":" <YulIdentifier>)?> <r:@R> => {
        let v = s.to_string();
        let hex_len = v.len() - 5;
        YulExpression::HexStringLiteral(HexLiteral{ loc: Loc::File(file_no, l, r), hex: v.chars().skip(4).filter(|c| *c != '_' && *c != '"' && *c != '\'').collect()},
        t_type)
    },
    <str:StringLiteral> <t_type:(":" <YulIdentifier>)?> => {
        YulExpression::StringLiteral(str, t_type)
    },
}

YulFunctionCall: YulFunctionCall = {
    <l:@L> <id:YulIdentifier> "(" <arguments:Comma<YulExpression>> ")" <r:@R> => {
        YulFunctionCall{loc: Loc::File(file_no, l, r), id, arguments}
    }
}

YulPath: YulExpression = {
    <YulIdentifier> => YulExpression::Variable(<>),
    <l:@L> <array:YulPath> "." <member:YulIdentifier> <r:@R> => {
        YulExpression::SuffixAccess(Loc::File(file_no, l, r), Box::new(array), member)
    },
}

YulExpression: YulExpression = {
    <YulPath> => <>,
    <YulFunctionCall> => YulExpression::FunctionCall(Box::new(<>)),
    <YulLiteral> => <>,
}

YulFunctionDefinition: YulStatement = {
    <l:@L> "function" <name: YulIdentifier> "(" <params: YulTypedIdentifierList?> ")" <returns:("->" <YulTypedIdentifierList>)?>
    <body: YulBlock> <r:@R> => {
        YulStatement::FunctionDefinition(Box::new(YulFunctionDefinition {
            loc: Loc::File(file_no, l, r),
            id: name,
            params: params.unwrap_or_default(),
            returns: returns.unwrap_or_default(),
            body
        }))
    }
}

YulTypedIdentifierList: Vec<YulTypedIdentifier> = {
    CommaOne<<YulType>> => <>
}

YulType: YulTypedIdentifier = {
    <l:@L> <var_name:YulIdentifier> ":" <var_type:YulIdentifier> <r:@R> => {
       YulTypedIdentifier {
            loc: Loc::File(file_no, l, r),
            id: var_name, ty: Some(var_type)
        }
    },
    <l:@L> <id:YulIdentifier> <r:@R> => {
       YulTypedIdentifier{loc: Loc::File(file_no, l, r), id, ty: None}
    },
}

YulSwitch: YulStatement = {
    <l:@L> "switch" <condition:YulExpression> <default:YulSwitchDefault> <r:@R> =>
    YulStatement::Switch(YulSwitch{
        loc: Loc::File(file_no, l, r),
        condition,
        cases: Vec::new(),
        default: Some(default)
    }),
    <l:@L> "switch" <condition:YulExpression> <cases:YulSwitchCase+> <default:YulSwitchDefault?> <r:@R> =>
    YulStatement::Switch(YulSwitch{
        loc: Loc::File(file_no, l, r),
        condition,
        cases,
        default
    }),
}

YulSwitchDefault: YulSwitchOptions = {
    <l:@L> "default" <body:YulBlock> <r:@R> => {
        YulSwitchOptions::Default(Loc::File(file_no, l, r), body)
    }
}

YulSwitchCase: YulSwitchOptions = {
    <l:@L> "case" <case:YulLiteral> <body:YulBlock> <r:@R> => {
        YulSwitchOptions::Case(Loc::File(file_no, l, r), case, body)
    }
}

YulFor: YulStatement = {
    <l:@L> "for" <init:YulBlock> <cond:YulExpression> <post_iter:YulBlock> <body:YulBlock> <r:@R> => {
        YulStatement::For(YulFor{
            loc: Loc::File(file_no, l, r),
            init_block: init,
            condition: cond,
            post_block: post_iter,
            execution_block: body
        })
    },
}

YulIf: YulStatement = {
    <l:@L> "if" <cond:YulExpression> <body:YulBlock> <r:@R> => {
        YulStatement::If(Loc::File(file_no, l, r), cond, body)
    },
}

YulAssignment: YulStatement = {
    <l:@L> <paths:CommaOne<YulPath>> ":=" <expr:YulExpression> <r:@R> => {
        YulStatement::Assign(Loc::File(file_no, l, r), paths, expr)
    }
}

YulVariableDeclaration: YulStatement = {
    <l:@L>  "let" <names:YulTypedIdentifierList> <expr:(":=" <YulExpression>)?> <r:@R> => {
        YulStatement::VariableDeclaration(Loc::File(file_no, l, r), names, expr)
    }
}


Comma<T>: Vec<T> = {
    => Vec::new(),
    CommaOne<T> => <>,
};

CommaOne<T>: Vec<T> = {
    <e:T> <v:("," <T>)*> => {
        let mut v = v;
        v.insert(0, e);
        v
    }
};

CommaTwo<T>: Vec<T> = {
    <e:T> <v:("," <T>)+> => {
        let mut v = v;
        v.insert(0, e);
        v
    }
};

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

    enum Token<'input> {
        identifier => Token::Identifier(<&'input str>),
        string => Token::StringLiteral(<bool>, <&'input str>),
        hexstring => Token::HexLiteral(<&'input str>),
        address => Token::AddressLiteral(<&'input str>),
        number => Token::Number(<&'input str>, <&'input str>),
        rational => Token::RationalNumber(<&'input str>, <&'input str>, <&'input str>),
        hexnumber => Token::HexNumber(<&'input str>),
        ";" => Token::Semicolon,
        "{" => Token::OpenCurlyBrace,
        "}" => Token::CloseCurlyBrace,
        "(" => Token::OpenParenthesis,
        ")" => Token::CloseParenthesis,
        "=" => Token::Assign,
        "==" => Token::Equal,
        "=>" => Token::Arrow,
        "->" => Token::YulArrow,
        "|=" => Token::BitwiseOrAssign,
        "^=" => Token::BitwiseXorAssign,
        "&=" => Token::BitwiseAndAssign,
        "<<=" => Token::ShiftLeftAssign,
        ">>=" => Token::ShiftRightAssign,
        "+=" => Token::AddAssign,
        "-=" => Token::SubtractAssign,
        "*=" => Token::MulAssign,
        "/=" => Token::DivideAssign,
        "%=" => Token::ModuloAssign,
        "?" => Token::Question,
        ":" => Token::Colon,
        ":=" => Token::ColonAssign,
        "||" => Token::Or,
        "&&" => Token::And,
        "!=" => Token::NotEqual,
        "<" => Token::Less,
        "<=" => Token::LessEqual,
        ">" => Token::More,
        ">=" => Token::MoreEqual,
        "|" => Token::BitwiseOr,
        "&" => Token::BitwiseAnd,
        "^" => Token::BitwiseXor,
        "<<" => Token::ShiftLeft,
        ">>" => Token::ShiftRight,
        "+" => Token::Add,
        "-" => Token::Subtract,
        "*" => Token::Mul,
        "/" => Token::Divide,
        "%" => Token::Modulo,
        "**" => Token::Power,
        "!" => Token::Not,
        "~" => Token::Complement,
        "++" => Token::Increment,
        "--" => Token::Decrement,
        "[" => Token::OpenBracket,
        "]" => Token::CloseBracket,
        "." => Token::Member,
        "," => Token::Comma,
        Uint => Token::Uint(<u16>),
        Int => Token::Int(<u16>),
        Bytes => Token::Bytes(<u8>),
        "byte" => Token::Byte,
        "struct" => Token::Struct,
        "memory" => Token::Memory,
        "calldata" => Token::Calldata,
        "storage" => Token::Storage,
        "import" => Token::Import,
        "contract" => Token::Contract,
        "pragma" => Token::Pragma,
        "bool" => Token::Bool,
        "address" => Token::Address,
        "string" => Token::String,
        "bytes" => Token::DynamicBytes,
        "delete" => Token::Delete,
        "new" => Token::New,
        "interface" => Token::Interface,
        "library" => Token::Library,
        "event" => Token::Event,
        "error" => Token::Error,
        "enum" => Token::Enum,
        "type" => Token::Type,
        "public" => Token::Public,
        "private" => Token::Private,
        "external" => Token::External,
        "internal" => Token::Internal,
        "constant" => Token::Constant,
        "true" => Token::True,
        "false" => Token::False,
        "pure" => Token::Pure,
        "view" => Token::View,
        "payable" => Token::Payable,
        "constructor" => Token::Constructor,
        "function" => Token::Function,
        "returns" => Token::Returns,
        "return" => Token::Return,
        "revert" => Token::Revert,
        "if" => Token::If,
        "for" => Token::For,
        "while" => Token::While,
        "else" => Token::Else,
        "do" => Token::Do,
        "continue" => Token::Continue,
        "break" => Token::Break,
        "throw" => Token::Throw,
        "emit" => Token::Emit,
        "anonymous" => Token::Anonymous,
        "indexed" => Token::Indexed,
        "mapping" => Token::Mapping,
        "try" => Token::Try,
        "catch" => Token::Catch,
        "receive" => Token::Receive,
        "fallback" => Token::Fallback,
        "seconds" => Token::Seconds,
        "minutes" => Token::Minutes,
        "hours" => Token::Hours,
        "days" => Token::Days,
        "weeks" => Token::Weeks,
        "wei" => Token::Wei,
        "gwei" => Token::Gwei,
        "ether" => Token::Ether,
        "this" => Token::This,
        "as" => Token::As,
        "is" => Token::Is,
        "abstract" => Token::Abstract,
        "virtual" => Token::Virtual,
        "override" => Token::Override,
        "using" => Token::Using,
        "modifier" => Token::Modifier,
        "immutable" => Token::Immutable,
        "unchecked" => Token::Unchecked,
        "assembly" => Token::Assembly,
        "let" => Token::Let,
        "leave" => Token::Leave,
        "switch" => Token::Switch,
        "case" => Token::Case,
        "default" => Token::Default,
    }
}
