sicompiler 1.0.1

A basic compiler for SiCoMe programs
Documentation
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::Write;

use crate::models::{program::Program, instruction::Instruction};
use crate::errors::error::SicompilerError;

/// The `Validator` struct is responsible for validating a sequence of tokens
/// representing a custom assembly language. It ensures that each instruction
/// is valid and adheres to the expected format, raising errors if any issues are detected.
pub struct Validator {
    output_file: String,
    tokens: Program
}

impl Validator {
    /// Check if a parameter is in hexadecimal base
    /// 
    /// ## Arguments
    /// 
    /// - `params` - A vector with all parameters of the instruction or variable
    /// 
    /// ## Returns
    /// 
    /// - True if is in hexadecimal base
    /// - False if is not in hexadecimal base
    ///
    fn is_hex(params: &Vec<String>) -> bool {
        params.iter().all(|param: &String| {
            param.chars()
                .next()
                .map_or(false, |first_char: char| first_char.is_ascii_hexdigit())
        })
    }

    /// Writes the tokenized information to an output file.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if any issues occur during file writing.
    ///     
    fn write_file(&self) -> Result<(), SicompilerError> {
        let mut file: File = OpenOptions::new()
            .create(true)
            .write(true)
            .open(&self.output_file)
            .map_err(|e: io::Error| 
                SicompilerError::Io(io::Error::new(e.kind(), format!("Can't open {}", self.output_file)))
            )?;

        for variable in self.tokens.variables() {
            file.write_all(format!("{} {}\n", variable.dir(), variable.name()).as_bytes())?;
        }

        writeln!(file, "@")?;
        writeln!(file, "{}", self.tokens.init().dir())?;
        writeln!(file, "@")?;

        for instruction in self.tokens.instructions() {
            file.write_all(format!("{} {}\n", instruction.mnemonic(), instruction.params().join(" ")).as_bytes())?;
        }

        Ok(())
    }

    /// Validates the tokenized program to ensure it contains both instructions and variables sections.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if there is no instructions or variables section.
    /// 
    fn validate_program(&self) -> Result<(), SicompilerError> {
        if self.tokens.variables().is_empty() || self.tokens.instructions().is_empty() {
            return Err(SicompilerError::ValidationError("There is not any instructions or variables section".to_string()));
        }

        Ok(())
    }

    /// Validates the tokenized variables to ensure both directory and name are in hexadecimal format.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if any variable has a non-hexadecimal directory or name.
    /// 
    fn validate_variables(&self) -> Result<(), SicompilerError> {
        for variable in self.tokens.variables() {
            let var: Vec<String> = vec![variable.dir().to_string(), variable.name().to_string()];
            
            if !Validator::is_hex(&var) {
                return Err(SicompilerError::ValidationError(
                    format!("The varibale dir and name must be in hex base '{} {}'", variable.dir(), variable.name())
                ));
            } 
        }

        Ok(())
    }

    /// Validates the tokenized initialization directory to ensure it is in hexadecimal format.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if the initialization directory is not in hexadecimal format.
    /// 
    fn validate_init(&self) -> Result<(), SicompilerError> {
        if !Validator::is_hex(&vec![self.tokens.init().dir().to_string()]) {
            return Err(SicompilerError::ValidationError(
                format!("The init dir must be in hex base '{}'", self.tokens.init().dir())
            ));
        }

        Ok(())
    }

    /// Validates the tokenized instructions to ensure they are valid and have the correct parameters.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if any instruction is invalid or has incorrect parameters.
    /// 
    fn validate_instructions(&self, repertoire: &HashMap<String, Instruction>) -> Result<(), SicompilerError> {
        for instruction in self.tokens.instructions() {
            if !repertoire.contains_key(instruction.mnemonic()) {
                return Err(SicompilerError::ValidationError(
                    format!("Invalid instruction, '{}' does not appear in the repertoire", instruction.mnemonic())
                ));
            }
            
            let rep_instruction: &Instruction = match repertoire.get(instruction.mnemonic()) {
                Some(instruction) => instruction,
                None => {
                    return Err(SicompilerError::ValidationError(
                        format!("The instruction '{}' is not defined in the repertoire", instruction.mnemonic())
                    ));
                }
            };
            
            if instruction.flag() != rep_instruction.flag() {                
                return Err(SicompilerError::ValidationError(
                    format!("The instruction '{}' must have some parameters", instruction.mnemonic())
                ));
            }

            let params: &Vec<String> = instruction.params();
            let rep_params: &Vec<String> = rep_instruction.params();
            
            if params.len() != rep_params.len() {
                return Err(SicompilerError::ValidationError(
                    format!(
                        "Invalid number of parameters in '{}', only has {} but get {}", 
                        instruction.mnemonic(), 
                        rep_params.len(), 
                        instruction.params().len()
                    )
                ));
            } 

            if instruction.flag() && !Validator::is_hex(&params) {
                return Err(SicompilerError::ValidationError(
                    format!("Invalid parameters in '{}', the parameters must be in hex base", instruction.mnemonic())
                ));
            }
        }

        Ok(())
    }
    
