rewrite the stdio module to be less broken. also add sehbang support

pull/21/head
Ondřej Hruška 3 years ago
parent 982ba27ed3
commit ace92a6411
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 48
      Cargo.lock
  2. 8
      README.md
  3. 18
      crsn/crsn-sexp/src/lib.rs
  4. 67
      crsn/src/asm/instr/cond.rs
  5. 2
      crsn/src/asm/instr/flatten.rs
  6. 11
      crsn/src/asm/mod.rs
  7. 1
      crsn/src/asm/parse/arg_parser.rs
  8. 3
      crsn/src/asm/parse/mod.rs
  9. 3
      crsn/src/asm/parse/parse_instr.rs
  10. 4
      crsn/src/builtin/exec.rs
  11. 63
      crsn/src/runtime/frame/status.rs
  12. 8
      crsn/src/runtime/run_thread/state.rs
  13. 34
      crsn_arith/src/exec.rs
  14. 18
      crsn_buf/src/exec.rs
  15. 24
      crsn_screen/src/exec.rs
  16. 2
      crsn_stdio/Cargo.toml
  17. 189
      crsn_stdio/src/lib.rs
  18. 2
      examples/number_array.csn
  19. 10
      examples/stdio.csn
  20. 6
      examples/stdio_rev.csn
  21. 7
      examples/stdio_rot13.csn

48
Cargo.lock generated

