From a0054004772d3c4bca57fdc70951636daeda0269 Mon Sep 17 00:00:00 2001 From: n3taeli Date: Mon, 5 Feb 2024 09:05:48 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + Cargo.lock | 7 +++ Cargo.toml | 14 +++++ LICENSE.txt | 21 +++++++ README.md | 5 ++ SPECIFICATION.md | 51 +++++++++++++++ build.bat | 5 ++ example-scripts/expr.lb | 9 +++ example-scripts/hello-world.lb | 30 +++++++++ example-scripts/labels.lb | 20 ++++++ example-scripts/math.lb | 23 +++++++ example-scripts/modules/main.lb | 12 ++++ example-scripts/modules/second_part.lb | 8 +++ example-scripts/pick.lb | 15 +++++ example-scripts/size.lb | 10 +++ src/errors.rs | 45 ++++++++++++++ src/execute.rs | 86 ++++++++++++++++++++++++++ src/instructions/add.rs | 8 +++ src/instructions/args.rs | 21 +++++++ src/instructions/div.rs | 8 +++ src/instructions/dump.rs | 5 ++ src/instructions/dup.rs | 5 ++ src/instructions/exec.rs | 20 ++++++ src/instructions/exit.rs | 3 + src/instructions/expr.rs | 31 ++++++++++ src/instructions/getc.rs | 13 ++++ src/instructions/jmp.rs | 10 +++ src/instructions/jnz.rs | 12 ++++ src/instructions/jz.rs | 12 ++++ src/instructions/meow.rs | 5 ++ src/instructions/mod.rs | 22 +++++++ src/instructions/mul.rs | 8 +++ src/instructions/pick.rs | 11 ++++ src/instructions/pop.rs | 3 + src/instructions/push.rs | 3 + src/instructions/putc.rs | 5 ++ src/instructions/scan.rs | 10 +++ src/instructions/size.rs | 3 + src/instructions/sub.rs | 8 +++ src/instructions/swap.rs | 9 +++ src/main.rs | 27 ++++++++ src/parse.rs | 34 ++++++++++ src/stack.rs | 25 ++++++++ 43 files changed, 685 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 SPECIFICATION.md create mode 100644 build.bat create mode 100644 example-scripts/expr.lb create mode 100644 example-scripts/hello-world.lb create mode 100644 example-scripts/labels.lb create mode 100644 example-scripts/math.lb create mode 100644 example-scripts/modules/main.lb create mode 100644 example-scripts/modules/second_part.lb create mode 100644 example-scripts/pick.lb create mode 100644 example-scripts/size.lb create mode 100644 src/errors.rs create mode 100644 src/execute.rs create mode 100644 src/instructions/add.rs create mode 100644 src/instructions/args.rs create mode 100644 src/instructions/div.rs create mode 100644 src/instructions/dump.rs create mode 100644 src/instructions/dup.rs create mode 100644 src/instructions/exec.rs create mode 100644 src/instructions/exit.rs create mode 100644 src/instructions/expr.rs create mode 100644 src/instructions/getc.rs create mode 100644 src/instructions/jmp.rs create mode 100644 src/instructions/jnz.rs create mode 100644 src/instructions/jz.rs create mode 100644 src/instructions/meow.rs create mode 100644 src/instructions/mod.rs create mode 100644 src/instructions/mul.rs create mode 100644 src/instructions/pick.rs create mode 100644 src/instructions/pop.rs create mode 100644 src/instructions/push.rs create mode 100644 src/instructions/putc.rs create mode 100644 src/instructions/scan.rs create mode 100644 src/instructions/size.rs create mode 100644 src/instructions/sub.rs create mode 100644 src/instructions/swap.rs create mode 100644 src/main.rs create mode 100644 src/parse.rs create mode 100644 src/stack.rs 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