<commit_before>use parser::Identifier;
use operations::{Operation};
use operations::item_type::{ItemType, FuncArgType};
use operations::scope::{ScopeStack, ScopeItem, TypeId};

pub fn define_boolean(scope: &mut ScopeStack) -> TypeId {
    // Taking advantage of the scope system to simulate modules
    // This will be replaced with something better in:
    // https://github.com/brain-lang/brain/issues/37
    scope.push_scope();

    let bool_type = scope.declare_type(
        Identifier::from("bool"),
        ItemType::Primitive(1)
    );
    scope.register_primitive("bool", bool_type);

    scope.declare_constant(
        Identifier::from("true"),
        bool_type,
        vec![1u8]
    );

    scope.declare_constant(
        Identifier::from("false"),
        bool_type,
        vec![0u8]
    );

    let unit_type = scope.primitives().unit();

    scope.declare_builtin_function(
        // Special method for displaying this primitive (used from print/println)
        // This name is such that it could never be called directly
        // from the language itself
        Identifier::from("std::fmt::Display::print"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type)],
            return_type: unit_type,
        },
        move |scope, args, _| {
            match args[0] {
                ScopeItem::TypedBlock {memory, ..} => Ok(vec![
                    Operation::Increment {
                        target: memory.position(),
                        amount: b'0',
                    },
                    Operation::Write {
                        target: memory,
                    },
                    // Need to make sure we reverse the operation after so this function has no
                    // side effects
                    Operation::Decrement {
                        target: memory.position(),
                        amount: b'0',
                    },
                ]),
                ScopeItem::Constant {type_id, ref bytes} => {
                    let bool_type = scope.primitives().bool();
                    debug_assert_eq!(type_id, bool_type);

                    let u8_type = scope.primitives().u8();
                    let mem = scope.allocate(u8_type);

                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);
                    let value = b'0' + bytes[0];
                    Ok(vec![Operation::TempAllocate {
                        temp: mem,
                        body: vec![
                            Operation::Increment {
                                target: mem.position(),
                                amount: value,
                            },
                            Operation::Write {
                                target: mem,
                            },
                            Operation::Decrement {
                                target: mem.position(),
                                amount: value,
                            },
                        ],
                        should_zero: false,
                    }])
                },
                _ => unreachable!(),
            }
        }
    );

    scope.declare_builtin_function(
        Identifier::from("std::ops::Not"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();
            match args[0] {
                ScopeItem::TypedBlock {memory, ..} => {
                    let temp0 = scope.allocate(bool_type);
                    Ok(vec![Operation::TempAllocate {
                        temp: temp0,
                        body: vec![
                            // Need to copy to the destination because the algorithm consumes
                            // the operand
                            // After this copy the operand becomes `target` (x in the algorithm)
                            Operation::Copy {
                                source: memory.position(),
                                target: target.position(),
                                size: target.size(),
                            },

                            // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#x_.3D_not_x_.28boolean.2C_logical.29
                            //
                            // temp0[-]+
                            // x[-temp0-]temp0[x+temp0-]
                            //
                            // Results in x = !x and temp0 = 0
                            Operation::Increment {
                                target: temp0.position(),
                                amount: 1,
                            },
                            Operation::Loop {
                                cond: target.position(),
                                body: vec![
                                    Operation::Decrement {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                            Operation::Loop {
                                cond: temp0.position(),
                                body: vec![
                                    Operation::Increment {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                        ],
                        should_zero: false,
                    }])
                },
                ScopeItem::Constant {type_id, ref bytes} => {
                    debug_assert_eq!(type_id, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    Ok(match bytes[0] {
                        //TODO: Verify if this makes sense or if we should explicitly zero
                        1 => Vec::new(),
                        0 => vec![
                            Operation::Increment {
                                target: target.position(),
                                amount: 1,
                            },
                        ],
                        _ => unreachable!(),
                    })
                },
                _ => unreachable!(),
            }
        }
    );

    // boolean and operator (operator&&) and boolean or operator (operator||)
    // These operations have special names because they are not regular functions
    // that can be defined or overloaded
    // The reason these are not definable is because they have to support short
    // circuiting. This behaviour cannot be modelled by a trait, so these special
    // operators are not definable by the user.
    scope.declare_builtin_function(
        Identifier::from("operator||"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type), FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();

            Ok(match (&args[0], &args[1]) {
                (&ScopeItem::Constant {type_id: xt, bytes: ref xb}, &ScopeItem::Constant {type_id: yt, bytes: ref yb}) => {
                    debug_assert_eq!(xt, bool_type);
                    debug_assert_eq!(yt, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(xb.len(), 1);
                    debug_assert_eq!(yb.len(), 1);

                    // The `> 0` converts the byte-representation into a Rust boolean
                    let x = xb[0] > 0;
                    let y = yb[0] > 0;

                    match x || y {
                        false => Vec::new(),
                        true => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                    }
                },
                /// Thanks to certain properties of booleans, we can evaluate certain things during
                /// compilation and avoid a lot of extra computation.
                (&ScopeItem::Constant {type_id: const_type, ref bytes}, &ScopeItem::TypedBlock {type_id: other_type, memory}) |
                (&ScopeItem::TypedBlock {type_id: other_type, memory}, &ScopeItem::Constant {type_id: const_type, ref bytes}) => {
                    debug_assert_eq!(const_type, bool_type);
                    debug_assert_eq!(other_type, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    match bytes[0] {
                        // If either operand is true, the || operator always returns true
                        1 => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                        // If either operand is false, the || operator always returns its other operand
                        0 => vec![Operation::Copy {
                            source: memory.position(),
                            target: target.position(),
                            size: memory.size(),
                        }],
                        _ => unreachable!(),
                    }
                },
                (&ScopeItem::TypedBlock {memory: x, ..}, &ScopeItem::TypedBlock {memory: y, ..}) => {
                    let temp0 = scope.allocate(bool_type);
                    vec![Operation::TempAllocate {
                        temp: temp0,
                        body: vec![
                            // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#z_.3D_x_or_y_.28boolean.2C_logical.29
                            //
                            // z[-]
                            // temp0[-]+
                            // x[z+temp0-x-]
                            // temp0[-
                            //  y[z+y-]
                            // ]
                            // y[-]
                            Operation::Increment {
                                target: temp0.position(),
                                amount: 1,
                            },
                            Operation::Loop {
                                cond: x.position(),
                                body: vec![
                                    Operation::Increment {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: x.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                            Operation::Loop {
                                cond: temp0.position(),
                                body: vec![
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                    Operation::Loop {
                                        cond: y.position(),
                                        body: vec![
                                            Operation::Increment {
                                                target: target.position(),
                                                amount: 1,
                                            },
                                            Operation::Decrement {
                                                target: y.position(),
                                                amount: 1,
                                            },
                                        ],
                                    },
                                ],
                            },
                            Operation::Zero {
                                target: y,
                            }
                        ],
                        should_zero: false,
                    }]
                },
                _ => unreachable!(),
            })
        }
    );

    scope.declare_builtin_function(
        Identifier::from("operator&&"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type), FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();

            Ok(match (&args[0], &args[1]) {
                (&ScopeItem::Constant {type_id: xt, bytes: ref xb}, &ScopeItem::Constant {type_id: yt, bytes: ref yb}) => {
                    debug_assert_eq!(xt, bool_type);
                    debug_assert_eq!(yt, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(xb.len(), 1);
                    debug_assert_eq!(yb.len(), 1);

                    // The `> 0` converts the byte-representation into a Rust boolean
                    let x = xb[0] > 0;
                    let y = yb[0] > 0;

                    match x && y {
                        false => Vec::new(),
                        true => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                    }
                },
                /// Thanks to certain properties of booleans, we can evaluate certain things during
                /// compilation and avoid a lot of extra computation.
                (&ScopeItem::Constant {type_id: const_type, ref bytes}, &ScopeItem::TypedBlock {type_id: other_type, memory}) |
                (&ScopeItem::TypedBlock {type_id: other_type, memory}, &ScopeItem::Constant {type_id: const_type, ref bytes}) => {
                    debug_assert_eq!(const_type, bool_type);
                    debug_assert_eq!(other_type, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    match bytes[0] {
                        // If either operand is false, the && operator always returns false
                        0 => Vec::new(),
                        // If either operand is true, the && operator always returns its other operand
                        1 => vec![Operation::Copy {
                            source: memory.position(),
                            target: target.position(),
                            size: memory.size(),
                        }],
                        _ => unreachable!(),
                    }
                },
                (&ScopeItem::TypedBlock {memory: x, ..}, &ScopeItem::TypedBlock {memory: y, ..}) => vec![
                    // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#z_.3D_x_and_y_.28boolean.2C_logical.29
                    //
                    // z[-]
                    // x[
                    //  y[z+y-]
                    //  x-
                    // ]
                    // y[-]
                    Operation::Loop {
                        cond: x.position(),
                        body: vec![
                            Operation::Loop {
                                cond: y.position(),
                                body: vec![
                                    Operation::Increment {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: y.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                            Operation::Decrement {
                                target: x.position(),
                                amount: 1,
                            },
                        ],
                    },
                    Operation::Zero {
                        target: y,
                    },
                ],
                _ => unreachable!(),
            })
        }
    );

    bool_type
}

#[cfg(test)]
mod tests {
    use super::*;

    use parser::Identifier;
    use operations::scope::{ScopeType, ScopeItem};

    #[test]
    fn defines_primitive() {
        let mut scope = ScopeStack::new();
        define_boolean(&mut scope);

        let bool_type_id = match **scope.lookup_type(&Identifier::from("bool")).first().unwrap() {
            ScopeType::Type(id) => id,
        };
        assert_eq!(scope.primitives().bool(), bool_type_id);
    }

    #[test]
    fn constants() {
        let mut scope = ScopeStack::new();
        define_boolean(&mut scope);

        let bool_type_id = match **scope.lookup_type(&Identifier::from("bool")).first().unwrap() {
            ScopeType::Type(id) => id,
        };

        let true_bytes = match **scope.lookup(&Identifier::from("true")).first().unwrap() {
            ScopeItem::Constant {type_id, ref bytes} => {
                assert_eq!(type_id, bool_type_id);
                assert_eq!(*bytes, vec![1]);
                bytes
            },
            _ => unreachable!(),
        };

        let false_bytes = match **scope.lookup(&Identifier::from("false")).first().unwrap() {
            ScopeItem::Constant {type_id, ref bytes} => {
                assert_eq!(type_id, bool_type_id);
                assert_eq!(*bytes, vec![0]);
                bytes
            },
            _ => unreachable!(),
        };

        // No matter what the value of both, this property must always hold
        assert!(true_bytes != false_bytes);
    }
}
<commit_msg>Copying memory blocks to avoid consuming them in && and || operators<commit_after>use parser::Identifier;
use operations::{Operation};
use operations::item_type::{ItemType, FuncArgType};
use operations::scope::{ScopeStack, ScopeItem, TypeId};

pub fn define_boolean(scope: &mut ScopeStack) -> TypeId {
    // Taking advantage of the scope system to simulate modules
    // This will be replaced with something better in:
    // https://github.com/brain-lang/brain/issues/37
    scope.push_scope();

    let bool_type = scope.declare_type(
        Identifier::from("bool"),
        ItemType::Primitive(1)
    );
    scope.register_primitive("bool", bool_type);

    scope.declare_constant(
        Identifier::from("true"),
        bool_type,
        vec![1u8]
    );

    scope.declare_constant(
        Identifier::from("false"),
        bool_type,
        vec![0u8]
    );

    let unit_type = scope.primitives().unit();

    scope.declare_builtin_function(
        // Special method for displaying this primitive (used from print/println)
        // This name is such that it could never be called directly
        // from the language itself
        Identifier::from("std::fmt::Display::print"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type)],
            return_type: unit_type,
        },
        move |scope, args, _| {
            match args[0] {
                ScopeItem::TypedBlock {memory, ..} => Ok(vec![
                    Operation::Increment {
                        target: memory.position(),
                        amount: b'0',
                    },
                    Operation::Write {
                        target: memory,
                    },
                    // Need to make sure we reverse the operation after so this function has no
                    // side effects
                    Operation::Decrement {
                        target: memory.position(),
                        amount: b'0',
                    },
                ]),
                ScopeItem::Constant {type_id, ref bytes} => {
                    let bool_type = scope.primitives().bool();
                    debug_assert_eq!(type_id, bool_type);

                    let u8_type = scope.primitives().u8();
                    let mem = scope.allocate(u8_type);

                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);
                    let value = b'0' + bytes[0];
                    Ok(vec![Operation::TempAllocate {
                        temp: mem,
                        body: vec![
                            Operation::Increment {
                                target: mem.position(),
                                amount: value,
                            },
                            Operation::Write {
                                target: mem,
                            },
                            Operation::Decrement {
                                target: mem.position(),
                                amount: value,
                            },
                        ],
                        should_zero: false,
                    }])
                },
                _ => unreachable!(),
            }
        }
    );

    scope.declare_builtin_function(
        Identifier::from("std::ops::Not"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();
            match args[0] {
                ScopeItem::TypedBlock {memory, ..} => {
                    let temp0 = scope.allocate(bool_type);
                    Ok(vec![Operation::TempAllocate {
                        temp: temp0,
                        body: vec![
                            // Need to copy to the destination because the algorithm consumes
                            // the operand
                            // After this copy the operand becomes `target` (x in the algorithm)
                            Operation::Copy {
                                source: memory.position(),
                                target: target.position(),
                                size: target.size(),
                            },

                            // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#x_.3D_not_x_.28boolean.2C_logical.29
                            //
                            // temp0[-]+
                            // x[-temp0-]temp0[x+temp0-]
                            //
                            // Results in x = !x and temp0 = 0
                            Operation::Increment {
                                target: temp0.position(),
                                amount: 1,
                            },
                            Operation::Loop {
                                cond: target.position(),
                                body: vec![
                                    Operation::Decrement {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                            Operation::Loop {
                                cond: temp0.position(),
                                body: vec![
                                    Operation::Increment {
                                        target: target.position(),
                                        amount: 1,
                                    },
                                    Operation::Decrement {
                                        target: temp0.position(),
                                        amount: 1,
                                    },
                                ],
                            },
                        ],
                        should_zero: false,
                    }])
                },
                ScopeItem::Constant {type_id, ref bytes} => {
                    debug_assert_eq!(type_id, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    Ok(match bytes[0] {
                        //TODO: Verify if this makes sense or if we should explicitly zero
                        1 => Vec::new(),
                        0 => vec![
                            Operation::Increment {
                                target: target.position(),
                                amount: 1,
                            },
                        ],
                        _ => unreachable!(),
                    })
                },
                _ => unreachable!(),
            }
        }
    );

    // boolean and operator (operator&&) and boolean or operator (operator||)
    // These operations have special names because they are not regular functions
    // that can be defined or overloaded
    // The reason these are not definable is because they have to support short
    // circuiting. This behaviour cannot be modelled by a trait, so these special
    // operators are not definable by the user.
    scope.declare_builtin_function(
        Identifier::from("operator||"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type), FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();

            Ok(match (&args[0], &args[1]) {
                (&ScopeItem::Constant {type_id: xt, bytes: ref xb}, &ScopeItem::Constant {type_id: yt, bytes: ref yb}) => {
                    debug_assert_eq!(xt, bool_type);
                    debug_assert_eq!(yt, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(xb.len(), 1);
                    debug_assert_eq!(yb.len(), 1);

                    // The `> 0` converts the byte-representation into a Rust boolean
                    let x = xb[0] > 0;
                    let y = yb[0] > 0;

                    match x || y {
                        false => Vec::new(),
                        true => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                    }
                },
                /// Thanks to certain properties of booleans, we can evaluate certain things during
                /// compilation and avoid a lot of extra computation.
                (&ScopeItem::Constant {type_id: const_type, ref bytes}, &ScopeItem::TypedBlock {type_id: other_type, memory}) |
                (&ScopeItem::TypedBlock {type_id: other_type, memory}, &ScopeItem::Constant {type_id: const_type, ref bytes}) => {
                    debug_assert_eq!(const_type, bool_type);
                    debug_assert_eq!(other_type, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    match bytes[0] {
                        // If either operand is true, the || operator always returns true
                        1 => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                        // If either operand is false, the || operator always returns its other operand
                        0 => vec![Operation::Copy {
                            source: memory.position(),
                            target: target.position(),
                            size: memory.size(),
                        }],
                        _ => unreachable!(),
                    }
                },
                (&ScopeItem::TypedBlock {memory: x, ..}, &ScopeItem::TypedBlock {memory: y, ..}) => {
                    let temp0 = scope.allocate(bool_type);
                    let temp_x = scope.allocate(bool_type);
                    let temp_y = scope.allocate(bool_type);
                    let ops = vec![
                        // The algorithm below consumes x and y so we need to copy them first to
                        // avoid any problems with that
                        Operation::Copy {
                            source: x.position(),
                            target: temp_x.position(),
                            size: x.size(),
                        },
                        Operation::Copy {
                            source: y.position(),
                            target: temp_y.position(),
                            size: y.size(),
                        },

                        // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#z_.3D_x_or_y_.28boolean.2C_logical.29
                        //
                        // z[-]
                        // temp0[-]+
                        // x[z+temp0-x-]
                        // temp0[-
                        //  y[z+y-]
                        // ]
                        // y[-]
                        Operation::Increment {
                            target: temp0.position(),
                            amount: 1,
                        },
                        Operation::Loop {
                            cond: temp_x.position(),
                            body: vec![
                                Operation::Increment {
                                    target: target.position(),
                                    amount: 1,
                                },
                                Operation::Decrement {
                                    target: temp0.position(),
                                    amount: 1,
                                },
                                Operation::Decrement {
                                    target: temp_x.position(),
                                    amount: 1,
                                },
                            ],
                        },
                        Operation::Loop {
                            cond: temp0.position(),
                            body: vec![
                                Operation::Decrement {
                                    target: temp0.position(),
                                    amount: 1,
                                },
                                Operation::Loop {
                                    cond: temp_y.position(),
                                    body: vec![
                                        Operation::Increment {
                                            target: target.position(),
                                            amount: 1,
                                        },
                                        Operation::Decrement {
                                            target: temp_y.position(),
                                            amount: 1,
                                        },
                                    ],
                                },
                            ],
                        },
                        Operation::Zero {
                            target: temp_y,
                        },
                    ];

                    vec![Operation::TempAllocate {
                        temp: temp0,
                        body: vec![Operation::TempAllocate {
                            temp: temp_x,
                            body: vec![Operation::TempAllocate {
                                temp: temp_y,
                                body: ops,
                                should_zero: false,
                            }],
                            should_zero: false,
                        }],
                        should_zero: false,
                    }]
                },
                _ => unreachable!(),
            })
        }
    );

    scope.declare_builtin_function(
        Identifier::from("operator&&"),
        ItemType::Function {
            args: vec![FuncArgType::Arg(bool_type), FuncArgType::Arg(bool_type)],
            return_type: bool_type,
        },
        move |scope, args, target| {
            let bool_type = scope.primitives().bool();

            Ok(match (&args[0], &args[1]) {
                (&ScopeItem::Constant {type_id: xt, bytes: ref xb}, &ScopeItem::Constant {type_id: yt, bytes: ref yb}) => {
                    debug_assert_eq!(xt, bool_type);
                    debug_assert_eq!(yt, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(xb.len(), 1);
                    debug_assert_eq!(yb.len(), 1);

                    // The `> 0` converts the byte-representation into a Rust boolean
                    let x = xb[0] > 0;
                    let y = yb[0] > 0;

                    match x && y {
                        false => Vec::new(),
                        true => vec![Operation::Increment {
                            target: target.position(),
                            amount: 1,
                        }],
                    }
                },
                /// Thanks to certain properties of booleans, we can evaluate certain things during
                /// compilation and avoid a lot of extra computation.
                (&ScopeItem::Constant {type_id: const_type, ref bytes}, &ScopeItem::TypedBlock {type_id: other_type, memory}) |
                (&ScopeItem::TypedBlock {type_id: other_type, memory}, &ScopeItem::Constant {type_id: const_type, ref bytes}) => {
                    debug_assert_eq!(const_type, bool_type);
                    debug_assert_eq!(other_type, bool_type);
                    // This code assumes that this is 1
                    debug_assert_eq!(bytes.len(), 1);

                    match bytes[0] {
                        // If either operand is false, the && operator always returns false
                        0 => Vec::new(),
                        // If either operand is true, the && operator always returns its other operand
                        1 => vec![Operation::Copy {
                            source: memory.position(),
                            target: target.position(),
                            size: memory.size(),
                        }],
                        _ => unreachable!(),
                    }
                },
                (&ScopeItem::TypedBlock {memory: x, ..}, &ScopeItem::TypedBlock {memory: y, ..}) => {
                    let temp_x = scope.allocate(bool_type);
                    let temp_y = scope.allocate(bool_type);

                    let ops = vec![
                        // The algorithm below consumes x and y so we need to copy them first to
                        // avoid any problems with that
                        Operation::Copy {
                            source: x.position(),
                            target: temp_x.position(),
                            size: x.size(),
                        },
                        Operation::Copy {
                            source: y.position(),
                            target: temp_y.position(),
                            size: y.size(),
                        },

                        // Algorithm from: https://esolangs.org/wiki/Brainfuck_algorithms#z_.3D_x_and_y_.28boolean.2C_logical.29
                        //
                        // z[-]
                        // x[
                        //  y[z+y-]
                        //  x-
                        // ]
                        // y[-]
                        Operation::Loop {
                            cond: temp_x.position(),
                            body: vec![
                                Operation::Loop {
                                    cond: temp_y.position(),
                                    body: vec![
                                        Operation::Increment {
                                            target: target.position(),
                                            amount: 1,
                                        },
                                        Operation::Decrement {
                                            target: temp_y.position(),
                                            amount: 1,
                                        },
                                    ],
                                },
                                Operation::Decrement {
                                    target: temp_x.position(),
                                    amount: 1,
                                },
                            ],
                        },
                        Operation::Zero {
                            target: temp_y,
                        },
                    ];

                    vec![Operation::TempAllocate {
                        temp: temp_x,
                        body: vec![Operation::TempAllocate {
                            temp: temp_y,
                            body: ops,
                            should_zero: false,
                        }],
                        should_zero: false,
                    }]
                },
                _ => unreachable!(),
            })
        }
    );

    bool_type
}

#[cfg(test)]
mod tests {
    use super::*;

    use parser::Identifier;
    use operations::scope::{ScopeType, ScopeItem};

    #[test]
    fn defines_primitive() {
        let mut scope = ScopeStack::new();
        define_boolean(&mut scope);

        let bool_type_id = match **scope.lookup_type(&Identifier::from("bool")).first().unwrap() {
            ScopeType::Type(id) => id,
        };
        assert_eq!(scope.primitives().bool(), bool_type_id);
    }

    #[test]
    fn constants() {
        let mut scope = ScopeStack::new();
        define_boolean(&mut scope);

        let bool_type_id = match **scope.lookup_type(&Identifier::from("bool")).first().unwrap() {
            ScopeType::Type(id) => id,
        };

        let true_bytes = match **scope.lookup(&Identifier::from("true")).first().unwrap() {
            ScopeItem::Constant {type_id, ref bytes} => {
                assert_eq!(type_id, bool_type_id);
                assert_eq!(*bytes, vec![1]);
                bytes
            },
            _ => unreachable!(),
        };

        let false_bytes = match **scope.lookup(&Identifier::from("false")).first().unwrap() {
            ScopeItem::Constant {type_id, ref bytes} => {
                assert_eq!(type_id, bool_type_id);
                assert_eq!(*bytes, vec![0]);
                bytes
            },
            _ => unreachable!(),
        };

        // No matter what the value of both, this property must always hold
        assert!(true_bytes != false_bytes);
    }
}
<|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2016-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! Initial 32-bit bootloader for x86_64
#![crate_name = "boot"]
#![crate_type = "staticlib"]
#![feature(asm)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(static_recursion)]
#![feature(linkage)]
#![no_std]

const TABLE_LENGTH: usize = 512;

type Table = [u64; TABLE_LENGTH];

use core::ptr;
//
// #[lang = "panic_fmt"]
// #[cold]
// unsafe extern fn panic_fmt() -> ! {
//     boot_write(b"boot panic occurred!");
//     die();
// }

extern {
    static mut pml4_table: Table;
    static mut pdp_table: Table;
    static mut pd_table: Table;
    // static mut page_table: Table;

}

#[repr(C, packed)]
pub struct Gdt { _null: u64
               , code: u64
           , ptr: GdtPointer
           }

#[repr(C, packed)]
pub struct GdtPointer { /// the length of the descriptor table
                     pub limit: u16
                   , /// pointer to the region in memory
                     /// containing the descriptor table.
                     pub base: &'static Gdt
                   }

#[repr(C, packed)]
pub struct SegmentDescriptor {
}

// TODO: this sucks please fix
#[link_name = ".gdt64"]
#[link_section = ".gdt"]
pub static GDT: Gdt
    = Gdt { _null: 0
          , code: (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
          , ptr: GdtPointer { limit: 23
                            , base: &GDT }
     };

#[inline(always)]
#[naked]
fn boot_write(s: &[u8]) {
	// use core::ptr;
    let mut offset = 0;
    unsafe {
        let vga_buf: *mut u16 = 0xb8000 as *mut u16;
    	for c in s {
    		ptr::write_volatile( vga_buf.offset(offset)
                               , 0x0200 + *c as u16);
    		offset += 1;
    	}

    }

}
macro_rules! write_char {
    ($c:expr) => {
        ptr::write_volatile(0xb8000 as *mut u16, 0x0200 + $c as u16)
    }
}

#[naked]
#[inline(always)]
pub unsafe fn create_page_tables() {
    const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

    //-- map the PML4 and PDP tables -----------------------------------------
    // recursive map last PML4 entry
    pml4_table[511] = (&pml4_table as *const Table as u64) | 3;
    // map first PML4 entry to PDP table
    pml4_table[0] = (&pdp_table as *const Table as u64) | 3;
    // map first PDPT entry to PD table
    pdp_table[0] = (&pd_table as *const Table as u64) | 3;

    //-- map the PD table ----------------------------------------------------
    for (number, entry) in pd_table.iter_mut().enumerate() {
        // set each PD entry equal to the start address of the page (the page
        // number times the page's size)
        let addr = number as u64 * HUGE_PAGE_SIZE;
        // with the appropriate flags (present + writable + huge)
        // TODO: do we want to do this using bitflags, or is that too
        //       heavyweight for the boot module?
        //          - eliza, 1/23/2017
        *entry = addr | 0b10000011;
    }
}

#[naked]
#[inline(always)]
pub unsafe fn set_long_mode() {
    // load PML4 addr to cr3
    asm!( "mov   cr3, $0"
        : "=r"(&pml4_table as *const Table)
        ::: "intel");
    // enable PAE flag in cr4
    asm!( "mov   eax, cr4
           or    eax, 1 << 5
           mov   cr4, eax"
        :::: "intel");
    // set the long mode bit in EFER MSR (model specific register)
    asm!( "mov   ecx, 0xC0000080
           rdmsr
           or    eax, 1 << 8
           wrmsr"
        :::: "intel");
    // enable paging in cr0
    asm!( "mov  eax, cr0
           or   eax, 1 << 31
           or   eax, 1 << 16
           mov  cr0, eax"
        :::: "intel");

    asm!("ret");
}

#[naked]
#[inline(always)] #[cold]
unsafe fn die() -> ! {
    loop { asm!("hlt"); }
}

#[cold]
#[no_mangle]
#[naked]
pub unsafe extern "C" fn _start() {
    // 0. Move the stack pointer to the top of the stack.
    asm!( "mov esp, stack_top"
        :::: "intel");
    boot_write(b"0");

    // 1. Move Multiboot info pointer to edi
    asm!("mov edi, edx" :::: "intel");
    boot_write(b"1");

    // 2. make sure the system supports SOS
    // TODO: port this from boot.asm
    boot_write(b"2");

    // 3. if everything is okay, create the page tables and start long mode
    const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

    //-- map the PML4 and PDP tables -----------------------------------------
    // recursive map last PML4 entry
    pml4_table[511] = (&pml4_table as *const Table as u64) | 3;
    // map first PML4 entry to PDP table
    pml4_table[0] = (&pdp_table as *const Table as u64) | 3;
    // map first PDPT entry to PD table
    pdp_table[0] = (&pd_table as *const Table as u64) | 3;

    boot_write(b"3.1");

    //-- map the PD table ----------------------------------------------------
    for (number, entry) in pd_table.iter_mut().enumerate() {
        // set each PD entry equal to the start address of the page (the page
        // number times the page's size)
        let addr = number as u64 * HUGE_PAGE_SIZE;
        // with the appropriate flags (present + writable + huge)
        // TODO: do we want to do this using bitflags, or is that too
        //       heavyweight for the boot module?
        //          - eliza, 1/23/2017
        *entry = addr | 0b10000011;
    }
    // load PML4 addr to cr3
    asm!( "mov   cr3, $0"
        :: "r"(&pml4_table as *const Table)
        :: "intel");
    boot_write(b"3.2");

    // enable PAE flag in cr4
    asm!( "mov   eax, cr4
           or    eax, 1 << 5
           mov   cr4, eax"
        :::: "intel");
    boot_write(b"3.3");

    // set the long mode bit in EFER MSR (model specific register)
    asm!( "mov   ecx, 0xC0000080
           rdmsr
           or    eax, 1 << 8
           wrmsr"
        :::: "intel");
    boot_write(b"3.4");

    // enable paging in cr0
    asm!( "mov  eax, cr0
           or   eax, 1 << 31
           or   eax, 1 << 16
           mov  cr0, eax"
        :::: "intel");
    boot_write(b"3.5");

    // 4. load the 64-bit GDT
    asm!( "lgdt ($0)"
        :: "r"(&GDT.ptr)
        :  "memory"
        );
    boot_write(b"4");

    // 5. update selectors
    // asm!("mov ax, 16
    //       mov ss, ax
    //       mov ds, ax
    //       mov es, ax"
    //      :::: "intel");
    // update selectors
    asm!("mov ax, 0x10" :::: "intel");
    boot_write(b"5.1");
    // stack selector
    asm!("mov ss, ax" :::: "intel");
    boot_write(b"5.2");
    // data selector
    asm!("mov ds, ax" :::: "intel");
     boot_write(b"5.3");
    // extra selector
    asm!("mov es, ax" :::: "intel");
    boot_write(b"5.4");

    // 6. jump to the 64-bit boot subroutine.
    // asm!("jmp $0:$1" :: "X"(&GDT.descriptors[1]), "X"(arch_init as unsafe extern "C" fn() -> !) :: "intel");
    // asm!("jmp $0:arch_init" :: "r"(&GDT.descriptors[1] as *const u64) :: "intel");
    // arch_init();
    asm!(  "jmp $0:arch_init"
        :: "r"(&GDT.code as *const _ as usize -
               &GDT as *const _ as usize)
        :: "intel");

    loop {
        boot_write(b"kernel returned unexpectedly!");
    }

}
<commit_msg>[boot] re-add functions for page table setup<commit_after>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2016-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! Initial 32-bit bootloader for x86_64
#![crate_name = "boot"]
#![crate_type = "staticlib"]
#![feature(asm)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(static_recursion)]
#![feature(linkage)]
#![no_std]

const TABLE_LENGTH: usize = 512;

type Table = [u64; TABLE_LENGTH];

use core::ptr;
//
// #[lang = "panic_fmt"]
// #[cold]
// unsafe extern fn panic_fmt() -> ! {
//     boot_write(b"boot panic occurred!");
//     die();
// }

#[repr(C, packed)]
pub struct Gdt { _null: u64
               , code: u64
           , ptr: GdtPointer
           }

#[repr(C, packed)]
pub struct GdtPointer { /// the length of the descriptor table
                     pub limit: u16
                   , /// pointer to the region in memory
                     /// containing the descriptor table.
                     pub base: &'static Gdt
                   }

#[repr(C, packed)]
pub struct SegmentDescriptor {
}

// TODO: this sucks please fix
#[link_name = ".gdt64"]
#[link_section = ".gdt"]
pub static GDT: Gdt
    = Gdt { _null: 0
          , code: (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
          , ptr: GdtPointer { limit: 23
                            , base: &GDT }
     };

#[inline(always)]
#[naked]
fn boot_write(s: &[u8]) {
	// use core::ptr;
    let mut offset = 0;
    unsafe {
        let vga_buf: *mut u16 = 0xb8000 as *mut u16;
    	for c in s {
    		ptr::write_volatile( vga_buf.offset(offset)
                               , 0x0200 + *c as u16);
    		offset += 1;
    	}

    }

}
macro_rules! write_char {
    ($c:expr) => {
        ptr::write_volatile(0xb8000 as *mut u16, 0x0200 + $c as u16)
    }
}

extern "C" {
    static mut pml4_table: Table;
    static mut pdp_table: Table;
    static mut pd_table: Table;    // static mut page_table: Table;
}

#[naked]
#[inline(always)]
pub unsafe fn create_page_tables() {
    // 3. if everything is okay, create the page tables and start long mode
    const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

    //-- map the PML4 and PDP tables -----------------------------------------
    // recursive map last PML4 entry
    pml4_table[511] = (&pml4_table as *const Table as u64) | 3;
    // map first PML4 entry to PDP table
    pml4_table[0] = (&pdp_table as *const Table as u64) | 3;
    // map first PDPT entry to PD table
    pdp_table[0] = (&pd_table as *const Table as u64) | 3;

    boot_write(b"3.1");

    //-- map the PD table ----------------------------------------------------
    for (number, entry) in pd_table.iter_mut().enumerate() {
        // set each PD entry equal to the start address of the page (the page
        // number times the page's size)
        let addr = number as u64 * HUGE_PAGE_SIZE;
        // with the appropriate flags (present + writable + huge)
        // TODO: do we want to do this using bitflags, or is that too
        //       heavyweight for the boot module?
        //          - eliza, 1/23/2017
        *entry = addr | 0b10000011;
    }
}

#[naked]
#[inline(always)]
pub unsafe fn set_long_mode() {
    // load PML4 addr to cr3
    asm!( "mov   cr3, $0"
        :: "r"(&pml4_table)
        :: "intel");
    boot_write(b"3.2");

    // enable PAE flag in cr4
    asm!( "mov   eax, cr4
           or    eax, 1 << 5
           mov   cr4, eax"
        :::: "intel");
    boot_write(b"3.3");

    // set the long mode bit in EFER MSR (model specific register)
    asm!( "mov   ecx, 0xC0000080
           rdmsr
           or    eax, 1 << 8
           wrmsr"
        :::: "intel");
    boot_write(b"3.4");

    // enable paging in cr0
    asm!( "mov  eax, cr0
           or   eax, 1 << 31
           or   eax, 1 << 16
           mov  cr0, eax"
        :::: "intel");
    boot_write(b"3.5");

}

#[cold]
#[no_mangle]
#[naked]
pub unsafe extern "C" fn _start() {
    // 0. Move the stack pointer to the top of the stack.
    asm!( "mov esp, stack_top"
        :::: "intel");
    boot_write(b"0");

    // 1. Move Multiboot info pointer to edi
    asm!("mov edi, edx" :::: "intel");
    boot_write(b"1");

    // 2. make sure the system supports SOS
    // TODO: port this from boot.asm
    boot_write(b"2");

    create_page_tables();
    set_long_mode();

    // 4. load the 64-bit GDT
    asm!( "lgdt ($0)"
        :: "r"(&GDT.ptr)
        :  "memory"
        );
    boot_write(b"4");

    // 5. update selectors
    asm!("mov ax, 0x10" :::: "intel");
    boot_write(b"5.1");
    // stack selector
    asm!("mov ss, ax" :::: "intel");
    boot_write(b"5.2");
    // data selector
    asm!("mov ds, ax" :::: "intel");
     boot_write(b"5.3");
    // extra selector
    asm!("mov es, ax" :::: "intel");
    boot_write(b"5.4");

    // 6. jump to the 64-bit boot subroutine.
    // asm!("jmp $0:$1" :: "X"(&GDT.descriptors[1]), "X"(arch_init as unsafe extern "C" fn() -> !) :: "intel");
    // asm!("jmp $0:arch_init" :: "r"(&GDT.descriptors[1] as *const u64) :: "intel");
    // arch_init();
    asm!(  "jmp $0:arch_init"
        :: "r"(&GDT.code as *const _ as usize -
               &GDT as *const _ as usize)
        :: "intel");

    loop {
        boot_write(b"kernel returned unexpectedly!");
    }

}
<|endoftext|>


<commit_before>use byteorder::{LittleEndian, WriteBytesExt, ByteOrder};
use std::ptr;
use libc;

bitflags! {
    pub struct EntryType: u8 {
        const Cell = 0b00010000;
        const Tomestone = 0b00110000;
    }
}

#[derive(Copy, Clone)]
pub struct EntryHeader {
    entry_type: EntryType,
    entry_length: u32,
}

fn count_len_bytes(len: u32) -> u8 {
    let in_bits = 32;
    let msb = 1 << (in_bits - 1);
    let mut count: u8 = 0;
    for i in 0..in_bits
    {
        if (len << i) & msb > 0 {
            break;
        };
        count += 1;
    }
    let bytes = count / 8;
    assert!(bytes <= 4);
    return bytes;
}

fn encode_len(len: u32, bytes: &mut[u8]) {
    LittleEndian::write_u32(bytes, len);
}

impl EntryHeader {
    pub fn encode_to<W>(mut pos: usize, entry_type: EntryType, content_len: u32, write_content: W)
        where W: Fn(usize)
    {
        let len_bytes_count = count_len_bytes(content_len);
        let len_bytes_count_usize = len_bytes_count as usize;
        let flag_byte = len_bytes_count | entry_type.bits;
        let mut len_bytes = [0u8; 4];
        encode_len(content_len, &mut len_bytes);
        let raw_len_bytes= Box::into_raw(box len_bytes);
        unsafe {
            // write entry flags
            *(pos as *mut u8) = flag_byte;
            pos += 1;
            // write entry length
            libc::memmove(
                pos as *mut libc::c_void,
                raw_len_bytes as *mut libc::c_void,
                len_bytes_count_usize);
            pos += len_bytes_count_usize;
            write_content(pos);
            // release raw pointers
            Box::from_raw(raw_len_bytes);
        }
    }

    // Returns the entry header and content position
    pub fn decode_from<R, RR>(mut pos: usize, read: R) -> (EntryHeader, RR)
        where R: Fn(usize, EntryHeader) -> RR
    {
        unsafe {
            let flag_byte = *(pos as *mut u8);
            pos += 1;
            let entry_type_bits = 0b11110000 & flag_byte;
            let entry_type = EntryType::from_bits(entry_type_bits).unwrap();
            let entry_bytes_len = 0b00001111 & flag_byte;
            let entry_bytes_len_usize = entry_bytes_len as usize;
            let raw_len_bytes= Box::into_raw(box [0u8; 4]);
            libc::memmove(
                pos as *mut libc::c_void,
                raw_len_bytes as *mut libc::c_void,
                entry_bytes_len_usize);
            let entry_length = LittleEndian::read_u32(&*Box::from_raw(raw_len_bytes));
            let entry = EntryHeader {
                entry_type,
                entry_length
            };
            pos += entry_bytes_len_usize;
            (entry, read(pos, entry))
        }
    }
}

<commit_msg>fix comment<commit_after>use byteorder::{LittleEndian, WriteBytesExt, ByteOrder};
use std::ptr;
use libc;

bitflags! {
    pub struct EntryType: u8 {
        const Cell = 0b00010000;
        const Tomestone = 0b00110000;
    }
}

#[derive(Copy, Clone)]
pub struct EntryHeader {
    entry_type: EntryType,
    entry_length: u32,
}

fn count_len_bytes(len: u32) -> u8 {
    let in_bits = 32;
    let msb = 1 << (in_bits - 1);
    let mut count: u8 = 0;
    for i in 0..in_bits
    {
        if (len << i) & msb > 0 {
            break;
        };
        count += 1;
    }
    let bytes = count / 8;
    assert!(bytes <= 4);
    return bytes;
}

fn encode_len(len: u32, bytes: &mut[u8]) {
    LittleEndian::write_u32(bytes, len);
}

impl EntryHeader {
    pub fn encode_to<W>(mut pos: usize, entry_type: EntryType, content_len: u32, write_content: W)
        where W: Fn(usize)
    {
        let len_bytes_count = count_len_bytes(content_len);
        let len_bytes_count_usize = len_bytes_count as usize;
        let flag_byte = len_bytes_count | entry_type.bits;
        let mut len_bytes = [0u8; 4];
        encode_len(content_len, &mut len_bytes);
        let raw_len_bytes= Box::into_raw(box len_bytes);
        unsafe {
            // write entry flags
            *(pos as *mut u8) = flag_byte;
            pos += 1;
            // write entry length
            libc::memmove(
                pos as *mut libc::c_void,
                raw_len_bytes as *mut libc::c_void,
                len_bytes_count_usize);
            pos += len_bytes_count_usize;
            write_content(pos);
            // release raw pointers
            Box::from_raw(raw_len_bytes);
        }
    }

    // Returns the entry header reader returns
    pub fn decode_from<R, RR>(mut pos: usize, read: R) -> (EntryHeader, RR)
        where R: Fn(usize, EntryHeader) -> RR
    {
        unsafe {
            let flag_byte = *(pos as *mut u8);
            pos += 1;
            let entry_type_bits = 0b11110000 & flag_byte;
            let entry_type = EntryType::from_bits(entry_type_bits).unwrap();
            let entry_bytes_len = 0b00001111 & flag_byte;
            let entry_bytes_len_usize = entry_bytes_len as usize;
            let raw_len_bytes= Box::into_raw(box [0u8; 4]);
            libc::memmove(
                pos as *mut libc::c_void,
                raw_len_bytes as *mut libc::c_void,
                entry_bytes_len_usize);
            let entry_length = LittleEndian::read_u32(&*Box::from_raw(raw_len_bytes));
            let entry = EntryHeader {
                entry_type,
                entry_length
            };
            pos += entry_bytes_len_usize;
            (entry, read(pos, entry))
        }
    }
}

<|endoftext|>


<commit_before><commit_msg>Remove mark_roots from Scope<commit_after><|endoftext|>


<commit_before>use std::fmt;

pub struct RawCodeItem {
    pub name: String,
    pub attributes: Vec<(String, String)>,
    pub children: Vec<RawCodeItem>
}

impl RawCodeItem {
    pub fn new(item_name: &str, item_attribs: Vec<(String, String)>) -> RawCodeItem {
        RawCodeItem {
            name: String::from(item_name),
            attributes: item_attribs,
            children: Vec::<RawCodeItem>::new()
        }
    }
}

pub struct RawCode {
    pub elements: Vec<RawCodeItem>
}

impl RawCode {
    pub fn new() -> RawCode {
        RawCode {
            elements: Vec::<RawCodeItem>::new()
        }
    }

    // convert raw data to cpp code
    pub fn to_cpp(&self) -> u8 {
        0
    }

    // convert raw data to Rust code
    pub fn to_rust(&self) -> u8 {
        0
    }

    // used by Display trait to print the tree of elements
    fn print_element(&self, e: &RawCodeItem, f: &mut fmt::Formatter, last_child: bool, num_empty: u8, depth: u8) {
        if depth > 0 {
            let _ = write!(f, "|  ");

            if num_empty > 0 {
                for _ in 0..depth-1-num_empty {
                    let _ = write!(f, "|  ");
                }
                for _ in 0..num_empty {
                    let _ = write!(f, "   ");
                }
            }
            else {
                for _ in 0..depth-1 {
                    let _ = write!(f, "|  ");
                }
            }
        }

        let _ = write!(f, "|--{}", e.name);
        if e.attributes.len() > 0 {
            let _ = write!(f, " [");
            for a in 0..e.attributes.len() {
                if a > 0 && a < e.attributes.len() {
                    let _ = write!(f, ", ");
                }
                let _ = write!(f, "{}:\"{}\"", e.attributes[a].0, e.attributes[a].1);
            }
            let _ = write!(f, "]");
        }
        let _ = write!(f, "\n");

        if e.children.len() > 0 {
            for c in 0..e.children.len() {
                let ne = if last_child && depth > 0 { num_empty+1 } else { 0 };
                let _ = self.print_element(&e.children[c], f, (e.children.len() - c) == 1, ne, depth+1);
            }

            if !last_child {
                for _ in 0..depth {
                    let _ = write!(f, "|  ");
                }

                let _ = write!(f, "|\n");
            }
        }
        else if depth == 0 {
            let _ = write!(f, "|\n");
        }
    }
}

impl fmt::Display for RawCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let _ = write!(f, "\n");
        for e in 0..self.elements.len() {
            let _ = self.print_element(&self.elements[e], f, (self.elements.len() - e) == 1, 0, 0);
        }
        write!(f, "\n")
    }
}
<commit_msg>variable rename<commit_after>use std::fmt;

pub struct RawCodeItem {
    pub name: String,
    pub attributes: Vec<(String, String)>,
    pub children: Vec<RawCodeItem>
}

impl RawCodeItem {
    pub fn new(item_name: &str, item_attribs: Vec<(String, String)>) -> RawCodeItem {
        RawCodeItem {
            name: String::from(item_name),
            attributes: item_attribs,
            children: Vec::<RawCodeItem>::new()
        }
    }
}

pub struct RawCode {
    pub elements: Vec<RawCodeItem>
}

impl RawCode {
    pub fn new() -> RawCode {
        RawCode {
            elements: Vec::<RawCodeItem>::new()
        }
    }

    // convert raw data to cpp code
    pub fn to_cpp(&self) -> u8 {
        0
    }

    // convert raw data to Rust code
    pub fn to_rust(&self) -> u8 {
        0
    }

    // used by Display trait to print the tree of elements
    fn print_element(&self, e: &RawCodeItem, f: &mut fmt::Formatter, is_last: bool, num_spaces: u8, depth: u8) {
        if depth > 0 {
            let _ = write!(f, "|  ");

            if num_spaces > 0 {
                for _ in 0..depth-1-num_spaces {
                    let _ = write!(f, "|  ");
                }
                for _ in 0..num_spaces {
                    let _ = write!(f, "   ");
                }
            }
            else {
                for _ in 0..depth-1 {
                    let _ = write!(f, "|  ");
                }
            }
        }

        let _ = write!(f, "|--{}", e.name);
        if e.attributes.len() > 0 {
            let _ = write!(f, " [");
            for a in 0..e.attributes.len() {
                if a > 0 && a < e.attributes.len() {
                    let _ = write!(f, ", ");
                }
                let _ = write!(f, "{}:\"{}\"", e.attributes[a].0, e.attributes[a].1);
            }
            let _ = write!(f, "]");
        }
        let _ = write!(f, "\n");

        if e.children.len() > 0 {
            for c in 0..e.children.len() {
                let ne = if is_last && depth > 0 { num_spaces+1 } else { 0 };
                let _ = self.print_element(&e.children[c], f, (e.children.len() - c) == 1, ne, depth+1);
            }

            if !is_last {
                for _ in 0..depth {
                    let _ = write!(f, "|  ");
                }

                let _ = write!(f, "|\n");
            }
        }
        else if depth == 0 {
            let _ = write!(f, "|\n");
        }
    }
}

impl fmt::Display for RawCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let _ = write!(f, "\n");
        for e in 0..self.elements.len() {
            let _ = self.print_element(&self.elements[e], f, (self.elements.len() - e) == 1, 0, 0);
        }
        write!(f, "\n")
    }
}
<|endoftext|>


<commit_before>use std::rc::Rc;
use std::marker::PhantomData;

use http::{Method, StatusCode};

use pred;
use body::Body;
use route::Route;
use handler::{Reply, Handler, Responder};
use middleware::Middleware;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;

/// *Resource* is an entry in route table which corresponds to requested URL.
///
/// Resource in turn has at least one route.
/// Route consists of an object that implements `Handler` trait (handler)
/// and list of predicates (objects that implement `Predicate` trait).
/// Route uses builder-like pattern for configuration.
/// During request handling, resource object iterate through all routes
/// and check all predicates for specific route, if request matches all predicates route
/// route considered matched and route handler get called.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
///     let app = Application::new()
///         .resource(
///             "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok()))
///         .finish();
/// }
pub struct Resource<S=()> {
    name: String,
    state: PhantomData<S>,
    routes: Vec<Route<S>>,
    middlewares: Rc<Vec<Box<Middleware<S>>>>,
}

impl<S> Default for Resource<S> {
    fn default() -> Self {
        Resource {
            name: String::new(),
            state: PhantomData,
            routes: Vec::new(),
            middlewares: Rc::new(Vec::new()) }
    }
}

impl<S> Resource<S> {

    pub(crate) fn default_not_found() -> Self {
        Resource {
            name: String::new(),
            state: PhantomData,
            routes: Vec::new(),
            middlewares: Rc::new(Vec::new()) }
    }

    /// Set resource name
    pub fn name<T: Into<String>>(&mut self, name: T) {
        self.name = name.into();
    }

    pub(crate) fn get_name(&self) -> &str {
        &self.name
    }
}

impl<S: 'static> Resource<S> {

    /// Register a new route and return mutable reference to *Route* object.
    /// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
    ///
    /// ```rust
    /// # extern crate actix_web;
    /// use actix_web::*;
    ///
    /// fn main() {
    ///     let app = Application::new()
    ///         .resource(
    ///             "/", |r| r.route()
    ///                  .filter(pred::Any(pred::Get()).or(pred::Put()))
    ///                  .filter(pred::Header("Content-Type", "text/plain"))
    ///                  .f(|r| HttpResponse::Ok()))
    ///         .finish();
    /// }
    /// ```
    pub fn route(&mut self) -> &mut Route<S> {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap()
    }

    /// Register a new route and add method check to route.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index)
    /// ```
    pub fn method(&mut self, method: Method) -> &mut Route<S> {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().filter(pred::Method(method))
    }

    /// Register a new route and add handler object.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().h(handler)
    /// ```
    pub fn h<H: Handler<S>>(&mut self, handler: H) {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().h(handler)
    }

    /// Register a new route and add handler function.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().f(index)
    /// ```
    pub fn f<F, R>(&mut self, handler: F)
        where F: Fn(HttpRequest<S>) -> R + 'static,
              R: Responder + 'static,
    {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().f(handler)
    }

    /// Register a middleware
    ///
    /// This is similar to `Application's` middlewares, but
    /// middlewares get invoked on resource level.
    pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
        Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw));
    }

    pub(crate) fn handle(&mut self,
                         mut req: HttpRequest<S>,
                         default: Option<&mut Resource<S>>) -> Reply
    {
        for route in &mut self.routes {
            if route.check(&mut req) {
                return if self.middlewares.is_empty() {
                    route.handle(req)
                } else {
                    route.compose(req, Rc::clone(&self.middlewares))
                };
            }
        }
        if let Some(resource) = default {
            resource.handle(req, None)
        } else {
            Reply::response(HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty))
        }
    }
}
<commit_msg>use small vec on hot path<commit_after>use std::rc::Rc;
use std::marker::PhantomData;

use smallvec::SmallVec;
use http::{Method, StatusCode};

use pred;
use body::Body;
use route::Route;
use handler::{Reply, Handler, Responder};
use middleware::Middleware;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;

/// *Resource* is an entry in route table which corresponds to requested URL.
///
/// Resource in turn has at least one route.
/// Route consists of an object that implements `Handler` trait (handler)
/// and list of predicates (objects that implement `Predicate` trait).
/// Route uses builder-like pattern for configuration.
/// During request handling, resource object iterate through all routes
/// and check all predicates for specific route, if request matches all predicates route
/// route considered matched and route handler get called.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
///     let app = Application::new()
///         .resource(
///             "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok()))
///         .finish();
/// }
pub struct Resource<S=()> {
    name: String,
    state: PhantomData<S>,
    routes: SmallVec<[Route<S>; 3]>,
    middlewares: Rc<Vec<Box<Middleware<S>>>>,
}

impl<S> Default for Resource<S> {
    fn default() -> Self {
        Resource {
            name: String::new(),
            state: PhantomData,
            routes: SmallVec::new(),
            middlewares: Rc::new(Vec::new()) }
    }
}

impl<S> Resource<S> {

    pub(crate) fn default_not_found() -> Self {
        Resource {
            name: String::new(),
            state: PhantomData,
            routes: SmallVec::new(),
            middlewares: Rc::new(Vec::new()) }
    }

    /// Set resource name
    pub fn name<T: Into<String>>(&mut self, name: T) {
        self.name = name.into();
    }

    pub(crate) fn get_name(&self) -> &str {
        &self.name
    }
}

impl<S: 'static> Resource<S> {

    /// Register a new route and return mutable reference to *Route* object.
    /// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
    ///
    /// ```rust
    /// # extern crate actix_web;
    /// use actix_web::*;
    ///
    /// fn main() {
    ///     let app = Application::new()
    ///         .resource(
    ///             "/", |r| r.route()
    ///                  .filter(pred::Any(pred::Get()).or(pred::Put()))
    ///                  .filter(pred::Header("Content-Type", "text/plain"))
    ///                  .f(|r| HttpResponse::Ok()))
    ///         .finish();
    /// }
    /// ```
    pub fn route(&mut self) -> &mut Route<S> {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap()
    }

    /// Register a new route and add method check to route.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index)
    /// ```
    pub fn method(&mut self, method: Method) -> &mut Route<S> {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().filter(pred::Method(method))
    }

    /// Register a new route and add handler object.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().h(handler)
    /// ```
    pub fn h<H: Handler<S>>(&mut self, handler: H) {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().h(handler)
    }

    /// Register a new route and add handler function.
    ///
    /// This is shortcut for:
    ///
    /// ```rust,ignore
    /// Resource::resource("/", |r| r.route().f(index)
    /// ```
    pub fn f<F, R>(&mut self, handler: F)
        where F: Fn(HttpRequest<S>) -> R + 'static,
              R: Responder + 'static,
    {
        self.routes.push(Route::default());
        self.routes.last_mut().unwrap().f(handler)
    }

    /// Register a middleware
    ///
    /// This is similar to `Application's` middlewares, but
    /// middlewares get invoked on resource level.
    pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
        Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw));
    }

    pub(crate) fn handle(&mut self,
                         mut req: HttpRequest<S>,
                         default: Option<&mut Resource<S>>) -> Reply
    {
        for route in &mut self.routes {
            if route.check(&mut req) {
                return if self.middlewares.is_empty() {
                    route.handle(req)
                } else {
                    route.compose(req, Rc::clone(&self.middlewares))
                };
            }
        }
        if let Some(resource) = default {
            resource.handle(req, None)
        } else {
            Reply::response(HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty))
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::{BTreeMap, HashMap};
use std::fs::{create_dir_all, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use config::Config;
use log::info;
use serde_derive::{Deserialize, Serialize};
use shellexpand::tilde;

use crate::error::Error;
use crate::platform::directories::*;

/// All settings which are used by both, the client and the daemon
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Shared {
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The directory that is used for all runtime information. \
    /// I.e. task logs, sockets, state dumps, etc.
    pub pueue_directory: PathBuf,
    /// If this is set to true, unix sockets will be used.
    /// Otherwise we default to TCP+TLS
    #[cfg(not(target_os = "windows"))]
    pub use_unix_socket: bool,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the unix socket.
    #[cfg(not(target_os = "windows"))]
    pub unix_socket_path: PathBuf,

    /// The TCP hostname/ip address.
    pub host: String,
    /// The TCP port.
    pub port: String,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS certificate used by the daemon. \
    /// This is also used by the client to verify the daemon's identity.
    pub daemon_cert: PathBuf,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS key used by the daemon.
    pub daemon_key: PathBuf,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the file containing the shared secret used to authenticate the client.
    pub shared_secret_path: PathBuf,
}

/// All settings which are used by the client
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Client {
    /// Whether the client should read the logs directly from disk or whether it should
    /// request the data from the daemon via socket.
    pub read_local_logs: bool,
    /// Whether the client should show a confirmation question on potential dangerous actions.
    pub show_confirmation_questions: bool,
    /// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
    /// or shown in their short form.
    pub show_expanded_aliases: bool,
    /// Whether the client should use dark shades instead of regular colors.
    pub dark_mode: bool,
    /// The max amount of lines each task get's in the `pueue status` view.
    pub max_status_lines: Option<usize>,
    /// The format that will be used to display time formats in `pueue status`.
    pub status_time_format: String,
    /// The format that will be used to display datetime formats in `pueue status`.
    pub status_datetime_format: String,
}

/// All settings which are used by the daemon
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Daemon {
    /// How many parallel tasks a group should have by default
    pub default_parallel_tasks: usize,
    /// Whether a group should be paused as soon as a single task fails
    pub pause_group_on_failure: bool,
    /// Whether the daemon (and all groups) should be paused as soon as a single task fails
    pub pause_all_on_failure: bool,
    /// The callback that's called whenever a task finishes.
    pub callback: Option<String>,
    /// The amount of log lines from stdout/stderr that are passed to the callback command.
    pub callback_log_lines: usize,
    /// This shouldn't be manipulated manually if the daemon is running.
    /// This represents all known groups and their amount of parallel tasks.
    pub groups: BTreeMap<String, usize>,
}

/// The parent settings struct. \
/// This contains all other setting structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Settings {
    pub client: Client,
    pub daemon: Daemon,
    pub shared: Shared,
}

impl Shared {
    pub fn expand(old_path: &Path) -> PathBuf {
        PathBuf::from(tilde(&old_path.to_string_lossy()).into_owned())
    }

    pub fn pueue_directory(&self) -> PathBuf {
        Shared::expand(&self.pueue_directory)
    }

    #[cfg(not(target_os = "windows"))]
    pub fn unix_socket_path(&self) -> PathBuf {
        Shared::expand(&self.unix_socket_path)
    }

    pub fn daemon_cert(&self) -> PathBuf {
        Shared::expand(&self.daemon_cert)
    }
    pub fn daemon_key(&self) -> PathBuf {
        Shared::expand(&self.daemon_key)
    }
    pub fn shared_secret_path(&self) -> PathBuf {
        Shared::expand(&self.shared_secret_path)
    }
}

impl Settings {
    /// Read from existing config files.
    /// If no config files can be found or fields are missing, an error is returned.
    pub fn read(from_file: &Option<PathBuf>) -> Result<Settings, Error> {
        let config = Config::new();

        parse_config(config, true, from_file)
    }

    /// Try to read existing config files and
    /// This function creates a new configuration instance and
    /// populates it with default values for every option. \
    /// If a local config file already exists, it is parsed and
    /// overrules the default option values.
    ///
    /// The default local config is located at "~/.config/pueue.yml".
    ///
    /// If `require_config` is `true`, an error will be thrown, if no configuration file can be found.
    /// This is utilized by the client, since only the daemon is allowed to touch the configuration
    /// file.
    pub fn read_with_defaults(
        require_config: bool,
        from_file: &Option<PathBuf>,
    ) -> Result<Settings, Error> {
        let config = Settings::default_config()?;

        parse_config(config, require_config, from_file)
    }

    pub fn default_config() -> Result<Config, Error> {
        let mut config = Config::new();
        let pueue_path = default_pueue_path()?;
        config
            .set_default("shared.pueue_directory", pueue_path.clone())
            .unwrap();
        #[cfg(not(target_os = "windows"))]
        config.set_default("shared.use_unix_socket", true).unwrap();
        #[cfg(not(target_os = "windows"))]
        config
            .set_default("shared.unix_socket_path", get_unix_socket_path()?)
            .unwrap();

        config.set_default("shared.host", "127.0.0.1").unwrap();
        config.set_default("shared.port", "6924").unwrap();
        config.set_default("shared.tls_enabled", true).unwrap();
        config
            .set_default(
                "shared.daemon_key",
                pueue_path.clone() + "/certs/daemon.key",
            )
            .unwrap();
        config
            .set_default(
                "shared.daemon_cert",
                pueue_path.clone() + "/certs/daemon.cert",
            )
            .unwrap();
        config
            .set_default("shared.shared_secret_path", pueue_path + "/shared_secret")
            .unwrap();

        // Client specific config
        config.set_default("client.read_local_logs", true).unwrap();
        config
            .set_default("client.show_expanded_aliases", false)
            .unwrap();
        config
            .set_default("client.show_confirmation_questions", false)
            .unwrap();
        config.set_default("client.dark_mode", false).unwrap();
        config
            .set_default("client.max_status_lines", None::<i64>)
            .unwrap();
        config
            .set_default("client.newline_between_date_and_time", false)
            .unwrap();

        // Daemon specific config
        config
            .set_default("daemon.default_parallel_tasks", 1)
            .unwrap();
        config
            .set_default("daemon.pause_group_on_failure", false)
            .unwrap();
        config
            .set_default("daemon.pause_all_on_failure", false)
            .unwrap();
        config
            .set_default("daemon.callback", None::<String>)
            .unwrap();
        config.set_default("daemon.callback_log_lines", 10).unwrap();
        config
            .set_default("daemon.groups", HashMap::<String, i64>::new())
            .unwrap();

        Ok(config)
    }

    /// Save the current configuration as a file to the given path. \
    /// If no path is given, the default configuration path will be used. \
    /// The file is then written to the main configuration directory of the respective OS.
    pub fn save(&self, path: &Option<PathBuf>) -> Result<(), Error> {
        let config_path = if let Some(path) = path {
            path.clone()
        } else {
            default_config_directory()?.join("pueue.yml")
        };
        let config_dir = config_path
            .parent()
            .ok_or_else(|| Error::InvalidPath("Couldn't resolve config directory".into()))?;

        // Create the config dir, if it doesn't exist yet
        if !config_dir.exists() {
            create_dir_all(config_dir)?;
        }

        let content = match serde_yaml::to_string(self) {
            Ok(content) => content,
            Err(error) => {
                return Err(Error::Generic(format!(
                    "Configuration file serialization failed:\n{}",
                    error
                )))
            }
        };
        let mut file = File::create(config_path)?;
        file.write_all(content.as_bytes())?;

        Ok(())
    }
}

/// Get all possible configuration paths and check if there are
/// configuration files at those locations.
/// All configs will be merged by importance.
///
/// If `require_config` is `true`, an error will be thrown, if no configuration file can be found.
fn parse_config(
    mut config: Config,
    require_config: bool,
    from_file: &Option<PathBuf>,
) -> Result<Settings, Error> {
    // Load the config from a very specific file path
    if let Some(path) = from_file {
        if !path.exists() || !path.is_file() {
            return Err(Error::FileNotFound(format!(
                "Couldn't find config at path {:?}",
                path
            )));
        }
        info!("Using config file at: {:?}", path);
        let config_file = config::File::with_name(path.to_str().unwrap());
        config.merge(config_file)?;
        return Ok(config.try_into()?);
    };

    let mut config_found = false;
    info!("Parsing config files");
    for directory in get_config_directories()?.into_iter() {
        let path = directory.join("pueue.yml");
        info!("Checking path: {:?}", &path);

        // Check if the file exists and parse it.
        if path.exists() && path.is_file() {
            info!("Found config file at: {:?}", path);
            config_found = true;
            let config_file = config::File::with_name(path.to_str().unwrap());
            config.merge(config_file)?;
        }
    }

    if require_config && !config_found {
        return Err(Error::FileNotFound(
            "Couldn't find a configuration file. Did you start the daemon yet?".into(),
        ));
    }

    // Try to can deserialize the entire configuration
    Ok(config.try_into()?)
}
<commit_msg>feat: Add 'restart_in_place' config flag for client<commit_after>use std::collections::{BTreeMap, HashMap};
use std::fs::{create_dir_all, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use config::Config;
use log::info;
use serde_derive::{Deserialize, Serialize};
use shellexpand::tilde;

use crate::error::Error;
use crate::platform::directories::*;

/// All settings which are used by both, the client and the daemon
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Shared {
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The directory that is used for all runtime information. \
    /// I.e. task logs, sockets, state dumps, etc.
    pub pueue_directory: PathBuf,
    /// If this is set to true, unix sockets will be used.
    /// Otherwise we default to TCP+TLS
    #[cfg(not(target_os = "windows"))]
    pub use_unix_socket: bool,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the unix socket.
    #[cfg(not(target_os = "windows"))]
    pub unix_socket_path: PathBuf,

    /// The TCP hostname/ip address.
    pub host: String,
    /// The TCP port.
    pub port: String,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS certificate used by the daemon. \
    /// This is also used by the client to verify the daemon's identity.
    pub daemon_cert: PathBuf,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the TLS key used by the daemon.
    pub daemon_key: PathBuf,
    /// Don't access this property directly, but rather use the getter with the same name.
    /// It's only public to allow proper integration testing.
    ///
    /// The path to the file containing the shared secret used to authenticate the client.
    pub shared_secret_path: PathBuf,
}

/// All settings which are used by the client
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Client {
    /// If set to true, all tasks will be restart in place, instead of creating a new task.
    /// False is the default, as you'll lose the logs of the previously failed tasks when
    /// restarting tasks in place.
    pub restart_in_place: bool,
    /// Whether the client should read the logs directly from disk or whether it should
    /// request the data from the daemon via socket.
    pub read_local_logs: bool,
    /// Whether the client should show a confirmation question on potential dangerous actions.
    pub show_confirmation_questions: bool,
    /// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
    /// or shown in their short form.
    pub show_expanded_aliases: bool,
    /// Whether the client should use dark shades instead of regular colors.
    pub dark_mode: bool,
    /// The max amount of lines each task get's in the `pueue status` view.
    pub max_status_lines: Option<usize>,
    /// The format that will be used to display time formats in `pueue status`.
    pub status_time_format: String,
    /// The format that will be used to display datetime formats in `pueue status`.
    pub status_datetime_format: String,
}

/// All settings which are used by the daemon
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Daemon {
    /// How many parallel tasks a group should have by default
    pub default_parallel_tasks: usize,
    /// Whether a group should be paused as soon as a single task fails
    pub pause_group_on_failure: bool,
    /// Whether the daemon (and all groups) should be paused as soon as a single task fails
    pub pause_all_on_failure: bool,
    /// The callback that's called whenever a task finishes.
    pub callback: Option<String>,
    /// The amount of log lines from stdout/stderr that are passed to the callback command.
    pub callback_log_lines: usize,
    /// This shouldn't be manipulated manually if the daemon is running.
    /// This represents all known groups and their amount of parallel tasks.
    pub groups: BTreeMap<String, usize>,
}

/// The parent settings struct. \
/// This contains all other setting structs.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Settings {
    pub client: Client,
    pub daemon: Daemon,
    pub shared: Shared,
}

impl Shared {
    pub fn expand(old_path: &Path) -> PathBuf {
        PathBuf::from(tilde(&old_path.to_string_lossy()).into_owned())
    }

    pub fn pueue_directory(&self) -> PathBuf {
        Shared::expand(&self.pueue_directory)
    }

    #[cfg(not(target_os = "windows"))]
    pub fn unix_socket_path(&self) -> PathBuf {
        Shared::expand(&self.unix_socket_path)
    }

    pub fn daemon_cert(&self) -> PathBuf {
        Shared::expand(&self.daemon_cert)
    }
    pub fn daemon_key(&self) -> PathBuf {
        Shared::expand(&self.daemon_key)
    }
    pub fn shared_secret_path(&self) -> PathBuf {
        Shared::expand(&self.shared_secret_path)
    }
}

impl Settings {
    /// Read from existing config files.
    /// If no config files can be found or fields are missing, an error is returned.
    pub fn read(from_file: &Option<PathBuf>) -> Result<Settings, Error> {
        let config = Config::new();

        parse_config(config, true, from_file)
    }

    /// Try to read existing config files and
    /// This function creates a new configuration instance and
    /// populates it with default values for every option. \
    /// If a local config file already exists, it is parsed and
    /// overrules the default option values.
    ///
    /// The default local config is located at "~/.config/pueue.yml".
    ///
    /// If `require_config` is `true`, an error will be thrown, if no configuration file can be found.
    /// This is utilized by the client, since only the daemon is allowed to touch the configuration
    /// file.
    pub fn read_with_defaults(
        require_config: bool,
        from_file: &Option<PathBuf>,
    ) -> Result<Settings, Error> {
        let config = Settings::default_config()?;

        parse_config(config, require_config, from_file)
    }

    pub fn default_config() -> Result<Config, Error> {
        let mut config = Config::new();
        let pueue_path = default_pueue_path()?;
        config
            .set_default("shared.pueue_directory", pueue_path.clone())
            .unwrap();
        #[cfg(not(target_os = "windows"))]
        config.set_default("shared.use_unix_socket", true).unwrap();
        #[cfg(not(target_os = "windows"))]
        config
            .set_default("shared.unix_socket_path", get_unix_socket_path()?)
            .unwrap();

        config.set_default("shared.host", "127.0.0.1").unwrap();
        config.set_default("shared.port", "6924").unwrap();
        config.set_default("shared.tls_enabled", true).unwrap();
        config
            .set_default(
                "shared.daemon_key",
                pueue_path.clone() + "/certs/daemon.key",
            )
            .unwrap();
        config
            .set_default(
                "shared.daemon_cert",
                pueue_path.clone() + "/certs/daemon.cert",
            )
            .unwrap();
        config
            .set_default("shared.shared_secret_path", pueue_path + "/shared_secret")
            .unwrap();

        // Client specific config
        config.set_default("client.restart_in_place", true).unwrap();
        config.set_default("client.read_local_logs", true).unwrap();
        config
            .set_default("client.show_expanded_aliases", false)
            .unwrap();
        config
            .set_default("client.show_confirmation_questions", false)
            .unwrap();
        config.set_default("client.dark_mode", false).unwrap();
        config
            .set_default("client.max_status_lines", None::<i64>)
            .unwrap();
        config
            .set_default("client.newline_between_date_and_time", false)
            .unwrap();

        // Daemon specific config
        config
            .set_default("daemon.default_parallel_tasks", 1)
            .unwrap();
        config
            .set_default("daemon.pause_group_on_failure", false)
            .unwrap();
        config
            .set_default("daemon.pause_all_on_failure", false)
            .unwrap();
        config
            .set_default("daemon.callback", None::<String>)
            .unwrap();
        config.set_default("daemon.callback_log_lines", 10).unwrap();
        config
            .set_default("daemon.groups", HashMap::<String, i64>::new())
            .unwrap();

        Ok(config)
    }

    /// Save the current configuration as a file to the given path. \
    /// If no path is given, the default configuration path will be used. \
    /// The file is then written to the main configuration directory of the respective OS.
    pub fn save(&self, path: &Option<PathBuf>) -> Result<(), Error> {
        let config_path = if let Some(path) = path {
            path.clone()
        } else {
            default_config_directory()?.join("pueue.yml")
        };
        let config_dir = config_path
            .parent()
            .ok_or_else(|| Error::InvalidPath("Couldn't resolve config directory".into()))?;

        // Create the config dir, if it doesn't exist yet
        if !config_dir.exists() {
            create_dir_all(config_dir)?;
        }

        let content = match serde_yaml::to_string(self) {
            Ok(content) => content,
            Err(error) => {
                return Err(Error::Generic(format!(
                    "Configuration file serialization failed:\n{}",
                    error
                )))
            }
        };
        let mut file = File::create(config_path)?;
        file.write_all(content.as_bytes())?;

        Ok(())
    }
}

/// Get all possible configuration paths and check if there are
/// configuration files at those locations.
/// All configs will be merged by importance.
///
/// If `require_config` is `true`, an error will be thrown, if no configuration file can be found.
fn parse_config(
    mut config: Config,
    require_config: bool,
    from_file: &Option<PathBuf>,
) -> Result<Settings, Error> {
    // Load the config from a very specific file path
    if let Some(path) = from_file {
        if !path.exists() || !path.is_file() {
            return Err(Error::FileNotFound(format!(
                "Couldn't find config at path {:?}",
                path
            )));
        }
        info!("Using config file at: {:?}", path);
        let config_file = config::File::with_name(path.to_str().unwrap());
        config.merge(config_file)?;
        return Ok(config.try_into()?);
    };

    let mut config_found = false;
    info!("Parsing config files");
    for directory in get_config_directories()?.into_iter() {
        let path = directory.join("pueue.yml");
        info!("Checking path: {:?}", &path);

        // Check if the file exists and parse it.
        if path.exists() && path.is_file() {
            info!("Found config file at: {:?}", path);
            config_found = true;
            let config_file = config::File::with_name(path.to_str().unwrap());
            config.merge(config_file)?;
        }
    }

    if require_config && !config_found {
        return Err(Error::FileNotFound(
            "Couldn't find a configuration file. Did you start the daemon yet?".into(),
        ));
    }

    // Try to can deserialize the entire configuration
    Ok(config.try_into()?)
}
<|endoftext|>