added more builtin functions

This commit is contained in:
Shautvast 2025-12-14 18:12:50 +01:00
parent e5b03d9037
commit 8eeb09855f
12 changed files with 148 additions and 23 deletions

View file

@ -130,8 +130,9 @@ fn get() -> [Customer] | Customer? | ():
* test support * test support
## What about performance? ## What about performance?
* Clueless really! We'll see. * Not optimizing for performance yet. First results look pretty bad.
* But it is written in rust * 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 * And it has no GC
* So, maybe it will compete with python? * So, maybe it will compete with python?

View file

@ -4,4 +4,4 @@ fn fib(n: u64):
else: else:
fib(n-1) + fib(n-2) fib(n-1) + fib(n-2)
println(fib(10)) println("fib = " + fib(38))

View file

@ -22,6 +22,11 @@ pub(crate) fn list_functions() -> FunctionMap {
"remove", "remove",
Signature::new(vec![Parameter::new("index", U64)], U64, remove), Signature::new(vec![Parameter::new("index", U64)], U64, remove),
); );
add(
functions,
"sort",
Signature::new(vec![], U64, sort),
);
list_functions list_functions
} }
@ -55,6 +60,19 @@ fn len(self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError
} }
} }
fn sort(mut self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
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 { fn expected_a_list() -> RuntimeError {
expected("list") expected("list")
} }

64
src/builtins/map.rs Normal file
View file

@ -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<Value>, mut args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>, mut args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
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")
}

View file

@ -1,6 +1,7 @@
mod string; mod string;
mod list; mod list;
pub(crate) mod globals; pub(crate) mod globals;
mod map;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use crate::builtins::string::string_functions; use crate::builtins::string::string_functions;
@ -12,6 +13,7 @@ use std::rc::Rc;
use std::sync::LazyLock; use std::sync::LazyLock;
use crate::compiler::ast_pass::Parameter; use crate::compiler::ast_pass::Parameter;
use crate::builtins::list::list_functions; use crate::builtins::list::list_functions;
use crate::builtins::map::map_functions;
pub(crate) struct Signature { pub(crate) struct Signature {
pub(crate) parameters: Vec<Parameter>, pub(crate) parameters: Vec<Parameter>,
@ -32,8 +34,8 @@ impl Signature {
} }
} }
pub(crate) fn arity(&self) -> usize { pub(crate) fn arity(&self) -> u8 {
self.parameters.len() self.parameters.len() as u8
} }
} }
@ -47,6 +49,7 @@ static FUNCTIONS: LazyLock<FunctionTable> = LazyLock::new(|| {
let mut table: FunctionTable = HashMap::new(); let mut table: FunctionTable = HashMap::new();
table.insert("string".to_string(), string_functions()); table.insert("string".to_string(), string_functions());
table.insert("list".to_string(), list_functions()); table.insert("list".to_string(), list_functions());
table.insert("map".to_string(), map_functions());
table table
}); });

View file

