initial commit
This commit is contained in:
commit
250e878c0e
4 changed files with 338 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "dolllang"
|
||||
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "dolllang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
324
src/main.rs
Normal file
324
src/main.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
use core::{fmt, slice};
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::num::TryFromIntError;
|
||||
use std::result::Result as StdResult;
|
||||
use std::{env, fs, io};
|
||||
|
||||
enum Input {
|
||||
File(fs::File),
|
||||
Stdin(io::Stdin),
|
||||
}
|
||||
|
||||
impl Input {
|
||||
fn into_reader(self) -> Box<dyn BufRead> {
|
||||
match self {
|
||||
Input::File(file) => Box::new(BufReader::new(file)),
|
||||
Input::Stdin(stdin) => Box::new(stdin.lock()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Input {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: &str) -> StdResult<Self, Self::Error> {
|
||||
match value {
|
||||
"-" => Ok(Self::Stdin(io::stdin())),
|
||||
path => File::open(path).map(Self::File),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Input {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: String) -> StdResult<Self, Self::Error> {
|
||||
value.as_str().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Tape<T>(T);
|
||||
|
||||
impl<T: Read> Tape<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read> Iterator for Tape<T> {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut byte = 0u8;
|
||||
match self.0.read(slice::from_mut(&mut byte)) {
|
||||
Ok(0usize) => None,
|
||||
Ok(_) => Some(byte),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProgramErrorKind {
|
||||
InvalidSyntax,
|
||||
IllegalMemoryAccess(isize),
|
||||
ConversionError,
|
||||
EndOfFile,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProgramErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidSyntax => write!(f, "invalid syntax"),
|
||||
Self::EndOfFile => write!(f, "end of file"),
|
||||
Self::IllegalMemoryAccess(loc) => write!(f, "illegal memory access @ {loc}"),
|
||||
Self::ConversionError => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProgramError {
|
||||
kind: ProgramErrorKind,
|
||||
source: Option<Box<dyn Error>>,
|
||||
}
|
||||
|
||||
impl ProgramError {
|
||||
pub fn new(kind: ProgramErrorKind, source: impl Into<Box<dyn Error>>) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProgramErrorKind> for ProgramError {
|
||||
fn from(kind: ProgramErrorKind) -> Self {
|
||||
Self { kind, source: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for ProgramError {
|
||||
fn from(value: TryFromIntError) -> Self {
|
||||
Self::new(ProgramErrorKind::ConversionError, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ProgramError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} ({:?})", self.kind, self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ProgramError {}
|
||||
|
||||
type Result<T> = std::result::Result<T, ProgramError>;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum State {
|
||||
Continue(usize),
|
||||
Halt,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Program {
|
||||
memory: Vec<isize>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
fn word_length(mut word: impl Iterator<Item = u8>) -> Result<isize> {
|
||||
let result = word.try_fold((0isize, true, true), |(count, was_w, pos), byte| {
|
||||
match (byte, was_w, pos) {
|
||||
(b'a', true, true) => Some((count, false, true)),
|
||||
(b'w', false, true) => Some((count + 1, true, true)),
|
||||
(b'!', false, true) => Some((count, false, false)),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
let sign = match result {
|
||||
Some((_, _, false)) => -1,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
match result {
|
||||
Some((count, false, _)) => Ok(count * sign),
|
||||
_ => Err(ProgramError::from(ProgramErrorKind::InvalidSyntax)),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_word(program: impl Iterator<Item = u8>) -> Result<isize> {
|
||||
let mut word = program
|
||||
.skip_while(|ch| *ch != b'a' && *ch != b'w' && *ch != b'!')
|
||||
.take_while(|ch| *ch == b'a' || *ch == b'w' || *ch == b'!')
|
||||
.peekable();
|
||||
|
||||
if word.peek().is_none() {
|
||||
Err(ProgramError::from(ProgramErrorKind::EndOfFile))
|
||||
} else {
|
||||
Self::word_length(word)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(mut program: impl Iterator<Item = u8>) -> Result<Vec<isize>> {
|
||||
let mut memory = Vec::new();
|
||||
|
||||
loop {
|
||||
match Self::next_word(&mut program) {
|
||||
Ok(word) => memory.push(word),
|
||||
Err(ProgramError {
|
||||
kind: ProgramErrorKind::EndOfFile,
|
||||
..
|
||||
}) => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(memory)
|
||||
}
|
||||
|
||||
pub fn from_program(program: impl Iterator<Item = u8>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
memory: Self::parse(program)?,
|
||||
index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn next_instruction(&self) -> Option<(isize, isize, isize)> {
|
||||
let [a, b, c] = self.memory.get(self.index..=self.index + 2)? else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((*a, *b, *c))
|
||||
}
|
||||
|
||||
fn set(&mut self, addr: usize, value: isize) -> Result<()> {
|
||||
if addr > self.memory.len() {
|
||||
Err(ProgramErrorKind::IllegalMemoryAccess(addr as isize).into())
|
||||
} else {
|
||||
self.memory[addr] = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_isize(&mut self, addr: isize, value: isize) -> Result<()> {
|
||||
if addr < 0 {
|
||||
Err(ProgramErrorKind::IllegalMemoryAccess(addr).into())
|
||||
} else {
|
||||
match usize::try_from(addr) {
|
||||
Ok(addr) => self.set(addr, value),
|
||||
Err(e) => Err(ProgramError::new(ProgramErrorKind::ConversionError, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_index(&mut self, value: isize) -> Result<()> {
|
||||
if value < 0 {
|
||||
Err(ProgramErrorKind::IllegalMemoryAccess(value).into())
|
||||
} else {
|
||||
match usize::try_from(value) {
|
||||
Ok(value) => {
|
||||
self.index = value;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(ProgramError::new(ProgramErrorKind::ConversionError, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, n: usize) -> Result<&isize> {
|
||||
match self.memory.get(n) {
|
||||
Some(n) => Ok(n),
|
||||
None => Err(ProgramError::from(ProgramErrorKind::IllegalMemoryAccess(
|
||||
n as isize,
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_isize(&self, n: isize) -> Result<&isize> {
|
||||
match usize::try_from(n) {
|
||||
Ok(m) => self.get(m),
|
||||
Err(_) => Err(ProgramError::from(ProgramErrorKind::IllegalMemoryAccess(n))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_as_usize(&self, n: usize) -> Result<usize> {
|
||||
match self.get(n) {
|
||||
Ok(n) => match usize::try_from(*n) {
|
||||
Ok(m) => Ok(m),
|
||||
Err(e) => Err(e.into()),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_isize_as_usize(&self, n: isize) -> Result<usize> {
|
||||
match usize::try_from(n) {
|
||||
Ok(n) => self.get_as_usize(n),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn step(&mut self) -> Result<State> {
|
||||
if let Some((a, b, c)) = self.next_instruction() {
|
||||
if b == -1 && a > -1 {
|
||||
let ax = self.get_isize_as_usize(a)?.try_into()?;
|
||||
char::from_u32(ax).inspect(|c| print!("{c}"));
|
||||
|
||||
let counter = c.try_into()?;
|
||||
Ok(State::Continue(counter))
|
||||
} else if a > -1 && b > -1 {
|
||||
let ax = self.get_isize(a)?;
|
||||
let bx = self.get_isize(b)?;
|
||||
let next = bx - ax;
|
||||
self.set_isize(b, next)?;
|
||||
|
||||
if next > 0 {
|
||||
Ok(State::Continue(self.index + 3))
|
||||
} else {
|
||||
let counter = c.try_into()?;
|
||||
Ok(State::Continue(counter))
|
||||
}
|
||||
} else {
|
||||
Ok(State::Halt)
|
||||
}
|
||||
} else {
|
||||
Ok(State::Halt)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(&mut self) -> Result<()> {
|
||||
loop {
|
||||
match self.step() {
|
||||
Ok(State::Continue(next)) => {
|
||||
self.index = next;
|
||||
}
|
||||
Ok(State::Halt) => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program: io::Result<Input> = args[1].as_str().try_into();
|
||||
|
||||
let program: &mut dyn Read = match program {
|
||||
Ok(input) => &mut input.into_reader(),
|
||||
Err(_) => &mut args[1].as_bytes(),
|
||||
};
|
||||
|
||||
let mut machine = Program::from_program(Tape::new(program)).expect("failed to load program");
|
||||
machine.execute().expect("<halt>");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
Loading…
Reference in a new issue