//! The YUL AST visitor interface definitions.

use crate::{
    lexer::token::location::Location,
    parser::{
        identifier::Identifier,
        statement::{
            assignment::Assignment,
            block::Block,
            code::Code,
            expression::{function_call::FunctionCall, literal::Literal, Expression},
            for_loop::ForLoop,
            function_definition::FunctionDefinition,
            if_conditional::IfConditional,
            object::Object,
            switch::{case::Case, Switch},
            variable_declaration::VariableDeclaration,
            Statement,
        },
    },
};

/// This trait is implemented by all AST node types.
///
/// It allows to define how the AST is visited on a per-node basis.
pub trait AstNode: std::fmt::Debug {
    /// Accept the given [AstVisitor].
    ///
    /// This is supposed to call the corresponding `AstVisitor::visit_*` method.
    fn accept(&self, ast_visitor: &mut impl AstVisitor);

    /// Let any child nodes accept the given [AstVisitor].
    ///
    /// This is supposed visit child nodes in the correct order.
    ///
    /// Visitor implementations call this method for traversing.
    fn visit_children(&self, _ast_visitor: &mut impl AstVisitor) {}

    /// Returns the lexer (source) location of the node.
    fn location(&self) -> Location;
}

/// This trait allows implementing custom AST visitor logic for each node type.
///
/// The visitor can call the nodes [AstNode::visit_children] method (from any
/// other trait method). This simplifies the implementation of AST visitors.
///
/// Default implementations which do nothing except accepting the visitor via the
/// [AstVisitor::visit] method are provided for each node type.
///
/// The [AstVisitor::visit] method is the generic visitor method, seen by all
/// nodes.
///
/// Visited nodes are given read only access (non-mutable references); it's a
/// compiler design practice to not mutate the AST after parsing.
/// Instead, mutable access to the [AstVisitor] instance itself is provided,
/// allowing to build a new representation if needed.
///
/// # Example
///
/// ```rust
/// use revive_yul::visitor::*;
///
/// /// A very simple visitor that counts all nodes in the AST.
/// #[derive(Default, Debug)]
/// pub struct CountVisitor(usize);
///
/// impl AstVisitor for CountVisitor {
///     /// Increment the counter for ech node we visit.
///     fn visit(&mut self, node: &impl AstNode) {
///         node.visit_children(self);
///         self.0 += 1;
///     }
///
///     /*
///
///     /// If we were interested in a per-statement breakdown of the AST,
///     /// we would implement `visit_*` methods to cover each node like this:
///     fn visit_block(&mut self, node: &Block) {
///         self.visit_children(node);
///         self.block_count += 1;
///     }
///
///     */
/// }
///
/// ```
pub trait AstVisitor {
    /// The generic visitor logic for all node types is executed upon visiting any statement.
    fn visit(&mut self, node: &impl AstNode);

    /// The logic to execute upon visiting [Assignment] statements.
    fn visit_assignment(&mut self, node: &Assignment) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [Block].
    fn visit_block(&mut self, node: &Block) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [Case] statements.
    fn visit_case(&mut self, node: &Case) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [Code] statements.
    fn visit_code(&mut self, node: &Code) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [Expression].
    fn visit_expression(&mut self, node: &Expression) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [ForLoop] statements.
    fn visit_for_loop(&mut self, node: &ForLoop) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [FunctionCall] statements.
    fn visit_function_call(&mut self, node: &FunctionCall) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [FunctionDefinition].
    fn visit_function_definition(&mut self, node: &FunctionDefinition) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [Identifier].
    fn visit_identifier(&mut self, node: &Identifier) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [IfConditional] statements.
    fn visit_if_conditional(&mut self, node: &IfConditional) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [Literal].
    fn visit_literal(&mut self, node: &Literal) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [Object] definitions.
    fn visit_object(&mut self, node: &Object) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any YUL [Statement].
    fn visit_statement(&mut self, node: &Statement) {
        self.visit(node);
    }

