1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Copyright (c) 2021 Saadi Save
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use super::{Context, PasmError::*, PasmResult};
use crate::inst::Op::{self, *};

#[inline]
fn checked_add(dest: &mut usize, val: usize, mar: usize) {
    if let Some(res) = dest.checked_add(val) {
        *dest = res;
    } else {
        warn!("Addition overflow detected at line {}", mar + 1);
        *dest += val;
    }
}

/// Add values
///
/// # Syntax
/// 1. `ADD [lit | reg | addr]` - add to `ACC`
/// 2. `ADD [reg | addr],[lit | reg | addr]` - add second value to first
/// 3. `ADD [reg | addr],[lit | reg | addr],[lit | reg | addr]` - add second and third value, store to first
pub fn add(ctx: &mut Context, op: &Op) -> PasmResult {
    match op {
        MultiOp(ops) => match ops[..] {
            [ref dest, ref val] if dest.is_read_write() && val.is_usizeable() => {
                let line = ctx.mar;
                let val = ctx.read(val)?;
                ctx.modify(dest, |d| checked_add(d, val, line))?;
            }
            [ref dest, ref a, ref b]
                if dest.is_read_write() && a.is_usizeable() && b.is_usizeable() =>
            {
                let mut a = ctx.read(a)?;
                checked_add(&mut a, ctx.read(b)?, ctx.mar);
                ctx.modify(dest, |d| *d = a)?;
            }
            _ => return Err(InvalidMultiOp),
        },
        Null => return Err(NoOperand),
        val if val.is_usizeable() => {
            let val = ctx.read(val)?;
            checked_add(&mut ctx.acc, val, ctx.mar);
        }
        _ => return Err(InvalidOperand),
    }

    Ok(())
}

#[inline]
fn checked_sub(dest: &mut usize, val: usize, mar: usize) {
    if let Some(res) = dest.checked_sub(val) {
        *dest = res;
    } else {
        warn!("Subtraction overflow detected at line {}", mar + 1);
        *dest -= val;
    }
}

/// Subtract values
///
/// # Syntax
/// 1. `ADD [lit | reg | addr]` - subtract from `ACC`
/// 2. `ADD [reg | addr],[lit | reg | addr]` - subtract second value from first
/// 3. `ADD [reg | addr],[lit | reg | addr],[lit | reg | addr]` - subtract third from second value, store to first
pub fn sub(ctx: &mut Context, op: &Op) -> PasmResult {
    match op {
        MultiOp(ops) => match ops[..] {
            [ref dest, ref val] if dest.is_read_write() && val.is_usizeable() => {
                let line = ctx.mar;
                let val = ctx.read(val)?;
                ctx.modify(dest, |d| checked_sub(d, val, line))?;
            }
            [ref dest, ref a, ref b]
                if dest.is_read_write() && a.is_usizeable() && b.is_usizeable() =>
            {
                let mut a = ctx.read(a)?;
                checked_sub(&mut a, ctx.read(b)?, ctx.mar);
                ctx.modify(dest, |d| *d = a)?;
            }
            _ => return Err(InvalidMultiOp),
        },
        val if val.is_usizeable() => {
            let val = ctx.read(val)?;
            checked_sub(&mut ctx.acc, val, ctx.mar);
        }
        Null => return Err(NoOperand),
        _ => return Err(InvalidOperand),
    }

    Ok(())
}

/// Increment register or memory address
///
/// # Syntax
/// `INC [reg | addr]`
pub fn inc(ctx: &mut Context, op: &Op) -> PasmResult {
    match op {
        dest if dest.is_read_write() => {
            let line = ctx.mar;
            ctx.modify(dest, |d| checked_add(d, 1, line))?;
        }
        Null => return Err(NoOperand),
        _ => return Err(InvalidOperand),
    }

    Ok(())
}

/// Decrement register or memory address
///
/// # Syntax
/// `DEC [reg | addr]`
pub fn dec(ctx: &mut Context, op: &Op) -> PasmResult {
    match op {
        dest if dest.is_read_write() => {
            let line = ctx.mar;
            ctx.modify(dest, |d| checked_sub(d, 1, line))?;
        }
        Null => return Err(NoOperand),
        _ => return Err(InvalidOperand),
    }

    Ok(())
}

/// Zero a register or memory address
///
/// # Syntax
/// `ZERO` - zeroes `ACC`
/// `ZERO [reg | addr]` - zeroes the given register or memory address
/// `ZERO [reg | addr], ...` - zeroes all operands
#[cfg(feature = "extended")]
pub fn zero(ctx: &mut Context, op: &Op) -> PasmResult {
    match op {
        MultiOp(ops) => {
            for op in ops.iter().filter(|op| op.is_read_write()) {
                ctx.modify(op, |val| *val = 0)?;
            }
        }
        Null => ctx.acc = 0,
        op if op.is_read_write() => ctx.modify(op, |val| *val = 0)?,
        _ => return Err(InvalidOperand),
    }

    Ok(())
}