diff --git a/Cargo.toml b/Cargo.toml index 1bb9b01..877a030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "phoenix-lang" description = "A simple, fast programming language" -version = "1.3.1" +version = "1.3.2" edition = "2021" license = "MIT" repository = "https://github.com/TomtheCoder2/phoenix" diff --git a/phoenix_examples/hash.phx b/phoenix_examples/hash.phx new file mode 100644 index 0000000..a6f190d --- /dev/null +++ b/phoenix_examples/hash.phx @@ -0,0 +1,5 @@ +var hashmap = { "one": 1, "two": 2, "three": 3 }; +print(hashmap); +print(hashmap.len()); +print(hashmap.get("one")); +print(hashmap.get("four")); \ No newline at end of file diff --git a/src/chunk.rs b/src/chunk.rs index fb55eeb..952ab2f 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -82,6 +82,8 @@ pub enum OpCode { OpCreateList(usize), // 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) } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/src/compiler.rs b/src/compiler.rs index f2f5639..b95c1ad 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -350,6 +350,7 @@ impl Compiler { ParseFn::Literal => self.literal(), ParseFn::String => self.string(), ParseFn::List => self.list(can_assign), + ParseFn::HashMap => self.hashmap(can_assign), ParseFn::Variable => self.variable(can_assign), ParseFn::And => self.and_operator(), ParseFn::Or => self.or_operator(), @@ -923,7 +924,7 @@ impl Compiler { self.emit_instr(OpPop); self.parse_precedence(Precedence::And); // Parse right hand side of the infix expression self.patch_jump(end_jump); // Jump to after it if the first argument was already false, leaving the false value on the top of the stack to be the result - // Otherwise the first argument is true, so the value of the whole and is equal to the value of the second argument, so just proceed as normal + // Otherwise the first argument is true, so the value of the whole and is equal to the value of the second argument, so just proceed as normal } fn or_operator(&mut self) { @@ -956,7 +957,7 @@ impl Compiler { } else { s } - .parse::() + .parse::() { self.emit_constant(Value::Float(value)); } else { @@ -985,6 +986,7 @@ impl Compiler { } fn list(&mut self, _can_assign: bool) { + // * list: [1, 2, 3, 4, ...] let mut size = 0; if !self.check(TokenType::RightBracket) { loop { @@ -999,6 +1001,26 @@ impl Compiler { self.emit_instr(OpCreateList(size)); } + fn hashmap(&mut self, _can_assign: bool) { + // hashmap looks like this: var hashmap = { "key": "value", "key2": "value2" } + // and we should push all the values in this order on the stack and then emit OpCode::CreateHashMap(size) + // with size equal to the amount of keys + let mut size = 0; + if !self.check(TokenType::RightBrace) { + loop { + self.expression(); + self.consume(TokenType::Colon, "Expected ':' after key"); + self.expression(); + size += 1; + if !self.match_cur(TokenType::Comma) { + break; + } + } + } + self.consume(TokenType::RightBrace, "Expected '}' after hashmap"); + self.emit_instr(OpCreateHashMap(size)); + } + /// Parse an identifier that we know to be a variable /// /// Eventually emits a get instr or a set instr + the instructions to process the expr diff --git a/src/gc.rs b/src/gc.rs index 2edd89e..92b02b0 100644 --- a/src/gc.rs +++ b/src/gc.rs @@ -181,6 +181,16 @@ impl GC { HeapObjVal::PhoenixString(_string) => { // to_mark.push(string.ptr); } + HeapObjVal::PhoenixHashMap(map) => { + for val in &map.map { + if let Value::PhoenixPointer(ptr) = val.0 { + to_mark.push(*ptr); + } + if let Value::PhoenixPointer(ptr) = val.1 { + to_mark.push(*ptr); + } + } + } } } None => panic!("VM panic! Why is there an unallocated pointer?"), diff --git a/src/native/native_methods.rs b/src/native/native_methods.rs index 313d15d..5d27bf6 100644 --- a/src/native/native_methods.rs +++ b/src/native/native_methods.rs @@ -83,6 +83,9 @@ lazy_static! { HeapObjVal::PhoenixString(ref mut string) => { Some(Ok(Long(string.value.len() as i64))) } + HeapObjVal::PhoenixHashMap(ref mut hashmap) => { + Some(Ok(Long(hashmap.map.len() as i64))) + } _ => None } } @@ -119,7 +122,25 @@ lazy_static! { _ => None } }) - ) + ), + ( + "get", + (Some(1), |this,args,_,state,_| { + match this { + PhoenixPointer(ptr) => { + let obj = state.deref_mut(ptr); + match obj.obj { + HeapObjVal::PhoenixHashMap(ref mut map) => { + println!("{:?}, arg: {:?}", map.map, args); + Some(Ok(map.map.get(&args[0]).unwrap_or(&Nil).clone())) + } + _ => None + } + } + _ => None + } + }) + ), ])); } diff --git a/src/precedence.rs b/src/precedence.rs index 8942edf..cb0797d 100644 --- a/src/precedence.rs +++ b/src/precedence.rs @@ -30,6 +30,7 @@ pub enum ParseFn { String, Variable, List, + HashMap, And, Or, Call, @@ -234,16 +235,23 @@ const PARSE_RULE_SUPER: ParseRule = ParseRule { infix: ParseFn::None, precedence: Precedence::None, }; -const PARSE_RULE_LB: ParseRule = ParseRule { +const PARSE_RULE_LBRACKET: ParseRule = ParseRule { prefix: ParseFn::List, infix: ParseFn::None, precedence: Precedence::None, }; +const PARSE_RULE_LBRACE: ParseRule = ParseRule { + prefix: ParseFn::HashMap, + infix: ParseFn::None, + precedence: Precedence::None, +}; + pub fn get_rule(operator: TokenType) -> ParseRule { match operator { TokenType::LeftParen => PARSE_RULE_LP, - TokenType::LeftBracket => PARSE_RULE_LB, + TokenType::LeftBracket => PARSE_RULE_LBRACKET, + TokenType::LeftBrace => PARSE_RULE_LBRACE, TokenType::Minus => PARSE_RULE_MINUS, TokenType::MinusMinus => PARSE_RULE_MINUS_MINUS, TokenType::MinusAssign => PARSE_RULE_MINUS_ASSIGN, diff --git a/src/scanner.rs b/src/scanner.rs index c37d220..0c72cbc 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -65,6 +65,7 @@ pub enum TokenType { GreaterEqual, Less, LessEqual, + Colon, Identifier, String, @@ -213,6 +214,7 @@ impl Scanner { } } b'"' => self.string(), + b':' => self.create_token(TokenType::Colon), _ => self.error_token("Unexpected character."), } } diff --git a/src/value.rs b/src/value.rs index 53da706..66cadf3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::hash::{Hash, Hasher}; use crate::chunk::ModuleChunk; use serde::{Deserialize, Serialize}; @@ -15,7 +16,7 @@ pub enum Value { #[default] Nil, // todo: fix this, cause this type is only used for initialisation of strings - // todo: create static strings, for example for prints, so we can allocate them once and have multiple references to them + // todo: create static strings, for example for prints, so we can allocate them once and have multiple immutable references to them PhoenixString(String), // Index of the function in the functions Vec in VM // Fixme: Is this even reachable? Can this be completely removed and the parameter put in OpClosure? PhoenixFunction(usize), @@ -31,6 +32,26 @@ pub enum Value { PhoenixBoundMethod(ObjBoundMethod), } +// todo fix this (dont hash the pointer, but the value) +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Value::Float(x) => x.to_bits().hash(state), + Value::Long(x) => x.hash(state), + Value::Bool(x) => x.hash(state), + Value::Nil => 0.hash(state), + Value::PhoenixString(x) => x.hash(state), + Value::PhoenixFunction(x) => x.hash(state), + Value::NativeFunction(x, _) => x.hash(state), + Value::NativeMethod(x, _) => x.hash(state), + Value::PhoenixClass(x) => x.hash(state), + Value::PhoenixModule(x) => x.hash(state), + Value::PhoenixPointer(x) => x.hash(state), + Value::PhoenixBoundMethod(x) => x.hash(state), + } + } +} + impl Eq for Value {} impl PartialEq for Value { @@ -86,6 +107,8 @@ impl Value { state.deref(*pointer).to_string(vm, state, modules) } else if let HeapObjVal::PhoenixString(_) = &state.deref(*pointer).obj { state.deref(*pointer).to_string(vm, state, modules) + } else if let HeapObjVal::PhoenixHashMap(_) = &state.deref(*pointer).obj { + state.deref(*pointer).to_string(vm, state, modules) } else { format!( " to {}", @@ -258,7 +281,7 @@ pub fn values_equal(t: (&Value, &Value)) -> bool { } } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Ord, PartialOrd, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Ord, PartialOrd, Eq, Hash)] pub struct ObjBoundMethod { pub method: usize, // Index into the functions vec for which function to call @@ -276,6 +299,7 @@ pub enum HeapObjType { PhoenixClosure, PhoenixList, PhoenixString, + PhoenixHashMap, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] @@ -329,6 +353,14 @@ impl HeapObj { is_marked: false, } } + + pub fn new_hashmap(map: ObjHashMap) -> HeapObj { + HeapObj { + obj: HeapObjVal::PhoenixHashMap(map), + obj_type: HeapObjType::PhoenixHashMap, + is_marked: false, + } + } } // I swear i really tried to not have this be duplicate with HeapObjType, but couldn't figure out a way to do it @@ -339,6 +371,7 @@ pub enum HeapObjVal { PhoenixClosure(ObjClosure), PhoenixString(ObjString), PhoenixList(ObjList), + PhoenixHashMap(ObjHashMap), } impl HeapObjVal { @@ -375,6 +408,14 @@ impl HeapObjVal { panic!("VM panic! How did a placeholder value get here?") } HeapObjVal::PhoenixString(string) => string.value.clone(), + HeapObjVal::PhoenixHashMap(map) => format!( + "{{{}}}", + map.map + .iter() + .map(|(k, v)| format!("{}: {}", k.to_string(vm, state, modules), v.to_string(vm, state, modules))) + .collect::>() + .join(", ") + ), } } @@ -441,6 +482,14 @@ impl HeapObjVal { panic!("VM panic!") } } + + pub fn as_hashmap(&self) -> &ObjHashMap { + if let HeapObjVal::PhoenixHashMap(map) = self { + map + } else { + panic!("VM panic!") + } + } } /// Runtime instantiation of class definitions @@ -499,3 +548,15 @@ impl ObjString { ObjString { value } } } + +/// Runtime representation of a HashMap +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)] +pub struct ObjHashMap { + pub map: HashMap, +} + +impl ObjHashMap { + pub fn new(map: HashMap) -> ObjHashMap { + ObjHashMap { map } + } +} diff --git a/src/vm.rs b/src/vm.rs index 1e05327..632502f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,10 +6,7 @@ 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, -}; +use crate::value::{is_falsey, values_equal, HeapObj, HeapObjType, HeapObjVal, ObjBoundMethod, ObjClosure, ObjInstance, ObjList, ObjString, Value, ObjHashMap}; use crate::{error, phoenix_error, warn, InterpretResult, VERSION}; use std::collections::HashMap; @@ -221,7 +218,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) => { @@ -262,7 +259,7 @@ impl VMState { phoenix_error!("VM panic! Attempted to push an upvalue that doesn't exist"); } } - .clone(); + .clone(); self.stack.push(val); } @@ -472,7 +469,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] = @@ -816,7 +813,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -837,7 +834,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -859,7 +856,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -881,7 +878,7 @@ impl VM { "Undefined variable '{}'", VM::get_variable_name(index, &state, &modules) ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1110,7 +1107,7 @@ impl VM { VM::get_variable_name(name_index, &state, &modules), instance ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1179,7 +1176,7 @@ impl VM { .unwrap() .name, ) - .as_str(), + .as_str(), &state, &modules, ); @@ -1532,6 +1529,17 @@ impl VM { return InterpretResult::InterpretRuntimeError; } } + OpCreateHashMap(n) => { + let mut map = HashMap::new(); + for _ in 0..n { + let value = state.pop(); + let key = state.pop(); + map.insert(key, value); + } + let map_obj = ObjHashMap::new(map); + let ptr = state.alloc(HeapObj::new_hashmap(map_obj)); + state.stack.push(ptr); + } } } }