From 5f9f36be0a24506fb82d4ee68995277977f01773 Mon Sep 17 00:00:00 2001 From: TomtheCoder2 Date: Tue, 26 Mar 2024 10:40:40 +0100 Subject: [PATCH] added wait function so you can step through the program, kinda like a debug mode ps: there are hashmaps now, but they dont work with strings yet (and other pointers as well) --- .idea/.gitignore | 2 ++ .idea/clox_rs.iml | 1 + Cargo.toml | 2 +- phoenix_examples/wait_test.phx | 6 ++++++ src/chunk.rs | 4 +++- src/compiler.rs | 33 ++++++++++++++++++++++++++-- src/lib.rs | 21 ++++++++++++------ src/run.rs | 36 +++++++++++++++++++------------ src/value.rs | 6 +++++- src/vm.rs | 39 +++++++++++++++++++++++++--------- tests/compiler_tests.rs | 4 ++-- tests/mod.rs | 4 ++-- 12 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 phoenix_examples/wait_test.phx diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..a9d7db9 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/.idea/clox_rs.iml b/.idea/clox_rs.iml index 457e3e6..d5ee091 100644 --- a/.idea/clox_rs.iml +++ b/.idea/clox_rs.iml @@ -4,6 +4,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 877a030..6a6fc8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "phoenix-lang" description = "A simple, fast programming language" -version = "1.3.2" +version = "1.4.0" edition = "2021" license = "MIT" repository = "https://github.com/TomtheCoder2/phoenix" diff --git a/phoenix_examples/wait_test.phx b/phoenix_examples/wait_test.phx new file mode 100644 index 0000000..f5ce20e --- /dev/null +++ b/phoenix_examples/wait_test.phx @@ -0,0 +1,6 @@ +print(1); +print(2); +print(3); +print(4); +print(5); +print(6); \ No newline at end of file diff --git a/src/chunk.rs b/src/chunk.rs index 952ab2f..c805642 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -83,7 +83,9 @@ pub enum OpCode { // Push the string on the stack to the heap and replace it with a pointer to the heap OpCreateString, /// Creates a hashmap of the last 2n items, where the first value is the key and the second one is the value - OpCreateHashMap(usize) + OpCreateHashMap(usize), + /// Waits until enter is pressed before continuing + OpWait, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/src/compiler.rs b/src/compiler.rs index b95c1ad..80c0f72 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -38,6 +38,8 @@ pub struct Compiler { panic_mode: bool, quiet_mode: bool, debug_mode: bool, + /// Means that before each line is executed, the VM will wait for user input + wait_mode: bool, } impl Compiler { @@ -131,8 +133,30 @@ impl Compiler { &self.current_code_module_ref().tokens[self.current_code_module_ref().tokens.len() - 1] } + // also adds the opWait code if the token_type is a semicolon fn consume(&mut self, token_type: TokenType, msg: &str) { self.advance(); + if self.previous().token_type == TokenType::Semicolon { + if self.wait_mode { + let file = self.current_module().scanner.file.clone(); + let line = self.current_module().scanner.cur_line; + let s = format!( + "{}:{}: {}", + file, line, + self.current_module() + .scanner + .code + .split("\n") + .collect::>() + .iter() + .nth(line - 1) + .unwrap() + ); + self.emit_constant(Value::PhoenixString(s)); + self.emit_instr(OpCreateString); + self.emit_instr(OpWait); + } + } if !(self.previous().token_type == token_type) { self.error(msg); } @@ -1280,6 +1304,7 @@ impl Compiler { quiet, start_line, false, + false, ) } @@ -1289,6 +1314,7 @@ impl Compiler { quiet: bool, start_line: usize, debug_mode: bool, + wait_mode: bool, ) -> Compiler { let mut compiler = Compiler { modules: vec![CompilerModuleChunk::new( @@ -1306,6 +1332,7 @@ impl Compiler { panic_mode: false, quiet_mode: quiet, debug_mode, + wait_mode, }; compiler.new_start(file, code, quiet, start_line, 0); compiler @@ -1390,8 +1417,9 @@ impl Compiler { } } - pub fn compile_code(code: String, debug: bool) -> Option { - let mut compiler = Compiler::new_file(DEFAULT_FILE_NAME.to_string(), code, false, 0, debug); + pub fn compile_code(code: String, debug: bool, wait: bool) -> Option { + let mut compiler = + Compiler::new_file(DEFAULT_FILE_NAME.to_string(), code, false, 0, debug, wait); compiler.compile(debug) } } @@ -1409,6 +1437,7 @@ impl Clone for Compiler { panic_mode: self.panic_mode, quiet_mode: self.quiet_mode, debug_mode: false, + wait_mode: self.wait_mode, } } } diff --git a/src/lib.rs b/src/lib.rs index 61a1242..6e43c82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use rustyline::config::Configurer; use std::fs::File; use std::io::Read; use std::path::Path; @@ -36,8 +37,8 @@ pub enum InterpretResult { pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub fn fly(file: String, source: String, debug: bool, quiet: bool) -> InterpretResult { - let mut compiler = Compiler::new_file(file, source, quiet, 0, DEBUG); +pub fn fly(file: String, source: String, debug: bool, quiet: bool, wait: bool) -> InterpretResult { + let mut compiler = Compiler::new_file(file, source, quiet, 0, DEBUG, wait); let result = compiler.compile(debug); if result.is_none() { return InterpretResult::InterpretCompileError; @@ -70,19 +71,21 @@ pub fn repl() -> Result<(), ReadlineError> { ); let mut state: Option = None; let mut modules = Vec::new(); - let mut compiler = Compiler::new_file(file_name, s.clone(), false, 0, DEBUG); + let mut compiler = Compiler::new_file(file_name, s.clone(), false, 0, DEBUG, false); let mut last_state; let mut last_compiler; let mut current_prompt = ">>"; let default_prompt = ">>"; let mut current_line = "".to_string(); // tracks how many blocks deep we are (and thus how many spaces to indent) - let mut block_offset=0; + let mut block_offset = 0; loop { // let readline = rl.readline(" \x1b[32m>>\x1b[0m"); let readline = rl.readline_with_initial(current_prompt, (&*" ".repeat(block_offset), "")); match readline { Ok(mut line) => { + rl.set_max_history_size(10000) + .expect("failed to set max history size"); rl.add_history_entry(line.as_str()) .expect("failed to add history"); line.push('\n'); @@ -113,7 +116,11 @@ pub fn repl() -> Result<(), ReadlineError> { // let _result = fly(file_name.clone(), line, DEBUG, false); // dbg!(&compiler.current_module().scanner.code); - compiler.current_module().scanner.code.push_str(¤t_line); + compiler + .current_module() + .scanner + .code + .push_str(¤t_line); current_line = "".to_string(); block_offset = 0; // dbg!(&compiler.current_module().scanner.code); @@ -179,7 +186,7 @@ pub fn repl() -> Result<(), ReadlineError> { Ok(()) } -pub fn run_file(filename: String, debug: bool) -> InterpretResult { +pub fn run_file(filename: String, debug: bool, wait: bool) -> InterpretResult { let path = Path::new(&filename); let path_display = path.display(); @@ -193,7 +200,7 @@ pub fn run_file(filename: String, debug: bool) -> InterpretResult { let mut s = String::new(); match file.read_to_string(&mut s) { - Ok(_) => fly(filename, s, debug, false), + Ok(_) => fly(filename, s, debug, false, wait), Err(why) => { eprintln!("Failed to read {}: {}", path_display, why); exit(1); diff --git a/src/run.rs b/src/run.rs index 90ea89d..35bf256 100644 --- a/src/run.rs +++ b/src/run.rs @@ -19,10 +19,10 @@ use crate::{error, info, repl, run_file, DEBUG}; #[derive(Parser, Debug)] #[command( -author, -version, -about, -long_about = "The compiler and interpreter for the phoenix programming language.\n\n\ + author, + version, + about, + long_about = "The compiler and interpreter for the phoenix programming language.\n\n\ To simply interpret a file, give it as the [input file] parameter.\n To compile to a phc file, specify an output file with the output-file flag.\n To start the REPL, simply dont provide any arguments.\n" @@ -42,6 +42,9 @@ enum Commands { Run { /// The file to run file: String, + /// Wait for use input before running each statement + #[arg(short, long, action)] + wait: bool, }, /// Build a file Build { @@ -79,8 +82,8 @@ pub fn main() { } compile(file, output_file, debug); } - Commands::Run { file } => { - run_f(file); + Commands::Run { file, wait } => { + run_f(file, wait); } } } else if cli.file.is_none() { @@ -93,14 +96,14 @@ pub fn main() { } exit(0); } else { - run_f(cli.file.unwrap()); + run_f(cli.file.unwrap(), false); } } -fn run_f(file: String) { +fn run_f(file: String, wait: bool) { // check if the file ends in .phx if file.ends_with(".phx") { - run_file(file, DEBUG); + run_file(file, DEBUG, wait); } else { compiled_run(file); } @@ -141,7 +144,7 @@ fn compile(input_file: String, actual_output_file: String, debug: bool) { exit(64); } }; - let mut compiler = Compiler::new_file(file_name, code.clone(), true, 0, debug); + let mut compiler = Compiler::new_file(file_name, code.clone(), true, 0, debug, false); let res = if let Some(res) = compiler.compile(debug) { res } else { @@ -159,9 +162,16 @@ fn compile(input_file: String, actual_output_file: String, debug: bool) { info!("Size after compression: {} bytes", compressed_data.len()); if DEBUG { info!("data: {:?}", compressed_data); - #[cfg(feature = "debug")]{ - write_to_file(&format!("{}.toml", &actual_output_file), toml::to_string(&res).unwrap().as_bytes().to_vec()); - write_to_file(&format!("{}.ron", &actual_output_file), ron::to_string(&res).unwrap().as_bytes().to_vec()); + #[cfg(feature = "debug")] + { + write_to_file( + &format!("{}.toml", &actual_output_file), + toml::to_string(&res).unwrap().as_bytes().to_vec(), + ); + write_to_file( + &format!("{}.ron", &actual_output_file), + ron::to_string(&res).unwrap().as_bytes().to_vec(), + ); } } info!("Size of code: {} bytes", code.len()); diff --git a/src/value.rs b/src/value.rs index 66cadf3..370893d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -412,7 +412,11 @@ impl HeapObjVal { "{{{}}}", map.map .iter() - .map(|(k, v)| format!("{}: {}", k.to_string(vm, state, modules), v.to_string(vm, state, modules))) + .map(|(k, v)| format!( + "{}: {}", + k.to_string(vm, state, modules), + v.to_string(vm, state, modules) + )) .collect::>() .join(", ") ), diff --git a/src/vm.rs b/src/vm.rs index 632502f..6f26113 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,9 +6,15 @@ use crate::gc::GC; use crate::native::native_functions::*; use crate::native::native_methods::{NativeMethod, NATIVE_METHODS}; use crate::resolver::UpValue; -use crate::value::{is_falsey, values_equal, HeapObj, HeapObjType, HeapObjVal, ObjBoundMethod, ObjClosure, ObjInstance, ObjList, ObjString, Value, ObjHashMap}; +use crate::value::{ + is_falsey, values_equal, HeapObj, HeapObjType, HeapObjVal, ObjBoundMethod, ObjClosure, + ObjHashMap, ObjInstance, ObjList, ObjString, Value, +}; use crate::{error, phoenix_error, warn, InterpretResult, VERSION}; use std::collections::HashMap; +use std::io::{stdin, stdout, Write}; +use std::thread::{sleep}; +use std::time::Duration; const FRAMES_MAX: usize = 255; @@ -218,7 +224,7 @@ impl VMState { phoenix_error!("VM panic! Unable to get current closure?"); } } - .clone(); + .clone(); match self.deref_into_mut(&pointer_val, HeapObjType::PhoenixClosure) { Ok(closure_obj) => closure_obj.as_closure_mut(), Err(x) => { @@ -259,7 +265,7 @@ impl VMState { phoenix_error!("VM panic! Attempted to push an upvalue that doesn't exist"); } } - .clone(); + .clone(); self.stack.push(val); } @@ -469,7 +475,7 @@ impl VMState { Ok(x) => x, Err(_) => phoenix_error!("Failed to lock native functions mutex"), } - .iter() + .iter() { if let Some(index) = identifiers.iter().position(|x| x == str) { self.globals[self.current_frame.module][index] = @@ -813,7 +819,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -834,7 +840,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -856,7 +862,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -878,7 +884,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1107,7 +1113,7 @@ impl VM { VM::get_variable_name(name_index, &state, &modules), instance ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1176,7 +1182,7 @@ impl VM { .unwrap() .name, ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1540,6 +1546,19 @@ impl VM { let ptr = state.alloc(HeapObj::new_hashmap(map_obj)); state.stack.push(ptr); } + OpWait => { + let mut stdout = stdout(); + // we know that the last value on the stack is the string we want to print + let s = state.pop().to_string(self, &state, &modules); + stdout.write(format!("{}", s).as_bytes()).unwrap(); + stdout.flush().unwrap(); + let mut buffer = String::new(); + + stdin() + .read_line(&mut buffer) + .expect("Failed to read line"); + sleep(Duration::from_millis(100)); + } } } } diff --git a/tests/compiler_tests.rs b/tests/compiler_tests.rs index 59a3c0a..c069109 100644 --- a/tests/compiler_tests.rs +++ b/tests/compiler_tests.rs @@ -1,4 +1,4 @@ -use phoenix_lang::chunk::FunctionType::{Script}; +use phoenix_lang::chunk::FunctionType::Script; use phoenix_lang::chunk::OpCode::*; use phoenix_lang::chunk::{Chunk, FunctionChunk, Instr, ModuleChunk}; use phoenix_lang::compiler::{CompilationResult, Compiler}; @@ -15,7 +15,7 @@ fn do_test_functions( functions: Vec, classes: Vec, ) { - let result = Compiler::compile_code(code.to_string(), false); + let result = Compiler::compile_code(code.to_string(), false, false); let cur_pos = result.clone().unwrap().cur_pos; assert_eq!( result, diff --git a/tests/mod.rs b/tests/mod.rs index ce2251e..a6c201e 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -22,7 +22,7 @@ use phoenix_lang::vm::{ExecutionMode, VM}; #[test] fn io_test() { let code = "print(\"hello\");".to_string(); - let mut compiler = Compiler::new_file("script".to_string(), code, false, 0, true); + let mut compiler = Compiler::new_file("script".to_string(), code, false, 0, true, false); let res = if let Some(res) = compiler.compile(false) { res } else { @@ -40,7 +40,7 @@ fn io_test() { #[test] fn lang_test() { let code = "print(1 + 11);".to_string(); - let mut compiler = Compiler::new_file("script".to_string(), code, false, 0, true); + let mut compiler = Compiler::new_file("script".to_string(), code, false, 0, true, false); let res = compiler.compile(false).unwrap(); // print the code println!("{:?}", res);