fix issues with arguments for builtin functions

This commit is contained in:
Shautvast 2025-11-14 11:50:20 +01:00
parent d2106ccf96
commit 44f063fb6b
9 changed files with 227 additions and 64 deletions

39
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@ -1355,6 +1364,35 @@ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
] ]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.24" version = "0.12.24"
@ -1838,6 +1876,7 @@ dependencies = [
"log", "log",
"log4rs", "log4rs",
"notify", "notify",
"regex",
"reqwest", "reqwest",
"serde", "serde",
"thiserror", "thiserror",

View file

@ -25,3 +25,4 @@ url = "2.5.7"
clap = { version = "4.5.51", features = ["derive"] } clap = { version = "4.5.51", features = ["derive"] }
notify = "8.2.0" notify = "8.2.0"
arc-swap = "1.7.1" arc-swap = "1.7.1"
regex = "1.12.2"

View file

@ -146,7 +146,7 @@ impl AstCompiler {
self.query_guard_expr(symbol_table) self.query_guard_expr(symbol_table)
} else { } else {
Err(self.raise(Expected("-> or ?"))) Err(self.raise(Expected("-> or ?")))
} };
} }
Ok(Stop { Ok(Stop {
line: self.peek().line, line: self.peek().line,
@ -803,6 +803,19 @@ pub struct Parameter {
pub(crate) var_type: TokenType, pub(crate) var_type: TokenType,
} }
impl Parameter {
pub(crate) fn new(name: impl Into<String>, value_type: TokenType) -> Self {
Self {
name: Token {
token_type: TokenType::StringType,
lexeme: name.into(),
line: 0,
},
var_type: value_type,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expression { pub enum Expression {
Binary { Binary {

View file

@ -1,13 +1,39 @@
mod string; mod string;
use crate::builtins::string::string_methods;
use crate::errors::{CompilerError, RuntimeError};
use crate::tokens::TokenType;
use crate::value::Value; use crate::value::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
use crate::builtins::string::string_methods; use crate::ast_compiler::Parameter;
use crate::errors::RuntimeError;
pub(crate) struct Signature {
pub(crate) parameters: Vec<Parameter>,
pub(crate) return_type: TokenType,
pub(crate) function: MethodFn,
}
impl Signature {
pub(crate) fn new(
parameters: Vec<Parameter>,
return_type: TokenType,
function: MethodFn,
) -> Self {
Self {
parameters,
return_type,
function,
}
}
pub(crate) fn arity(&self) -> usize {
self.parameters.len()
}
}
pub(crate) type MethodFn = fn(Value, Vec<Value>) -> Result<Value, RuntimeError>; pub(crate) type MethodFn = fn(Value, Vec<Value>) -> Result<Value, RuntimeError>;
pub(crate) type MethodMap = HashMap<String, MethodFn>; pub(crate) type MethodMap = HashMap<String, Signature>;
pub(crate) type MethodTable = HashMap<String, MethodMap>; pub(crate) type MethodTable = HashMap<String, MethodMap>;
static METHODS: LazyLock<MethodTable> = LazyLock::new(|| { static METHODS: LazyLock<MethodTable> = LazyLock::new(|| {
@ -16,21 +42,24 @@ static METHODS: LazyLock<MethodTable> = LazyLock::new(|| {
table table
}); });
pub(crate) fn insert(m: &mut MethodMap, name: &str, method: MethodFn) { pub(crate) fn add(m: &mut MethodMap, name: &str, method: Signature) {
m.insert(name.to_string(), method); m.insert(name.to_string(), method);
} }
pub fn call( pub(crate) fn lookup(type_name: &str, method_name: &str) -> Result<&'static Signature, CompilerError> {
METHODS
.get(type_name)
.and_then(|methods| methods.get(method_name))
.ok_or_else(|| CompilerError::FunctionNotFound(format!("{}.{}", type_name, method_name)))
}
pub(crate) fn call(
type_name: &str, type_name: &str,
method_name: &str, method_name: &str,
self_val: Value, self_val: Value,
args: Vec<Value>, args: Vec<Value>,
) -> Result<Value, RuntimeError> { ) -> Result<Value, RuntimeError> {
METHODS (lookup(type_name,method_name).map_err(|e|RuntimeError::FunctionNotFound(e.to_string()))?.function)(self_val, args)
.get(type_name)
.and_then(|methods| methods.get(method_name))
.ok_or_else(|| RuntimeError::FunctionNotFound(format!("{}.{}",type_name, method_name)))?
(self_val, args)
} }
pub(crate) fn expected(expected_type: &str) -> RuntimeError { pub(crate) fn expected(expected_type: &str) -> RuntimeError {

View file

@ -1,16 +1,45 @@
use std::collections::HashMap; use crate::builtins::{MethodMap, Parameter, Signature, add, expected};
use crate::builtins::{expected, insert, MethodMap};
use crate::errors::RuntimeError; use crate::errors::RuntimeError;
use crate::value::{bool, i64, string, Value}; use crate::tokens::TokenType::{StringType, U64};
use crate::value::{Value, bool, i64, string};
use regex::Regex;
use std::collections::HashMap;
pub(crate) fn string_methods() -> MethodMap { pub(crate) fn string_methods() -> MethodMap {
let mut string_methods: MethodMap = HashMap::new(); let mut string_methods: MethodMap = HashMap::new();
let m = &mut string_methods; let m = &mut string_methods;
insert(m, "len", string_len); add(m, "len", Signature::new(vec![], U64, string_len));
insert(m, "to_uppercase", string_to_uppercase); add(
insert(m, "to_lowercase", string_to_lowercase); m,
insert(m, "contains", string_contains); "to_uppercase",
insert(m, "reverse", string_reverse); Signature::new(vec![], StringType, string_to_uppercase),
);
add(
m,
"to_lowercase",
Signature::new(vec![], StringType, string_to_lowercase),
);
add(m, "contains", Signature::new(vec![], StringType, string_contains));
add(m, "reverse", Signature::new(vec![], StringType, string_reverse));
add(m, "trim", Signature::new(vec![], StringType, string_trim));
add(
m,
"trim_start",
Signature::new(vec![], StringType, string_trim_start),
);
add(m, "trim_end", Signature::new(vec![], StringType, string_trim_end));
add(
m,
"replace_all",
Signature::new(
vec![
Parameter::new("pattern", StringType),
Parameter::new("replacement", StringType),
],
StringType,
string_replace_all,
),
);
string_methods string_methods
} }
@ -37,18 +66,60 @@ fn string_to_lowercase(self_val: Value, _args: Vec<Value>) -> Result<Value, Runt
fn string_contains(self_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> { fn string_contains(self_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
match (self_val, args.first()) { match (self_val, args.first()) {
(Value::String(s), Some(Value::String(pat))) => { (Value::String(s), Some(Value::String(pat))) => Ok(bool(s.contains(pat.as_str()))),
Ok(bool(s.contains(pat.as_str())))
}
_ => Err(expected_a_string()), _ => Err(expected_a_string()),
} }
} }
fn string_reverse(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> { fn string_reverse(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> {
match self_val { match self_val {
Value::String(s) => { Value::String(s) => Ok(s.chars().rev().collect::<String>().into()),
Ok(s.chars().rev().collect::<String>().into()) _ => Err(expected_a_string()),
} }
}
fn string_trim(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> {
match self_val {
Value::String(s) => Ok(string(s.trim())),
_ => Err(expected_a_string()),
}
}
fn string_trim_start(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> {
match self_val {
Value::String(s) => Ok(string(s.trim_start())),
_ => Err(expected_a_string()),
}
}
fn string_trim_end(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> {
match self_val {
Value::String(s) => Ok(string(s.trim_end())),
_ => Err(expected_a_string()),
}
}
//TODO check arity in compiler (generically)
fn string_replace_all(receiver: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
let pattern = if let Value::String(s) = &args[0] {
Regex::new(s).map_err(|_| RuntimeError::IllegalArgumentException("Invalid regex".into()))?
} else {
return Err(RuntimeError::IllegalArgumentException(
format!("Illegal pattern. Expected a string, but got {}", &args[0]).into(),
));
};
let replacement = if let Value::String(repl) = &args[1] {
repl
} else {
return Err(RuntimeError::IllegalArgumentException(
format!(
"Illegal replacement. Expected a string but got {}",
&args[1]
)
.into(),
));
};
match receiver {
Value::String(ref str) => Ok(string(pattern.replace_all(str, replacement))),
_ => Err(expected_a_string()), _ => Err(expected_a_string()),
} }
} }

View file

@ -1,5 +1,6 @@
use crate::ast_compiler::Expression::NamedParameter; use crate::ast_compiler::Expression::NamedParameter;
use crate::ast_compiler::{Expression, Function, Parameter, Statement}; use crate::ast_compiler::{Expression, Function, Parameter, Statement};
use crate::builtins::lookup;
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::errors::CompilerError::{IncompatibleTypes, UndeclaredVariable}; use crate::errors::CompilerError::{IncompatibleTypes, UndeclaredVariable};
use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::errors::{CompilerError, CompilerErrorAtLine};
@ -205,17 +206,24 @@ impl Compiler {
self.compile_expression(namespace, receiver, symbols, registry)?; self.compile_expression(namespace, receiver, symbols, registry)?;
let receiver_type = infer_type(receiver, symbols).to_string(); let receiver_type = infer_type(receiver, symbols).to_string();
let type_index = self let type_index = self.chunk.find_constant(&receiver_type).unwrap_or_else(|| {
.chunk self.chunk
.find_constant(&receiver_type) .add_constant(Value::String(receiver_type.clone()))
.unwrap_or_else(|| self.chunk.add_constant(Value::String(receiver_type))); });
let name_index = self.chunk.find_constant(method_name).unwrap_or_else(|| { let name_index = self.chunk.find_constant(method_name).unwrap_or_else(|| {
self.chunk self.chunk
.add_constant(Value::String(method_name.to_string())) .add_constant(Value::String(method_name.to_string()))
}); });
//TODO lookup parameters for builtin let signature = lookup(&receiver_type, method_name).map_err(|e| self.raise(e))?;
self.get_arguments_in_order(namespace, symbols, registry, arguments, &vec![])?;
self.get_arguments_in_order(
namespace,
symbols,
registry,
arguments,
&signature.parameters,
)?;
self.emit_byte(OP_CALL_BUILTIN); self.emit_byte(OP_CALL_BUILTIN);
self.emit_byte(name_index as u16); self.emit_byte(name_index as u16);
self.emit_byte(type_index as u16); self.emit_byte(type_index as u16);
@ -312,19 +320,17 @@ impl Compiler {
namespace: &str, namespace: &str,
symbols: &SymbolTable, symbols: &SymbolTable,
registry: &mut Registry, registry: &mut Registry,
arguments: &Vec<Expression>, arguments: &[Expression],
parameters: &Vec<Parameter>, parameters: &[Parameter],
) -> Result<(), CompilerErrorAtLine> { ) -> Result<(), CompilerErrorAtLine> {
for parameter in parameters { for argument in arguments {
for argument in arguments { for parameter in parameters {
if let NamedParameter { name, value, .. } = argument { if let NamedParameter { name, value, .. } = argument {
if name.lexeme == parameter.name.lexeme { if name.lexeme == parameter.name.lexeme {
let value_type = infer_type(value, symbols); let value_type = infer_type(value, symbols);
if parameter.var_type != value_type { if parameter.var_type != value_type {
return Err(self.raise(CompilerError::IncompatibleTypes( return Err(self
parameter.var_type.clone(), .raise(IncompatibleTypes(parameter.var_type.clone(), value_type)));
value_type,
)));
} else { } else {
self.compile_expression(namespace, argument, symbols, registry)?; self.compile_expression(namespace, argument, symbols, registry)?;
break; break;

View file

@ -1,6 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::value::Value; use crate::value::{Value, string};
use crate::{compile, run}; use crate::{compile, run};
use chrono::DateTime; use chrono::DateTime;
@ -21,23 +21,20 @@ mod tests {
#[test] #[test]
fn literal_string() { fn literal_string() {
assert_eq!(run(r#""a""#), Ok(Value::String("a".into()))); assert_eq!(run(r#""a""#), Ok(string("a")));
} }
#[test] #[test]
fn literal_list() { fn literal_list() {
assert_eq!( assert_eq!(
run(r#"["abc","def"]"#), run(r#"["abc","def"]"#),
Ok(Value::List(vec![ Ok(Value::List(vec![string("abc"), string("def")]))
Value::String("abc".into()),
Value::String("def".into())
]))
); );
} }
#[test] #[test]
fn index_in_list_literal() { fn index_in_list_literal() {
assert_eq!(run(r#"["abc","def"][1]"#), Ok(Value::String("def".into()))) assert_eq!(run(r#"["abc","def"][1]"#), Ok(string("def")))
} }
#[test] #[test]
@ -45,7 +42,7 @@ mod tests {
assert_eq!( assert_eq!(
run(r#"let a:list = ["abc","def"] run(r#"let a:list = ["abc","def"]
a[1]"#), a[1]"#),
Ok(Value::String("def".into())) Ok(string("def"))
) )
} }
@ -118,7 +115,7 @@ a"#),
run(r#"fn add_hello(name: string) -> string: run(r#"fn add_hello(name: string) -> string:
"Hello " + name "Hello " + name
add_hello("world")"#), add_hello("world")"#),
Ok(Value::String("Hello world".to_string())) Ok(string("Hello world"))
); );
} }
@ -169,11 +166,11 @@ p"#);
let result = result.unwrap(); let result = result.unwrap();
if let Value::Map(map) = result { if let Value::Map(map) = result {
assert_eq!( assert_eq!(
map.get(&Value::String("name".to_string())).unwrap(), map.get(&string("name")).unwrap(),
&Value::String("Dent".to_string()) &string("Dent")
); );
assert_eq!( assert_eq!(
map.get(&Value::String("age".to_string())).unwrap(), map.get(&string("age")).unwrap(),
&Value::I64(40) &Value::I64(40)
); );
} }
@ -187,8 +184,8 @@ m"#);
let result = result.unwrap(); let result = result.unwrap();
if let Value::Map(map) = result { if let Value::Map(map) = result {
assert_eq!( assert_eq!(
map.get(&Value::String("name".to_string())).unwrap(), map.get(&string("name")).unwrap(),
&Value::String("Dent".to_string()) &string("Dent")
); );
} }
} }
@ -216,17 +213,17 @@ m["name"]"#);
#[test] #[test]
fn add_strings() { fn add_strings() {
assert_eq!(run(r#""a"+"b""#), Ok(Value::String("ab".into()))); assert_eq!(run(r#""a"+"b""#), Ok(string("ab")));
} }
#[test] #[test]
fn add_string_and_int() { fn add_string_and_int() {
assert_eq!(run(r#""a"+42"#), Ok(Value::String("a42".into()))); assert_eq!(run(r#""a"+42"#), Ok(string("a42")));
} }
#[test] #[test]
fn add_string_and_bool() { fn add_string_and_bool() {
assert_eq!(run(r#""a"+false"#), Ok(Value::String("afalse".into()))); assert_eq!(run(r#""a"+false"#), Ok(string("afalse")));
} }
#[test] #[test]
@ -259,20 +256,25 @@ date"#),
} }
#[test] #[test]
fn string_reverse(){ fn string_reverse() {
assert_eq!(run(r#""abc".reverse()"#), Ok(Value::String("cba".into()))); assert_eq!(run(r#""abc".reverse()"#), Ok(string("cba")));
} }
#[test] #[test]
fn string_to_upper(){ fn string_to_upper() {
assert_eq!(run(r#""abc".to_uppercase()"#), Ok(Value::String("ABC".into()))); assert_eq!(run(r#""abc".to_uppercase()"#), Ok(string("ABC")));
} }
#[test] #[test]
fn string_len(){ fn string_len() {
assert_eq!(run(r#""abc".len()"#), Ok(Value::I64(3))); assert_eq!(run(r#""abc".len()"#), Ok(Value::I64(3)));
} }
#[test]
fn string_replace() {
assert_eq!(run(r#""Hello".replace_all("l","p")"#), Ok(string("Heppo")));
}
// #[test] // #[test]
// fn package() { // fn package() {
// assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));

View file

@ -83,6 +83,8 @@ pub enum RuntimeError {
FunctionNotFound(String), FunctionNotFound(String),
#[error("The number of of arguments for {0} is not correct. Should be {1}, got {2}")] #[error("The number of of arguments for {0} is not correct. Should be {1}, got {2}")]
IllegalArgumentsException(String,usize,usize), IllegalArgumentsException(String,usize,usize),
#[error("{0}")]
IllegalArgumentException(String),
#[error("Expected {0}")] #[error("Expected {0}")]
ExpectedType(String), ExpectedType(String),
} }

View file

@ -194,7 +194,6 @@ impl Vm {
let function_type_index = self.read(chunk); let function_type_index = self.read(chunk);
let receiver_type_name = chunk.constants[function_type_index].to_string(); let receiver_type_name = chunk.constants[function_type_index].to_string();
let receiver = self.pop();
let num_args = self.read(chunk); let num_args = self.read(chunk);
let mut args = vec![]; let mut args = vec![];
for _ in 0..num_args { for _ in 0..num_args {
@ -202,6 +201,7 @@ impl Vm {
args.push(arg); args.push(arg);
} }
args.reverse(); args.reverse();
let receiver = self.pop();
let return_value = crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?; let return_value = crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?;
self.push(return_value); self.push(return_value);
} }