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);