diff --git a/fixtures/INKEY.BAS b/fixtures/INKEY.BAS new file mode 100644 index 00000000..0a2da702 --- /dev/null +++ b/fixtures/INKEY.BAS @@ -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$ diff --git a/src/bin/rusty_basic.rs b/src/bin/rusty_basic.rs index 8bca27cd..f6edebd1 100644 --- a/src/bin/rusty_basic.rs +++ b/src/bin/rusty_basic.rs @@ -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(); @@ -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. @@ -55,12 +65,6 @@ 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") { @@ -68,3 +72,25 @@ fn is_running_in_apache() -> bool { 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"); + } +} diff --git a/src/built_ins/err.rs b/src/built_ins/err.rs new file mode 100644 index 00000000..66b1caef --- /dev/null +++ b/src/built_ins/err.rs @@ -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) -> 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(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"); + } +} diff --git a/src/built_ins/inkey.rs b/src/built_ins/inkey.rs new file mode 100644 index 00000000..d899fa66 --- /dev/null +++ b/src/built_ins/inkey.rs @@ -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) -> 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(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 { + 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}") + ); + } + } +} diff --git a/src/built_ins/len.rs b/src/built_ins/len.rs index 122cddf7..d7b17f71 100644 --- a/src/built_ins/len.rs +++ b/src/built_ins/len.rs @@ -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(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); diff --git a/src/built_ins/mod.rs b/src/built_ins/mod.rs index f54d9319..a56ef447 100644 --- a/src/built_ins/mod.rs +++ b/src/built_ins/mod.rs @@ -24,6 +24,14 @@ pub enum BuiltInFunction { /// `EOF(file-number%)` -> checks if the end of file has been reached Eof, + /// `ERR` + Err, + + /// `INKEY$` + /// + /// Reads a character from the keyboard. + InKey, + /// `INSTR([start%,] hay$, needle$)` /// if start% is omitted, INSTR starts at position 1 /// returns the first occurrence of needle$ inside hay$ @@ -72,6 +80,9 @@ pub enum BuiltInFunction { /// `RTRIM$` RTrim, + /// `SPACE$(number-of-spaces)` + Space, + /// `STR$(numeric-expression)` returns a string representation of a number Str, @@ -100,11 +111,13 @@ pub enum BuiltInFunction { VarSeg, } -const SORTED_BUILT_IN_FUNCTIONS: [BuiltInFunction; 22] = [ +const SORTED_BUILT_IN_FUNCTIONS: [BuiltInFunction; 25] = [ BuiltInFunction::Chr, BuiltInFunction::Cvd, BuiltInFunction::Environ, BuiltInFunction::Eof, + BuiltInFunction::Err, + BuiltInFunction::InKey, BuiltInFunction::InStr, BuiltInFunction::LBound, BuiltInFunction::LCase, @@ -116,6 +129,7 @@ const SORTED_BUILT_IN_FUNCTIONS: [BuiltInFunction; 22] = [ BuiltInFunction::Peek, BuiltInFunction::Right, BuiltInFunction::RTrim, + BuiltInFunction::Space, BuiltInFunction::Str, BuiltInFunction::String_, BuiltInFunction::UBound, @@ -125,9 +139,10 @@ const SORTED_BUILT_IN_FUNCTIONS: [BuiltInFunction; 22] = [ BuiltInFunction::VarSeg, ]; -const SORTED_BUILT_IN_FUNCTION_NAMES: [&str; 22] = [ - "Chr", "Cvd", "Environ", "Eof", "InStr", "LBound", "LCase", "Left", "Len", "LTrim", "Mid", - "Mkd", "Peek", "Right", "RTrim", "Str", "String", "UBound", "UCase", "Val", "VarPtr", "VarSeg", +const SORTED_BUILT_IN_FUNCTION_NAMES: [&str; 25] = [ + "Chr", "Cvd", "Environ", "Eof", "Err", "InKey", "InStr", "LBound", "LCase", "Left", "Len", + "LTrim", "Mid", "Mkd", "Peek", "Right", "RTrim", "Space", "Str", "String", "UBound", "UCase", + "Val", "VarPtr", "VarSeg", ]; // BuiltInFunction -> &str @@ -158,6 +173,8 @@ impl From<&BuiltInFunction> for TypeQualifier { BuiltInFunction::Cvd => TypeQualifier::HashDouble, BuiltInFunction::Environ => TypeQualifier::DollarString, BuiltInFunction::Eof => TypeQualifier::PercentInteger, + BuiltInFunction::Err => TypeQualifier::PercentInteger, + BuiltInFunction::InKey => TypeQualifier::DollarString, BuiltInFunction::InStr => TypeQualifier::PercentInteger, BuiltInFunction::LBound => TypeQualifier::PercentInteger, BuiltInFunction::LCase => TypeQualifier::DollarString, @@ -169,6 +186,7 @@ impl From<&BuiltInFunction> for TypeQualifier { BuiltInFunction::Peek => TypeQualifier::PercentInteger, BuiltInFunction::Right => TypeQualifier::DollarString, BuiltInFunction::RTrim => TypeQualifier::DollarString, + BuiltInFunction::Space => TypeQualifier::DollarString, BuiltInFunction::Str => TypeQualifier::DollarString, BuiltInFunction::String_ => TypeQualifier::DollarString, BuiltInFunction::UBound => TypeQualifier::PercentInteger, @@ -212,6 +230,7 @@ impl TryFrom<&Name> for Option { Some(b) => match b { BuiltInFunction::Cvd | BuiltInFunction::Eof + | BuiltInFunction::Err | BuiltInFunction::InStr | BuiltInFunction::Len | BuiltInFunction::Peek @@ -221,6 +240,7 @@ impl TryFrom<&Name> for Option { | BuiltInFunction::VarPtr | BuiltInFunction::VarSeg => demand_unqualified(b, n), BuiltInFunction::Environ + | BuiltInFunction::InKey | BuiltInFunction::LCase | BuiltInFunction::Left | BuiltInFunction::LTrim @@ -228,6 +248,7 @@ impl TryFrom<&Name> for Option { | BuiltInFunction::Mkd | BuiltInFunction::Right | BuiltInFunction::RTrim + | BuiltInFunction::Space | BuiltInFunction::UCase => { // ENVIRON$ must be qualified match n { @@ -283,6 +304,8 @@ fn demand_unqualified( #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum BuiltInSub { + Beep, + CallAbsolute, Close, Cls, Color, @@ -350,6 +373,7 @@ pub enum BuiltInSub { Put, Read, + Screen, ViewPrint, Width, } @@ -362,7 +386,11 @@ impl BuiltInSub { /// they can't hit this function, as they are represented by keywords and are /// parsed by custom parsers. pub fn parse_non_keyword_sub(s: &str) -> Option { - if s.eq_ignore_ascii_case("Cls") { + if s.eq_ignore_ascii_case("Beep") { + Some(BuiltInSub::Beep) + } else if s.eq_ignore_ascii_case("Call") { + Some(BuiltInSub::CallAbsolute) + } else if s.eq_ignore_ascii_case("Cls") { Some(BuiltInSub::Cls) } else if s.eq_ignore_ascii_case("Color") { Some(BuiltInSub::Color) @@ -372,6 +400,8 @@ impl BuiltInSub { Some(BuiltInSub::Kill) } else if s.eq_ignore_ascii_case("Poke") { Some(BuiltInSub::Poke) + } else if s.eq_ignore_ascii_case("Screen") { + Some(BuiltInSub::Screen) } else { None } @@ -428,6 +458,8 @@ pub mod linter { name_context: NameContext, ) -> Result<(), QErrorNode> { match built_in_sub { + BuiltInSub::Beep => Ok(()), + BuiltInSub::CallAbsolute => Ok(()), BuiltInSub::Close => crate::built_ins::close::linter::lint(args), BuiltInSub::Cls => crate::built_ins::cls::linter::lint(args), BuiltInSub::Color => crate::built_ins::color::linter::lint(args), @@ -446,6 +478,7 @@ pub mod linter { BuiltInSub::Poke => crate::built_ins::poke::linter::lint(args), BuiltInSub::Put => crate::built_ins::put::linter::lint(args), BuiltInSub::Read => crate::built_ins::read::linter::lint(args), + BuiltInSub::Screen => Ok(()), BuiltInSub::ViewPrint => crate::built_ins::view_print::linter::lint(args), BuiltInSub::Width => crate::built_ins::width::linter::lint(args), } @@ -460,6 +493,8 @@ pub mod linter { BuiltInFunction::Cvd => crate::built_ins::cvd::linter::lint(args), BuiltInFunction::Environ => crate::built_ins::environ_fn::linter::lint(args), BuiltInFunction::Eof => crate::built_ins::eof::linter::lint(args), + BuiltInFunction::Err => crate::built_ins::err::linter::lint(args), + BuiltInFunction::InKey => crate::built_ins::inkey::linter::lint(args), BuiltInFunction::InStr => crate::built_ins::instr::linter::lint(args), BuiltInFunction::LBound => crate::built_ins::lbound::linter::lint(args), BuiltInFunction::LCase => crate::built_ins::lcase::linter::lint(args), @@ -471,6 +506,7 @@ pub mod linter { BuiltInFunction::Peek => crate::built_ins::peek::linter::lint(args), BuiltInFunction::Right => crate::built_ins::right::linter::lint(args), BuiltInFunction::RTrim => crate::built_ins::rtrim::linter::lint(args), + BuiltInFunction::Space => crate::built_ins::space::linter::lint(args), BuiltInFunction::Str => crate::built_ins::str_fn::linter::lint(args), BuiltInFunction::String_ => crate::built_ins::string_fn::linter::lint(args), BuiltInFunction::UBound => crate::built_ins::ubound::linter::lint(args), @@ -489,6 +525,8 @@ pub mod interpreter { pub fn run_sub(s: &BuiltInSub, interpreter: &mut S) -> Result<(), QError> { match s { + BuiltInSub::Beep => Ok(()), + BuiltInSub::CallAbsolute => Ok(()), BuiltInSub::Close => crate::built_ins::close::interpreter::run(interpreter), BuiltInSub::Cls => crate::built_ins::cls::interpreter::run(interpreter), BuiltInSub::Color => crate::built_ins::color::interpreter::run(interpreter), @@ -507,6 +545,7 @@ pub mod interpreter { BuiltInSub::Poke => crate::built_ins::poke::interpreter::run(interpreter), BuiltInSub::Put => crate::built_ins::put::interpreter::run(interpreter), BuiltInSub::Read => crate::built_ins::read::interpreter::run(interpreter), + BuiltInSub::Screen => Ok(()), BuiltInSub::ViewPrint => crate::built_ins::view_print::interpreter::run(interpreter), BuiltInSub::Width => crate::built_ins::width::interpreter::run(interpreter), } @@ -521,6 +560,8 @@ pub mod interpreter { BuiltInFunction::Cvd => crate::built_ins::cvd::interpreter::run(interpreter), BuiltInFunction::Environ => crate::built_ins::environ_fn::interpreter::run(interpreter), BuiltInFunction::Eof => crate::built_ins::eof::interpreter::run(interpreter), + BuiltInFunction::Err => crate::built_ins::err::interpreter::run(interpreter), + BuiltInFunction::InKey => crate::built_ins::inkey::interpreter::run(interpreter), BuiltInFunction::InStr => crate::built_ins::instr::interpreter::run(interpreter), BuiltInFunction::LBound => crate::built_ins::lbound::interpreter::run(interpreter), BuiltInFunction::LCase => crate::built_ins::lcase::interpreter::run(interpreter), @@ -532,6 +573,7 @@ pub mod interpreter { BuiltInFunction::Peek => crate::built_ins::peek::interpreter::run(interpreter), BuiltInFunction::Right => crate::built_ins::right::interpreter::run(interpreter), BuiltInFunction::RTrim => crate::built_ins::rtrim::interpreter::run(interpreter), + BuiltInFunction::Space => crate::built_ins::space::interpreter::run(interpreter), BuiltInFunction::Str => crate::built_ins::str_fn::interpreter::run(interpreter), BuiltInFunction::String_ => crate::built_ins::string_fn::interpreter::run(interpreter), BuiltInFunction::UBound => crate::built_ins::ubound::interpreter::run(interpreter), @@ -553,8 +595,10 @@ mod def_seg; mod environ_fn; mod environ_sub; mod eof; +mod err; mod field; mod get; +mod inkey; mod input; mod instr; mod kill; @@ -576,6 +620,7 @@ mod put; mod read; mod right; mod rtrim; +mod space; mod str_fn; mod string_fn; mod ubound; diff --git a/src/built_ins/space.rs b/src/built_ins/space.rs new file mode 100644 index 00000000..c99edccd --- /dev/null +++ b/src/built_ins/space.rs @@ -0,0 +1,39 @@ +pub mod linter { + use crate::common::QErrorNode; + use crate::linter::arg_validation::ArgValidation; + use crate::parser::ExpressionNode; + + pub fn lint(args: &Vec) -> Result<(), QErrorNode> { + args.require_one_numeric_argument() + } +} + +pub mod interpreter { + use crate::built_ins::BuiltInFunction; + use crate::common::QError; + use crate::interpreter::interpreter_trait::InterpreterTrait; + use crate::variant::QBNumberCast; + + pub fn run(interpreter: &mut S) -> Result<(), QError> { + let len: i32 = interpreter.context()[0].try_cast()?; + let mut s: String = String::new(); + for _ in 0..len { + s.push(' '); + } + interpreter + .context_mut() + .set_built_in_function_result(BuiltInFunction::Space, s); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::assert_prints_exact; + use crate::interpreter::interpreter_trait::InterpreterTrait; + + #[test] + fn test() { + assert_prints_exact!("PRINT SPACE$(4)", " ", ""); + } +} diff --git a/src/built_ins/view_print.rs b/src/built_ins/view_print.rs index 2c880acf..120193c2 100644 --- a/src/built_ins/view_print.rs +++ b/src/built_ins/view_print.rs @@ -54,10 +54,10 @@ pub mod interpreter { pub fn run(interpreter: &mut S) -> Result<(), QError> { if interpreter.context().variables().len() > 0 { // we have args - todo!() + Ok(()) } else { // reset full view - todo!() + Ok(()) } } } diff --git a/src/built_ins/width.rs b/src/built_ins/width.rs index 3e136fe4..91950781 100644 --- a/src/built_ins/width.rs +++ b/src/built_ins/width.rs @@ -52,7 +52,7 @@ pub mod interpreter { use crate::interpreter::interpreter_trait::InterpreterTrait; pub fn run(_interpreter: &mut S) -> Result<(), QError> { - todo!() + Ok(()) } } diff --git a/src/common/error.rs b/src/common/error.rs index c2a358c8..bfee9675 100644 --- a/src/common/error.rs +++ b/src/common/error.rs @@ -216,6 +216,27 @@ impl QError { pub fn syntax_error>(msg: S) -> Self { QError::SyntaxError(format!("{}", msg.as_ref())) } + + pub fn get_code(&self) -> i32 { + match self { + Self::ReturnWithoutGoSub => 3, + Self::IllegalFunctionCall => 5, + Self::Overflow => 6, + Self::SubscriptOutOfRange => 9, + Self::DivisionByZero => 11, + Self::TypeMismatch => 13, + Self::ResumeWithoutError => 20, + Self::BadFileNameOrNumber => 52, + Self::FileNotFound => 53, + Self::FileAlreadyOpen => 55, + Self::InputPastEndOfFile => 62, + // the following are not QBasic codes + Self::InternalError(_) => 256, + Self::Other(_) => 257, + Self::ForLoopZeroStep => 258, + _ => todo!(), + } + } } pub type QErrorNode = ErrorEnvelope; @@ -262,3 +283,37 @@ impl From for QError { Self::Other(x) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_code() { + let errors = [ + QError::ReturnWithoutGoSub, + QError::IllegalFunctionCall, + QError::Overflow, + QError::SubscriptOutOfRange, + QError::DivisionByZero, + QError::TypeMismatch, + QError::ResumeWithoutError, + QError::BadFileNameOrNumber, + QError::FileNotFound, + QError::FileAlreadyOpen, + QError::InputPastEndOfFile, + // the following are not qbasic codes + QError::InternalError("whatever".to_owned()), + QError::Other("whatever".to_owned()), + QError::ForLoopZeroStep, + ]; + let codes = [3, 5, 6, 9, 11, 13, 20, 52, 53, 55, 62, 256, 257, 258]; + + assert_eq!(errors.len(), codes.len()); + for i in 0..errors.len() { + let error = &errors[i]; + let code = codes[i]; + assert_eq!(error.get_code(), code); + } + } +} diff --git a/src/common/error_envelope.rs b/src/common/error_envelope.rs index 11616759..a64d040e 100644 --- a/src/common/error_envelope.rs +++ b/src/common/error_envelope.rs @@ -11,6 +11,14 @@ pub enum ErrorEnvelope { Stacktrace(T, Vec), } +impl AsRef for ErrorEnvelope { + fn as_ref(&self) -> &T { + match self { + Self::NoPos(t) | Self::Pos(t, _) | Self::Stacktrace(t, _) => t, + } + } +} + impl ErrorEnvelope { pub fn into_err(self) -> T { match self { diff --git a/src/interpreter/context.rs b/src/interpreter/context.rs index e54f0766..4bdb3685 100644 --- a/src/interpreter/context.rs +++ b/src/interpreter/context.rs @@ -5,7 +5,7 @@ use crate::interpreter::arguments::Arguments; use crate::interpreter::variables::Variables; use crate::linter::SubprogramName; use crate::parser::{BareName, TypeQualifier}; -use crate::variant::{QBNumberCast, Variant}; +use crate::variant::{AsciiSize, QBNumberCast, Variant}; use std::collections::HashMap; // This is an arbitrary value, not what QBasic is doing @@ -226,11 +226,7 @@ impl Context { let mut result: usize = 0; for i in 0..memory_block_index { // add the total bytes of the previous variables in previous memory blocks - result += self.memory_blocks[i] - .variables - .iter() - .map(Variant::size_in_bytes) - .sum::(); + result += self.memory_blocks[i].variables.ascii_size(); } // add the varptr of this variable result += self.memory_blocks[memory_block_index] @@ -341,7 +337,7 @@ impl Context { let mut offset: usize = 0; for block in self.memory_blocks.iter() { for var in block.variables.iter() { - let len = var.size_in_bytes(); + let len = var.ascii_size(); if offset <= address && address < offset + len { return var.peek_non_array(address - offset); } @@ -376,7 +372,7 @@ impl Context { let mut offset: usize = 0; for block in self.memory_blocks.iter_mut() { for var in block.variables.iter_mut() { - let len = var.size_in_bytes(); + let len = var.ascii_size(); if offset <= address && address < offset + len { return var.poke_non_array(address - offset, byte_value); } diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 672986b4..e00be64d 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -67,6 +67,8 @@ pub struct Interpreter, + last_error_code: Option, + print_interpreter: Rc>, data_segment: DataSegment, @@ -161,6 +163,10 @@ impl Interpret fn set_def_seg(&mut self, def_seg: Option) { self.def_seg = def_seg; } + + fn get_last_error_code(&self) -> Option { + self.last_error_code + } } pub type DefaultInterpreter = Interpreter< @@ -208,6 +214,7 @@ impl function_result: None, value_stack: vec![], last_error_address: None, + last_error_code: None, print_interpreter: Rc::new(RefCell::new(PrintInterpreter::new())), data_segment: DataSegment::new(), def_seg: None, @@ -390,40 +397,26 @@ impl return Err(QError::ReturnWithoutGoSub).with_err_at(pos); } }, - Instruction::Resume => match self.last_error_address.take() { - Some(last_error_address) => { - ctx.opt_next_index = Some( - ctx.nearest_statement_finder - .find_current(last_error_address), - ); - self.context.pop(); - } - _ => { - // TODO test this - return Err(QError::ResumeWithoutError).with_err_at(pos); - } - }, - Instruction::ResumeNext => match self.last_error_address.take() { - Some(last_error_address) => { - ctx.opt_next_index = - Some(ctx.nearest_statement_finder.find_next(last_error_address)); - self.context.pop(); - } - _ => { - // TODO test this - return Err(QError::ResumeWithoutError).with_err_at(pos); - } - }, - Instruction::ResumeLabel(resume_label) => match self.last_error_address.take() { - Some(_) => { - ctx.opt_next_index = Some(resume_label.address()); - self.context.pop(); - } - _ => { - // TODO test this - return Err(QError::ResumeWithoutError).with_err_at(pos); - } - }, + Instruction::Resume => { + let last_error_address = self.take_last_error_address().with_err_at(pos)?; + ctx.opt_next_index = Some( + ctx.nearest_statement_finder + .find_current(last_error_address), + ); + self.context.pop(); + } + Instruction::ResumeNext => { + let last_error_address = self.take_last_error_address().with_err_at(pos)?; + ctx.opt_next_index = + Some(ctx.nearest_statement_finder.find_next(last_error_address)); + self.context.pop(); + } + Instruction::ResumeLabel(resume_label) => { + // not using the last error address but need to clear it which also clears the err code + self.take_last_error_address().with_err_at(pos)?; + ctx.opt_next_index = Some(resume_label.address()); + self.context.pop(); + } Instruction::Throw(interpreter_error) => { return Err(interpreter_error.clone()).with_err_at(pos); } @@ -569,6 +562,7 @@ impl } }, Err(e) => { + self.last_error_code = Some(e.as_ref().get_code()); match ctx.error_handler { ErrorHandler::Address(handler_address) => { // store error address, so we can call RESUME and RESUME NEXT from within the error handler @@ -588,6 +582,16 @@ impl } Ok(()) } + + /// Gets the instruction address where the most recent error occurred. + /// Clears that address and also clears the most recent error code. + fn take_last_error_address(&mut self) -> Result { + self.last_error_code = None; + match self.last_error_address.take() { + Some(a) => Ok(a), + None => Err(QError::ResumeWithoutError), + } + } } /// Context available to the execution of a single instruction. diff --git a/src/interpreter/interpreter_trait.rs b/src/interpreter/interpreter_trait.rs index 5a6b5f9c..6cf7ac60 100644 --- a/src/interpreter/interpreter_trait.rs +++ b/src/interpreter/interpreter_trait.rs @@ -73,4 +73,6 @@ pub trait InterpreterTrait { } fn set_def_seg(&mut self, def_seg: Option); + + fn get_last_error_code(&self) -> Option; } diff --git a/src/interpreter/tests/go_sub.rs b/src/interpreter/tests/go_sub.rs index a27bceab..9877a03b 100644 --- a/src/interpreter/tests/go_sub.rs +++ b/src/interpreter/tests/go_sub.rs @@ -76,3 +76,17 @@ fn return_without_go_sub() { "#; assert_interpreter_err!(input, QError::ReturnWithoutGoSub, 2, 5); } + +#[test] +fn variable_assigned_in_go_sub_before_definition() { + let input = r#" + GOSUB Alpha + PRINT A + END + + Alpha: + A = 42 + RETURN + "#; + assert_prints!(input, "42"); +} diff --git a/src/interpreter/variables.rs b/src/interpreter/variables.rs index 7dd9b903..a08c2d75 100644 --- a/src/interpreter/variables.rs +++ b/src/interpreter/variables.rs @@ -4,7 +4,7 @@ use crate::interpreter::arguments::{ArgumentInfo, Arguments}; use crate::parser::{ BareName, DimName, DimType, Name, ParamName, ParamType, QualifiedName, TypeQualifier, }; -use crate::variant::{Variant, V_FALSE}; +use crate::variant::{AsciiSize, Variant, V_FALSE}; #[derive(Debug)] pub struct Variables { @@ -206,7 +206,7 @@ impl Variables { .keys() .take_while(|k| *k != name) .map(|k| self.get_by_name(k).unwrap()) - .map(Variant::size_in_bytes) + .map(Variant::ascii_size) .sum() } @@ -220,7 +220,7 @@ impl Variables { let mut offset: usize = 0; for RuntimeVariableInfo { value, .. } in self.map.values() { if !value.is_array() { - let len = value.size_in_bytes(); + let len = value.ascii_size(); if offset <= address && address < offset + len { return value.peek_non_array(address - offset); } @@ -237,7 +237,7 @@ impl Variables { let mut offset: usize = 0; for RuntimeVariableInfo { value, .. } in self.map.values_mut() { if !value.is_array() { - let len = value.size_in_bytes(); + let len = value.ascii_size(); if offset <= address && address < offset + len { return value.poke_non_array(address - offset, byte_value); } @@ -258,3 +258,9 @@ impl From for Variables { variables } } + +impl AsciiSize for Variables { + fn ascii_size(&self) -> usize { + self.iter().map(Variant::ascii_size).sum() + } +} diff --git a/src/linter/arg_validation.rs b/src/linter/arg_validation.rs index e83b3c8a..9f213c6e 100644 --- a/src/linter/arg_validation.rs +++ b/src/linter/arg_validation.rs @@ -32,6 +32,8 @@ pub trait ArgValidation { fn require_one_argument(&self) -> Result<(), QErrorNode>; + fn require_zero_arguments(&self) -> Result<(), QErrorNode>; + fn require_one_double_argument(&self) -> Result<(), QErrorNode> { self.require_one_argument() .and_then(|_| self.require_double_argument(0)) @@ -180,4 +182,12 @@ impl ArgValidation for ExpressionNodes { Ok(()) } } + + fn require_zero_arguments(&self) -> Result<(), QErrorNode> { + if self.is_empty() { + Ok(()) + } else { + Err(QError::ArgumentCountMismatch).with_err_no_pos() + } + } } diff --git a/src/linter/converter/conversion_traits.rs b/src/linter/converter/conversion_traits.rs index ba07de2a..4e747fbe 100644 --- a/src/linter/converter/conversion_traits.rs +++ b/src/linter/converter/conversion_traits.rs @@ -1,43 +1,4 @@ use super::{Implicits, R}; -use crate::common::QErrorNode; - -/// Converts an item to a different item of the same type. -pub trait SameTypeConverter { - fn convert_same_type(&mut self, item: T) -> Result; -} - -// blanket for Option -impl SameTypeConverter> for X -where - X: SameTypeConverter, -{ - fn convert_same_type(&mut self, item: Option) -> Result, QErrorNode> { - match item { - Some(t) => self.convert_same_type(t).map(Some), - _ => Ok(None), - } - } -} - -/// Converts an item to multiple items of the same type. -pub trait OneToManyConverter { - fn convert_to_many(&mut self, item: T) -> Result, QErrorNode>; -} - -// blanket for Vec -> Vec, if T -> Vec exists -impl SameTypeConverter> for X -where - X: OneToManyConverter, -{ - fn convert_same_type(&mut self, item: Vec) -> Result, QErrorNode> { - let mut items: Vec = vec![]; - for t in item { - let mut expanded = self.convert_to_many(t)?; - items.append(&mut expanded); - } - Ok(items) - } -} /// Converts an object into another, possibly collecting implicitly defined /// variables along the way. diff --git a/src/linter/converter/do_loop.rs b/src/linter/converter/do_loop.rs index f84479c8..ff52924e 100644 --- a/src/linter/converter/do_loop.rs +++ b/src/linter/converter/do_loop.rs @@ -1,6 +1,4 @@ -use crate::linter::converter::conversion_traits::{ - SameTypeConverter, SameTypeConverterWithImplicits, -}; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; use crate::linter::converter::{ConverterImpl, ExprContext, R}; use crate::parser::DoLoopNode; @@ -12,10 +10,11 @@ impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { position, kind, } = do_loop_node; - let (condition, implicit_vars) = self + let (condition, mut implicit_vars) = self .context .on_expression(condition, ExprContext::Default)?; - let statements = self.convert_same_type(statements)?; + let (statements, mut block_implicits) = self.convert_block_keeping_implicits(statements)?; + implicit_vars.append(&mut block_implicits); Ok(( DoLoopNode { condition, diff --git a/src/linter/converter/for_loop.rs b/src/linter/converter/for_loop.rs index 7f22a8f9..8ed6e70c 100644 --- a/src/linter/converter/for_loop.rs +++ b/src/linter/converter/for_loop.rs @@ -1,44 +1,43 @@ -use crate::linter::converter::conversion_traits::{ - SameTypeConverter, SameTypeConverterWithImplicits, -}; -use crate::linter::converter::{ConverterImpl, ExprContext, R}; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; +use crate::linter::converter::{ConverterImpl, ExprContext, Implicits, R}; use crate::parser::ForLoopNode; impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { fn convert_same_type_with_implicits(&mut self, a: ForLoopNode) -> R { - let (variable_name, implicit_variables_variable_name) = self + let mut implicits: Implicits = vec![]; + let (variable_name, mut implicit_variables_variable_name) = self .context .on_expression(a.variable_name, ExprContext::Assignment)?; - let (lower_bound, implicit_variables_lower_bound) = self + implicits.append(&mut implicit_variables_variable_name); + let (lower_bound, mut implicit_variables_lower_bound) = self .context .on_expression(a.lower_bound, ExprContext::Default)?; - let (upper_bound, implicit_variables_upper_bound) = self + implicits.append(&mut implicit_variables_lower_bound); + let (upper_bound, mut implicit_variables_upper_bound) = self .context .on_expression(a.upper_bound, ExprContext::Default)?; - let (step, implicit_variables_step) = self + implicits.append(&mut implicit_variables_upper_bound); + let (step, mut implicit_variables_step) = self .context .on_opt_expression(a.step, ExprContext::Default)?; - let (next_counter, implicit_variables_next_counter) = self + implicits.append(&mut implicit_variables_step); + let (next_counter, mut implicit_variables_next_counter) = self .context .on_opt_expression(a.next_counter, ExprContext::Assignment)?; - let implicit_vars = Self::merge_implicit_vars(vec![ - implicit_variables_variable_name, - implicit_variables_lower_bound, - implicit_variables_upper_bound, - implicit_variables_step, - implicit_variables_next_counter, - ]); - + implicits.append(&mut implicit_variables_next_counter); + let (statements, mut implicit_variables_block) = + self.convert_block_keeping_implicits(a.statements)?; + implicits.append(&mut implicit_variables_block); Ok(( ForLoopNode { variable_name, lower_bound, upper_bound, step, - statements: self.convert_same_type(a.statements)?, + statements, next_counter, }, - implicit_vars, + implicits, )) } } diff --git a/src/linter/converter/function_implementation.rs b/src/linter/converter/function_implementation.rs index 20820682..415a7906 100644 --- a/src/linter/converter/function_implementation.rs +++ b/src/linter/converter/function_implementation.rs @@ -1,5 +1,4 @@ use crate::common::{AtLocation, Locatable, QErrorNode}; -use crate::linter::converter::conversion_traits::SameTypeConverter; use crate::linter::converter::ConverterImpl; use crate::parser::{FunctionImplementation, TopLevelToken}; @@ -24,7 +23,7 @@ impl<'a> ConverterImpl<'a> { let mapped = TopLevelToken::FunctionImplementation(FunctionImplementation { name: resolved_function_name.at(pos), params: resolved_params, - body: self.convert_same_type(body)?, + body: self.convert_block_hoisting_implicits(body)?, is_static, }); self.context.pop_context(); diff --git a/src/linter/converter/if_blocks.rs b/src/linter/converter/if_blocks.rs index 1c4e0ee8..a342bee7 100644 --- a/src/linter/converter/if_blocks.rs +++ b/src/linter/converter/if_blocks.rs @@ -1,6 +1,4 @@ -use crate::linter::converter::conversion_traits::{ - SameTypeConverter, SameTypeConverterWithImplicits, -}; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; use crate::linter::converter::{ConverterImpl, ExprContext, R}; use crate::parser::{ConditionalBlockNode, IfBlockNode}; @@ -9,13 +7,16 @@ impl<'a> SameTypeConverterWithImplicits for ConverterImpl< &mut self, a: ConditionalBlockNode, ) -> R { - let (condition, implicit_vars) = self + let (condition, mut implicit_vars) = self .context .on_expression(a.condition, ExprContext::Default)?; + let (statements, mut block_implicits) = + self.convert_block_keeping_implicits(a.statements)?; + implicit_vars.append(&mut block_implicits); Ok(( ConditionalBlockNode { condition, - statements: self.convert_same_type(a.statements)?, + statements, }, implicit_vars, )) @@ -24,20 +25,20 @@ impl<'a> SameTypeConverterWithImplicits for ConverterImpl< impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { fn convert_same_type_with_implicits(&mut self, a: IfBlockNode) -> R { - let (if_block, mut implicit_vars_if_block) = - self.convert_same_type_with_implicits(a.if_block)?; + let (if_block, mut implicits) = self.convert_same_type_with_implicits(a.if_block)?; let (else_if_blocks, mut implicit_vars_else_if_blocks) = self.convert_same_type_with_implicits(a.else_if_blocks)?; - - implicit_vars_if_block.append(&mut implicit_vars_else_if_blocks); - + implicits.append(&mut implicit_vars_else_if_blocks); + let (else_block, mut implicits_else) = + self.convert_same_type_with_implicits(a.else_block)?; + implicits.append(&mut implicits_else); Ok(( IfBlockNode { if_block, else_if_blocks, - else_block: self.convert_same_type(a.else_block)?, + else_block, }, - implicit_vars_if_block, + implicits, )) } } diff --git a/src/linter/converter/mod.rs b/src/linter/converter/mod.rs index a5de9440..f29a0693 100644 --- a/src/linter/converter/mod.rs +++ b/src/linter/converter/mod.rs @@ -15,21 +15,17 @@ mod select_case; mod statement; mod sub_call; mod sub_implementation; -mod top_level_token; use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; use crate::common::*; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; use crate::linter::type_resolver::TypeResolver; use crate::linter::type_resolver_impl::TypeResolverImpl; use crate::linter::{DimContext, NameContext}; -use crate::parser::{ - BareName, DimList, ExpressionNode, ExpressionNodes, FunctionMap, Name, NameNode, - ParamNameNodes, ProgramNode, QualifiedNameNode, SubMap, TypeQualifier, UserDefinedTypes, -}; -use conversion_traits::SameTypeConverter; +use crate::parser::*; use names::Names; pub fn convert( @@ -39,7 +35,7 @@ pub fn convert( user_defined_types: &UserDefinedTypes, ) -> Result<(ProgramNode, HashSet), QErrorNode> { let mut converter = ConverterImpl::new(user_defined_types, f_c, s_c); - let result = converter.convert_same_type(program)?; + let result = converter.convert_program(program)?; // consume let names_without_dot = converter.consume(); Ok((result, names_without_dot)) @@ -86,12 +82,97 @@ impl<'a> ConverterImpl<'a> { self.context.names_without_dot() } - pub fn merge_implicit_vars(lists: Vec) -> Implicits { - let mut result: Implicits = vec![]; - for mut list in lists { - result.append(&mut list); + pub fn convert_block_keeping_implicits( + &mut self, + statements: StatementNodes, + ) -> R { + let mut result: StatementNodes = vec![]; + let mut implicits: Implicits = vec![]; + for statement in statements { + let (converted_statement_node, mut part_implicits) = + self.convert_same_type_with_implicits(statement)?; + if let Statement::Const(_, _) = converted_statement_node.as_ref() { + // filter out CONST statements, they've been registered into context as values + debug_assert!( + part_implicits.is_empty(), + "Should not introduce implicits in a CONST" + ); + } else { + result.push(converted_statement_node); + implicits.append(&mut part_implicits); + } + } + Ok((result, implicits)) + } + + // A statement can be expanded into multiple statements to convert implicitly + // declared variables into explicit. + // Example: + // A = B + C + // becomes + // DIM B + // DIM C + // DIM A + // A = B + C + pub fn convert_block_hoisting_implicits( + &mut self, + statements: StatementNodes, + ) -> Result { + let (mut result, implicits) = self.convert_block_keeping_implicits(statements)?; + let mut index = 0; + for implicit_var in implicits { + let Locatable { + element: q_name, + pos, + } = implicit_var; + result.insert( + index, + Statement::Dim(DimName::from(q_name).into_list(pos)).at(pos), + ); + index += 1; + } + Ok(result) + } + + pub fn convert_program(&mut self, program: ProgramNode) -> Result { + let mut result: ProgramNode = vec![]; + let mut index: usize = 0; + for Locatable { element, pos } in program { + match element { + TopLevelToken::DefType(def_type) => { + self.resolver.borrow_mut().set(&def_type); + } + TopLevelToken::FunctionDeclaration(_name, _params) => {} + TopLevelToken::FunctionImplementation(f) => { + let converted = self.convert_function_implementation(f)?; + result.push(converted.at(pos)); + } + TopLevelToken::Statement(s) => { + let (converted, implicits) = + self.convert_block_keeping_implicits(vec![s.at(pos)])?; + // insert implicits at the top + for Locatable { element, pos } in implicits { + let implicit_statement = + Statement::Dim(DimName::from(element).into_list(pos)); + result.insert(index, TopLevelToken::Statement(implicit_statement).at(pos)); + index += 1; + } + // insert statements + for Locatable { element, pos } in converted { + result.push(TopLevelToken::Statement(element).at(pos)); + } + } + TopLevelToken::SubDeclaration(_name, _params) => {} + TopLevelToken::SubImplementation(s) => { + let converted = self.convert_sub_implementation(s)?; + result.push(converted.at(pos)); + } + TopLevelToken::UserDefinedType(_) => { + // already handled at first pass + } + } } - result + Ok(result) } } diff --git a/src/linter/converter/select_case.rs b/src/linter/converter/select_case.rs index b0205539..759b0a29 100644 --- a/src/linter/converter/select_case.rs +++ b/src/linter/converter/select_case.rs @@ -1,6 +1,4 @@ -use crate::linter::converter::conversion_traits::{ - SameTypeConverter, SameTypeConverterWithImplicits, -}; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; use crate::linter::converter::{ConverterImpl, ExprContext, R}; use crate::parser::{CaseBlockNode, CaseExpression, SelectCaseNode}; @@ -11,11 +9,14 @@ impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { let (case_blocks, mut implicit_vars_case_blocks) = self.convert_same_type_with_implicits(a.case_blocks)?; implicit_vars_expr.append(&mut implicit_vars_case_blocks); + let (else_block, mut implicits_else) = + self.convert_same_type_with_implicits(a.else_block)?; + implicit_vars_expr.append(&mut implicits_else); Ok(( SelectCaseNode { expr, case_blocks, - else_block: self.convert_same_type(a.else_block)?, + else_block, inline_comments: a.inline_comments, }, implicit_vars_expr, @@ -25,12 +26,15 @@ impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { impl<'a> SameTypeConverterWithImplicits for ConverterImpl<'a> { fn convert_same_type_with_implicits(&mut self, a: CaseBlockNode) -> R { - let (expression_list, implicit_vars_expr) = + let (expression_list, mut implicit_vars_expr) = self.convert_same_type_with_implicits(a.expression_list)?; + let (statements, mut implicits_block) = + self.convert_block_keeping_implicits(a.statements)?; + implicit_vars_expr.append(&mut implicits_block); Ok(( CaseBlockNode { expression_list, - statements: self.convert_same_type(a.statements)?, + statements, }, implicit_vars_expr, )) diff --git a/src/linter/converter/statement.rs b/src/linter/converter/statement.rs index 434f78f8..6c91a234 100644 --- a/src/linter/converter/statement.rs +++ b/src/linter/converter/statement.rs @@ -1,44 +1,24 @@ use crate::common::*; -use crate::linter::converter::conversion_traits::{ - OneToManyConverter, SameTypeConverterWithImplicits, -}; +use crate::linter::converter::conversion_traits::SameTypeConverterWithImplicits; use crate::linter::converter::{ConverterImpl, ExprContext, R}; use crate::linter::{DimContext, NameContext}; use crate::parser::{ - BareName, DimName, ExitObject, Expression, Name, Statement, StatementNode, StatementNodes, + BareName, ExitObject, Expression, Name, Statement, StatementNode, StatementNodes, }; -// A statement can be expanded into multiple statements to convert implicitly -// declared variables into explicit. -// Example: -// A = B + C -// becomes -// DIM B -// DIM C -// DIM A -// A = B + C - -impl<'a> OneToManyConverter for ConverterImpl<'a> { - fn convert_to_many( +impl<'a> SameTypeConverterWithImplicits> for ConverterImpl<'a> { + fn convert_same_type_with_implicits( &mut self, - statement_node: StatementNode, - ) -> Result { - let mut result: StatementNodes = vec![]; - let (converted_statement_node, implicit_vars) = - self.convert_same_type_with_implicits(statement_node)?; - if let Statement::Const(_, _) = converted_statement_node.as_ref() { - // filter out CONST statements, they've been registered into context as values - } else { - for implicit_var in implicit_vars { - let Locatable { - element: q_name, - pos, - } = implicit_var; - result.push(Statement::Dim(DimName::from(q_name).into_list(pos)).at(pos)); + item: Option, + ) -> R> { + match item { + Some(statements) => { + let (converted_statements, implicits) = + self.convert_block_keeping_implicits(statements)?; + Ok((Some(converted_statements), implicits)) } - result.push(converted_statement_node); + None => Ok((None, vec![])), } - Ok(result) } } diff --git a/src/linter/converter/sub_implementation.rs b/src/linter/converter/sub_implementation.rs index 93b40b7d..ec0c419a 100644 --- a/src/linter/converter/sub_implementation.rs +++ b/src/linter/converter/sub_implementation.rs @@ -1,5 +1,4 @@ use crate::common::QErrorNode; -use crate::linter::converter::conversion_traits::SameTypeConverter; use crate::linter::converter::ConverterImpl; use crate::parser::{SubImplementation, TopLevelToken}; @@ -18,7 +17,7 @@ impl<'a> ConverterImpl<'a> { let mapped = TopLevelToken::SubImplementation(SubImplementation { name, params: mapped_params, - body: self.convert_same_type(body)?, + body: self.convert_block_hoisting_implicits(body)?, is_static, }); self.context.pop_context(); diff --git a/src/linter/converter/top_level_token.rs b/src/linter/converter/top_level_token.rs deleted file mode 100644 index 73251421..00000000 --- a/src/linter/converter/top_level_token.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::common::{AtLocation, Locatable, QErrorNode}; -use crate::linter::converter::conversion_traits::OneToManyConverter; -use crate::linter::converter::ConverterImpl; -use crate::parser::{TopLevelToken, TopLevelTokenNode}; - -// Vec because: -// 1. we filter out DefType and UserDefinedType -// 2. a top level statement might expand into multiple due to implicit variables -impl<'a> OneToManyConverter for ConverterImpl<'a> { - fn convert_to_many( - &mut self, - top_level_token_node: TopLevelTokenNode, - ) -> Result, QErrorNode> { - let Locatable { - element: top_level_token, - pos, - } = top_level_token_node; - match top_level_token { - TopLevelToken::DefType(d) => { - self.resolver.borrow_mut().set(&d); - Ok(vec![]) - } - TopLevelToken::FunctionDeclaration(_, _) | TopLevelToken::SubDeclaration(_, _) => { - Ok(vec![]) - } - TopLevelToken::FunctionImplementation(function_implementation) => self - .convert_function_implementation(function_implementation) - .map(|top_level_token| vec![top_level_token.at(pos)]), - TopLevelToken::SubImplementation(sub_implementation) => self - .convert_sub_implementation(sub_implementation) - .map(|top_level_token| vec![top_level_token.at(pos)]), - TopLevelToken::Statement(statement) => { - let statement_node = statement.at(pos); - self.convert_to_many(statement_node) - .map(|converted_statement_nodes| { - converted_statement_nodes - .into_iter() - .map(|Locatable { element, pos }| { - TopLevelToken::Statement(element).at(pos) - }) - .collect() - }) - } - TopLevelToken::UserDefinedType(_) => { - // already handled by first pass - Ok(vec![]) - } - } - } -} diff --git a/src/linter/tests/array.rs b/src/linter/tests/array.rs index eeaf07ec..af23a66b 100644 --- a/src/linter/tests/array.rs +++ b/src/linter/tests/array.rs @@ -167,6 +167,11 @@ fn test_passing_array_without_parenthesis() { assert_eq!( linter_ok(input), vec![ + // X is hoisted first, even though it's implicitly defined later at line 3 + TopLevelToken::Statement(Statement::Dim( + DimName::new_compact_local("X", TypeQualifier::BangSingle).into_list_rc(3, 5) + )) + .at_rc(3, 5), TopLevelToken::Statement(Statement::Dim( DimNameBuilder::new() .bare_name("choice") @@ -184,10 +189,6 @@ fn test_passing_array_without_parenthesis() { .into_list_rc(2, 9) )) .at_rc(2, 5), - TopLevelToken::Statement(Statement::Dim( - DimName::new_compact_local("X", TypeQualifier::BangSingle).into_list_rc(3, 5) - )) - .at_rc(3, 5), TopLevelToken::Statement(Statement::Assignment( Expression::Variable( "X!".into(), diff --git a/src/parser/pc/binary.rs b/src/parser/pc/binary.rs index 95780c32..0dd59ecd 100644 --- a/src/parser/pc/binary.rs +++ b/src/parser/pc/binary.rs @@ -166,25 +166,53 @@ where } } -/// Implements the "or" parser, returning the first successful -/// result out of the two given parsers. -pub struct LeftOrRight { - left: Box>, - right: Box>, +/// Implements an "or" parser, returning the first successful +/// result out of the given parsers. +pub struct VecOr { + alt: Vec>>, } -impl Parser for LeftOrRight +impl VecOr +where + R: Reader, +{ + pub fn new(left: A, right: B) -> Self + where + A: Parser + 'static, + B: Parser + 'static, + { + let mut alt: Vec>> = vec![]; + alt.push(Box::new(left)); + alt.push(Box::new(right)); + Self { alt } + } + + pub fn or(self, right: B) -> Self + where + B: Parser + 'static, + { + let Self { mut alt } = self; + alt.push(Box::new(right)); + Self { alt } + } +} + +impl Parser for VecOr where R: Reader, { type Output = T; fn parse(&mut self, reader: R) -> ReaderResult { - let (reader, opt_a) = self.left.parse(reader)?; - match opt_a { - Some(a) => Ok((reader, Some(a))), - _ => self.right.parse(reader), + let mut r = reader; + for x in self.alt.iter_mut() { + let (tmp, opt_a) = x.as_mut().parse(r)?; + r = tmp; + if let Some(a) = opt_a { + return Ok((r, Some(a))); + } } + Ok((r, None)) } } @@ -251,15 +279,12 @@ pub trait BinaryParser: Parser + Sized { /// Returns a parser which will return the result of this parser if it /// is successful, otherwise it will use the given parser. - fn or(self, other: B) -> LeftOrRight + fn or(self, other: B) -> VecOr where Self: 'static, B: Sized + Parser + 'static, { - LeftOrRight { - left: Box::new(self), - right: Box::new(other), - } + VecOr::new(self, other) } } diff --git a/src/variant/array_value.rs b/src/variant/array_value.rs index cfa40837..cfa95219 100644 --- a/src/variant/array_value.rs +++ b/src/variant/array_value.rs @@ -1,5 +1,5 @@ use crate::common::QError; -use crate::variant::Variant; +use crate::variant::{AsciiSize, Variant}; #[derive(Clone, Debug)] pub struct VArray { @@ -60,11 +60,6 @@ impl VArray { self.dimensions.get(dimension_index) } - pub fn size_in_bytes(&self) -> usize { - let array_length = dimensions_to_array_length(&self.dimensions); - self.element_size_in_bytes() * array_length - } - pub fn address_offset_of_element(&self, indices: &[i32]) -> Result { let abs_index = self.abs_index(indices)?; Ok(self.element_size_in_bytes() * abs_index) @@ -73,7 +68,7 @@ impl VArray { fn element_size_in_bytes(&self) -> usize { self.elements .first() - .map(Variant::size_in_bytes) + .map(Variant::ascii_size) .unwrap_or_default() } @@ -108,6 +103,13 @@ impl VArray { } } +impl AsciiSize for VArray { + fn ascii_size(&self) -> usize { + let array_length = dimensions_to_array_length(&self.dimensions); + self.element_size_in_bytes() * array_length + } +} + /// Calculates the number of elements in a multi-dimensional array. fn dimensions_to_array_length(dimensions: &[(i32, i32)]) -> usize { let mut len: usize = 1; diff --git a/src/variant/mod.rs b/src/variant/mod.rs index 9de94a0f..9cca0135 100644 --- a/src/variant/mod.rs +++ b/src/variant/mod.rs @@ -24,3 +24,13 @@ where self.iter().map(QBNumberCast::try_cast).collect() } } + +/// Calculates the size in bytes of this object. +/// For strings, it is the length in characters, to keep compatibility with +/// the ASCII expectations of QBasic. +pub trait AsciiSize { + /// Calculates the size in bytes of this object. + /// For strings, it is the length in characters, to keep compatibility with + /// the ASCII expectations of QBasic. + fn ascii_size(&self) -> usize; +} diff --git a/src/variant/user_defined_type_value.rs b/src/variant/user_defined_type_value.rs index c4105bb3..f4a342c7 100644 --- a/src/variant/user_defined_type_value.rs +++ b/src/variant/user_defined_type_value.rs @@ -1,6 +1,7 @@ use super::Variant; use crate::common::{CaseInsensitiveString, IndexedMap, Locatable}; use crate::parser::{Element, UserDefinedTypes}; +use crate::variant::AsciiSize; /// Holds a value of a user defined type. #[derive(Clone, Debug)] @@ -47,10 +48,6 @@ impl UserDefinedTypeValue { } } - pub fn size_in_bytes(&self) -> usize { - self.map.values().map(Variant::size_in_bytes).sum() - } - pub fn address_offset_of_property(&self, property: &CaseInsensitiveString) -> usize { self.map .keys() @@ -62,7 +59,13 @@ impl UserDefinedTypeValue { fn property_size_in_bytes(&self, property: &CaseInsensitiveString) -> usize { self.map .get(property) - .map(Variant::size_in_bytes) + .map(Variant::ascii_size) .unwrap_or_default() } } + +impl AsciiSize for UserDefinedTypeValue { + fn ascii_size(&self) -> usize { + self.map.values().map(Variant::ascii_size).sum() + } +} diff --git a/src/variant/variant.rs b/src/variant/variant.rs index bdf4c4be..1d8a8a8e 100644 --- a/src/variant/variant.rs +++ b/src/variant/variant.rs @@ -3,7 +3,7 @@ use super::UserDefinedTypeValue; use crate::common::QError; use crate::parser::TypeQualifier; use crate::variant::bits::{bytes_to_i32, i32_to_bytes}; -use crate::variant::{qb_and, qb_or, QBNumberCast, VArray}; +use crate::variant::{qb_and, qb_or, AsciiSize, QBNumberCast, VArray}; use std::cmp::Ordering; use std::convert::TryFrom; use std::fmt::Display; @@ -374,17 +374,6 @@ impl Variant { } } - pub fn size_in_bytes(&self) -> usize { - match self { - Self::VInteger(_) => 2, - Self::VLong(_) | Self::VSingle(_) => 4, - Self::VDouble(_) => 8, - Self::VString(s) => s.len(), - Self::VArray(v_array) => v_array.size_in_bytes(), - Self::VUserDefined(user_defined_type_value) => user_defined_type_value.size_in_bytes(), - } - } - pub fn is_array(&self) -> bool { match self { Self::VArray(_) => true, @@ -419,6 +408,19 @@ impl Variant { } } +impl AsciiSize for Variant { + fn ascii_size(&self) -> usize { + match self { + Self::VInteger(_) => 2, + Self::VLong(_) | Self::VSingle(_) => 4, + Self::VDouble(_) => 8, + Self::VString(s) => s.chars().count(), + Self::VArray(v_array) => v_array.ascii_size(), + Self::VUserDefined(user_defined_type_value) => user_defined_type_value.ascii_size(), + } + } +} + impl PartialEq for Variant { fn eq(&self, other: &Self) -> bool { match self.cmp_same_type_only(other) {