    /// The logic to execute upon visiting [Switch] statements.
    fn visit_switch(&mut self, node: &Switch) {
        self.visit(node);
    }

    /// The logic to execute upon visiting any [VariableDeclaration].
    fn visit_variable_declaration(&mut self, node: &VariableDeclaration) {
        self.visit(node);
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        lexer::Lexer,
        parser::{
            identifier::Identifier,
            statement::{
                assignment::Assignment,
                block::Block,
                code::Code,
                expression::{function_call::FunctionCall, literal::Literal, Expression},
                for_loop::ForLoop,
                function_definition::FunctionDefinition,
                if_conditional::IfConditional,
                object::Object,
                switch::{case::Case, Switch},
                variable_declaration::VariableDeclaration,
                Statement,
            },
        },
    };

    use super::{AstNode, AstVisitor};

    /// The [Printer] visitor builds the AST back into its textual representation.
    #[derive(Default)]
    struct Printer {
        /// The print buffer.
        buffer: String,
        /// The current indentation level.
        indentation: usize,
    }

    impl Printer {
        /// Append a newline with the current identation to the print buffer.
        fn newline(&mut self) {
            self.buffer.push('\n');
            self.indent();
        }

        /// Append the current identation to the print buffer.
        fn indent(&mut self) {
            for _ in 0..self.indentation {
                self.buffer.push_str("  ");
            }
        }

        /// Append the given `nodes` comma-separated.
        fn separate(&mut self, nodes: &[impl AstNode]) {
            for (index, argument) in nodes.iter().enumerate() {
                argument.accept(self);

                if index < nodes.len() - 1 {
                    self.buffer.push_str(", ");
                }
            }
        }
    }

    impl AstVisitor for Printer {
        fn visit(&mut self, node: &impl AstNode) {
            node.accept(self);
        }

        fn visit_assignment(&mut self, node: &Assignment) {
            self.separate(&node.bindings);

            self.buffer.push_str(" := ");

            node.initializer.visit_children(self);
        }

        fn visit_block(&mut self, node: &Block) {
            self.newline();
            self.buffer.push('{');
            self.indentation += 1;

            node.visit_children(self);

            self.indentation -= 1;
            self.newline();
            self.buffer.push('}');
        }

        fn visit_case(&mut self, node: &Case) {
            self.newline();
            self.buffer.push_str("case ");
            node.visit_children(self);
        }

        fn visit_code(&mut self, node: &Code) {
            self.buffer.push_str("code ");
            node.visit_children(self);
        }

        fn visit_expression(&mut self, node: &Expression) {
            node.visit_children(self);
        }

        fn visit_for_loop(&mut self, node: &ForLoop) {
            self.buffer.push_str("for ");
            node.visit_children(self);
        }

        fn visit_function_call(&mut self, node: &FunctionCall) {
            self.buffer.push_str(&format!("{}", node.name));
            self.buffer.push('(');

            self.separate(&node.arguments);

            self.buffer.push(')');
        }

        fn visit_function_definition(&mut self, node: &FunctionDefinition) {
            self.buffer
                .push_str(&format!("function {}", node.identifier));

            self.buffer.push('(');
            self.separate(&node.arguments);
            self.buffer.push(')');

            self.buffer.push_str(" -> ");
            self.separate(&node.result);

            node.body.accept(self);
        }

        fn visit_identifier(&mut self, node: &Identifier) {
            self.buffer.push_str(&node.inner);
        }

        fn visit_if_conditional(&mut self, node: &IfConditional) {
            self.buffer.push_str("if ");
            node.visit_children(self);
        }

        fn visit_literal(&mut self, node: &Literal) {
            self.buffer.push_str(&format!("{}", node.inner));
        }

        fn visit_object(&mut self, node: &Object) {
            self.newline();
            self.buffer.push_str("object \"");
            self.buffer.push_str(&node.identifier);
            self.buffer.push_str("\" {");
            self.indentation += 1;
            self.newline();

            node.visit_children(self);

            self.indentation -= 1;
            self.newline();
            self.buffer.push('}');
        }