@ -270,7 +270,7 @@ impl AsmPass {
namespace, symbols, registry, arguments, parameters, namespace, symbols, registry, arguments, parameters,
)?; )?;
self.emit(Call(name_index, arguments.len())); self.emit(Call(name_index, arguments.len() as u8));
} }
// constructor function // constructor function
Some(Symbol::Object { fields, .. }) => { Some(Symbol::Object { fields, .. }) => {
@ -280,7 +280,7 @@ impl AsmPass {
self.get_arguments_in_order( self.get_arguments_in_order(
namespace, symbols, registry, arguments, fields, namespace, symbols, registry, arguments, fields,
)?; )?;
self.emit(Call(name_index, arguments.len())); self.emit(Call(name_index, arguments.len() as u8));
} }
// maybe global function // maybe global function
_ => { _ => {
@ -325,7 +325,7 @@ impl AsmPass {
}); });
let signature = let signature =
lookup(&receiver_type, method_name).map_err(|e| self.error_at_line(e))?; 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( return Err(self.error_at_line(CompilerError::IllegalArgumentsException(
format!("{}.{}", receiver_type, method_name), format!("{}.{}", receiver_type, method_name),
signature.parameters.len(), signature.parameters.len(),
@ -339,7 +339,7 @@ impl AsmPass {
arguments, arguments,
&signature.parameters, &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, .. } => { Expression::Variable { name, .. } => {
let name_index = self.vars.get(name); let name_index = self.vars.get(name);
@ -444,11 +444,7 @@ impl AsmPass {
} }
Expression::MapGet { .. } => {} Expression::MapGet { .. } => {}
Expression::FieldGet { .. } => {} Expression::FieldGet { .. } => {}
Expression::Range { lower, upper, .. } => { Expression::Range { lower, .. } => {}
// 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::ForStatement { Expression::ForStatement {
loop_var, loop_var,
range, range,
@ -550,7 +546,7 @@ pub enum Op {
Divide, Divide,
Negate, Negate,
Return, Return,
Call(usize, usize), Call(usize, u8),
And, And,
Or, Or,
Not, Not,
@ -571,7 +567,7 @@ pub enum Op {
DefMap(usize), DefMap(usize),
Assign(usize), Assign(usize),
ListGet, ListGet,
CallBuiltin(usize, usize, usize), CallBuiltin(usize, usize, u8),
Dup, Dup,
GotoIf(usize), GotoIf(usize),
GotoIfNot(usize), GotoIfNot(usize),

View file

@ -4,9 +4,9 @@ mod tests {
use crate::compiler::{compile, run}; use crate::compiler::{compile, run};
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName}; use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName};
use crate::errors::CompilerErrorAtLine; 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::errors::TipiLangError::{Compiler, Runtime};
use crate::value::{Value, string}; use crate::value::{Value, string, i64};
use chrono::DateTime; use chrono::DateTime;
#[test] #[test]
@ -299,6 +299,28 @@ date"#),
assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3))); 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] #[test]
fn list_push() { fn list_push() {
assert_eq!( assert_eq!(
@ -449,7 +471,8 @@ let a:i64 = if true:
42 42
else: else:
0 0
a"#).unwrap(); a"#)
.unwrap();
} }
// #[test] // #[test]
// fn package() { // fn package() {

View file

@ -18,7 +18,7 @@ pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, AsmChunk>,
for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) { for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path().to_str().unwrap(); let path = entry.path().to_str().unwrap();
if path.ends_with(TIPI_EXT) { if path.ends_with(TIPI_EXT) {
print!("-- Compiling {} -- ", path); println!("-- Compiling {} -- ", path);
let source = fs::read_to_string(path).map_err(map_underlying())?; let source = fs::read_to_string(path).map_err(map_underlying())?;
let tokens = scan_pass::scan(&source)?; let tokens = scan_pass::scan(&source)?;
let mut symbol_table = HashMap::new(); let mut symbol_table = HashMap::new();

View file

@ -96,6 +96,8 @@ pub enum RuntimeError {
ExpectedType(String), ExpectedType(String),
#[error("Index out of bounds: {0} > {1}")] #[error("Index out of bounds: {0} > {1}")]
IndexOutOfBounds(usize, usize), IndexOutOfBounds(usize, usize),
#[error("The list contains values that are not comparable.")]
NotSortable,
} }
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]

View file

@ -11,7 +11,7 @@ pub mod file_watch;
mod keywords; mod keywords;
pub mod repl; pub mod repl;
mod symbol_builder; mod symbol_builder;
mod value; pub mod value;
pub mod vm; pub mod vm;
pub(crate) type SymbolTable = HashMap<String, Symbol>; pub(crate) type SymbolTable = HashMap<String, Symbol>;

View file

@ -37,7 +37,7 @@ pub(crate) fn string(v: impl Into<String>) -> Value {
Value::String(v.into()) Value::String(v.into())
} }
pub(crate) fn _i64(v: impl Into<i64>) -> Value { pub(crate) fn i64(v: impl Into<i64>) -> Value {
Value::I64(v.into()) Value::I64(v.into())
} }
@ -109,6 +109,24 @@ impl Value {
_ => Err(ValueError::IllegalCast), _ => 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<Ordering> {
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<i32> for Value { impl From<i32> for Value {