@ -168,23 +168,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "console"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b1aacfaffdbff75be81c15a399b4bedf78aaefe840e8af1d299ac2ade885d2"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"regex",
"terminal_size",
"termios",
"unicode-width",
"winapi",
"winapi-util",
]
[[package]]
name = "crsn"
version = "0.1.0"
@ -228,8 +211,8 @@ dependencies = [
name = "crsn_stdio"
version = "0.1.0"
dependencies = [
"console",
"crsn",
"libc",
]
[[package]]
@ -274,12 +257,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c53dc3a653e0f64081026e4bf048d48fec9fce90c66e8326ca7292df0ff2d82"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "env_logger"
version = "0.7.1"
@ -394,9 +371,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.77"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
[[package]]
name = "lock_api"
@ -996,25 +973,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "termios"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
dependencies = [
"libc",
]
[[package]]
name = "textwrap"
version = "0.11.0"

@ -15,10 +15,16 @@ It's probably faster than you need for most things, actually.
You can slow it down using the `-C` argument, or using sleep instructions.
#### What if I don't enjoy writing assembly that looks like weird Lisp?
### What if I don't enjoy writing assembly that looks like weird Lisp?
Maybe this is not for you
### Shebang?
Yes! You can use crsn as a scripting language!
The first line from a source file is skipped if it starts with `#!`
### Contributing
Yup, go ahead. You can also develop your own private *crsn* extensions, they work like plugins.

@ -144,7 +144,7 @@ fn atom_of_string(s: String) -> Atom {
// returns the char it found, and the new pos if you wish to consume that char
fn peek(s: &str, pos: usize) -> ERes<(char, usize)> {
trace!("peek {}", pos);
//trace!("peek {}", pos);
if pos == s.len() { return err("unexpected eof", s, pos); }
if s.is_char_boundary(pos) {
let ch = s[pos..].chars().next().unwrap();
@ -158,7 +158,7 @@ fn peek(s: &str, pos: usize) -> ERes<(char, usize)> {
// returns the char it found, and the new pos if you wish to consume that char
fn peekn(s: &str, nth: usize, pos: usize) -> Option<(char, usize)> {
trace!("peekn {}", pos);
//trace!("peekn {}", pos);
if nth == 0 {
panic!("peekn with nth=0");
}
@ -183,7 +183,7 @@ fn peekn(s: &str, nth: usize, pos: usize) -> Option<(char, usize)> {
}
fn expect(s: &str, pos: &mut usize, c: char) -> ERes<()> {
trace!("expect {}", pos);
//trace!("expect {}", pos);
let (ch, next) = peek(s, *pos)?;
*pos = next;
if ch == c {
@ -208,7 +208,7 @@ fn consume_until_newline(s: &str, pos: &mut usize) -> ERes<()> {
// zero or more spaces
fn zspace(s: &str, pos: &mut usize) -> ERes<()> {
trace!("zspace {}", pos);
//trace!("zspace {}", pos);
loop {
if *pos == s.len() {
return Ok(());
@ -226,7 +226,7 @@ fn zspace(s: &str, pos: &mut usize) -> ERes<()> {
}
fn parse_quoted_atom(s: &str, quote: char, pos: &mut usize) -> ERes<Atom> {
trace!("parse_quoted_atom {}", pos);
//trace!("parse_quoted_atom {}", pos);
let pos0 = *pos;
let mut cs: String = String::new();
@ -266,7 +266,7 @@ fn parse_quoted_atom(s: &str, quote: char, pos: &mut usize) -> ERes<Atom> {
}
fn parse_unquoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_unquoted_atom {}", pos);
//trace!("parse_unquoted_atom {}", pos);
let mut cs: String = String::new();
loop {
@ -288,7 +288,7 @@ fn parse_unquoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
}
fn parse_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_atom {}", pos);
//trace!("parse_atom {}", pos);
let (ch, _) = peek(s, *pos)?;
if ch == '"' {
@ -309,7 +309,7 @@ fn parse_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
}
fn parse_list(s: &str, pos: &mut usize) -> ERes<Vec<Sexp>> {
trace!("parse_list {}", pos);
//trace!("parse_list {}", pos);
zspace(s, pos)?;
expect(s, pos, '(')?;
@ -331,7 +331,7 @@ fn parse_list(s: &str, pos: &mut usize) -> ERes<Vec<Sexp>> {
}
fn parse_sexp(s: &str, pos: &mut usize) -> ERes<Sexp> {
trace!("parse_sexp {}", pos);
//trace!("parse_sexp {}", pos);
zspace(s, pos)?;
let (c, _) = peek(s, *pos)?;
let r = if c == '(' {

@ -5,6 +5,55 @@ use sexp::SourcePosition;
use crate::asm::error::CrsnError;
/// Condition flag
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Flag {
/// Equality
Equal,
/// Equal to zero
Zero,
/// A < B
Lower,
/// A > B
Greater,
/// A > 0
Positive,
/// A < 0
Negative,
/// Arithmetic operation caused an integer operand to overflow
Overflow,
/// Arithmetic (or other) operation was invalid.
/// Example: division by zero
Invalid,
/// Arithmetic carry
Carry,
// Full
Full,
// Empty
Empty,
// Empty
Eof,
}
impl From<Flag> for Cond {
fn from(flag: Flag) -> Self {
match flag {
Flag::Equal => Cond::Equal,
Flag::Zero => Cond::Zero,
Flag::Lower => Cond::Lower,
Flag::Greater => Cond::Greater,
Flag::Positive => Cond::Positive,
Flag::Negative => Cond::Negative,
Flag::Overflow => Cond::Overflow,
Flag::Invalid => Cond::Invalid,
Flag::Carry => Cond::Carry,
Flag::Full => Cond::Full,
Flag::Empty => Cond::Empty,
Flag::Eof => Cond::Eof,
}
}
}
/// Condition flag
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Cond {
@ -53,6 +102,10 @@ pub enum Cond {
Empty,
/// Not empty
NotEmpty,
// Empty
Eof,
/// Not empty
NotEof,
}
pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
@ -77,6 +130,8 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
"nf" | "nfull" => Cond::NotFull,
"ok" | "val" | "valid" => Cond::Valid,
"inval" | "invalid" | "nval" | "nok" => Cond::Invalid,
"eof" => Cond::Eof,
"neof" => Cond::NotEof,
"ov" => Cond::Overflow,
"nov" => Cond::NotOverflow,
_ => {
@ -85,6 +140,13 @@ pub fn parse_cond(text: &str, pos: &SourcePosition) -> Result<Cond, CrsnError> {
})
}
impl Display for Flag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let cond : Cond = (*self).into();
write!(f, "{}", cond)
}
}
impl Display for Cond {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
@ -110,6 +172,8 @@ impl Display for Cond {
Cond::NotCarry => "nc",
Cond::Invalid => "inval",
Cond::Valid => "valid",
Cond::Eof => "eof",
Cond::NotEof => "neof",
})
}
}
@ -147,6 +211,9 @@ impl Not for Cond {
Cond::Invalid => Cond::Valid,
Cond::Valid => Cond::Invalid,
Cond::NotEof => Cond::Eof,
Cond::Eof => Cond::NotEof,
}
}
}

@ -39,6 +39,8 @@ impl Flatten for InstrWithBranches {
let parent_pos = self.pos;
if let Some(branches) = self.branches {
// trace!("Branches {:?}", branches);
let labels = HashMap::<Cond, u32>::new();
let branch_count = branches.len();
let end_lbl = Label::unique(label_num);

@ -23,6 +23,17 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
p.init(uniq);
}
// remove first line if it looks like a shebang
let source = if source.starts_with("#!") {
if let Some(nl) = source.find('\n') {
&source[nl + 1..]
} else {
source
}
} else {
source
};
let pcx = ParserContext {
parsers: &parsers,
state: RefCell::new(ParserState {

@ -7,6 +7,7 @@ use crate::asm::parse::ParserContext;
use crate::asm::parse::sexp_expect::expect_string_atom;
/// Utility for argument parsing
#[derive(Debug)]
pub struct TokenParser<'a> {
orig_len: usize,
args: Vec<Sexp>,

@ -20,6 +20,7 @@ pub mod parse_op;
pub mod arg_parser;
pub mod parse_routine;
#[derive(Debug)]
pub struct ParserContext<'a> {
/// Extension modules
pub parsers: &'a [Box<dyn CrsnExtension>],
@ -27,7 +28,7 @@ pub struct ParserContext<'a> {
pub state: RefCell<ParserState>,
}
#[derive(Default)]
#[derive(Default, Debug)]
pub struct ParserState {
/// Register aliases within the routine
pub reg_aliases: HashMap<RegisterAlias, Register>,

@ -63,10 +63,11 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
.collect::<Vec<_>>();
let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx);
// debug!("branch_tokens: {:#?}", branch_tokens);
let branches = {
let mut branches = vec![];
for t in branch_tokens {
for t in branch_tokens.into_iter().rev() {
branches.push(parse_cond_branch(t, pcx)?);
}
if branches.is_empty() {

@ -4,12 +4,12 @@ use sexp::Sexp;
use crate::asm::data::{Rd, RdData};
use crate::asm::data::literal::Addr;
use crate::asm::instr::Cond;
use crate::builtin::defs::{Barrier, BuiltinOp};
use crate::module::{EvalRes, OpTrait};
use crate::runtime::fault::Fault;
use crate::runtime::frame::StackFrame;
use crate::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::asm::instr::cond::Flag;
impl OpTrait for BuiltinOp {
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
@ -138,7 +138,7 @@ impl OpTrait for BuiltinOp {
}
if !dropped {
warn!("Object {:#x} to del does not exist!", x);
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}

@ -3,6 +3,7 @@ use std::fmt;
use crate::asm::data::literal::{is_negative, is_positive, Value};
use crate::asm::instr::Cond;
use crate::asm::instr::cond::Flag;
#[derive(Default, Clone, Debug)]
pub struct StatusFlags {
@ -28,6 +29,8 @@ pub struct StatusFlags {
pub full: bool,
/// Buffer empty
pub empty: bool,
/// Stream ended
pub eof: bool,
}
impl StatusFlags {
@ -49,6 +52,7 @@ impl StatusFlags {
if self.carry { val |= 0x100; }
if self.full { val |= 0x200; }
if self.empty { val |= 0x400; }
if self.eof { val |= 0x800; }
val
}
@ -64,6 +68,7 @@ impl StatusFlags {
if val & 0x100 != 0 { self.carry = true; }
if val & 0x200 != 0 { self.full = true; }
if val & 0x400 != 0 { self.empty = true; }
if val & 0x800 != 0 { self.eof = true; }
}
#[inline(always)]
@ -98,50 +103,49 @@ impl StatusFlags {
Cond::NotFull => !self.full,
Cond::Empty => self.empty,
Cond::NotEmpty => !self.empty,
Cond::Eof => self.eof,
Cond::NotEof => !self.eof,
}
}
#[inline(always)]
pub fn set(&mut self, cond: Cond) {
pub fn set(&mut self, cond: Flag, val : bool) {
match cond {
Cond::Equal => {
self.equal = true;
Flag::Equal => {
self.equal = val;
}
Cond::Zero => {
self.zero = true;
Flag::Zero => {
self.zero = val;
}
Cond::Lower => {
self.lower = true;
Flag::Lower => {
self.lower = val;
}
Cond::Greater => {
self.lower = false;
self.equal = false;
self.greater = true;
Flag::Greater => {
self.greater = val;
}
Cond::Positive => {
self.positive = true;
Flag::Positive => {
self.positive = val;
}
Cond::Negative => {
self.negative = true;
Flag::Negative => {
self.negative = val;
}
Cond::Overflow => {
self.overflow = true;
Flag::Overflow => {
self.overflow = val;
}
Cond::Invalid => {
self.invalid = true;
Flag::Invalid => {
self.invalid = val;
}
Cond::Carry => {
self.carry = true;
Flag::Carry => {
self.carry = val;
}
Cond::Empty => {
self.empty = true;
Flag::Empty => {
self.empty = val;
}
Cond::Full => {
self.full = true;
Flag::Full => {
self.full = val;
}
other => {
error!("Cannot set cond by {:?}", other);
// ...and do nothing. Don't panic, we don't want to crash the runtime!
Flag::Eof => {
self.eof = val;
}
}
}
@ -183,6 +187,9 @@ impl Display for StatusFlags {
if self.empty {
f.write_str(" Emp")?;
}
if self.eof {
f.write_str(" Eof")?;
}
f.write_str(" ]")?;
Ok(())

@ -9,6 +9,7 @@ use crate::runtime::frame::{CallStack, REG_COUNT, StackFrame};
use std::sync::Arc;
use crate::runtime::run_thread::ThreadInfo;
use nudge::{likely};
use crate::asm::instr::cond::Flag;
pub struct RunState {
pub thread_info: Arc<ThreadInfo>,
@ -51,10 +52,9 @@ impl RunState {
/// Set a status flag. Only supports simple, positive conds (i.e. not GreaterOrEqual)
#[inline(always)]
pub fn set_flag(&mut self, cond: Cond, set: bool) {
if set {
self.frame.status.set(cond);
}
pub fn set_flag(&mut self, flag: Flag, set: bool) {
trace!("Flag {} = {:?}", flag, set);
self.frame.status.set(flag, set);
}
/// Check status flags for a condition

@ -3,7 +3,6 @@ use std::ops::Rem;
use num_traits::PrimInt;
use crsn::asm::data::{Rd, Wr};
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
@ -12,6 +11,7 @@ use crsn::sexp::Sexp;
use crsn::utils::A;
use crate::defs::ArithOp;
use crsn::asm::instr::cond::Flag;
impl OpTrait for ArithOp {
fn execute(&self, _ti: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
@ -26,9 +26,9 @@ impl OpTrait for ArithOp {
state.clear_status();
let x = state.read(a)?;
let y = state.read(b)?;
state.set_flag(Cond::Equal, x == y);
state.set_flag(Cond::Lower, x < y);
state.set_flag(Cond::Greater, x > y);
state.set_flag(Flag::Equal, x == y);
state.set_flag(Flag::Lower, x < y);
state.set_flag(Flag::Greater, x > y);
// Test flags are set when both arguments have the property
if x == y {
state.update_status(x);
@ -39,9 +39,9 @@ impl OpTrait for ArithOp {
let val = state.read(val)?;
let a = state.read(a)?;
let b = state.read(b)?;
state.set_flag(Cond::Equal, val >= a && val <= b);
state.set_flag(Cond::Lower, val < a);
state.set_flag(Cond::Greater, val > b);
state.set_flag(Flag::Equal, val >= a && val <= b);
state.set_flag(Flag::Lower, val < a);
state.set_flag(Flag::Greater, val > b);
state.update_status(val);
}
ArithOp::Add { dst, a, b } => {
@ -54,7 +54,7 @@ impl OpTrait for ArithOp {
(x.wrapping_add(y), true)
};
state.update_status(res);
state.set_flag(Cond::Overflow, ov);
state.set_flag(Flag::Overflow, ov);
state.write(dst, res)?;
}
ArithOp::Sub { dst, a, b } => {
@ -67,7 +67,7 @@ impl OpTrait for ArithOp {
(x.wrapping_sub(y), true)
};
state.update_status(res);
state.set_flag(Cond::Overflow, ov);
state.set_flag(Flag::Overflow, ov);
state.write(dst, res)?;
}
ArithOp::Mul { dst, a, b } => {
@ -77,7 +77,7 @@ impl OpTrait for ArithOp {
let res = if let Some(v) = x.checked_mul(y) {
v
} else {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
x.wrapping_mul(y)
};
state.update_status(res);
@ -88,7 +88,7 @@ impl OpTrait for ArithOp {
let x = state.read(a)?;
let d = state.read(div)?;
if d == 0 {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
let (res, remainder, ov) = if let Some(v) = x.checked_div(d) {
(v, x.rem(d), false)
@ -96,7 +96,7 @@ impl OpTrait for ArithOp {
(x.wrapping_div(d), x.wrapping_rem(d), true)
};
state.update_status(res);
state.set_flag(Cond::Overflow, ov);
state.set_flag(Flag::Overflow, ov);
state.write(dst, res)?;
state.write(rem, remainder)?;
}
@ -106,7 +106,7 @@ impl OpTrait for ArithOp {
let x = state.read(a)?;
let d = state.read(div)?;
if d == 0 {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
let (remainder, ov) = if let Some(v) = x.checked_rem(d) {
(v, false)
@ -114,7 +114,7 @@ impl OpTrait for ArithOp {
(x.wrapping_rem(d), true)
};
state.update_status(remainder);
state.set_flag(Cond::Overflow, ov);
state.set_flag(Flag::Overflow, ov);
state.write(dst, remainder)?;
}
}
@ -154,7 +154,7 @@ impl OpTrait for ArithOp {
let x = state.read(a)?;
let y = state.read(n)?;
if y > u32::MAX as u64 {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
let res = x.rotate_left(y as u32);
state.update_status(res);
@ -166,7 +166,7 @@ impl OpTrait for ArithOp {
let x = state.read(a)?;
let y = state.read(n)?;
if y > u32::MAX as u64 {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
let res = x.rotate_right(y as u32);
state.update_status(res);
@ -194,7 +194,7 @@ impl OpTrait for ArithOp {
let x = state.read(a)?;
let y = state.read(n)?;
if y > u32::MAX as u64 {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
let res = x.signed_shr(y as u32);
state.update_status(res);

@ -1,7 +1,6 @@
use std::collections::{HashMap, VecDeque};
use crsn::asm::data::literal::Value;
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
@ -10,6 +9,7 @@ use crsn::sexp::{Sexp, atom_qs};
use crsn::utils::A;
use crate::defs::{BufOps, BufValue};
use crsn::asm::instr::cond::Flag;
#[derive(Debug, Default)]
struct ExtData {
@ -71,10 +71,10 @@ impl OpTrait for BufOps {
let empty = stack.is_empty();
state.write(dst, val)?;
state.update_status(val);
state.set_flag(Cond::Empty, empty);
state.set_flag(Flag::Empty, empty);
} else {
state.set_flag(Cond::Empty, true);
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Empty, true);
state.set_flag(Flag::Overflow, true);
}
}
@ -89,7 +89,7 @@ impl OpTrait for BufOps {
state.update_status(val);
state.write(dst, val)?;
} else {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
}
}
@ -115,7 +115,7 @@ impl OpTrait for BufOps {
} else if idx == stack.len() {
stack.push_back(val);
} else {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
}
}
@ -131,7 +131,7 @@ impl OpTrait for BufOps {
} else if idx == stack.len() {
stack.push_back(val);
} else {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
}
}
@ -146,9 +146,9 @@ impl OpTrait for BufOps {
let empty = stack.is_empty();
state.update_status(val);
state.write(dst, val)?;
state.set_flag(Cond::Empty, empty);
state.set_flag(Flag::Empty, empty);
} else {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
}
}

@ -4,7 +4,6 @@ use std::time::{Duration, Instant};
use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions};
use crsn::asm::data::literal::Value;
use crsn::asm::instr::Cond;
use crsn::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
@ -13,6 +12,7 @@ use crsn::sexp::Sexp;
use crsn::utils::A;
use crate::defs::ScreenOp;
use crsn::asm::instr::cond::Flag;
#[derive(Debug)]
struct Opts {
@ -87,7 +87,7 @@ impl OpTrait for ScreenOp {
}
other => {
warn!("Bad screen opt: {}", other);
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}
@ -115,7 +115,7 @@ impl OpTrait for ScreenOp {
w.update();
}
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}
@ -129,7 +129,7 @@ impl OpTrait for ScreenOp {
let backend: &mut Backend = state.ext_mut();
if x >= backend.width as u64 || y >= backend.height as u64 {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
return Ok(eres);
}
@ -138,7 +138,7 @@ impl OpTrait for ScreenOp {
let index = y * backend.width as u64 + x;
if index as usize > backend.buffer.len() {
warn!("Screen set pixel out of bounds");
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
} else {
backend.buffer[index as usize] = color as u32;
@ -148,7 +148,7 @@ impl OpTrait for ScreenOp {
}
}
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}
@ -162,7 +162,7 @@ impl OpTrait for ScreenOp {
debug!("mp = {:?}", mp);
match mp {
None => {
state.set_flag(Cond::Overflow, true);
state.set_flag(Flag::Overflow, true);
}
Some((xf, yf)) => {
let xval = xf.round() as u64;
@ -174,7 +174,7 @@ impl OpTrait for ScreenOp {
}
}
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}
@ -187,7 +187,7 @@ impl OpTrait for ScreenOp {
Some(w) => {
match num {
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
Some(kn) => {
let down = w.is_key_down(kn) as u64;
@ -197,7 +197,7 @@ impl OpTrait for ScreenOp {
}
}
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}
@ -210,7 +210,7 @@ impl OpTrait for ScreenOp {
1 => Some(MouseButton::Right),
2 => Some(MouseButton::Middle),
_ => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
None
}
};
@ -225,7 +225,7 @@ impl OpTrait for ScreenOp {
}
}
None => {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
}
}

@ -8,4 +8,4 @@ edition = "2018"
[dependencies]
crsn = { path = "../crsn" }
console = "0.12.0"
libc = "0.2.79"

@ -6,43 +6,139 @@ use crsn::module::{CrsnExtension, ParseRes, CrsnUniq};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{RunState};
use crsn::sexp::SourcePosition;
use std::io::{Write};
use std::convert::TryFrom;
use crsn::asm::instr::Cond;
use console::Term;
use std::io;
use crsn::asm::instr::cond::Flag;
#[derive(Debug, Clone)]
pub struct StdioOps {
hdl_stdin : Value,
hdl_stdout : Value,
}
mod console {
use std::{io};
use std::os::unix::io::RawFd;
#[derive(Debug)]
struct StdioData {
console: Term,
}
use std::ffi::c_void;
use std::mem::{self, MaybeUninit};
fn setup_fd(fd: RawFd) -> io::Result<libc::termios> {
use libc::*;
let mut tio = MaybeUninit::uninit();
if 0 != unsafe { tcgetattr(fd, tio.as_mut_ptr()) } {
return Err(io::Error::last_os_error());
}
let mut tio = unsafe { MaybeUninit::assume_init(tio) };
let old_tio : termios = unsafe { mem::transmute_copy(&tio) };
tio.c_iflag &= !(/*BRKINT |*/ /*ICRNL |*/ INPCK | ISTRIP | IXON);
tio.c_oflag |= ONLCR;
tio.c_cflag |= CS8;
tio.c_lflag &= !(ECHO | ICANON | IEXTEN /*| ISIG*/);
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 0;
if 0 != unsafe { tcsetattr(fd, TCSANOW, &tio) } {
return Err(io::Error::last_os_error());
}
Ok(old_tio)
}
pub fn init_io() -> io::Result<libc::termios> {
setup_fd(libc::STDIN_FILENO)
}
impl Default for StdioData {
fn default() -> Self {
Self {
console : Term::stdout()
pub fn read_byte() -> io::Result<u8> {
let mut buf = 0u8;
let len = unsafe { libc::read(libc::STDIN_FILENO, &mut buf as *mut u8 as *mut c_void, 1) };
if len <= 0 {
Err(io::Error::last_os_error())
} else {
Ok(buf as u8)
}
}
pub fn write_byte(b : u8) -> io::Result<()> {
let len = unsafe { libc::write(libc::STDOUT_FILENO, &b as *const u8 as *const c_void, 1) };
if len <= 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn write_char(c : char) -> io::Result<()> {
let mut buf = [0u8; 4];
for b in c.encode_utf8(&mut buf).as_bytes() {
write_byte(*b)?;
}
Ok(())
}
pub fn read_char() -> io::Result<char> {
let first = read_byte()?;
if first & 0x80 == 0 {
return Ok(first as char);
}
let mut bytes = [first, 0, 0, 0];
let remain = if first & 0b1110_0000 == 0b1100_0000 {
1
} else if first & 0b1111_0000 == 0b1110_0000 {
2
} else /*if first & 0b1111_1000 == 0b1111_0000*/ {
3
};
for n in 1..=remain {
bytes[n] = read_byte()?;
}
std::str::from_utf8(&bytes[..=remain])
.map(|s| s.chars().nth(0).unwrap())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
}
#[derive(Debug, Clone)]
pub struct StdioOps {
old_tio: Option<libc::termios>,
hdl_stdin : Value,
hdl_stdin_raw : Value,
hdl_stdout : Value,
hdl_stdout_raw : Value,
}
impl StdioOps {
pub fn new() -> Box<dyn CrsnExtension> {
Box::new(Self {
old_tio: None,
hdl_stdin: 0,
hdl_stdout: 0
hdl_stdin_raw: 0,
hdl_stdout: 0,
hdl_stdout_raw: 0,
})
}
}
impl Drop for StdioOps {
fn drop(&mut self) {
// Un-break the terminal
if let Some(tio) = self.old_tio.take() {
let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) };
}
}
}
impl CrsnExtension for StdioOps {
fn init(&mut self, uniq: &CrsnUniq) {
self.hdl_stdin = uniq.unique_handle();
self.hdl_stdin_raw = uniq.unique_handle();
self.hdl_stdout = uniq.unique_handle();
self.hdl_stdout_raw = uniq.unique_handle();
// This can fail if the input is not a tty
if let Ok(tio) = console::init_io() {
self.old_tio = Some(tio);
}
}
/// Get value of an extension-provided constant.
@ -50,8 +146,10 @@ impl CrsnExtension for StdioOps {
fn get_constant_value<'a>(&self, name: &str) -> Option<Value>
{
match name {
"stdin" => Some(self.hdl_stdin),
"stdout" => Some(self.hdl_stdout),
"cin" => Some(self.hdl_stdin),
"cin_r" => Some(self.hdl_stdin_raw),
"cout" => Some(self.hdl_stdout),
"cout_r" => Some(self.hdl_stdout_raw),
_ => None
}
}
@ -69,12 +167,36 @@ impl CrsnExtension for StdioOps {
-> Result<Option<Value>, Fault>
{
if handle == self.hdl_stdin {
let data = state.ext_mut::<StdioData>();
return Ok(Some(data.console.read_char().expect("stdin read") as u64));
match console::read_char() {
Ok(c) => {
return Ok(Some(c as Value));
}
Err(e) => {
state.set_flag(Flag::Invalid, true);
if e.kind() != io::ErrorKind::InvalidData {
state.set_flag(Flag::Eof, true);
}
return Ok(Some(0));
}
}
}
if handle == self.hdl_stdout {
return Err(Fault::NotAllowed("Cannot read stdout".into()));
if handle == self.hdl_stdin_raw {
match console::read_byte() {
Ok(b) => {
return Ok(Some(b as Value));
}
Err(_e) => {
state.set_flag(Flag::Invalid, true);
state.set_flag(Flag::Eof, true);
return Ok(Some(0));
}
}
}
if handle == self.hdl_stdout || handle == self.hdl_stdout_raw {
state.set_flag(Flag::Invalid, true);
return Ok(Some(0));
}
Ok(None)
@ -87,18 +209,25 @@ impl CrsnExtension for StdioOps {
if handle == self.hdl_stdout {
if let Ok(a_char) = char::try_from((value & 0xFFFF_FFFF) as u32) {
let data = state.ext_mut::<StdioData>();
let mut b = [0; 4];
data.console.write(a_char.encode_utf8(&mut b).as_bytes()).expect("stdout write");
if console::write_char(a_char).is_err() {
state.set_flag(Flag::Eof, true);
}
} else {
state.set_flag(Cond::Invalid, true);
state.set_flag(Flag::Invalid, true);
}
return Ok(Some(()));
}
if handle == self.hdl_stdin {
return Err(Fault::NotAllowed("Cannot write stdin".into()));
if handle == self.hdl_stdout_raw {
if console::write_byte((value & 0xFF) as u8).is_err() {
state.set_flag(Flag::Eof, true);
}
return Ok(Some(()));
}
if handle == self.hdl_stdin || handle == self.hdl_stdin_raw {
state.set_flag(Flag::Invalid, true);
return Ok(Some(()));
}
Ok(None)

@ -3,6 +3,6 @@
(sym buf r7)
(mkbf buf (r0 66 67 68 '\n'))
(bfrpop @stdout @buf)
(bfrpop @cout @buf)
(s.nem -1)
)

@ -20,27 +20,27 @@
(:next)
(bfrd ch @str pos)
(ret.ov)
(ld @stdout ch)
(ld @cout ch)
(inc pos)
(j :next)
)
(:loop)
(ld r0 @stdin)
(ld r0 @cin)
(cmp r0 'q'
(eq? (ld @stdout '\n')
(eq? (ld @cout '\n')
(halt)))
; uppercase ASCII
(cmp r0 'a' (<? (j :badchar)))
(cmp r0 'z' (>? (j :badchar)))
(sub r0 ' ')
(ld @stdout r0)
(ld @cout r0)
(j :loop)
(:badchar)