        fn visit_statement(&mut self, node: &Statement) {
            self.newline();
            node.visit_children(self);
        }

        fn visit_switch(&mut self, node: &Switch) {
            self.buffer.push_str("switch ");
            node.visit_children(self);
        }

        fn visit_variable_declaration(&mut self, node: &VariableDeclaration) {
            self.buffer.push_str("let ");
            self.separate(&node.bindings);

            if let Some(initializer) = node.expression.as_ref() {
                self.buffer.push_str(" := ");
                initializer.visit_children(self);
            }
        }
    }

    const ERC20: &str = r#"/// @use-src 0:"crates/integration/contracts/ERC20.sol"
object "ERC20_247" {
    code {
        {
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            mstore(64, memoryguard(0x80))
            if callvalue() { revert(0, 0) }
            let oldLen := extract_byte_array_length(sload(/** @src 0:1542:1563  "\"Solidity by Example\"" */ 0x03))
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            if gt(oldLen, 31)
            {
                mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1542:1563  "\"Solidity by Example\"" */ 0x03)
                /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                let data := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0x20)
                let deleteStart := add(data, 1)
                deleteStart := data
                let _1 := add(data, shr(5, add(oldLen, 31)))
                let start := data
                for { } lt(start, _1) { start := add(start, 1) }
                {
                    sstore(start, /** @src -1:-1:-1 */ 0)
                }
            }
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            sstore(/** @src 0:1542:1563  "\"Solidity by Example\"" */ 0x03, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ add("Solidity by Example", 38))
            let oldLen_1 := extract_byte_array_length(sload(/** @src 0:1592:1601  "\"SOLBYEX\"" */ 0x04))
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            if gt(oldLen_1, 31)
            {
                mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1592:1601  "\"SOLBYEX\"" */ 0x04)
                /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                let data_1 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0x20)
                let deleteStart_1 := add(data_1, 1)
                deleteStart_1 := data_1
                let _2 := add(data_1, shr(5, add(oldLen_1, 31)))
                let start_1 := data_1
                for { } lt(start_1, _2) { start_1 := add(start_1, 1) }
                {
                    sstore(start_1, /** @src -1:-1:-1 */ 0)
                }
            }
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            sstore(/** @src 0:1592:1601  "\"SOLBYEX\"" */ 0x04, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ add("SOLBYEX", 14))
            sstore(/** @src 0:1631:1633  "18" */ 0x05, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ or(and(sload(/** @src 0:1631:1633  "18" */ 0x05), /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ not(255)), /** @src 0:1631:1633  "18" */ 0x12))
            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
            let _3 := mload(64)
            let _4 := datasize("ERC20_247_deployed")
            codecopy(_3, dataoffset("ERC20_247_deployed"), _4)
            return(_3, _4)
        }
        function extract_byte_array_length(data) -> length
        {
            length := shr(1, data)
            let outOfPlaceEncoding := and(data, 1)
            if iszero(outOfPlaceEncoding) { length := and(length, 0x7f) }
            if eq(outOfPlaceEncoding, lt(length, 32))
            {
                mstore(0, shl(224, 0x4e487b71))
                mstore(4, 0x22)
                revert(0, 0x24)
            }
        }
    }
    /// @use-src 0:"crates/integration/contracts/ERC20.sol"
    object "ERC20_247_deployed" {
        code {
            {
                /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                mstore(64, memoryguard(0x80))
                if iszero(lt(calldatasize(), 4))
                {
                    switch shr(224, calldataload(0))
                    case 0x06fdde03 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
                        /// @src 0:1521:1563  "string public name = \"Solidity by Example\""
                        let value := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0
                        let offset := 0
                        offset := 0
                        let memPtr := mload(64)
                        let ret := 0
                        let slotValue := sload(/** @src 0:1521:1563  "string public name = \"Solidity by Example\"" */ 3)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let length := 0
                        length := shr(1, slotValue)
                        let outOfPlaceEncoding := and(slotValue, 1)
                        if iszero(outOfPlaceEncoding) { length := and(length, 0x7f) }
                        if eq(outOfPlaceEncoding, lt(length, 32))
                        {
                            mstore(0, shl(224, 0x4e487b71))
                            mstore(4, 0x22)
                            revert(0, 0x24)
                        }
                        mstore(memPtr, length)
                        switch outOfPlaceEncoding
                        case 0 {
                            mstore(add(memPtr, 32), and(slotValue, not(255)))
                            ret := add(add(memPtr, shl(5, iszero(iszero(length)))), 32)
                        }
                        case 1 {
                            mstore(0, /** @src 0:1521:1563  "string public name = \"Solidity by Example\"" */ 3)
                            /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                            let dataPos := keccak256(0, 32)
                            let i := 0
                            for { } lt(i, length) { i := add(i, 32) }
                            {
                                mstore(add(add(memPtr, i), 32), sload(dataPos))
                                dataPos := add(dataPos, 1)
                            }
                            ret := add(add(memPtr, i), 32)
                        }
                        let newFreePtr := add(memPtr, and(add(sub(ret, memPtr), 31), not(31)))
                        if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))
                        {
                            mstore(0, shl(224, 0x4e487b71))
                            mstore(4, 0x41)
                            revert(0, 0x24)
                        }
                        mstore(64, newFreePtr)
                        value := memPtr
                        let memPos := mload(64)
                        return(memPos, sub(abi_encode_string(memPos, memPtr), memPos))
                    }
                    case 0x095ea7b3 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) }
                        let value0 := abi_decode_address_3473()
                        let value_1 := calldataload(36)
                        mstore(0, /** @src 0:1974:1984  "msg.sender" */ caller())
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(32, /** @src 0:1964:1973  "allowance" */ 0x02)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let dataSlot := keccak256(0, 64)
                        /// @src 0:1964:1994  "allowance[msg.sender][spender]"
                        let dataSlot_1 := /** @src -1:-1:-1 */ 0
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ and(/** @src 0:1964:1994  "allowance[msg.sender][spender]" */ value0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1)))
                        mstore(0x20, /** @src 0:1964:1985  "allowance[msg.sender]" */ dataSlot)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        dataSlot_1 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0x40)
                        sstore(/** @src 0:1964:1994  "allowance[msg.sender][spender]" */ dataSlot_1, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ value_1)
                        /// @src 0:2018:2055  "Approval(msg.sender, spender, amount)"
                        let _1 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ mload(64)
                        mstore(_1, value_1)
                        /// @src 0:2018:2055  "Approval(msg.sender, spender, amount)"
                        log3(_1, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2018:2055  "Approval(msg.sender, spender, amount)" */ 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, /** @src 0:1974:1984  "msg.sender" */ caller(), /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ and(/** @src 0:2018:2055  "Approval(msg.sender, spender, amount)" */ value0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1)))
                        let memPos_1 := mload(64)
                        mstore(memPos_1, 1)
                        return(memPos_1, 32)
                    }
                    case 0x18160ddd {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
                        let _2 := sload(0)
                        let memPos_2 := mload(64)
                        mstore(memPos_2, _2)
                        return(memPos_2, 32)
                    }
                    case 0x23b872dd {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 96) { revert(0, 0) }
                        let value0_1 := abi_decode_address_3473()
                        let value1 := abi_decode_address()
                        let value_2 := calldataload(68)
                        let _3 := and(value0_1, sub(shl(160, 1), 1))
                        mstore(0, _3)
                        mstore(32, /** @src 0:2223:2232  "allowance" */ 0x02)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let dataSlot_2 := keccak256(0, 64)
                        /// @src 0:2223:2252  "allowance[sender][msg.sender]"
                        let dataSlot_3 := /** @src -1:-1:-1 */ 0
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ and(/** @src 0:2241:2251  "msg.sender" */ caller(), /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1)))
                        mstore(0x20, /** @src 0:2223:2252  "allowance[sender][msg.sender]" */ dataSlot_2)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        dataSlot_3 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0x40)
                        sstore(/** @src 0:2223:2252  "allowance[sender][msg.sender]" */ dataSlot_3, /** @src 0:2223:2262  "allowance[sender][msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2223:2252  "allowance[sender][msg.sender]" */ dataSlot_3), /** @src 0:2223:2262  "allowance[sender][msg.sender] -= amount" */ value_2))
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(0, _3)
                        mstore(32, 1)
                        let dataSlot_4 := keccak256(0, 64)
                        sstore(dataSlot_4, /** @src 0:2272:2299  "balanceOf[sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2272:2299  "balanceOf[sender] -= amount" */ dataSlot_4), value_2))
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let _4 := and(value1, sub(shl(160, 1), 1))
                        mstore(0, _4)
                        mstore(32, 1)
                        let dataSlot_5 := keccak256(0, 64)
                        sstore(dataSlot_5, /** @src 0:2309:2339  "balanceOf[recipient] += amount" */ checked_add_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2309:2339  "balanceOf[recipient] += amount" */ dataSlot_5), value_2))
                        /// @src 0:2354:2389  "Transfer(sender, recipient, amount)"
                        let _5 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ mload(64)
                        mstore(_5, value_2)
                        /// @src 0:2354:2389  "Transfer(sender, recipient, amount)"
                        log3(_5, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2354:2389  "Transfer(sender, recipient, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, _3, _4)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let memPos_3 := mload(64)
                        mstore(memPos_3, 1)
                        return(memPos_3, 32)
                    }
                    case 0x313ce567 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
                        let value_3 := and(sload(/** @src 0:1607:1633  "uint8 public decimals = 18" */ 5), /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0xff)
                        let memPos_4 := mload(64)
                        mstore(memPos_4, value_3)
                        return(memPos_4, 32)
                    }
                    case 0x42966c68 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) }
                        let value_4 := calldataload(4)
                        mstore(0, /** @src 0:2655:2665  "msg.sender" */ caller())
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(32, 1)
                        let dataSlot_6 := keccak256(0, 64)
                        sstore(dataSlot_6, /** @src 0:2645:2676  "balanceOf[msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2645:2676  "balanceOf[msg.sender] -= amount" */ dataSlot_6), value_4))
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        sstore(0, /** @src 0:2686:2707  "totalSupply -= amount" */ checked_sub_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(0), /** @src 0:2686:2707  "totalSupply -= amount" */ value_4))
                        /// @src 0:2722:2762  "Transfer(msg.sender, address(0), amount)"
                        let _6 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ mload(64)
                        mstore(_6, value_4)
                        /// @src 0:2722:2762  "Transfer(msg.sender, address(0), amount)"
                        log3(_6, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2722:2762  "Transfer(msg.sender, address(0), amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:2655:2665  "msg.sender" */ caller(), /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0)
                        return(0, 0)
                    }
                    case 0x70a08231 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) }
                        mstore(0, and(abi_decode_address_3473(), sub(shl(160, 1), 1)))
                        mstore(32, /** @src 0:1407:1448  "mapping(address => uint) public balanceOf" */ 1)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let _7 := sload(keccak256(0, 64))
                        let memPos_5 := mload(64)
                        mstore(memPos_5, _7)
                        return(memPos_5, 32)
                    }
                    case 0x95d89b41 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
                        /// @src 0:1569:1601  "string public symbol = \"SOLBYEX\""
                        let value_5 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0
                        let offset_1 := 0
                        offset_1 := 0
                        let memPtr_1 := mload(64)
                        let ret_1 := 0
                        let slotValue_1 := sload(4)
                        let length_1 := 0
                        length_1 := shr(1, slotValue_1)
                        let outOfPlaceEncoding_1 := and(slotValue_1, 1)
                        if iszero(outOfPlaceEncoding_1)
                        {
                            length_1 := and(length_1, 0x7f)
                        }
                        if eq(outOfPlaceEncoding_1, lt(length_1, 32))
                        {
                            mstore(0, shl(224, 0x4e487b71))
                            mstore(4, 0x22)
                            revert(0, 0x24)
                        }
                        mstore(memPtr_1, length_1)
                        switch outOfPlaceEncoding_1
                        case 0 {
                            mstore(add(memPtr_1, 32), and(slotValue_1, not(255)))
                            ret_1 := add(add(memPtr_1, shl(5, iszero(iszero(length_1)))), 32)
                        }
                        case 1 {
                            mstore(0, 4)
                            let dataPos_1 := keccak256(0, 32)
                            let i_1 := 0
                            for { } lt(i_1, length_1) { i_1 := add(i_1, 32) }
                            {
                                mstore(add(add(memPtr_1, i_1), 32), sload(dataPos_1))
                                dataPos_1 := add(dataPos_1, 1)
                            }
                            ret_1 := add(add(memPtr_1, i_1), 32)
                        }
                        let newFreePtr_1 := add(memPtr_1, and(add(sub(ret_1, memPtr_1), 31), not(31)))
                        if or(gt(newFreePtr_1, 0xffffffffffffffff), lt(newFreePtr_1, memPtr_1))
                        {
                            mstore(0, shl(224, 0x4e487b71))
                            mstore(4, 0x41)
                            revert(0, 0x24)
                        }
                        mstore(64, newFreePtr_1)
                        value_5 := memPtr_1
                        let memPos_6 := mload(64)
                        return(memPos_6, sub(abi_encode_string(memPos_6, memPtr_1), memPos_6))
                    }
                    case 0xa0712d68 {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) }
                        let value_6 := calldataload(4)
                        mstore(0, /** @src 0:2479:2489  "msg.sender" */ caller())
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(32, 1)
                        let dataSlot_7 := keccak256(0, 64)
                        sstore(dataSlot_7, /** @src 0:2469:2500  "balanceOf[msg.sender] += amount" */ checked_add_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2469:2500  "balanceOf[msg.sender] += amount" */ dataSlot_7), value_6))
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        sstore(0, /** @src 0:2510:2531  "totalSupply += amount" */ checked_add_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(0), /** @src 0:2510:2531  "totalSupply += amount" */ value_6))
                        /// @src 0:2546:2586  "Transfer(address(0), msg.sender, amount)"
                        let _8 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ mload(64)
                        mstore(_8, value_6)
                        /// @src 0:2546:2586  "Transfer(address(0), msg.sender, amount)"
                        log3(_8, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2546:2586  "Transfer(address(0), msg.sender, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0, /** @src 0:2479:2489  "msg.sender" */ caller())
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        return(0, 0)
                    }
                    case 0xa9059cbb {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) }
                        let value0_2 := abi_decode_address_3473()
                        let value_7 := calldataload(36)
                        mstore(0, /** @src 0:1734:1744  "msg.sender" */ caller())
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(32, 1)
                        let dataSlot_8 := keccak256(0, 64)
                        sstore(dataSlot_8, /** @src 0:1724:1755  "balanceOf[msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:1724:1755  "balanceOf[msg.sender] -= amount" */ dataSlot_8), value_7))
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let _9 := and(value0_2, sub(shl(160, 1), 1))
                        mstore(0, _9)
                        mstore(32, 1)
                        let dataSlot_9 := keccak256(0, 64)
                        sstore(dataSlot_9, /** @src 0:1765:1795  "balanceOf[recipient] += amount" */ checked_add_uint256(/** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sload(/** @src 0:1765:1795  "balanceOf[recipient] += amount" */ dataSlot_9), value_7))
                        /// @src 0:1810:1849  "Transfer(msg.sender, recipient, amount)"
                        let _10 := /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ mload(64)
                        mstore(_10, value_7)
                        /// @src 0:1810:1849  "Transfer(msg.sender, recipient, amount)"
                        log3(_10, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 32, /** @src 0:1810:1849  "Transfer(msg.sender, recipient, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:1734:1744  "msg.sender" */ caller(), /** @src 0:1810:1849  "Transfer(msg.sender, recipient, amount)" */ _9)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let memPos_7 := mload(64)
                        mstore(memPos_7, 1)
                        return(memPos_7, 32)
                    }
                    case 0xdd62ed3e {
                        if callvalue() { revert(0, 0) }
                        if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) }
                        let value0_3 := abi_decode_address_3473()
                        let value1_1 := abi_decode_address()
                        mstore(0, and(value0_3, sub(shl(160, 1), 1)))
                        mstore(32, /** @src 0:1454:1515  "mapping(address => mapping(address => uint)) public allowance" */ 2)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let dataSlot_10 := keccak256(0, 64)
                        /// @src 0:1454:1515  "mapping(address => mapping(address => uint)) public allowance"
                        let dataSlot_11 := /** @src -1:-1:-1 */ 0
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ and(/** @src 0:1454:1515  "mapping(address => mapping(address => uint)) public allowance" */ value1_1, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1)))
                        mstore(0x20, /** @src 0:1454:1515  "mapping(address => mapping(address => uint)) public allowance" */ dataSlot_10)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        dataSlot_11 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771  "contract ERC20 is IERC20 {..." */ 0x40)
                        let _11 := sload(/** @src 0:1454:1515  "mapping(address => mapping(address => uint)) public allowance" */ dataSlot_11)
                        /// @src 0:1347:2771  "contract ERC20 is IERC20 {..."
                        let memPos_8 := mload(64)
                        mstore(memPos_8, _11)
                        return(memPos_8, 32)
                    }
                }
                revert(0, 0)
            }
            function abi_encode_string(headStart, value0) -> tail
            {
                mstore(headStart, 32)
                let length := mload(value0)
                mstore(add(headStart, 32), length)
                mcopy(add(headStart, 64), add(value0, 32), length)
                mstore(add(add(headStart, length), 64), 0)
                tail := add(add(headStart, and(add(length, 31), not(31))), 64)
            }
            function abi_decode_address_3473() -> value
            {
                value := calldataload(4)
                if iszero(eq(value, and(value, sub(shl(160, 1), 1)))) { revert(0, 0) }
            }
            function abi_decode_address() -> value
            {
                value := calldataload(36)
                if iszero(eq(value, and(value, sub(shl(160, 1), 1)))) { revert(0, 0) }
            }
            function checked_sub_uint256(x, y) -> diff
            {
                diff := sub(x, y)
                if gt(diff, x)
                {
                    mstore(0, shl(224, 0x4e487b71))
                    mstore(4, 0x11)
                    revert(0, 0x24)
                }
            }
            function checked_add_uint256(x, y) -> sum
            {
                sum := add(x, y)
                if gt(x, sum)
                {
                    mstore(0, shl(224, 0x4e487b71))
                    mstore(4, 0x11)
                    revert(0, 0x24)
                }
            }
        }
        data ".metadata" hex"a264697066735822122050b3876a7c06489a119481ba2b8611bfb9d92d0624a61503d2b86a77af8e277164736f6c634300081c0033"
    }
}"#;

    /// Parsing the output of the print visitor as a basic integration test.
    #[test]
    fn print_visitor_works() {
        let mut printer = Printer::default();
        Object::parse(&mut Lexer::new(ERC20.into()), None)
            .unwrap()
            .accept(&mut printer);

        let mut printer2 = Printer::default();
        Object::parse(&mut Lexer::new(printer.buffer.clone()), None)
            .unwrap()
            .accept(&mut printer2);

        assert_eq!(
            printer.buffer, printer2.buffer,
            "the output from the printers must converge immediately"
        );

        assert!(
            !printer.buffer.is_empty(),
            "the printer must produce output"
        );
    }
}
