aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/node.rs
blob: c7873946f7a38e3f6995cc0e6f68b6f0f3b61567 (plain)
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use std::fmt;

/// A Positioned String. That is, a String which also has information on the
/// line number and the column range.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PString {
    /// The actual value.
    pub value: String,

    /// Line number where it has been found.
    pub line: usize,

    /// The start column where it has been found.
    pub start: usize,

    /// The end column where it has been found.
    pub end: usize,
}

impl PString {
    /// Returns true if the string has either an empty value or an empty range.
    pub fn is_empty(&self) -> bool {
        self.value.is_empty() || (self.start == self.end)
    }

    /// Returns an empty tuple if the string contains a valid identifier, or a
    /// String containing the error otherwise. If `allow_scoped` is set to true,
    /// then identifiers with the "::" operator in it will be considered valid.
    pub fn is_valid_identifier(&self, allow_scoped: bool) -> Result<(), String> {
        if self.value.trim().is_empty() {
            return Err(String::from("empty identifier"));
        }

        // You cannot assign into a name which is reserved.
        if matches!(self.value.to_lowercase().as_str(), "x" | "y" | "a") {
            return Err(format!("cannot use reserved name '{}'", self.value));
        }

        // You cannot assign into scoped names: declare them into their
        // respective scopes instead.
        if !allow_scoped && self.value.contains("::") {
            return Err(format!(
                "the name '{}' is scoped: do not declare things this way",
                self.value
            ));
        }

        // Let's gather info from the variable name which is relevant to later
        // checks.
        let mut alpha_seen = false;
        let mut valid_hex = matches!(self.value.len(), 1..=4);
        for c in self.value.to_lowercase().chars() {
            if c == '_' {
                valid_hex = false;
            } else if c.is_alphabetic() {
                alpha_seen = true;
                if c > 'f' && c <= 'z' {
                    valid_hex = false;
                }
            }
        }

        // We need at least one alphabetic character. Otherwise it might be
        // confusing with numbers.
        if !alpha_seen {
            return Err(format!(
                "name '{}' requires at least one alphabetic character",
                self.value
            ));
        }

        // To avoid problems down the line, you cannot assign into names which
        // are proper hexadecimal values.
        if valid_hex {
            return Err(format!(
                "cannot use names which are valid hexadecimal values such as '{}'",
                self.value
            ));
        }

        Ok(())
    }
}

/// The type of control function being used. Use this enum in order to detect
/// which control function was detected instead of the node value.
#[derive(Debug, Clone, PartialEq)]
pub enum ControlType {
    Hibyte,
    Lobyte,
    StartMacro,
    EndMacro,
    StartProc,
    EndProc,
    StartScope,
    EndScope,
    Segment,
    Byte,
    Word,
    Addr,
}

/// The PNode type.
#[derive(Debug, Clone, PartialEq)]
pub enum NodeType {
    /// A general/abstract value. This can either be a variable, or a macro call
    /// with no arguments. This is, thus, to be determined by the assembler
    /// after a proper context has been set.
    Value,

    /// An instruction. The value of the PNode has the instruction mnemonic. The
    /// left node is the "left arm", which is whatever is left of the comma from
    /// expressions like "lda Left, x", or an Indirection on indirect addressing
    /// mode (e.g. "lda (Indirect, x)"). The righ node contains the "right arm",
    /// which basically contains the index on an indexed addressing mode.
    Instruction,

    /// An Indirection node, which might have two arms (e.g. "lda (Left,
    /// Right)"), or just a left one (e.g. "lda (Left), y")
    Indirection,

    /// An assignment holds the name of the variable on the `value` and the
    /// actual expression that initializes it on the `left`. The rest is None.
    Assignment,

    /// A control statement (e.g. ".proc foo"). The `value` string contains the
    /// name of the function as it was provided (use the `ControlType` value of
    /// the enum to detect which function was exactly provided), the `left` an
    /// optional identifier (e.g. the "foo" on ".proc foo"), and the `args`
    /// contain any possible arguments that have been passed to this control
    /// statement.
    Control(ControlType),

    /// A literal expression, that is, something that starts with '#', '%' or
    /// '$'. The `left` node contains the inner expression.
    Literal,

    /// A label statement, which only sets the `value`, the name of the label.
    Label,

    /// A macro call. Note that a Value might also encode this, but when a Call
    /// has been detected, then there is no doubt on it.
    Call,
}

impl fmt::Display for NodeType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            NodeType::Value => write!(f, "value"),
            NodeType::Instruction => write!(f, "instruction"),
            NodeType::Indirection => write!(f, "indirection"),
            NodeType::Assignment => write!(f, "assignment"),
            NodeType::Control(_) => write!(f, "control function"),
            NodeType::Literal => write!(f, "literal"),
            NodeType::Label => write!(f, "label"),
            NodeType::Call => write!(f, "call"),
        }
    }
}

/// A Position Node. This is a node on a binary tree which holds a PString as a
/// value. The node type determines the actual representation of the value and
/// both childs (see the `NodeType` enum). Moreover, out of convenience, a node
/// also holds an optional list of arguments, which simplifies arrangements such
/// as the `Call` or the `Control` node types.
#[derive(Debug, Clone)]
pub struct PNode {
    /// The type of the PNode.
    pub node_type: NodeType,

    /// A Positioned String acting as the value. Check with the `NodeType`
    /// documentation to check when it makes sense to use it.
    pub value: PString,

    /// The left child of the node. Check with the `NodeType` documentation to
    /// check when it makes sense to use it.
    pub left: Option<Box<PNode>>,

    /// The right child of the node. Check with the `NodeType` documentation to
    /// check when it makes sense to use it.
    pub right: Option<Box<PNode>>,

    /// Convenience list used by some node types in order to express a list of
    /// optional arguments.
    pub args: Option<Vec<PNode>>,
}

impl PNode {
    pub fn is_branch(&self) -> bool {
        if self.node_type != NodeType::Instruction {
            return false;
        }

        let opcode = self.value.value.to_lowercase();
        matches!(
            opcode.as_str(),
            "bcc" | "bcs" | "beq" | "bmi" | "bne" | "bpl" | "bvc" | "bvs"
        )
    }
}