Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions fixtures/INKEY.BAS
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
' clear buffer
PRINT "Clearing input buffer..."
WHILE INKEY$ <> ""
WEND
PRINT "Press any key..."
k$ = INKEY$
WHILE k$ = ""
k$ = INKEY$
WEND
PRINT "You pressed:", k$
70 changes: 48 additions & 22 deletions src/bin/rusty_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rusty_basic::instruction_generator;
use rusty_basic::interpreter::interpreter::new_default_interpreter;
use rusty_basic::linter;
use rusty_basic::parser;
use rusty_basic::parser::{ProgramNode, UserDefinedTypes};

fn main() {
let is_running_in_apache = is_running_in_apache();
Expand All @@ -13,27 +14,36 @@ fn main() {
eprintln!("Please specify the program to run.");
return;
}
let f = File::open(&filename).expect(format!("Could not find program {}", filename).as_ref());
let run_options = RunOptions {
is_running_in_apache,
filename,
};
let f = run_options.open_file();
match parser::parse_main_file(f) {
Ok(program) => match linter::lint(program) {
Ok((linted_program, user_defined_types)) => {
let instruction_generator_result =
instruction_generator::generate_instructions(linted_program);
if is_running_in_apache {
set_current_dir(&filename); // Note: only needed to make it work inside Apache.
}
let mut interpreter = new_default_interpreter(user_defined_types);
match interpreter.interpret(instruction_generator_result) {
Ok(_) => (),
Err(e) => eprintln!("Runtime error. {:?}", e),
}
}
Err(e) => eprintln!("Could not lint program. {:?}", e),
},
Ok(program) => on_parsed(program, run_options),
Err(e) => eprintln!("Could not parse program. {:?}", e),
}
}

fn on_parsed(program: ProgramNode, run_options: RunOptions) {
match linter::lint(program) {
Ok((linted_program, user_defined_types)) => {
on_linted(linted_program, user_defined_types, run_options)
}
Err(e) => eprintln!("Could not lint program. {:?}", e),
}
}

fn on_linted(program: ProgramNode, user_defined_types: UserDefinedTypes, run_options: RunOptions) {
let instruction_generator_result = instruction_generator::generate_instructions(program);
let mut interpreter = new_default_interpreter(user_defined_types);
run_options.set_current_dir_if_apache();
match interpreter.interpret(instruction_generator_result) {
Ok(_) => (),
Err(e) => eprintln!("Runtime error. {:?}", e),
}
}

fn get_filename(is_running_in_apache: bool) -> String {
// Normally it should just be the first command line argument.
// We also check the variable BLR_PROGRAM in order to make it work inside Apache.
Expand All @@ -55,16 +65,32 @@ fn get_filename_from_env_var() -> String {
})
}

fn set_current_dir(filename: &String) {
let canonical = std::fs::canonicalize(&filename).unwrap();
let parent = canonical.parent().unwrap();
std::env::set_current_dir(parent).expect("Could not set current directory");
}

/// Checks if we're running inside Apache with mod_cgi.
fn is_running_in_apache() -> bool {
match std::env::var("SERVER_NAME") {
Ok(x) => !x.is_empty(),
Err(_) => false,
}
}

struct RunOptions {
filename: String,
is_running_in_apache: bool,
}

impl RunOptions {
pub fn open_file(&self) -> File {
File::open(&self.filename)
.expect(format!("Could not find program {}", &self.filename).as_ref())
}

pub fn set_current_dir_if_apache(&self) {
if !self.is_running_in_apache {
return;
}

let canonical = std::fs::canonicalize(&self.filename).unwrap();
let parent = canonical.parent().unwrap();
std::env::set_current_dir(parent).expect("Could not set current directory");
}
}
65 changes: 65 additions & 0 deletions src/built_ins/err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pub mod linter {
use crate::common::QErrorNode;
use crate::linter::arg_validation::ArgValidation;
use crate::parser::ExpressionNode;

pub fn lint(args: &Vec<ExpressionNode>) -> Result<(), QErrorNode> {
args.require_zero_arguments()
}
}

