commit a0054004772d3c4bca57fdc70951636daeda0269 Author: n3taeli Date: Mon Feb 5 09:05:48 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92e31db --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/archive +/bin \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1c53b1f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "labast" +version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..28abd70 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "labast" +version = "1.0.1" +authors = [ "n3tael" ] +edition = "2021" +description = "A zero-dependencies Labaski interpreter written in Rust." +readme = "README.md" +license = "MIT" +publish = false + +[profile.dev] +overflow-checks = false + +[dependencies] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1d24e62 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 n3tael + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9af8bc7 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Labast +A zero-dependencies Labaski interpreter written in Rust. Fully supports [Labashki specs 1.4.1](./SPECIFICATION.md). + +## Examples +See `example-scripts` directory. \ No newline at end of file diff --git a/SPECIFICATION.md b/SPECIFICATION.md new file mode 100644 index 0000000..f6c4984 --- /dev/null +++ b/SPECIFICATION.md @@ -0,0 +1,51 @@ +# Labaski specification +Last update: 2024-02-05 + +Программа состоит из команд в формате `ИНСТРУКЦИЯ АРГУМЕНТ` (assembler-like) + +Аргумент всегда должен быть `uint16` (`unsigned short`, `u16`) + +Все данные которые программа использует хранятся в стеке, сам стек это массив из этих `unsigned short`'ов + +Размер стека без разницы, но желательно минимум 256 + +Выполнение программы начинается либо с начала файла, либо с лейбла 0, если он есть + +Почти каждая инструкция которые читает что-то стека должна POP'ать прочитаннное значение + +Если название инструкции начинается с #, то парсер программы должен прочитать аргумент к инструкции как строку(до первого пробела) + +## Инструкции + +| Инструкция | Аргумент | Описание | +|-------------|----------------|----------------------------------------------------------------------| +| PUSH | `uint16` | Добавить в стек | +| POP | - | Удалить из стека | +| DUP | - | Дублировать последнее значение в стеке | +| SWAP | - | Поменять местами последние 2 значения в стеке | +| ADD | - | Прибавление последних 2-х значений из стека | +| SUB | - | Вычитание последних 2-х значений из стека | +| MUL | - | Умножение последних 2-х значений из стека | +| DIV | - | Деление последних 2-х значений из стека | +| JMP | `uint16` лейбл | Перейти к лейблу <имя> | +| JNZ | `uint16` лейбл | Перейти к лейблу <имя> если значение с верхушки стека не 0 | +| JZ | `uint16` лейбл | Перейти к лейблу <имя> если значение с верхушки стека 0 | +| NOP | - | Ничего не делает | +| EXIT | - | Выход из программы с кодом 0 | +| PUTC | - | Вывести символ в консоль | +| GETC | - | Получить символ с консоли, добавить символ в стек | +| MEOW | - | Принт, POP'ает со стека | +| DUMP | - | Вывести весь стек в консоль, понятное дело не попает со стека | +| SCAN | - | Сканирует ввод из консоли на `short`'ы и пушит в стек | +| #EXEC | arg | Выполнить код из файла, указанного в аргументе* | +| ARGS | `uint16` | POP'ает аргумент значений с основного стека и пушит их в стек модуля, если указать аргумент 0, то нужное кол-во аргументов она возьмет(попнув) с оригинального стека | +| SIZE | | Пушит в стек значение размера стека | +| QUIT | `uint16` | Полностью завершает выполнение программы. Как аргумент попает exit-код. | + +> \*путь относительно места вызова интерпретатора. Создает отдельный стек для модуля, но передает основной для того чтобы взять оттуда аргументы. После того как модуль выполнился, пушит весь стек модуля в основной. + +## Лейблы + +Лейблы могут быть только `unsigned short`'ами +От 0 (main-лейбл) до минимум 256 +Если юзер высрал два лейбла с одинаковыми идентификаторами, то интерпретатор/компилятор должен выдать ошибку \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..27b6875 --- /dev/null +++ b/build.bat @@ -0,0 +1,5 @@ +@echo off +cargo build --release +cargo zigbuild --release --target x86_64-unknown-linux-gnu +copy target\release\labast.exe bin\ /Y +copy target\x86_64-unknown-linux-gnu\release\labast bin\ /Y \ No newline at end of file diff --git a/example-scripts/expr.lb b/example-scripts/expr.lb new file mode 100644 index 0000000..ef58001 --- /dev/null +++ b/example-scripts/expr.lb @@ -0,0 +1,9 @@ +; written by Labashki developer - aeris +@ 0 + #EXPR >++:+\ + DUMP + +; Expected output +; +; 0: 3 +; 1: 2 \ No newline at end of file diff --git a/example-scripts/hello-world.lb b/example-scripts/hello-world.lb new file mode 100644 index 0000000..16c056e --- /dev/null +++ b/example-scripts/hello-world.lb @@ -0,0 +1,30 @@ +PUSH 72 ; H +PUTC ; Print to console +PUSH 101 ; e +PUTC +PUSH 108 ; l +PUTC +PUSH 108 ; l +PUTC +PUSH 111 ; o +PUTC +PUSH 32 ; +PUTC +PUSH 76 ; L +PUTC +PUSH 97 ; a +PUTC +PUSH 98 ; b +PUTC +PUSH 97 ; a +PUTC +PUSH 115 ; s +PUTC +PUSH 104 ; h +PUTC +PUSH 107 ; k +PUTC +PUSH 105 ; i +PUTC +PUSH 33 ; ! +PUTC \ No newline at end of file diff --git a/example-scripts/labels.lb b/example-scripts/labels.lb new file mode 100644 index 0000000..df1a64a --- /dev/null +++ b/example-scripts/labels.lb @@ -0,0 +1,20 @@ +@ 1 + PUSH 115 ; s + PUTC + PUSH 104 ; h + PUTC + PUSH 107 ; k + PUTC + PUSH 105 ; i + PUTC + EXIT +@ 0 + PUSH 76 ; L + PUTC + PUSH 97 ; a + PUTC + PUSH 98 ; b + PUTC + PUSH 97 ; a + PUTC + JMP 1 \ No newline at end of file diff --git a/example-scripts/math.lb b/example-scripts/math.lb new file mode 100644 index 0000000..3325c7d --- /dev/null +++ b/example-scripts/math.lb @@ -0,0 +1,23 @@ +; Addition +PUSH 2 ; a = 2 +PUSH 3 ; b = 2 +ADD ; a + b +MEOW ; = 5 + +; Subtraction +PUSH 9 ; a = 9 +PUSH 4 ; b = 4 +SUB ; b - a +MEOW ; = 5 + +; Multiplication +PUSH 9 ; a = 9 +PUSH 5 ; b = 5 +MUL ; b * a +MEOW ; = 45 + +; Division +PUSH 81; a = 81 +PUSH 9 ; b = 9 +DIV ; b / a +MEOW ; = 9 \ No newline at end of file diff --git a/example-scripts/modules/main.lb b/example-scripts/modules/main.lb new file mode 100644 index 0000000..6b2d261 --- /dev/null +++ b/example-scripts/modules/main.lb @@ -0,0 +1,12 @@ +PUSH 76 ; L +PUTC +PUSH 97 ; a +PUTC +PUSH 98 ; b +PUTC +PUSH 97 ; a +PUTC +#EXEC second_part.lb +PUSH 33 ; ! +PUTC +QUIT \ No newline at end of file diff --git a/example-scripts/modules/second_part.lb b/example-scripts/modules/second_part.lb new file mode 100644 index 0000000..7f4d727 --- /dev/null +++ b/example-scripts/modules/second_part.lb @@ -0,0 +1,8 @@ +PUSH 115 ; s +PUTC +PUSH 104 ; h +PUTC +PUSH 107 ; k +PUTC +PUSH 105 ; i +PUTC \ No newline at end of file diff --git a/example-scripts/pick.lb b/example-scripts/pick.lb new file mode 100644 index 0000000..961c0ab --- /dev/null +++ b/example-scripts/pick.lb @@ -0,0 +1,15 @@ +; written by Labashki developer - aeris +@ 0 + PUSH 2 + PUSH 3 + PUSH 4 + PUSH 1 + PICK + DUMP + +; Expected output +; +; 0: 2 +; 1: 3 +; 2: 4 +; 3: 3 \ No newline at end of file diff --git a/example-scripts/size.lb b/example-scripts/size.lb new file mode 100644 index 0000000..336c692 --- /dev/null +++ b/example-scripts/size.lb @@ -0,0 +1,10 @@ +@ 0 + PUSH 1 + PUSH 2 + PUSH 3 + SIZE + MEOW + +; Expected output +; +; 3 \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..12d58de --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,45 @@ +use std::fmt::Display; + +pub enum RunError { + RequestArgsInMainModule, + ExecuteItself(String), + FailToReadFile, + FailToGetCharFromConsole, + InvalidInputUShortInt, + PickTooDeep, + PickOutOfBounds, + FailToReadLineFromConsole, + UnknownLabel(u16), + InvalidExpressionUnknownOperator(String), + MemoryEmpty, +} + +impl Display for RunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RunError::RequestArgsInMainModule => write!(f, "Can't require arguments in the main module"), + RunError::ExecuteItself(mod_name) => write!(f, "Module {}: Can't execute itself", mod_name), + RunError::FailToReadFile => write!(f, "Unable to read file"), + RunError::FailToGetCharFromConsole => write!(f, "Failed to get character from console"), + RunError::InvalidInputUShortInt => write!(f, "Invalid input. Please enter a valid unsigned short integer"), + RunError::PickTooDeep => write!(f, "Picking too deep"), + RunError::PickOutOfBounds => write!(f, "Trying to get a value out of bounds"), + RunError::FailToReadLineFromConsole => write!(f, "Failed to read line from console"), + RunError::UnknownLabel(label_name) => write!(f, "Unknown label: {}", label_name), + RunError::InvalidExpressionUnknownOperator(name) => write!(f, "Invalid expression: unknown operator '{}'", name), + RunError::MemoryEmpty => write!(f, "No elements in memory!"), + } + } +} + +pub enum ParseError { + DataNotAUInt(String, usize) +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::DataNotAUInt(name, line) => write!(f, "Argument for {0} at {1} isn't a number", name, line), + } + } +} \ No newline at end of file diff --git a/src/execute.rs b/src/execute.rs new file mode 100644 index 0000000..51fae58 --- /dev/null +++ b/src/execute.rs @@ -0,0 +1,86 @@ +use crate::{instructions, stack::Stack}; + +fn find_labels(stack: &mut Stack) { + while stack.program_counter < (stack.program.len() as u16) { + let label = &stack.program[stack.program_counter as usize]; + stack.program_counter += 1; + + if !label.name.starts_with('@') { + continue; + } + + if stack.labels[label.data as usize].is_some() { + eprintln!("Label {} already defined", label.data); + return; + } + + if label.data == 0 { + stack.labels[0] = Some(stack.program_counter as i16); + continue; + } + + stack.labels[label.data as usize] = Some(stack.program_counter as i16); + } + + if stack.labels[0].is_some() { + stack.program_counter = stack.labels[0].unwrap() as u16; + } else { + stack.program_counter = 0; + } +} + +pub fn execute(stack: &mut Stack, mut origin_stack: Option<&mut Stack>, mod_name: &String) { + find_labels(stack); + + while let Some(instruction) = stack.program.get(stack.program_counter as usize) { + stack.program_counter += 1; + + if instruction.name.starts_with('@') { + continue; + } + + match instruction.name.as_str() { + // Stack operations + "PUSH" => instructions::push::push(&mut stack.memory, &instruction.data), + "PICK" => instructions::pick::pick(&mut stack.memory, &instruction.data), + "POP" => instructions::pop::pop(&mut stack.memory), + "DUP" => instructions::dup::dup(&mut stack.memory), + "SWAP" => instructions::swap::swap(&mut stack.memory), + "MEOW" => instructions::meow::meow(&mut stack.memory), + "DUMP" => instructions::dump::dump(&mut stack.memory), + "SIZE" => instructions::size::size(&mut stack.memory), + + "#EXPR" => instructions::expr::expr(&mut stack.memory, &instruction.arg), + + // Labels + "JMP" => instructions::jmp::jmp(&mut stack.labels, &mut stack.program_counter, &instruction.data), + "JNZ" => instructions::jnz::jnz(&mut stack.memory, &mut stack.labels, &mut stack.program_counter, &instruction.data), + "JZ" => instructions::jz::jz(&mut stack.memory, &mut stack.labels, &mut stack.program_counter, &instruction.data), + + // Math operations + "ADD" => instructions::add::add(&mut stack.memory), + "SUB" => instructions::sub::sub(&mut stack.memory), + "MUL" => instructions::mul::mul(&mut stack.memory), + "DIV" => instructions::div::div(&mut stack.memory), + + // Console operations + "PUTC" => instructions::putc::putc(&mut stack.memory), + "GETC" => instructions::getc::getc(&mut stack.memory), + "SCAN" => instructions::scan::scan(&mut stack.memory), + + // Modules + "ARGS" => instructions::args::args(&mut stack.memory, &mut origin_stack, &instruction.data), + "#EXEC" => instructions::exec::exec(stack, &instruction.arg.clone(), &mod_name), + + // ? + "NOP" => continue, + "EXIT" => instructions::exit::exit(&instruction.data), + "QUIT" => std::process::exit(0), + + _ => { + eprintln!("Unknown instruction: {}", instruction.name); + std::process::exit(2); + } + } + } +} \ No newline at end of file diff --git a/src/instructions/add.rs b/src/instructions/add.rs new file mode 100644 index 0000000..6bbd0c2 --- /dev/null +++ b/src/instructions/add.rs @@ -0,0 +1,8 @@ +use crate::errors::RunError; + +pub fn add(memory: &mut Vec) { + let a: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let b: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(a + b); +} \ No newline at end of file diff --git a/src/instructions/args.rs b/src/instructions/args.rs new file mode 100644 index 0000000..8bbeebe --- /dev/null +++ b/src/instructions/args.rs @@ -0,0 +1,21 @@ +use crate::{errors::RunError, stack::Stack}; + +pub fn args(memory: &mut Vec, origin_stack: &mut Option<&mut Stack>, data: &u16) { + let origin_memory = &mut origin_stack.as_mut().expect("msg").memory; + + if *origin_memory == *memory { + eprintln!("{}", RunError::RequestArgsInMainModule); + std::process::exit(2); + } + + let quanity: u16; + if *data == 0 { + quanity = origin_memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + } else { + quanity = *data; + } + + for _ in 0..quanity { + memory.push(origin_memory.pop().expect(&format!("{}", RunError::MemoryEmpty))); + } +} \ No newline at end of file diff --git a/src/instructions/div.rs b/src/instructions/div.rs new file mode 100644 index 0000000..b1bc4b0 --- /dev/null +++ b/src/instructions/div.rs @@ -0,0 +1,8 @@ +use crate::errors::RunError; + +pub fn div(memory: &mut Vec) { + let a: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let b: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(b / a); +} \ No newline at end of file diff --git a/src/instructions/dump.rs b/src/instructions/dump.rs new file mode 100644 index 0000000..a2df7f4 --- /dev/null +++ b/src/instructions/dump.rs @@ -0,0 +1,5 @@ +pub fn dump(memory: &mut Vec) { + for (i, entry) in memory.iter().enumerate() { + println!("{0}: {1}", i, entry); + } +} \ No newline at end of file diff --git a/src/instructions/dup.rs b/src/instructions/dup.rs new file mode 100644 index 0000000..dfeb398 --- /dev/null +++ b/src/instructions/dup.rs @@ -0,0 +1,5 @@ +use crate::errors::RunError; + +pub fn dup(memory: &mut Vec) { + memory.push(*memory.last().expect(&format!("{}", RunError::MemoryEmpty))); +} \ No newline at end of file diff --git a/src/instructions/exec.rs b/src/instructions/exec.rs new file mode 100644 index 0000000..7476731 --- /dev/null +++ b/src/instructions/exec.rs @@ -0,0 +1,20 @@ +use std::fs; +use crate::{parse, execute, Stack, errors::RunError}; + +pub fn exec(mut stack: &mut Stack, arg: &String, mod_name: &String) { + if mod_name == arg { + eprintln!("{}", RunError::ExecuteItself(arg.to_string())); + std::process::exit(2); + } + + let lines = fs::read_to_string(&arg).expect(&format!("{}", RunError::FailToReadFile)); + let mut temp_stack = Stack::new(); + + parse(&mut temp_stack, &lines); + + execute(&mut temp_stack, Some(&mut stack), &arg.clone()); + + for item in temp_stack.memory { + stack.memory.push(item); + } +} \ No newline at end of file diff --git a/src/instructions/exit.rs b/src/instructions/exit.rs new file mode 100644 index 0000000..32038ac --- /dev/null +++ b/src/instructions/exit.rs @@ -0,0 +1,3 @@ +pub fn exit(data: &u16) { + std::process::exit((*data).into()); +} \ No newline at end of file diff --git a/src/instructions/expr.rs b/src/instructions/expr.rs new file mode 100644 index 0000000..eca34be --- /dev/null +++ b/src/instructions/expr.rs @@ -0,0 +1,31 @@ +use crate::errors::RunError; + +pub fn expr(memory: &mut Vec, arg: &String) { + for code in arg.chars() { + match code { + '>' => memory.push(0), + '$' => { memory.pop(); }, + ':' => memory.push(*memory.last().expect(&format!("{}", RunError::MemoryEmpty))), + '\\' => { + let ax = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let ax2 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(ax); + memory.push(ax2); + } + '+' => { + let ax = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + memory.push(ax + 1); + } + '-' => { + let ax = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + memory.push(ax - 1); + } + + _ => { + eprintln!("{}", RunError::InvalidExpressionUnknownOperator(code.to_string())); + std::process::exit(2); + } + } + } +} \ No newline at end of file diff --git a/src/instructions/getc.rs b/src/instructions/getc.rs new file mode 100644 index 0000000..3575913 --- /dev/null +++ b/src/instructions/getc.rs @@ -0,0 +1,13 @@ +use std::io::Read; +use crate::errors::RunError; + +pub fn getc(memory: &mut Vec) { + let char = std::io::stdin() + .bytes() + .next() + .and_then(|result| result.ok()) + .map(|byte| byte as u16) + .expect(&format!("{}", RunError::FailToGetCharFromConsole)); + + memory.push(char); +} \ No newline at end of file diff --git a/src/instructions/jmp.rs b/src/instructions/jmp.rs new file mode 100644 index 0000000..788249e --- /dev/null +++ b/src/instructions/jmp.rs @@ -0,0 +1,10 @@ +use crate::errors::RunError; + +pub fn jmp(labels: &mut [Option; 256], program_counter: &mut u16, data: &u16) { + if labels[*data as usize].is_none() { + eprintln!("{}", RunError::UnknownLabel(*data)); + std::process::exit(2); + } + + *program_counter = (labels[*data as usize].unwrap() - 1) as u16; +} \ No newline at end of file diff --git a/src/instructions/jnz.rs b/src/instructions/jnz.rs new file mode 100644 index 0000000..2093234 --- /dev/null +++ b/src/instructions/jnz.rs @@ -0,0 +1,12 @@ +use crate::errors::RunError; + +pub fn jnz(memory: &mut Vec, labels: &mut [Option; 256], program_counter: &mut u16, data: &u16) { + if labels[*data as usize].is_none() { + eprintln!("{}", RunError::UnknownLabel(*data)); + std::process::exit(2); + } + + if memory.pop() != Some(0) { + *program_counter = (labels[*data as usize].unwrap() - 1) as u16; + } +} \ No newline at end of file diff --git a/src/instructions/jz.rs b/src/instructions/jz.rs new file mode 100644 index 0000000..f85838d --- /dev/null +++ b/src/instructions/jz.rs @@ -0,0 +1,12 @@ +use crate::errors::RunError; + +pub fn jz(memory: &mut Vec, labels: &mut [Option; 256], program_counter: &mut u16, data: &u16) { + if labels[*data as usize].is_none() { + eprintln!("{}", RunError::UnknownLabel(*data)); + std::process::exit(2); + } + + if memory.pop() == Some(0) { + *program_counter = (labels[*data as usize].unwrap() - 1) as u16; + } +} \ No newline at end of file diff --git a/src/instructions/meow.rs b/src/instructions/meow.rs new file mode 100644 index 0000000..4cb4797 --- /dev/null +++ b/src/instructions/meow.rs @@ -0,0 +1,5 @@ +use crate::errors::RunError; + +pub fn meow(memory: &mut Vec) { + println!("{}", memory.pop().expect(&format!("{}", RunError::MemoryEmpty))); +} \ No newline at end of file diff --git a/src/instructions/mod.rs b/src/instructions/mod.rs new file mode 100644 index 0000000..1fa4217 --- /dev/null +++ b/src/instructions/mod.rs @@ -0,0 +1,22 @@ +pub mod push; +pub mod pop; +pub mod dup; +pub mod swap; +pub mod add; +pub mod sub; +pub mod mul; +pub mod div; +pub mod jmp; +pub mod jnz; +pub mod jz; +pub mod pick; +pub mod putc; +pub mod getc; +pub mod meow; +pub mod dump; +pub mod scan; +pub mod exec; +pub mod args; +pub mod expr; +pub mod size; +pub mod exit; \ No newline at end of file diff --git a/src/instructions/mul.rs b/src/instructions/mul.rs new file mode 100644 index 0000000..d92fbda --- /dev/null +++ b/src/instructions/mul.rs @@ -0,0 +1,8 @@ +use crate::errors::RunError; + +pub fn mul(memory: &mut Vec) { + let a: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let b: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(b * a); +} \ No newline at end of file diff --git a/src/instructions/pick.rs b/src/instructions/pick.rs new file mode 100644 index 0000000..9a2143f --- /dev/null +++ b/src/instructions/pick.rs @@ -0,0 +1,11 @@ +use crate::errors::RunError; + +pub fn pick(memory: &mut Vec, data: &u16) { + if *data > memory.len() as u16 { + eprintln!("{}", RunError::PickTooDeep); + std::process::exit(2); + } + + let arg = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + memory.push(*memory.get(memory.len() - (arg as usize) - 1).expect(&format!("{}", RunError::PickOutOfBounds))); +} \ No newline at end of file diff --git a/src/instructions/pop.rs b/src/instructions/pop.rs new file mode 100644 index 0000000..8f45b61 --- /dev/null +++ b/src/instructions/pop.rs @@ -0,0 +1,3 @@ +pub fn pop(memory: &mut Vec) { + memory.pop(); +} \ No newline at end of file diff --git a/src/instructions/push.rs b/src/instructions/push.rs new file mode 100644 index 0000000..028c9d8 --- /dev/null +++ b/src/instructions/push.rs @@ -0,0 +1,3 @@ +pub fn push(memory: &mut Vec, data: &u16) { + memory.push(*data); +} \ No newline at end of file diff --git a/src/instructions/putc.rs b/src/instructions/putc.rs new file mode 100644 index 0000000..6165051 --- /dev/null +++ b/src/instructions/putc.rs @@ -0,0 +1,5 @@ +use crate::errors::RunError; + +pub fn putc(memory: &mut Vec) { + print!("{}", memory.pop().expect(&format!("{}", RunError::MemoryEmpty)) as u8 as char); +} \ No newline at end of file diff --git a/src/instructions/scan.rs b/src/instructions/scan.rs new file mode 100644 index 0000000..fca29f8 --- /dev/null +++ b/src/instructions/scan.rs @@ -0,0 +1,10 @@ +use crate::errors::RunError; + +pub fn scan(memory: &mut Vec) { + let mut input = String::new(); + + std::io::stdin().read_line(&mut input).expect(&format!("{}", RunError::FailToReadLineFromConsole)); + + let num = input.trim().parse().expect(&format!("{}", RunError::InvalidInputUShortInt)); + memory.push(num); +} \ No newline at end of file diff --git a/src/instructions/size.rs b/src/instructions/size.rs new file mode 100644 index 0000000..fea8032 --- /dev/null +++ b/src/instructions/size.rs @@ -0,0 +1,3 @@ +pub fn size(memory: &mut Vec) { + memory.push(memory.len() as u16); +} \ No newline at end of file diff --git a/src/instructions/sub.rs b/src/instructions/sub.rs new file mode 100644 index 0000000..064419f --- /dev/null +++ b/src/instructions/sub.rs @@ -0,0 +1,8 @@ +use crate::errors::RunError; + +pub fn sub(memory: &mut Vec) { + let a: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let b: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(b - a); +} \ No newline at end of file diff --git a/src/instructions/swap.rs b/src/instructions/swap.rs new file mode 100644 index 0000000..2655af4 --- /dev/null +++ b/src/instructions/swap.rs @@ -0,0 +1,9 @@ +use crate::errors::RunError; + +pub fn swap(memory: &mut Vec) { + let a: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + let b: u16 = memory.pop().expect(&format!("{}", RunError::MemoryEmpty)); + + memory.push(a); + memory.push(b); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..60c70d5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +use std::{env, fs}; +use errors::RunError; +use execute::execute; +use parse::parse; +use stack::Stack; + +mod stack; +mod parse; +mod execute; +mod instructions; +mod errors; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + eprintln!("Usage: {0} [FILE]", args[0]); + return; + } + + let mut stack = Stack::new(); + let lines = fs::read_to_string(&args[1]).expect(&format!("{}", RunError::FailToReadFile)); + + parse(&mut stack, &lines); + + execute(&mut stack, None, &args[1]); +} \ No newline at end of file diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..b05e6d1 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,34 @@ +use crate::{errors::ParseError, stack::{Instruction, Stack}}; + +pub fn parse(stack: &mut Stack, file_content: &str) { + for (i, line) in file_content.lines().enumerate() { + if line.trim().starts_with(';') || line.is_empty() { + continue; + } + + let inst_line: String; + if let Some(comment_index) = line.trim().find(';') { + inst_line = line.trim().chars().take(comment_index).collect(); + } else { + inst_line = line.trim().chars().collect(); + } + + let command: Vec = inst_line.split_whitespace().map(String::from).collect(); + + let name: String; + let mut arg: String = String::new(); + let mut data: u16 = 0; + + name = command[0].clone(); + + if command.len() >= 2 { + match name.chars().nth(0) { + Some('#') => arg = command[1].to_string(), + _ => data = command[1].parse().expect(&format!("{}", ParseError::DataNotAUInt(command[0].to_string(), i + 1))), + } + } + + let inst = Instruction { name, arg, data }; + stack.program.push(inst); + } +} \ No newline at end of file diff --git a/src/stack.rs b/src/stack.rs new file mode 100644 index 0000000..7a191b3 --- /dev/null +++ b/src/stack.rs @@ -0,0 +1,25 @@ +#[derive(Clone, Debug)] +pub struct Instruction { + pub name: String, + pub arg: String, + pub data: u16 +} + +#[derive(Clone, Debug)] +pub struct Stack { + pub program: Vec, + pub program_counter: u16, + pub labels: [Option; 256], + pub memory: Vec +} + +impl Stack { + pub fn new() -> Stack { + return Stack { + program: Vec::new(), + program_counter: 0, + labels: [None; 256], + memory: Vec::new() + }; + } +} \ No newline at end of file