From bf2df69a090290f24c2d581273bf5d4111548137 Mon Sep 17 00:00:00 2001 From: Wim Pomp Date: Mon, 12 Dec 2022 19:08:36 +0100 Subject: [PATCH] First commit of working code. --- .idea/.gitignore | 3 + .idea/befunge.iml | 13 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.toml | 12 + examples/99.bf | 12 + examples/cat_v.bf | 4 + examples/concurrent.bf | 3 + examples/dna.bf | 9 + examples/ex1.bf | 3 + examples/factorial.bf | 8 + examples/factorial0.bf | 10 + examples/factorial_eso.bf | 3 + examples/factorial_heap.bf | 3 + examples/fibonacci.bf | 2 + examples/guess.bf | 25 + examples/guess2.bf | 16 + examples/hello_b98.bf | 2 + examples/hello_world.bf | 5 + examples/hello_world2.bf | 3 + examples/mill.bf | 9 + examples/multiplier.bf | 1 + examples/pi.bf | 4 + examples/quine1.bf | 2 + examples/random.bf | 8 + examples/random_n.bf | 6 + examples/sieve.bf | 5 + examples/soup.bf | 23 + examples/test.bf | 2 + examples/v | 1 + src/debug.rs | 139 ++++++ src/lib.rs | 943 +++++++++++++++++++++++++++++++++++++ src/main.rs | 34 ++ 33 files changed, 1327 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/befunge.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100755 examples/99.bf create mode 100755 examples/cat_v.bf create mode 100755 examples/concurrent.bf create mode 100755 examples/dna.bf create mode 100755 examples/ex1.bf create mode 100755 examples/factorial.bf create mode 100755 examples/factorial0.bf create mode 100755 examples/factorial_eso.bf create mode 100755 examples/factorial_heap.bf create mode 100755 examples/fibonacci.bf create mode 100755 examples/guess.bf create mode 100755 examples/guess2.bf create mode 100755 examples/hello_b98.bf create mode 100755 examples/hello_world.bf create mode 100755 examples/hello_world2.bf create mode 100755 examples/mill.bf create mode 100755 examples/multiplier.bf create mode 100755 examples/pi.bf create mode 100755 examples/quine1.bf create mode 100755 examples/random.bf create mode 100755 examples/random_n.bf create mode 100755 examples/sieve.bf create mode 100755 examples/soup.bf create mode 100755 examples/test.bf create mode 100755 examples/v create mode 100644 src/debug.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/befunge.iml b/.idea/befunge.iml new file mode 100644 index 0000000..b47e22d --- /dev/null +++ b/.idea/befunge.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..af4ac55 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4c3e086 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "befunge" +version = "2022.12.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.0.29", features = ["derive"] } +chrono = "0.4.23" +cursive = { version = "0.20.0", features = ["termion-backend"] } +rand = "0.8.5" diff --git a/examples/99.bf b/examples/99.bf new file mode 100755 index 0000000..153f7c7 --- /dev/null +++ b/examples/99.bf @@ -0,0 +1,12 @@ +#!/usr/bin/env befunge +992+*: v: < +v" ".:<_091+".reeb fo selttob erom on ,llaw eht no reeb fo selttob erom oN">:v +,v"Go to the store and buy some more, 99 bottles of beer on the wall."+910<^,_v + >:#,_@>:101-*+v >$0" ,llaw eht no reeb fo ">:#,_$:.1v ^ $< +>0\ >> ^ v_0"elttob">:#,_$\:!| >091+".reeb fo ">:#,_$ v + >0"selttob"^ >1-!| >" ",\v +>$2\ ^\2<," ".< >091+:".llaw eht no reeb fo "v +^_v > ^ |:< v!:<"Take one down and pass it around, "0< v!:< + !,v ^ ^:-1$_, ^ v$_>,^ +>^ ^ < +^:<"no more "0< > ^ \ No newline at end of file diff --git a/examples/cat_v.bf b/examples/cat_v.bf new file mode 100755 index 0000000..fb2c8da --- /dev/null +++ b/examples/cat_v.bf @@ -0,0 +1,4 @@ +#!/usr/bin/env befunge + w4110'vin10 +">#,:#<_@ \ No newline at end of file diff --git a/examples/concurrent.bf b/examples/concurrent.bf new file mode 100755 index 0000000..c2b5959 --- /dev/null +++ b/examples/concurrent.bf @@ -0,0 +1,3 @@ +#!/usr/bin/env befunge + v +@000_1t111@ \ No newline at end of file diff --git a/examples/dna.bf b/examples/dna.bf new file mode 100755 index 0000000..60d2cca --- /dev/null +++ b/examples/dna.bf @@ -0,0 +1,9 @@ +#!/usr/bin/env befunge +7^DN>vA +v_#v? v +7^<"""" +3 ACGT +90!"""" +4*:>>>v ++8^-1,< +> ,+,@) \ No newline at end of file diff --git a/examples/ex1.bf b/examples/ex1.bf new file mode 100755 index 0000000..c5568e2 --- /dev/null +++ b/examples/ex1.bf @@ -0,0 +1,3 @@ + v < +>?"/",^ + >"\",^ diff --git a/examples/factorial.bf b/examples/factorial.bf new file mode 100755 index 0000000..dc2ad20 --- /dev/null +++ b/examples/factorial.bf @@ -0,0 +1,8 @@ +" ?tupni",v +v.:&,,,,,,< +>" = !",,,v +v1: <\0< ,< +> -:| +>* v$ +|:\<< +>$.25*,@ \ No newline at end of file diff --git a/examples/factorial0.bf b/examples/factorial0.bf new file mode 100755 index 0000000..c5d1b17 --- /dev/null +++ b/examples/factorial0.bf @@ -0,0 +1,10 @@ +#!/usr/bin/env befunge +v +& +>>:1v + |:-< + $ +>v + \ +*: +^_$.55+,@ \ No newline at end of file diff --git a/examples/factorial_eso.bf b/examples/factorial_eso.bf new file mode 100755 index 0000000..35c4c04 --- /dev/null +++ b/examples/factorial_eso.bf @@ -0,0 +1,3 @@ +#!/usr/bin/env befunge +&>:1-:v v *_$.@ + ^ _$>\:^ \ No newline at end of file diff --git a/examples/factorial_heap.bf b/examples/factorial_heap.bf new file mode 100755 index 0000000..c85b5d4 --- /dev/null +++ b/examples/factorial_heap.bf @@ -0,0 +1,3 @@ +#!/usr/bin/env befunge +&:>00p1-::v + ^ *g00 _g.25*,@ diff --git a/examples/fibonacci.bf b/examples/fibonacci.bf new file mode 100755 index 0000000..6ddfb0d --- /dev/null +++ b/examples/fibonacci.bf @@ -0,0 +1,2 @@ +#!/usr/bin/env befunge +j1\:b0p+:.' 1 \ No newline at end of file diff --git a/examples/guess.bf b/examples/guess.bf new file mode 100755 index 0000000..9cd5771 --- /dev/null +++ b/examples/guess.bf @@ -0,0 +1,25 @@ +#!/usr/bin/env befunge +vv < < + 2 + ^ v< + v13v4 + ^ ^ +> >?> ?>5^ + v v + v97v6 + v v< + 8 + > > ^ + vv < < + 2 + ^ v< + v13v4 + ^ ^ + > >?> ?>5^ + v v v ,*25 << + v97v6 ,, + v v< "" + 8 >< + > > ^ ""v + >*: >0"!rebmun tupnI">:#,_$25*,:&:99p`|^< _0"!niw uoY">:#,_$25*,@ + ^ < >:99g01-*+^ \ No newline at end of file diff --git a/examples/guess2.bf b/examples/guess2.bf new file mode 100755 index 0000000..9f138a2 --- /dev/null +++ b/examples/guess2.bf @@ -0,0 +1,16 @@ +#!/usr/bin/env befunge +v>>> > v>>> > v + 012 3 012 3 + ^?^ ^?^ +>>?#v?4>>?#v?4v + v?v v?v + 98765 98765 + >>>>> ^>>>>> v +v 0 + * + : 5 < +>"!sseuG">:#,_v +0v_v#:-&:,+:5$< +, v>0"!niw uoY" ++0>:#,_$5:+,@ +:>`0\"!"\v + v"small"_"gib" +^>" ooT">:#,_$5 \ No newline at end of file diff --git a/examples/hello_b98.bf b/examples/hello_b98.bf new file mode 100755 index 0000000..621d2ab --- /dev/null +++ b/examples/hello_b98.bf @@ -0,0 +1,2 @@ +#!/usr/bin/env befunge +<>:#,_# @#"Hello, World!" \ No newline at end of file diff --git a/examples/hello_world.bf b/examples/hello_world.bf new file mode 100755 index 0000000..7ccf6d1 --- /dev/null +++ b/examples/hello_world.bf @@ -0,0 +1,5 @@ +> v +v ,,,,,"Hello"< +>48*, v +v,,,,,,"World!"< +>25*,@ \ No newline at end of file diff --git a/examples/hello_world2.bf b/examples/hello_world2.bf new file mode 100755 index 0000000..2c3185c --- /dev/null +++ b/examples/hello_world2.bf @@ -0,0 +1,3 @@ + >25*"!dlrow ,olleH":v + v:,_@ + > ^ diff --git a/examples/mill.bf b/examples/mill.bf new file mode 100755 index 0000000..d7d9b4a --- /dev/null +++ b/examples/mill.bf @@ -0,0 +1,9 @@ +#!/usr/bin/env befunge + ] v + >v +v?t1? + 1 t + t 1 + ?1t?v +v< < +>29*y.@ \ No newline at end of file diff --git a/examples/multiplier.bf b/examples/multiplier.bf new file mode 100755 index 0000000..61112ca --- /dev/null +++ b/examples/multiplier.bf @@ -0,0 +1 @@ +&&*.25*,@ \ No newline at end of file diff --git a/examples/pi.bf b/examples/pi.bf new file mode 100755 index 0000000..f7c90ba --- /dev/null +++ b/examples/pi.bf @@ -0,0 +1,4 @@ +#!/usr/bin/env befunge +"^a&EPm=kY}t/qYC+i9wHye$m N@~x+"v +"|DsY<"-"z6n<[Yo2x|UP5VD:">:#v_@> +-:19+/"0"+,19+%"0"+, ^ >39* \ No newline at end of file diff --git a/examples/quine1.bf b/examples/quine1.bf new file mode 100755 index 0000000..724c8b1 --- /dev/null +++ b/examples/quine1.bf @@ -0,0 +1,2 @@ +#!/usr/bin/env befunge +01->1# +# :# 0# g# ,# :# 5# 8# *# 4# +# -# _@ \ No newline at end of file diff --git a/examples/random.bf b/examples/random.bf new file mode 100755 index 0000000..4ea14af --- /dev/null +++ b/examples/random.bf @@ -0,0 +1,8 @@ + v>>>>>v + 12345 + ^?^ + > ? ?^ + v?v + 6789 + >>>> v + ^ .< diff --git a/examples/random_n.bf b/examples/random_n.bf new file mode 100755 index 0000000..bab7643 --- /dev/null +++ b/examples/random_n.bf @@ -0,0 +1,6 @@ +#!/usr/bin/env befunge +& :v>00g2/.@ +v00_^#!`/2g00:< +>0p:1>>:10p` !| +>+00p^?<*2g01:< +^ g00:< \ No newline at end of file diff --git a/examples/sieve.bf b/examples/sieve.bf new file mode 100755 index 0000000..30d8810 --- /dev/null +++ b/examples/sieve.bf @@ -0,0 +1,5 @@ +#!/usr/bin/env befunge +2>:3g" "-!v\ g30 < + |!`"O":+1_:.:03p>03g+:"O"`| + @ ^ p3\" ":< +2 234567890123456789012345678901234567890123456789012345678901234567890123456789 \ No newline at end of file diff --git a/examples/soup.bf b/examples/soup.bf new file mode 100755 index 0000000..abfe598 --- /dev/null +++ b/examples/soup.bf @@ -0,0 +1,23 @@ +#!/usr/bin/env befunge + 060p070 p'O80v + pb2*90p4$4> $4$>v> + v4$>4$>4$>4$># ARGH>! + <{[BEFUNGE_97]}> FUNGE! + ##:-:## #####* 4$*>4$ >060p> 60g80g -!#v_ 60g1+ 60p60v + #vOOGAH **>4$>^!!eg nufeB^ $4$4$4 $470g>90g-! #@_^Befunge!! 123456 123456 VvVv!#!>Weird! >0ggv* + ^$4$4p07+1g07 ,a<$4< <$4$4< <$4$4< <$4$4< <<#<*-=-=-=-=-* -=-=v* + ::48*-#v_>,4$> 4$4$4 $4$4$ 4$4$4$ 4$4$4$ 4$^*!* XXXXXX XXX> + BOINK>$60g1-7 0g+d2* %'A+,1 $1$1$1 $1$1$1 $>^<$ HAR!!! 8888 + Befunge_is such_a pretty langua ge,_is n't_i t?_It_ 8888 + looks_so much_l ike_li ne_noi se_and it's_ STILL_ ā€˜88’ +Turing- Complet e!_Cam ouflag e_your code!! Confu se_the +hell_out of_every one_re ading_ your_co de._Oh, AND_y ou.:-) ,o88o. + Once_this_thing_i s_code d,_rea ding_it_back_ver ges_on the_imp 888888 + ossible._Obfusc ate_the_obfus cated!_Befunge_ debuggers_are__ 888888 + your_friends! By:_Alexios Chouchou las... X-X-X-X-X-X-X! 888888 + -=*##*=- \*****/ 9797* -=97=- !@-*= ***** ā€˜"88P’ + *!@-* + =*!@- + -=*!@ + @-=*! \ No newline at end of file diff --git a/examples/test.bf b/examples/test.bf new file mode 100755 index 0000000..5c61223 --- /dev/null +++ b/examples/test.bf @@ -0,0 +1,2 @@ +#!/usr/bin/env befunge +45*1-y>>#,:#<_$:w@ \ No newline at end of file diff --git a/examples/v b/examples/v new file mode 100755 index 0000000..6769dd6 --- /dev/null +++ b/examples/v @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..4b9499b --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,139 @@ +use std::cmp::{min, max}; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::sync::{Arc, Mutex}; +use std::thread::{sleep, spawn}; +use std::time::Duration; +use cursive::{Cursive, CursiveExt, Vec2}; +use cursive::view::View; +use cursive::Printer; +use cursive::event::{Event, EventResult}; +use cursive::theme::ColorStyle; +use crate::Funge; + + +struct FungeMutex { + funge: Option +} + +impl FungeMutex { + fn new(funge: Funge) -> Self { + Self { funge: Some(funge) } + } + + fn step(&mut self) -> Result> { + let mut funge = self.funge.to_owned().ok_or("No funge found")?; + if !funge.terminated { + funge = funge.step()?; + } + let terminated = funge.terminated; + self.funge = Some(funge); + Ok(terminated) + } + + fn funge_ref(&self) -> Option<&Funge> { + self.funge.as_ref() + } +} + +impl Display for FungeMutex { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.funge.as_ref().expect("No funge found")) + } +} + + +pub(crate) struct FungeView { + funge: Arc> +} + +impl FungeView { + pub (crate) fn new(funge: Funge) -> Result> { + Ok(FungeView { funge: Arc::new(Mutex::new(FungeMutex::new(funge.with_output()?))) } ) + } + + fn step(&mut self) -> Result> { + self.funge.lock().unwrap().step() + } + + fn get_mutex(&self) -> Self { + Self { funge: Arc::clone(&self.funge) } + } + + pub(crate) fn debug(self, interval: Option) -> Result<(), Box> { + let mut app = Cursive::new(); + match interval { + None => {} + Some(interval) => { + let duration = Duration::from_micros((interval * 1e6) as u64); + let mut funge_mutex = self.get_mutex(); + app.set_fps(max((1f64 / interval) as u32, 50)); + spawn(move || { + loop { + sleep(duration); + match funge_mutex.step() { + Ok(terminated) => if terminated { break }, + Err(_) => break + } + } + }); + } + } + app.add_layer(self); + app.add_global_callback('q', |app| app.quit()); + app.run(); + Ok(()) + } +} + +impl View for FungeView { + fn draw(&self, printer: &Printer) { + let (text, ips_pos, terminated) = { + let lock = self.funge.lock(); + let funge = lock.as_ref().unwrap().funge_ref().unwrap(); + (format!("{}", funge), funge.ips_pos(), funge.terminated) + }; + let lines: Vec<&str> = text.lines().collect(); + for (i, line) in lines.iter().enumerate() { + printer.print((0, i), line); + } + for pos in ips_pos.iter() { + if (pos[0] >= 0) & (pos[1] >= 0) { + let x = pos[0] as usize; + let y = pos[1] as usize + 1; + printer.with_color(ColorStyle::highlight(), |printer| printer.print((x, y), &*lines[y].chars().nth(x).unwrap().to_string())); + } + } + let mut bottom = String::from("Press 'q' to quit"); + if terminated { + bottom.push_str("."); + } else { + bottom.push_str(", any other key to continue."); + } + printer.print((0, lines.len() + 1), &*bottom); + } + + fn required_size(&mut self, constraint: Vec2) -> Vec2 { + let text = format!("{}", self.funge.lock().as_ref().unwrap()); + let x = match text.lines().map(|line| line.len()).collect::>().iter().max() { + None => 0, + Some(x) => *x + }; + Vec2::new(min(max(80, x), constraint.x), min(max(25, text.lines().count() + 2), constraint.y)) + } + + fn on_event(&mut self, event: Event) -> EventResult { + match event { + Event::Char('q') => EventResult::Ignored, + Event::Char(_) => { + self.step().ok(); + EventResult::Consumed(None) + } + Event::Key(_) => { + self.step().ok(); + EventResult::Consumed(None) + } + _ => EventResult::Ignored + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3c730bd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,943 @@ +use std::collections::HashMap; +use std::{fmt, fs}; +use std::fmt::{Debug, Display, Formatter}; +use std::io::stdin; +use std::ops::{Index, IndexMut}; +use std::error::Error; +use std::path::Path; +use chrono::{Datelike, Timelike}; +use rand::Rng; +use chrono::offset::Local; + + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + + +fn join(v: &Vec, s: &str) -> String { + let mut string = String::new(); + if v.len() > 1 { + for i in 0..v.len() - 1 { + string.push_str(&v[i].to_string()); + string.push_str(&s); + } + } + if v.len() > 0 { + string.push_str(&v[v.len() - 1].to_string()); + } + string +} + +fn ord(c: char) -> Result> { + Ok(u32::try_from(c)?.try_into()?) +} + +fn chr(i: isize) -> Result> { + Ok(u32::try_from(i)?.try_into()?) +} + +fn add(a: &Vec, b: &Vec) -> Vec { + a.iter().zip(b.iter()).map(|(&a, &b)| a + b).collect() +} + +fn sub(a: &Vec, b: &Vec) -> Vec { + a.iter().zip(b.iter()).map(|(&a, &b)| a - b).collect() +} + + +#[derive(Clone)] +enum InputEnum { + StdIn, + Vector(Vec) +} + +#[derive(Clone)] +struct Input { + source: InputEnum +} + +impl Input { + fn get(&mut self) -> Result> { + Ok(match self.source { + InputEnum::StdIn => { + let mut s = String::new(); + stdin().read_line(&mut s)?; + s + } + InputEnum::Vector(ref mut v) => v.pop().ok_or("No more input!")? + }) + } +} + + +#[derive(Clone)] +enum OutputEnum { + StdOut, + Vector(Vec) +} + +#[derive(Clone)] +struct Output { + sink: OutputEnum +} + +impl Output { + fn print(&mut self, string: String) { + match self.sink { + OutputEnum::StdOut => println!("{}", string), + OutputEnum::Vector(ref mut v) => v.push(string) + } + } +} + + +#[derive(Clone)] +struct Stack { + stack: Vec +} + + +impl Stack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn pop(&mut self) -> isize { + match self.stack.pop() { + Some(value) => { value } + None => { 0 } + } + } + + fn push(&mut self, cell: isize) { + self.stack.push(cell) + } + + fn len(&self) -> usize { self.stack.len() } +} + +impl Display for Stack { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}]", join(&self.stack, ", ")) + } +} + +impl Index for Stack { + type Output = isize; + + fn index(&self, index: usize) -> &Self::Output { + &self.stack[index] + } +} + +#[derive(Clone)] +struct StackStack { + stackstack: Vec +} + +impl StackStack { + fn new() -> Self { + Self { stackstack: vec![Stack::new()] } + } + + fn check_stack(&mut self) { + if self.is_empty() { + self.push_stack(Stack::new()); + } + } + + fn pop(&mut self) -> isize { + self.check_stack(); + let x = self.len_stack(); + self.stackstack[x - 1].pop() + } + + fn push(&mut self, cell: isize) { + self.check_stack(); + let x = self.len_stack(); + self.stackstack[x - 1].push(cell); + } + + fn pop_stack(&mut self) -> Stack { + match self.stackstack.pop() { + Some(stack) => { stack } + None => { Stack::new() } + } + } + + fn push_stack(&mut self, stack: Stack) { + self.stackstack.push(stack) + } + + fn len_stack(&self) -> usize { self.stackstack.len() } + + fn len(&self) -> usize { + if self.len_stack() == 0 { + 0 + } else { + self.stackstack[self.len_stack() - 1].len() + } + } + + fn is_empty(&self) -> bool { + self.stackstack.is_empty() + } + + fn clear(&mut self) { + let l = self.len_stack(); + if l > 0 { + self.pop_stack(); + } + self.push_stack(Stack::new()); + } +} + +impl Index for StackStack { + type Output = Stack; + + fn index(&self, index: usize) -> &Self::Output { + &self.stackstack[index] + } +} + +impl IndexMut for StackStack { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.stackstack[index] + } +} + +impl Display for StackStack { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", join(&self.stackstack, "\n")) + } +} + + +#[derive(Clone)] +struct IP { + id: usize, + position: Vec, + delta: Vec, + offset: Vec, + string: bool, + stack: StackStack, + fingerprint_ops: HashMap, +} + + +impl IP { + fn new(funge: &Funge) -> Result> { + let mut new = IP { + id: funge.ips.len(), + position: vec![0, 0], + delta: vec![1, 0], + offset: vec![0, 0], + string: false, + stack: StackStack::new(), + fingerprint_ops: HashMap::new() + }; + if (new.op(funge) == ord(' ')?) | (new.op(funge) == ord(';')?) { + new.advance(funge); + }; + Ok(new) + } + + fn copy(&self, funge: &Funge) -> Self { + Self { + id: funge.ips.len(), + position: self.position.to_owned(), + delta: self.delta.to_owned(), + offset: self.offset.to_owned(), + string: self.string, + stack: self.stack.to_owned(), + fingerprint_ops: self.fingerprint_ops.to_owned() + } + } + + fn op(&self, funge: &Funge) -> isize { + if funge.code.contains_key(&self.position) { + funge.code[&self.position] + } else { + 32 + } + } + + fn reverse(&mut self) { + self.delta = self.delta.iter().map(|i| -i).collect(); + } + + fn turn_right(&mut self) { + self.delta = vec![-self.delta[1], self.delta[0]]; + } + + fn turn_left(&mut self) { + self.delta = vec![self.delta[1], -self.delta[0]]; + } + + fn advance(&mut self, funge: &Funge) { + if self.string { + if funge.code[&self.position] == 32 { + while funge.code[&self.position] == 32 { + self.movep(funge) + } + } else { + self.movep(funge) + } + } else { + loop { + if self.op(funge) != 59 { + self.movep(funge) + } + if self.op(funge) == 59 { + self.movep(funge); + while self.op(funge) == 59 { + self.movep(funge) + } + self.movep(funge) + } + while self.op(funge) == 32 { + self.movep(funge) + } + if self.op(funge) != 59 { + break; + } + } + } + } + + fn movep(&mut self, funge: &Funge) { + self.position = self.next_pos(funge); + } + + fn check_pos(&self, pos: &Vec, funge: &Funge) -> bool { + (funge.extent[0] <= pos[0]) & (pos[0] < funge.extent[1]) & + (funge.extent[2] <= pos[1]) & (pos[1] < funge.extent[3]) + } + + fn next_pos(&self, funge: &Funge) -> Vec { + let mut pos= add(&self.position, &self.delta); + if !self.check_pos(&pos, funge) { + loop { + pos = sub(&pos, &self.delta); + if !self.check_pos(&pos, funge) { + pos = add(&pos, &self.delta); + break + } + } + } + pos + } + + fn read_string(&mut self) -> Result> { + let mut string = String::new(); + loop { + let f = self.stack.pop(); + if f == 0 { + return Ok(string) + } else { + string.push_str(&chr(f)?.to_string()) + } + } + } + + fn get_info(&self, funge: &Funge, n: isize) -> Result, Box> { + let time = Local::now(); + Ok(match n { + 1 => { vec![15] } + 2 => { vec![isize::BITS as isize] } + 3 => { + let mut f = 0; + for (i, c) in "wpfunge".chars().enumerate() { + f += (256 as isize).pow(i as u32) * ord(c)?; + } + vec![f] + } + 4 => { vec![VERSION.replace(".", "").parse()?] } + 5 => { vec![1] } + 6 => { vec![ord(std::path::MAIN_SEPARATOR)?] } + 7 => { vec![2] } + 8 => { vec![self.id as isize] } + 9 => { vec![0] } + 10 => { self.position.to_owned() } + 11 => { self.delta.to_owned() } + 12 => { self.offset.to_owned() } + 13 => { funge.extent.chunks(2).map(|i| i[0]).collect() } + 14 => { funge.extent.chunks(2).map(|i| i[1]).collect() } + 15 => { vec![((time.year() as isize) - 1900) * 256 * 256 + (time.month() as isize) * 256 + (time.day() as isize)] } + 16 => { vec![(time.hour() as isize) * 256 * 256 + (time.minute() as isize) * 256 + (time.second() as isize)] } + 17 => { vec![self.stack.len_stack() as isize] } + 18 => { + let mut l = Vec::new(); + for stack in &self.stack.stackstack { + l.push(stack.len() as isize); + } + l.reverse(); + l + } + 19 => { + let mut r = Vec::new(); + let mut args = std::env::args(); + if args.len() > 2 { + for i in 2..args.len() { + let j: Vec = args.nth(i).expect("We checked the length.") + .chars().map(|i| ord(i).expect("")).collect(); + r.extend(j); + r.push(0); + } + } + r.push(0); + let file = args.nth(1).expect("We checked the length."); + let path = Path::new(&file); + let j: Vec = path.file_name().ok_or("No file name.")? + .to_str().ok_or("Cannot convert String.")? + .chars().map(|i| ord(i).expect("")).collect(); + r.extend(j); + r.push(0); + r.push(0); + r.reverse(); + r + } + 20 => { + let mut r = Vec::new(); + let vars = std::env::vars(); + for (key, value) in vars { + let j: Vec = key.chars().map(|i| ord(i).expect("")).collect(); + r.extend(j); + r.push(ord('=')?); + let j: Vec = value.chars().map(|i| ord(i).expect("")).collect(); + r.extend(j); + r.push(0); + } + r.push(0); + r.reverse(); + r + } + i => { + let j = i as usize - 20; + let l = self.stack.len(); + if l >= j { + vec![self.stack.stackstack[0][l - j]] + } else { + vec![0] + } + } + }) + } + + fn not_implemented(&mut self, funge: &Funge) { + println!("operator {} at {} not implemented", self.op(funge), join(&self.position, ", ")); + self.reverse() + } + + fn step(mut self, mut funge: Funge, k: bool) -> Result<(Funge, Option>), Box> { + let mut new_ips = Vec::new(); + if self.string { + match self.op(&funge) { + 34 => { self.string = false } // ' + s => { self.stack.push(s) } + } + } else if self.fingerprint_ops.contains_key(&self.op(&funge)) { + // self.fingerprint_ops[self.op(funge)]? + } else if (0 <= self.op(&funge)) & (self.op(&funge) < 255) { + match self.op(&funge) { + 43 => { // + + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push(a + b); + } + 45 => { // - + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push(a - b); + } + 42 => { // * + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push(a * b); + } + 47 => { // / + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push(a / b); + } + 37 => { // % + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push(a % b); + } + 33 => { // ! + let a = self.stack.pop(); + self.stack.push(!a as isize); + } + 96 => { // ` + let b = self.stack.pop(); + let a = self.stack.pop(); + self.stack.push((a > b) as isize); + } + 62 => self.delta = vec![1, 0], // > + 60 => self.delta = vec![-1, 0], // < + 94 => self.delta = vec![0, -1], // ^ + 118 => self.delta = vec![0, 1], // v + 63 => { // ? + let mut rng = rand::thread_rng(); + self.delta = match rng.gen_range(0..4) { + 0 => { vec![-1, 0] } + 1 => { vec![1, 0] } + 2 => { vec![0, -1] } + _ => { vec![0, 1] } + }; + } + 95 => { // _ + if self.stack.pop() == 0 { + self.delta = vec![1, 0] + } else { + self.delta = vec![-1, 0] + } + } + 124 => { // | + if self.stack.pop() == 0 { + self.delta = vec![0, 1]; + } else { + self.delta = vec![0, -1]; + } + } + 34 => self.string = true, // " + 58 => { // : + let v = self.stack.pop(); + self.stack.push(v); + self.stack.push(v); + } + 92 => { // \ + let a = self.stack.pop(); + let b = self.stack.pop(); + self.stack.push(a); + self.stack.push(b); + } + 36 => { self.stack.pop(); } // $ + 46 => funge.output.print(format!("{} ", self.stack.pop())), // . + 44 => funge.output.print(format!("{}", chr(self.stack.pop())?)), // , + 35 => self.movep(&funge), // # + 112 => { // p + let y = self.stack.pop(); + let x = self.stack.pop(); + let v = self.stack.pop(); + funge.insert(v, x + self.offset[0], y + self.offset[1]); + } + 103 => { // g + let y = self.stack.pop(); + let x = self.stack.pop(); + self.stack.push(funge.code[&vec![x + self.offset[0], y + self.offset[1]]]); + } + 38 => { // & + let s = funge.inputs.get()?; + let i: Vec = s.chars() + .skip_while(|i| !i.is_digit(10)) + .take_while(|i| i.is_digit(10)).collect(); + self.stack.push(join(&i, "").parse()?); + } + 126 => { // ~ + let s = funge.inputs.get()?; + self.stack.push(ord(s.chars().nth(0).ok_or("No valid input.")?)?); + } + 64 => { return Ok((funge, Some(Vec::new()))); } // @ + 32 => { // space + self.advance(&funge); + return self.step(funge, false); + } + // 98 from here + 91 => self.turn_left(), // [ + 93 => self.turn_right(), // ] + 39 => { // ' + self.movep(&funge); + self.stack.push(self.op(&funge)); + } + 123 => { // { + let n = self.stack.pop(); + let cells = if n > 0 { + let mut cells = Vec::new(); + for _ in 0..n { + cells.push(self.stack.pop()); + } + cells.reverse(); + cells + } else { + vec![0; -n as usize] + }; + for coordinate in &self.offset { + self.stack.push(*coordinate); + } + self.stack.push_stack(Stack::new()); + for cell in cells { + self.stack.push(cell); + } + self.offset = self.next_pos(&funge); + } + 125 => { // } + let n = self.stack.pop(); + let cells = if n > 0 { + let mut cells = Vec::new(); + for _ in 0..n { + cells.push(self.stack.pop()); + } + cells.reverse(); + cells + } else { + vec![0; -n as usize] + }; + self.stack.pop_stack(); + let y = self.stack.pop(); + let x = self.stack.pop(); + self.offset = vec![x, y]; + for cell in cells { + self.stack.push(cell); + } + } + 61 => { // = + self.reverse(); + // self.stack.push(syscall(self.read_string())); + } + 40 => { // ( no fingerprints are implemented + // self.read_fingerprint(); + // self.fingerprint_ops[] = self.reverse; + self.reverse(); + } + 41 => { // ) + // self.read_fingerprint() + // self.fingerprint_ops.pop() + self.reverse(); + } + 105 => { // i + let file = self.read_string()?; + let flags = self.stack.pop(); + let y0 = self.stack.pop(); + let x0 = self.stack.pop(); + let text = fs::read_to_string(file)?; + let (width, height) = if flags % 2 != 0 { + let code: Vec = text.chars().collect(); + funge.insert_code(vec![join(&code, "")], x0, y0)?; + (text.len(), 1) + } else { + let text: Vec<&str> = text.lines().collect(); + let height = text.len(); + let width = text.iter().map(|i| i.len()).min().ok_or("Cannot calculate width.")?; + let mut code: Vec = Vec::new(); + for line in text { + let a = format!("{}{}", line, join(&vec![" "; width - line.len()], "")); + code.push(a); + } + funge.insert_code(code, x0, y0)?; + (width, height) + }; + self.stack.push(x0); + self.stack.push(y0); + self.stack.push(width as isize); + self.stack.push(height as isize); + } + 106 => { // j + for _ in 0..self.stack.pop() { + self.movep(&funge); + } + } + 107 => { // k + self.advance(&funge); + let n = self.stack.pop(); + let mut ips = vec![self]; + for _ in 0..n { + let mut new_ips = Vec::new(); + for ip in ips { + funge = match ip.step(funge, true)? { + (f, None) => { return Ok((f, None)) } + (f, Some(ips)) => { + new_ips.extend(ips); + f + } + } + } + ips = new_ips; + } + return Ok((funge, Some(ips))) + } + 110 => self.stack.clear(), // n + 111 => { // o + let file = self.read_string()?; + let flags = self.stack.pop(); + let x0 = self.stack.pop(); + let y0 = self.stack.pop(); + let width = self.stack.pop(); + let height = self.stack.pop(); + let mut text = Vec::new(); + if flags % 2 != 0 { + for x in x0..x0 + width { + let mut line = String::new(); + for y in y0..y0 + height { + line.push(chr(funge.code[&vec![x, y]])?); + } + line.rsplit(' '); + text.push(line); + } + } else { + for x in x0..x0 + width { + let mut line = String::new(); + for y in y0..y0 + height { + line.push(chr(funge.code[&vec![x, y]])?); + } + text.push(line); + } + } + let text = join(&text, "\n"); + fs::write(file, text)?; + } + 113 => { return Ok((funge, None)) } // q + 114 => self.reverse(), // r + 115 => { // s + self.movep(&funge); + funge.insert(self.stack.pop(), self.position[0], self.position[1]); + } + 116 => { // t + let mut new = self.copy(&funge); + new.reverse(); + new_ips.push(new); + } + 117 => { // u + if self.stack.is_empty() { + self.reverse(); + } else { + let n = self.stack.pop(); + let l = self.stack.len_stack(); + if n > 0 { + for _ in 0..n { + let a = self.stack[l - 2].pop(); + self.stack.push(a); + } + } else if n < 0 { + for _ in 0..-n { + let a = self.stack.pop(); + self.stack[l - 2].push(a); + } + } + } + } + 119 => { // w + let b = self.stack.pop(); + let a = self.stack.pop(); + if a < b { + self.turn_left(); + } else if a > b { + self.turn_right(); + } + } + 120 => { // x + let dy = self.stack.pop(); + let dx = self.stack.pop(); + self.delta = vec![dx, dy]; + } + 121 => { // y + let n = self.stack.pop(); + if n <= 0 { + for j in 1..21 { + for i in self.get_info(&funge,j) { + for cell in i { + self.stack.push(cell); + } + } + } + } else { + for i in self.get_info(&funge, n) { + for cell in i { + self.stack.push(cell); + } + } + } + } + 122 => { } // z + d => { + if (48 <= d) & (d <= 57) { // 0123456789 + self.stack.push(d - 48); + } else if (97 <= d) & (d <= 102) { + self.stack.push(d - 87); + } else { + self.not_implemented(&funge); + } + } + } + } else { + self.not_implemented(&funge); + } + if !k { + self.advance(&funge); + } + let mut ips = vec![self]; + ips.extend(new_ips); + Ok((funge, Some(ips))) + } +} + +#[derive(Clone)] +pub struct Funge { + extent: Vec, + code: HashMap, isize>, + steps: isize, + ips: Vec, + inputs: Input, + output: Output, + pub terminated: bool +} + +impl Funge { + pub fn new(code: T) -> Result> { + let mut new = Self { + extent: vec![0, 0, 0, 0], + code: HashMap::new(), + steps: 0, + ips: Vec::new(), + inputs: Input { source: InputEnum::StdIn }, + output: Output { sink: OutputEnum::StdOut }, + terminated: false + }; + let mut code: Vec = code.to_string().lines().map(|i| String::from(i)).collect(); + if code[0].starts_with(r"#!/usr/bin/env befunge") | code[0].starts_with(r"#!/usr/bin/env -S befunge") { + code.remove(0); + } + new.insert_code(code, 0, 0)?; + new.ips.push(IP::new(&new)?); + Ok(new) + } + + pub fn from_file(file: &String) -> Result> { + Ok(Self::new(fs::read_to_string(file)?)?) + } + + pub fn with_inputs(mut self, inputs: Vec) -> Result> { + self.inputs = Input { source: InputEnum::Vector(inputs) }; + Ok(self) + } + + pub fn with_output(mut self) -> Result> { + self.output = Output { sink: OutputEnum::Vector(Vec::new()) }; + Ok(self) + } + + fn insert(&mut self, i: isize, x: isize, y: isize) { + self.code.insert(vec![x, y], i); + } + + fn insert_code(&mut self, code: Vec, x0: isize, y0: isize) -> Result<(), Box> { + for (y, line) in code.iter().enumerate() { + for (x, char) in line.chars().enumerate() { + let x1: isize = x.try_into()?; + let y1: isize = y.try_into()?; + let position = vec![x0 + x1, y0 + y1]; + if position[0] < self.extent[0] { + self.extent[0] = position[0]; + } else if position[0] >= self.extent[1] { + self.extent[1] = position[0] + 1; + } + if position[1] < self.extent[2] { + self.extent[2] = position[1]; + } else if position[1] >= self.extent[3] { + self.extent[3] = position[1] + 1; + } + self.code.insert(position, ord(char)?); + } + } + Ok(()) + } + + pub fn run(mut self) -> Result>{ + while !self.terminated { + self = self.step()?; + } + Ok(self) + } + + pub fn step(mut self) -> Result> { + if !self.terminated { + self.ips.reverse(); + let mut new_ips = Vec::new(); + for _ in 0..self.ips.len() { + let ip = self.ips.pop().expect(""); + self = match ip.step(self, false)? { + (f, Some(ips)) => { + new_ips.extend(ips); + f + } + (mut f, None) => { + f.terminated = true; + return Ok(f) + } + } + } + self.ips.extend(new_ips); + self.steps += 1; + if self.ips.len() == 0 { + self.terminated = true; + } + } + Ok(self) + } + + pub fn ips_pos(&self) -> Vec> { + let mut pos = Vec::new(); + for ip in self.ips.iter() { + pos.push(ip.position.to_owned()); + } + pos + } + + fn to_string(&self, show_ips: bool) -> String { + let mut lines = Vec::new(); + for (key, value) in (&self.code).into_iter() { + let x= key[0] as usize; + let y= key[1] as usize; + while lines.len() <= y { + lines.push(Vec::new()); + } + while lines[y].len() <= x { + lines[y].push(String::from(" ")); + } + if ((32 <= *value) & (*value <= 126)) | ((161 <= *value) & (*value <= 255)) { + lines[y][x] = chr(*value).unwrap().to_string(); + } else { + lines[y][x] = chr(164).unwrap().to_string(); + } + } + + if show_ips { + for ip in &self.ips { + let x = ip.position[0] as usize; + let y = ip.position[1] as usize; + lines[y][x] = format!("\x1b[37m\x1b[40m{}\u{001b}[0m", lines[y][x]); + } + } + + let mut string = String::from("grid:\n"); + string.push_str(&join(&lines.iter().map(|i| join(&i, "")).collect(), "\n")); + string.push_str("\n\nstacks:\n"); + for ip in &self.ips { + string.push_str(&ip.stack.to_string()); + } + + match &self.output.sink { + OutputEnum::StdOut => { }, + OutputEnum::Vector(v) => { + string.push_str("\n\nOutput:\n"); + string.push_str(&*join(&v, "")); + } + }; + + string.push_str("\n\nsteps:\n"); + string.push_str(&self.steps.to_string()); + string + } +} + +impl Display for Funge { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_string(false)) + } +} + +impl Debug for Funge { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_string(true)) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1a8d054 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,34 @@ +mod debug; + +use std::error::Error; +use clap::Parser; +use befunge::Funge; +use debug::FungeView; + + +#[derive(Parser)] +#[command(version)] +struct Args { + #[arg(id = "funge code file")] + input: String, + #[arg(help = "debug, step on key press or steps / second", + short, long, value_name = "interval", num_args = 0..=1)] + debug: Option>, + #[arg(id = "arguments to the funge (& or ~)")] + arguments: Vec, +} + + +fn main() -> Result<(), Box> { + let args = Args::parse(); + let mut funge = Funge::from_file(&args.input)?; + if args.arguments.len() > 0 { + funge = funge.with_inputs(args.arguments)?; + } + + match args.debug { + Some(interval) => FungeView::new(funge)?.debug(interval)?, + None => { funge.run()?; } + } + Ok(()) +} \ No newline at end of file