diff --git a/README.md b/README.md index 0b46a6a..fce769a 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,9 @@ fn get() -> [Customer] | Customer? | (): * test support ## What about performance? -* Clueless really! We'll see. -* But it is written in rust +* Not optimizing for performance yet. First results look pretty bad. + * as in: purely recursive fibonacci of 38 (the 38th fibonacci number) takes 42 seconds on my machine (in release mode) :sad-face. +* That said the idea is that a lot of processing is done within the virtual machine. * And it has no GC * So, maybe it will compete with python? diff --git a/examples/fibonacci/fib.tp b/examples/fibonacci/fib.tp index 92311a6..ae71050 100644 --- a/examples/fibonacci/fib.tp +++ b/examples/fibonacci/fib.tp @@ -4,4 +4,4 @@ fn fib(n: u64): else: fib(n-1) + fib(n-2) -println(fib(10)) \ No newline at end of file +println("fib = " + fib(38)) \ No newline at end of file diff --git a/source/hello/web.tp b/source/hello/web.tp index ecea46d..12b2f69 100644 --- a/source/hello/web.tp +++ b/source/hello/web.tp @@ -2,4 +2,4 @@ object Person: name: string fn get(path: string) -> string: - "hello" + path \ No newline at end of file + "hello " + path \ No newline at end of file diff --git a/src/builtins/list.rs b/src/builtins/list.rs index 42f4a93..af4f34a 100644 --- a/src/builtins/list.rs +++ b/src/builtins/list.rs @@ -22,6 +22,11 @@ pub(crate) fn list_functions() -> FunctionMap { "remove", Signature::new(vec![Parameter::new("index", U64)], U64, remove), ); + add( + functions, + "sort", + Signature::new(vec![], U64, sort), + ); list_functions } @@ -55,6 +60,19 @@ fn len(self_val: RefMut, _args: Vec) -> Result, _args: Vec) -> Result { + if let Value::List(list) = self_val.deref_mut() { + if list.windows(2).any(|w| w[0].sort_cmp(&w[1]).is_none()) { + return Err(RuntimeError::NotSortable); + } + + list.sort_by(|a, b| a.sort_cmp(b).unwrap()); + Ok(Value::Void) + } else { + Err(expected_a_list()) + } +} + fn expected_a_list() -> RuntimeError { expected("list") } diff --git a/src/builtins/map.rs b/src/builtins/map.rs new file mode 100644 index 0000000..0df972f --- /dev/null +++ b/src/builtins/map.rs @@ -0,0 +1,64 @@ +use crate::builtins::{FunctionMap, Signature, add, expected}; +use crate::compiler::ast_pass::Parameter; +use crate::compiler::tokens::TokenType; +use crate::compiler::tokens::TokenType::{U64, Void}; +use crate::errors::RuntimeError; +use crate::value::{Value, u64}; +use std::cell::RefMut; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; + +pub(crate) fn map_functions() -> FunctionMap { + let mut map_functions: FunctionMap = HashMap::new(); + let functions = &mut map_functions; + add(functions, "len", Signature::new(vec![], U64, len)); + add( + functions, + "insert", + Signature::new( + vec![ + Parameter::new("key", TokenType::Any), + Parameter::new("value", TokenType::Any), + ], + Void, + insert, + ), + ); + add( + functions, + "remove", + Signature::new(vec![Parameter::new("key", TokenType::Any)], Void, remove), + ); + map_functions +} + +fn remove(mut self_val: RefMut, mut args: Vec) -> Result { + if let Value::Map(map) = self_val.deref_mut() { + let key = args.remove(0); + map.remove(&key); + Ok(Value::Void) + } else { + Err(expected_a_map()) + } +} + +fn insert(mut self_val: RefMut, mut args: Vec) -> Result { + if let Value::Map(map) = self_val.deref_mut() { + map.insert(args.remove(0),args.remove(0)); + Ok(Value::Void) + } else { + Err(expected_a_map()) + } +} + +fn len(self_val: RefMut, _args: Vec) -> Result { + if let Value::Map(map) = self_val.deref() { + Ok(u64(map.len() as u64)) + } else { + Err(expected_a_map()) + } +} + +fn expected_a_map() -> RuntimeError { + expected("map") +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index f82a76d..c98c450 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,6 +1,7 @@ mod string; mod list; pub(crate) mod globals; +mod map; use std::cell::{RefCell, RefMut}; use crate::builtins::string::string_functions; @@ -12,6 +13,7 @@ use std::rc::Rc; use std::sync::LazyLock; use crate::compiler::ast_pass::Parameter; use crate::builtins::list::list_functions; +use crate::builtins::map::map_functions; pub(crate) struct Signature { pub(crate) parameters: Vec, @@ -32,8 +34,8 @@ impl Signature { } } - pub(crate) fn arity(&self) -> usize { - self.parameters.len() + pub(crate) fn arity(&self) -> u8 { + self.parameters.len() as u8 } } @@ -47,6 +49,7 @@ static FUNCTIONS: LazyLock = LazyLock::new(|| { let mut table: FunctionTable = HashMap::new(); table.insert("string".to_string(), string_functions()); table.insert("list".to_string(), list_functions()); + table.insert("map".to_string(), map_functions()); table }); diff --git a/src/compiler/assembly_pass.rs b/src/compiler/assembly_pass.rs index 993ab19..b8dfb1b 100644 --- a/src/compiler/assembly_pass.rs +++ b/src/compiler/assembly_pass.rs @@ -270,7 +270,7 @@ impl AsmPass { namespace, symbols, registry, arguments, parameters, )?; - self.emit(Call(name_index, arguments.len())); + self.emit(Call(name_index, arguments.len() as u8)); } // constructor function Some(Symbol::Object { fields, .. }) => { @@ -280,7 +280,7 @@ impl AsmPass { self.get_arguments_in_order( namespace, symbols, registry, arguments, fields, )?; - self.emit(Call(name_index, arguments.len())); + self.emit(Call(name_index, arguments.len() as u8)); } // maybe global function _ => { @@ -325,7 +325,7 @@ impl AsmPass { }); let signature = lookup(&receiver_type, method_name).map_err(|e| self.error_at_line(e))?; - if signature.arity() != arguments.len() { + if signature.arity() != arguments.len() as u8 { return Err(self.error_at_line(CompilerError::IllegalArgumentsException( format!("{}.{}", receiver_type, method_name), signature.parameters.len(), @@ -339,7 +339,7 @@ impl AsmPass { arguments, &signature.parameters, )?; - self.emit(CallBuiltin(name_index, type_index, arguments.len())); + self.emit(CallBuiltin(name_index, type_index, arguments.len() as u8)); } Expression::Variable { name, .. } => { let name_index = self.vars.get(name); @@ -444,11 +444,7 @@ impl AsmPass { } Expression::MapGet { .. } => {} Expression::FieldGet { .. } => {} - Expression::Range { lower, upper, .. } => { - // opposite order, because we have to assign last one first to the loop variable - // self.compile_expression(namespace, upper, symbols, registry)?; - // self.compile_expression(namespace, lower, symbols, registry)?; - } + Expression::Range { lower, .. } => {} Expression::ForStatement { loop_var, range, @@ -550,7 +546,7 @@ pub enum Op { Divide, Negate, Return, - Call(usize, usize), + Call(usize, u8), And, Or, Not, @@ -571,7 +567,7 @@ pub enum Op { DefMap(usize), Assign(usize), ListGet, - CallBuiltin(usize, usize, usize), + CallBuiltin(usize, usize, u8), Dup, GotoIf(usize), GotoIfNot(usize), diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index d4d9135..5a1fed6 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -4,9 +4,9 @@ mod tests { use crate::compiler::{compile, run}; use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName}; use crate::errors::CompilerErrorAtLine; - use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds}; + use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds, NotSortable}; use crate::errors::TipiLangError::{Compiler, Runtime}; - use crate::value::{Value, string}; + use crate::value::{Value, string, i64}; use chrono::DateTime; #[test] @@ -299,6 +299,28 @@ date"#), assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3))); } + #[test] + fn list_sort() { + assert_eq!( + run(r#" +let a = [3,2,1] +a.sort() +a"#), + Ok(Value::List(vec![i64(1), i64(2), i64(3)])) + ); + } + + #[test] + fn list_sort_invalid() { + assert_eq!( + run(r#" +let a = [3,2,"a"] +a.sort() +a"#), + Err(Runtime(NotSortable)) + ); + } + #[test] fn list_push() { assert_eq!( @@ -449,7 +471,8 @@ let a:i64 = if true: 42 else: 0 -a"#).unwrap(); +a"#) + .unwrap(); } // #[test] // fn package() { diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 4613571..9c9ec14 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -18,7 +18,7 @@ pub fn compile_sourcedir(source_dir: &str) -> Result, for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) { let path = entry.path().to_str().unwrap(); if path.ends_with(TIPI_EXT) { - print!("-- Compiling {} -- ", path); + println!("-- Compiling {} -- ", path); let source = fs::read_to_string(path).map_err(map_underlying())?; let tokens = scan_pass::scan(&source)?; let mut symbol_table = HashMap::new(); diff --git a/src/errors.rs b/src/errors.rs index 7d8f7b1..663d148 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -96,6 +96,8 @@ pub enum RuntimeError { ExpectedType(String), #[error("Index out of bounds: {0} > {1}")] IndexOutOfBounds(usize, usize), + #[error("The list contains values that are not comparable.")] + NotSortable, } #[derive(Error, Debug, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index ea2233c..3ef8583 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ pub mod file_watch; mod keywords; pub mod repl; mod symbol_builder; -mod value; +pub mod value; pub mod vm; pub(crate) type SymbolTable = HashMap; diff --git a/src/value.rs b/src/value.rs index 0fac67f..5d3919a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -37,7 +37,7 @@ pub(crate) fn string(v: impl Into) -> Value { Value::String(v.into()) } -pub(crate) fn _i64(v: impl Into) -> Value { +pub(crate) fn i64(v: impl Into) -> Value { Value::I64(v.into()) } @@ -109,6 +109,24 @@ impl Value { _ => Err(ValueError::IllegalCast), } } + + /// Comparison used for sorting lists. + /// Returns `None` when values are not comparable (different variants, unsupported types, etc.). + pub fn sort_cmp(&self, rhs: &Self) -> Option { + match (self, rhs) { + (Value::I32(a), Value::I32(b)) => Some(a.cmp(b)), + (Value::I64(a), Value::I64(b)) => Some(a.cmp(b)), + (Value::U32(a), Value::U32(b)) => Some(a.cmp(b)), + (Value::U64(a), Value::U64(b)) => Some(a.cmp(b)), + (Value::F32(a), Value::F32(b)) => Some(a.total_cmp(b)), + (Value::F64(a), Value::F64(b)) => Some(a.total_cmp(b)), + (Value::String(a), Value::String(b)) => Some(a.cmp(b)), + (Value::Char(a), Value::Char(b)) => Some(a.cmp(b)), + (Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)), + (Value::DateTime(a), Value::DateTime(b)) => Some(a.cmp(b)), + _ => None, + } + } } impl From for Value {