    /// Creates a new `Validator` instance with the specified tokens and output file.
    pub fn new(tokens: Program, output_file: &str) -> Validator {
        Validator { tokens, output_file: output_file.to_string() }
    }

    /// Validates the tokenized program, variables, initialization directory, and instructions,
    /// and writes the validated information to an output file.
    /// 
    /// ## Arguments
    /// 
    /// - `&self` - Reference to the `Tokenizer` instance.
    /// 
    /// ## Returns
    /// 
    /// - `Result<(), SicompilerError>` - Result indicating success or an `Error` if any validation step fails.
    /// 
    pub fn validate(&self, repertoire: &HashMap<String, Instruction>) -> Result<(), SicompilerError> {
        self.validate_program()?;
        self.validate_variables()?;
        self.validate_init()?;
        self.validate_instructions(repertoire)?;
        self.write_file()?;
        
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::models::{
        init::Init, 
        variable::Variable,
        instruction::Instruction, 
        program::Program, 
    };

    use super::*;

    #[test]
    fn test_is_hex() {
        let good_params: Vec<String> = vec!["ABCD".to_string(), "1234".to_string()];
        let bad_params: Vec<String> = vec!["GGGGG".to_string(), "845648".to_string()];
        
        assert!(Validator::is_hex(&good_params));
        assert!(!Validator::is_hex(&bad_params));
    }

    #[test]
    fn test_write_file() {
        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")], 
            Init::new("2"), 
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "tests-files/test.txt");

        assert!(validator.write_file().is_ok());

        let content: String = std::fs::read_to_string("tests-files/test.txt").unwrap();
        let expected_content: &str = "A B
@
2
@
ADD 1
";

        assert_eq!(content, expected_content);
    }

    #[test]
    fn test_validate_program() {
        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");

        assert!(validator.validate_program().is_ok());

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_program();

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: There is not any instructions or variables section");
    }

    #[test]
    fn test_validate_variables() {
        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");

        assert!(validator.validate_variables().is_ok());

        let tokens: Program = Program::new(
            vec![Variable::new("HHHHH", "UUUUU")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_variables();

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: The varibale dir and name must be in hex base 'HHHHH UUUUU'");
    }

    #[test]
    fn test_validate_init() {
        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");

        assert!(validator.validate_init().is_ok());

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("GGGGG"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_init();

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: The init dir must be in hex base 'GGGGG'");
    }

    #[test]
    fn test_validate_instructions() {
        let repertoire: HashMap<String, Instruction> = HashMap::from([
            ("ADD".to_string(), Instruction::new("ADD", vec!["0x123"])),
            ("HALT".to_string(), Instruction::new("HALT", vec![]))
        ]);
        
        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");

        assert!(validator.validate_instructions(&repertoire).is_ok());

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("SUB", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_instructions(&repertoire);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: Invalid instruction, 'SUB' does not appear in the repertoire");

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec![])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_instructions(&repertoire);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: The instruction 'ADD' must have some parameters");

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1", "2"])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_instructions(&repertoire);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: Invalid number of parameters in 'ADD', only has 1 but get 2");

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["GGGGG"])]
        );

        let validator: Validator = Validator::new(tokens, "");
        let result: Result<(), SicompilerError> = validator.validate_instructions(&repertoire);

        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Validation error: Invalid parameters in 'ADD', the parameters must be in hex base");
    }

    #[test]
    fn test_validate() {
        let repertoire: HashMap<String, Instruction> = HashMap::from([
            ("ADD".to_string(), Instruction::new("ADD", vec!["0x123"])),
            ("HALT".to_string(), Instruction::new("HALT", vec![]))
        ]);

        let tokens: Program = Program::new(
            vec![Variable::new("A", "B")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "tests-files/test.txt");

        assert!(validator.validate(&repertoire).is_ok());

        let tokens: Program = Program::new(
            vec![Variable::new("HHHHH", "UUUUU")],
            Init::new("2"),
            vec![Instruction::new("ADD", vec!["1"])]
        );

        let validator: Validator = Validator::new(tokens, "tests-files/test.txt");
        let result: Result<(), SicompilerError> = validator.validate(&repertoire);

        assert!(result.is_err());
    }
}