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.
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]]
name = "android_system_properties"
version = "0.1.5"
@ -1355,6 +1364,35 @@ dependencies = [
"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]]
name = "reqwest"
version = "0.12.24"
@ -1838,6 +1876,7 @@ dependencies = [
"log",
"log4rs",
"notify",
"regex",
"reqwest",
"serde",
"thiserror",

View file

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

View file

@ -146,7 +146,7 @@ impl AstCompiler {
self.query_guard_expr(symbol_table)
} else {
Err(self.raise(Expected("-> or ?")))
}
};
}
Ok(Stop {
line: self.peek().line,
@ -803,6 +803,19 @@ pub struct Parameter {
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)]
pub enum Expression {
Binary {

View file

@ -1,13 +1,39 @@
mod string;
use crate::builtins::string::string_methods;
use crate::errors::{CompilerError, RuntimeError};
use crate::tokens::TokenType;
use crate::value::Value;
use std::collections::HashMap;
use std::sync::LazyLock;
use crate::builtins::string::string_methods;
use crate::errors::RuntimeError;
use crate::ast_compiler::Parameter;
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 MethodMap = HashMap<String, MethodFn>;
pub(crate) type MethodMap = HashMap<String, Signature>;
pub(crate) type MethodTable = HashMap<String, MethodMap>;
static METHODS: LazyLock<MethodTable> = LazyLock::new(|| {
@ -16,21 +42,24 @@ static METHODS: LazyLock<MethodTable> = LazyLock::new(|| {
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);
}
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,
method_name: &str,
self_val: Value,
args: Vec<Value>,
) -> Result<Value, RuntimeError> {
METHODS
.get(type_name)
.and_then(|methods| methods.get(method_name))
.ok_or_else(|| RuntimeError::FunctionNotFound(format!("{}.{}",type_name, method_name)))?
(self_val, args)
(lookup(type_name,method_name).map_err(|e|RuntimeError::FunctionNotFound(e.to_string()))?.function)(self_val, args)
}
pub(crate) fn expected(expected_type: &str) -> RuntimeError {

View file

@ -1,16 +1,45 @@
use std::collections::HashMap;
use crate::builtins::{expected, insert, MethodMap};
use crate::builtins::{MethodMap, Parameter, Signature, add, expected};
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 {
let mut string_methods: MethodMap = HashMap::new();
let m = &mut string_methods;
insert(m, "len", string_len);
insert(m, "to_uppercase", string_to_uppercase);
insert(m, "to_lowercase", string_to_lowercase);
insert(m, "contains", string_contains);
insert(m, "reverse", string_reverse);
add(m, "len", Signature::new(vec![], U64, string_len));
add(
m,
"to_uppercase",
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
}
@ -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> {
match (self_val, args.first()) {
(Value::String(s), Some(Value::String(pat))) => {
Ok(bool(s.contains(pat.as_str())))
}
(Value::String(s), Some(Value::String(pat))) => Ok(bool(s.contains(pat.as_str()))),
_ => Err(expected_a_string()),
}
}
fn string_reverse(self_val: Value, _: Vec<Value>) -> Result<Value, RuntimeError> {
match self_val {
Value::String(s) => {
Ok(s.chars().rev().collect::<String>().into())
Value::String(s) => 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()),
}
}

View file

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

View file

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

View file

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

View file

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