pub mod interpreter {
use crate::built_ins::BuiltInFunction;
use crate::common::QError;
use crate::interpreter::interpreter_trait::InterpreterTrait;

pub fn run<S: InterpreterTrait>(interpreter: &mut S) -> Result<(), QError> {
let error_code: i32 = interpreter.get_last_error_code().unwrap_or_default();
interpreter
.context_mut()
.set_built_in_function_result(BuiltInFunction::Err, error_code);
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::assert_prints;
use crate::interpreter::interpreter_trait::InterpreterTrait;

#[test]
fn test_err() {
let input = r#"
ON ERROR GOTO ErrTrap
OPEN "whatever.txt" FOR INPUT AS #1
CLOSE
END

ErrTrap:
SELECT CASE ERR
CASE 53
PRINT "File not found"
CASE ELSE
PRINT "oops"
END SELECT
RESUME NEXT
"#;
assert_prints!(input, "File not found");
}

#[test]
fn test_resume_clears_err() {
let input = r#"
ON ERROR GOTO ErrTrap
OPEN "whatever.txt" FOR INPUT AS #1
CLOSE
PRINT ERR
END

ErrTrap:
PRINT ERR
RESUME NEXT
"#;
assert_prints!(input, "53", "0");
}
}
169 changes: 169 additions & 0 deletions src/built_ins/inkey.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
pub mod linter {
use crate::common::QErrorNode;
use crate::linter::arg_validation::ArgValidation;
use crate::parser::ExpressionNode;

pub fn lint(args: &Vec<ExpressionNode>) -> Result<(), QErrorNode> {
args.require_zero_arguments()
}
}

pub mod interpreter {
use crate::built_ins::BuiltInFunction;
use crate::common::QError;
use crate::interpreter::interpreter_trait::InterpreterTrait;
use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers};
use std::time::Duration;

pub fn run<S: InterpreterTrait>(interpreter: &mut S) -> Result<(), QError> {
let s: String = poll_one()?;
interpreter
.context_mut()
.set_built_in_function_result(BuiltInFunction::InKey, s);
Ok(())
}

fn poll_one() -> Result<String, QError> {
if poll(Duration::from_millis(100))? {
let event = read()?;
Ok(handle_event(event))
} else {
Ok(String::new())
}
}

fn handle_event(event: Event) -> String {
if let Event::Key(KeyEvent { code, modifiers }) = event {
handle_key(code, modifiers)
} else {
String::new()
}
}

fn handle_key(code: KeyCode, modifiers: KeyModifiers) -> String {
match code {
KeyCode::Char(ch) => {
if modifiers == KeyModifiers::NONE {
String::from(ch)
} else {
// TODO
String::new()
}
}
KeyCode::Enter => String::from(13_u8 as char),
KeyCode::Tab => {
if modifiers == KeyModifiers::NONE {
String::from(9 as char)
} else if modifiers == KeyModifiers::SHIFT {
String::from("\0\u{15}")
} else {
String::new()
}
}
KeyCode::Up => String::from("\0H"),
KeyCode::Down => String::from("\0P"),
KeyCode::Left => String::from("\0K"),
KeyCode::Right => String::from("\0M"),
KeyCode::Esc => String::from(27 as char),
KeyCode::F(f) => {
let mut s = String::new();
s.push(0 as char);
s.push((58 + f) as char);
s
}
_ => String::new(),
}
}

#[cfg(test)]
mod tests {
use crate::built_ins::inkey::interpreter::handle_key;
use crossterm::event::{KeyCode, KeyModifiers};

#[test]
fn test_mapping_lowercase_letters() {
for ch in 'a'..'z' {
assert_eq!(
handle_key(KeyCode::Char(ch), KeyModifiers::NONE),
String::from(ch)
);
}
}

#[test]
fn test_mapping_function_keys() {
assert_eq!(
handle_key(KeyCode::F(2), KeyModifiers::NONE),
String::from("\0<")
);
assert_eq!(
handle_key(KeyCode::F(3), KeyModifiers::NONE),
String::from("\0=")
);
}

#[test]
fn test_enter() {
assert_eq!(
handle_key(KeyCode::Enter, KeyModifiers::NONE),
String::from(13 as char)
);
}

#[test]
fn test_escape() {
assert_eq!(
handle_key(KeyCode::Esc, KeyModifiers::NONE),
String::from(27 as char)
);
}

#[test]
fn test_up_arrow() {
assert_eq!(
handle_key(KeyCode::Up, KeyModifiers::NONE),
String::from("\0H")
);
}

#[test]
fn test_down_arrow() {
assert_eq!(
handle_key(KeyCode::Down, KeyModifiers::NONE),
String::from("\0P")
);
}

#[test]
fn test_left_arrow() {
assert_eq!(
handle_key(KeyCode::Left, KeyModifiers::NONE),
String::from("\0K")
);
}

#[test]
fn test_right_arrow() {
assert_eq!(
handle_key(KeyCode::Right, KeyModifiers::NONE),
String::from("\0M")
);
}

#[test]
fn test_tab() {
assert_eq!(
handle_key(KeyCode::Tab, KeyModifiers::NONE),
String::from(9 as char)
);
}

#[test]
fn test_shift_tab() {
assert_eq!(
handle_key(KeyCode::Tab, KeyModifiers::SHIFT),
String::from("\0\u{15}")
);
}
}
}
4 changes: 2 additions & 2 deletions src/built_ins/len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ pub mod interpreter {
use crate::built_ins::BuiltInFunction;
use crate::common::QError;
use crate::interpreter::interpreter_trait::InterpreterTrait;
use crate::variant::Variant;
use crate::variant::{AsciiSize, Variant};

pub fn run<S: InterpreterTrait>(interpreter: &mut S) -> Result<(), QError> {
let v: &Variant = &interpreter.context()[0];
let len: i32 = v.size_in_bytes() as i32;
let len: i32 = v.ascii_size() as i32;
interpreter
.context_mut()
.set_built_in_function_result(BuiltInFunction::Len, len);
Expand Down
Loading