From 1315b2878a26b2986b1a65ab5c66eb6c8c790c18 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 10 Nov 2025 18:12:09 +0100 Subject: [PATCH] added support for builtin functions and added some for strings --- source/hello/web.crud | 6 ++-- src/ast_compiler.rs | 56 +++++++++++++++++++++++++--------- src/builtin_functions.rs | 66 ++++++++++++++++++++++++++++++++++++++++ src/bytecode_compiler.rs | 40 +++++++++++++++++++----- src/compiler_tests.rs | 18 ++++++++--- src/errors.rs | 2 ++ src/lib.rs | 1 + src/main.rs | 17 ++++++----- src/symbol_builder.rs | 8 +++-- src/vm.rs | 22 ++++++++++++++ 10 files changed, 198 insertions(+), 38 deletions(-) create mode 100644 src/builtin_functions.rs diff --git a/source/hello/web.crud b/source/hello/web.crud index 70b30a4..100f0ad 100644 --- a/source/hello/web.crud +++ b/source/hello/web.crud @@ -1,6 +1,6 @@ object Person: name: string -fn get(path: string) -> string: - let p = Person(name: path) - service.add("hello", p.name) +// fn get(path: string) -> string: +// let p = Person(name: path) +// service.add("hello", p.name) diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index b500014..3207a68 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -1,5 +1,5 @@ use crate::ast_compiler::Expression::{ - FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable, + FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable, }; use crate::errors::CompilerError::{ self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable, @@ -517,7 +517,7 @@ impl AstCompiler { } else if self.match_token(vec![Dot]) { let name = self.peek().clone(); self.advance(); - self.field(expr, name) + self.field_or_method(expr, name, symbol_table) } else { Ok(expr) } @@ -557,21 +557,33 @@ impl AstCompiler { } // work in progress - fn field( + fn field_or_method( &mut self, - _operand: Expression, - index: Token, + receiver: Expression, + op: Token, + symbol_table: &mut HashMap, ) -> Result { - Ok(FieldGet { - field: index.lexeme.clone(), - }) + if self.match_token(vec![LeftParen]) { + let arguments = self.arguments(symbol_table)?; + Ok(MethodCall { + receiver: Box::new(receiver.clone()), + method_name: op.lexeme, + arguments, + line: op.line, + }) + } else { + // no test yet + Ok(FieldGet { + receiver: Box::new(receiver.clone()), + field: op.lexeme.clone(), + }) + } } fn primary( &mut self, symbol_table: &mut HashMap, ) -> Result { - debug!("primary {:?}", self.peek()); Ok(if self.match_token(vec![LeftBracket]) { self.list(symbol_table)? } else if self.match_token(vec![LeftBrace]) { @@ -755,6 +767,18 @@ impl AstCompiler { name: Token, symbol_table: &mut HashMap, ) -> Result { + let arguments = self.arguments(symbol_table)?; + Ok(FunctionCall { + line: self.peek().line, + name: name.lexeme.to_string(), + arguments, + }) + } + + fn arguments( + &mut self, + symbol_table: &mut HashMap, + ) -> Result, CompilerErrorAtLine> { let mut arguments = vec![]; while !self.match_token(vec![RightParen]) { if arguments.len() >= 25 { @@ -769,11 +793,7 @@ impl AstCompiler { break; } } - Ok(FunctionCall { - line: self.peek().line, - name: name.lexeme.to_string(), - arguments, - }) + Ok(arguments) } fn consume( @@ -919,6 +939,12 @@ pub enum Expression { name: String, arguments: Vec, }, + MethodCall { + line: usize, + receiver: Box, + method_name: String, + arguments: Vec, + }, Stop { line: usize, }, @@ -936,6 +962,7 @@ pub enum Expression { index: Box, }, FieldGet { + receiver: Box, field: String, }, } @@ -951,6 +978,7 @@ impl Expression { Self::Map { line, .. } => *line, Variable { line, .. } => *line, FunctionCall { line, .. } => *line, + MethodCall {line,..} => *line, Stop { line } => *line, NamedParameter { line, .. } => *line, MapGet { .. } => 0, diff --git a/src/builtin_functions.rs b/src/builtin_functions.rs new file mode 100644 index 0000000..e247725 --- /dev/null +++ b/src/builtin_functions.rs @@ -0,0 +1,66 @@ +use crate::value::Value; +use std::collections::HashMap; +use std::sync::LazyLock; +use crate::errors::RuntimeError; + +type MethodFn = fn(Value, Vec) -> Result; +type MethodMap = HashMap; +type MethodTable = HashMap; + +const METHODS: LazyLock = LazyLock::new(|| { + let mut table: MethodTable = HashMap::new(); + + let mut string_methods: MethodMap = HashMap::new(); + string_methods.insert("len".to_string(), string_len); + string_methods.insert("to_uppercase".to_string(), string_to_uppercase); + string_methods.insert("contains".to_string(), string_contains); + string_methods.insert("reverse".to_string(), string_reverse); + + table.insert("string".to_string(), string_methods); + + table +}); + +pub fn call_builtin( + type_name: &str, + method_name: &str, + self_val: Value, + args: Vec, +) -> Result { + METHODS + .get(type_name) + .and_then(|methods| methods.get(method_name)) + .ok_or_else(|| RuntimeError::FunctionNotFound(format!("{}.{}",type_name, method_name)))? + (self_val, args) +} + +fn string_len(self_val: Value, _args: Vec) -> Result { + match self_val { + Value::String(s) => Ok(Value::I64(s.len() as i64)), + _ => Err(RuntimeError::ExpectedType("string".to_string())), + } +} + +fn string_to_uppercase(self_val: Value, _args: Vec) -> Result { + match self_val { + Value::String(s) => Ok(Value::String(s.to_uppercase())), + _ => Err(RuntimeError::ExpectedType("string".to_string())), + } +} + +fn string_contains(self_val: Value, args: Vec) -> Result { + match (self_val, args.first()) { + (Value::String(s), Some(Value::String(pat))) => { + Ok(Value::Bool(s.contains(pat.as_str()))) + } + _ => Err(RuntimeError::ExpectedType("string".to_string())), + } +} +fn string_reverse(self_val: Value, _: Vec) -> Result { + match self_val { + Value::String(s) => { + Ok(s.chars().rev().collect::().into()) + } + _ => Err(RuntimeError::ExpectedType("string".to_string())), + } +} \ No newline at end of file diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index 60949e4..639cb6c 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -6,12 +6,7 @@ use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::tokens::TokenType; use crate::tokens::TokenType::Unknown; use crate::value::Value; -use crate::vm::{ - OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_LIST, - OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, - OP_LIST_GET, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, - OP_SUBTRACT, -}; +use crate::vm::{OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CALL_BUILTIN, OP_CONSTANT, OP_DEF_LIST, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_LIST_GET, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT}; use std::collections::HashMap; pub fn compile( @@ -193,6 +188,31 @@ impl Compiler { } } } + Expression::MethodCall { + receiver, + method_name, + arguments, + .. + } => { + 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 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![])?; + self.emit_byte(OP_CALL_BUILTIN); + self.emit_byte(name_index as u16); + self.emit_byte(type_index as u16); + self.emit_byte(arguments.len() as u16); + } Expression::Variable { name, line, .. } => { let name_index = self.vars.get(name); if let Some(name_index) = name_index { @@ -296,7 +316,13 @@ impl Compiler { if name.lexeme == parameter.name.lexeme { let value_type = infer_type(value, symbols); if parameter.var_type != value_type { - return Err(CompilerErrorAtLine::raise(CompilerError::IncompatibleTypes(parameter.var_type.clone(), value_type), argument.line())); + return Err(CompilerErrorAtLine::raise( + CompilerError::IncompatibleTypes( + parameter.var_type.clone(), + value_type, + ), + argument.line(), + )); } else { self.compile_expression(namespace, argument, symbols, registry)?; break; diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index bd2bf12..cd4f3b9 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -258,10 +258,20 @@ date"#), ); } - // #[test] - // fn string_reverse(){ - // assert_eq!(run(r#""abc".reverse()"#), Ok(Value::String("cba".into()))); - // } + #[test] + fn string_reverse(){ + assert_eq!(run(r#""abc".reverse()"#), Ok(Value::String("cba".into()))); + } + + #[test] + fn string_to_upper(){ + assert_eq!(run(r#""abc".to_uppercase()"#), Ok(Value::String("ABC".into()))); + } + + #[test] + fn string_len(){ + assert_eq!(run(r#""abc".len()"#), Ok(Value::I64(3))); + } // #[test] // fn package() { diff --git a/src/errors.rs b/src/errors.rs index a7c1e92..3231734 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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("Expected {0}")] + ExpectedType(String), } #[derive(Error, Debug, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 9988bc7..c615acf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ mod symbol_builder; mod tokens; mod value; pub mod vm; +mod builtin_functions; pub fn compile_sourcedir(source_dir: &str) -> Result, CrudLangError> { let mut registry = HashMap::new(); diff --git a/src/main.rs b/src/main.rs index 62dc998..db2e12f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,10 +34,10 @@ async fn main() -> Result<(), CrudLangError> { let args = Args::parse(); let source = args.source.unwrap_or("./source".to_string()); let registry = compile_sourcedir(&source)?; + let empty = registry.is_empty(); - - if !registry.is_empty() { - let swap = Arc::new(ArcSwap::from(Arc::new(registry))); + let swap = Arc::new(ArcSwap::from(Arc::new(registry))); + if !empty { if args.watch { crudlang::file_watch::start_watch_daemon(&source, swap.clone()); } @@ -63,12 +63,13 @@ async fn main() -> Result<(), CrudLangError> { } axum::serve(listener, app).await.map_err(map_underlying())?; - Ok(()) } else { - Err(Platform( - "No source files found or compilation error".to_string(), - )) + println!("No source files found or compilation error"); + if args.repl { + crudlang::repl::start(swap.clone())?; + } } + Ok(()) } #[derive(Clone)] @@ -111,7 +112,7 @@ async fn handle_any( .await { Ok(value) => Ok(Json(value.to_string())), - Err(e) => { + Err(_) => { // url checks out but function for method not found if state.registry.load().get(&format!("{}.main", component)).is_some() { Err(StatusCode::METHOD_NOT_ALLOWED) diff --git a/src/symbol_builder.rs b/src/symbol_builder.rs index ddd130c..9c1f27d 100644 --- a/src/symbol_builder.rs +++ b/src/symbol_builder.rs @@ -3,8 +3,8 @@ use crate::errors::CompilerError; use crate::errors::CompilerError::IncompatibleTypes; use crate::tokens::TokenType::{ Bool, DateTime, F32, F64, FloatingPoint, Greater, GreaterEqual, I32, I64, Integer, Less, - LessEqual, ListType, MapType, Minus, ObjectType, Plus, SignedInteger, StringType, U32, - U64, Unknown, UnsignedInteger, + LessEqual, ListType, MapType, Minus, ObjectType, Plus, SignedInteger, StringType, U32, U64, + Unknown, UnsignedInteger, }; use crate::tokens::{Token, TokenType}; use log::debug; @@ -225,6 +225,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap) -> Token _ => Unknown, } } + Expression::MethodCall { + receiver, + .. + } => infer_type(receiver, symbols), Expression::Stop { .. } => TokenType::Unknown, // Expression::PathMatch { .. } => TokenType::Unknown, Expression::NamedParameter { .. } => TokenType::Unknown, diff --git a/src/vm.rs b/src/vm.rs index 8a96f34..721b235 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,6 +7,7 @@ use arc_swap::Guard; use std::collections::HashMap; use std::sync::Arc; use tracing::debug; +use crate::builtin_functions::call_builtin; pub struct Vm { ip: usize, @@ -198,6 +199,26 @@ impl Vm { self.push(list.get(index.cast_usize()?).cloned().unwrap()) } } + OP_CALL_BUILTIN => { + let function_name_index = self.read(chunk); + let function_name = chunk.constants[function_name_index].to_string(); + 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 { + let arg = self.pop(); + args.push(arg); + } + args.reverse(); + + let return_value = call_builtin(&receiver_type_name, &function_name, receiver, args)?; + self.push(return_value); + } OP_CALL => { let function_name_index = self.read(chunk); let num_args = self.read(chunk); @@ -346,3 +367,4 @@ pub const OP_DEF_F32: u16 = 39; pub const OP_DEF_F64: u16 = 40; pub const OP_ASSIGN: u16 = 41; pub const OP_LIST_GET: u16 = 42; +pub const OP_CALL_BUILTIN: u16 = 43;