added support for builtin functions and added some for strings

This commit is contained in:
Shautvast 2025-11-10 18:12:09 +01:00
parent b83c4bb0cc
commit 1315b2878a
10 changed files with 198 additions and 38 deletions

View file

@ -1,6 +1,6 @@
object Person: object Person:
name: string name: string
fn get(path: string) -> string: // fn get(path: string) -> string:
let p = Person(name: path) // let p = Person(name: path)
service.add("hello", p.name) // service.add("hello", p.name)

View file

@ -1,5 +1,5 @@
use crate::ast_compiler::Expression::{ use crate::ast_compiler::Expression::{
FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable, FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable,
}; };
use crate::errors::CompilerError::{ use crate::errors::CompilerError::{
self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable, self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable,
@ -517,7 +517,7 @@ impl AstCompiler {
} else if self.match_token(vec![Dot]) { } else if self.match_token(vec![Dot]) {
let name = self.peek().clone(); let name = self.peek().clone();
self.advance(); self.advance();
self.field(expr, name) self.field_or_method(expr, name, symbol_table)
} else { } else {
Ok(expr) Ok(expr)
} }
@ -557,21 +557,33 @@ impl AstCompiler {
} }
// work in progress // work in progress
fn field( fn field_or_method(
&mut self, &mut self,
_operand: Expression, receiver: Expression,
index: Token, op: Token,
symbol_table: &mut HashMap<String, Symbol>,
) -> Result<Expression, CompilerErrorAtLine> { ) -> Result<Expression, CompilerErrorAtLine> {
Ok(FieldGet { if self.match_token(vec![LeftParen]) {
field: index.lexeme.clone(), 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( fn primary(
&mut self, &mut self,
symbol_table: &mut HashMap<String, Symbol>, symbol_table: &mut HashMap<String, Symbol>,
) -> Result<Expression, CompilerErrorAtLine> { ) -> Result<Expression, CompilerErrorAtLine> {
debug!("primary {:?}", self.peek());
Ok(if self.match_token(vec![LeftBracket]) { Ok(if self.match_token(vec![LeftBracket]) {
self.list(symbol_table)? self.list(symbol_table)?
} else if self.match_token(vec![LeftBrace]) { } else if self.match_token(vec![LeftBrace]) {
@ -755,6 +767,18 @@ impl AstCompiler {
name: Token, name: Token,
symbol_table: &mut HashMap<String, Symbol>, symbol_table: &mut HashMap<String, Symbol>,
) -> Result<Expression, CompilerErrorAtLine> { ) -> Result<Expression, CompilerErrorAtLine> {
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<String, Symbol>,
) -> Result<Vec<Expression>, CompilerErrorAtLine> {
let mut arguments = vec![]; let mut arguments = vec![];
while !self.match_token(vec![RightParen]) { while !self.match_token(vec![RightParen]) {
if arguments.len() >= 25 { if arguments.len() >= 25 {
@ -769,11 +793,7 @@ impl AstCompiler {
break; break;
} }
} }
Ok(FunctionCall { Ok(arguments)
line: self.peek().line,
name: name.lexeme.to_string(),
arguments,
})
} }
fn consume( fn consume(
@ -919,6 +939,12 @@ pub enum Expression {
name: String, name: String,
arguments: Vec<Expression>, arguments: Vec<Expression>,
}, },
MethodCall {
line: usize,
receiver: Box<Expression>,
method_name: String,
arguments: Vec<Expression>,
},
Stop { Stop {
line: usize, line: usize,
}, },
@ -936,6 +962,7 @@ pub enum Expression {
index: Box<Expression>, index: Box<Expression>,
}, },
FieldGet { FieldGet {
receiver: Box<Expression>,
field: String, field: String,
}, },
} }
@ -951,6 +978,7 @@ impl Expression {
Self::Map { line, .. } => *line, Self::Map { line, .. } => *line,
Variable { line, .. } => *line, Variable { line, .. } => *line,
FunctionCall { line, .. } => *line, FunctionCall { line, .. } => *line,
MethodCall {line,..} => *line,
Stop { line } => *line, Stop { line } => *line,
NamedParameter { line, .. } => *line, NamedParameter { line, .. } => *line,
MapGet { .. } => 0, MapGet { .. } => 0,

66
src/builtin_functions.rs Normal file
View file

@ -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<Value>) -> Result<Value, RuntimeError>;
type MethodMap = HashMap<String, MethodFn>;
type MethodTable = HashMap<String, MethodMap>;
const METHODS: LazyLock<MethodTable> = 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<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)
}
fn string_len(self_val: Value, _args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
match self_val {
Value::String(s) => {
Ok(s.chars().rev().collect::<String>().into())
}
_ => Err(RuntimeError::ExpectedType("string".to_string())),
}
}

View file

@ -6,12 +6,7 @@ use crate::symbol_builder::{Symbol, calculate_type, infer_type};
use crate::tokens::TokenType; use crate::tokens::TokenType;
use crate::tokens::TokenType::Unknown; use crate::tokens::TokenType::Unknown;
use crate::value::Value; use crate::value::Value;
use crate::vm::{ 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};
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 std::collections::HashMap; use std::collections::HashMap;
pub fn compile( 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, .. } => { Expression::Variable { name, line, .. } => {
let name_index = self.vars.get(name); let name_index = self.vars.get(name);
if let Some(name_index) = name_index { if let Some(name_index) = name_index {
@ -296,7 +316,13 @@ impl Compiler {
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(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 { } else {
self.compile_expression(namespace, argument, symbols, registry)?; self.compile_expression(namespace, argument, symbols, registry)?;
break; break;

View file

@ -258,10 +258,20 @@ 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(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] // #[test]
// fn package() { // fn package() {

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("Expected {0}")]
ExpectedType(String),
} }
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]

View file

@ -23,6 +23,7 @@ mod symbol_builder;
mod tokens; mod tokens;
mod value; mod value;
pub mod vm; pub mod vm;
mod builtin_functions;
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, CrudLangError> { pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, CrudLangError> {
let mut registry = HashMap::new(); let mut registry = HashMap::new();

View file

@ -34,10 +34,10 @@ async fn main() -> Result<(), CrudLangError> {
let args = Args::parse(); let args = Args::parse();
let source = args.source.unwrap_or("./source".to_string()); let source = args.source.unwrap_or("./source".to_string());
let registry = compile_sourcedir(&source)?; 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 { if args.watch {
crudlang::file_watch::start_watch_daemon(&source, swap.clone()); 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())?; axum::serve(listener, app).await.map_err(map_underlying())?;
Ok(())
} else { } else {
Err(Platform( println!("No source files found or compilation error");
"No source files found or compilation error".to_string(), if args.repl {
)) crudlang::repl::start(swap.clone())?;
} }
}
Ok(())
} }
#[derive(Clone)] #[derive(Clone)]
@ -111,7 +112,7 @@ async fn handle_any(
.await .await
{ {
Ok(value) => Ok(Json(value.to_string())), Ok(value) => Ok(Json(value.to_string())),
Err(e) => { Err(_) => {
// url checks out but function for method not found // url checks out but function for method not found
if state.registry.load().get(&format!("{}.main", component)).is_some() { if state.registry.load().get(&format!("{}.main", component)).is_some() {
Err(StatusCode::METHOD_NOT_ALLOWED) Err(StatusCode::METHOD_NOT_ALLOWED)

View file

@ -3,8 +3,8 @@ use crate::errors::CompilerError;
use crate::errors::CompilerError::IncompatibleTypes; use crate::errors::CompilerError::IncompatibleTypes;
use crate::tokens::TokenType::{ use crate::tokens::TokenType::{
Bool, DateTime, F32, F64, FloatingPoint, Greater, GreaterEqual, I32, I64, Integer, Less, Bool, DateTime, F32, F64, FloatingPoint, Greater, GreaterEqual, I32, I64, Integer, Less,
LessEqual, ListType, MapType, Minus, ObjectType, Plus, SignedInteger, StringType, U32, LessEqual, ListType, MapType, Minus, ObjectType, Plus, SignedInteger, StringType, U32, U64,
U64, Unknown, UnsignedInteger, Unknown, UnsignedInteger,
}; };
use crate::tokens::{Token, TokenType}; use crate::tokens::{Token, TokenType};
use log::debug; use log::debug;
@ -225,6 +225,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> Token
_ => Unknown, _ => Unknown,
} }
} }
Expression::MethodCall {
receiver,
..
} => infer_type(receiver, symbols),
Expression::Stop { .. } => TokenType::Unknown, Expression::Stop { .. } => TokenType::Unknown,
// Expression::PathMatch { .. } => TokenType::Unknown, // Expression::PathMatch { .. } => TokenType::Unknown,
Expression::NamedParameter { .. } => TokenType::Unknown, Expression::NamedParameter { .. } => TokenType::Unknown,

View file

@ -7,6 +7,7 @@ use arc_swap::Guard;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use tracing::debug; use tracing::debug;
use crate::builtin_functions::call_builtin;
pub struct Vm { pub struct Vm {
ip: usize, ip: usize,
@ -198,6 +199,26 @@ impl Vm {
self.push(list.get(index.cast_usize()?).cloned().unwrap()) 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 => { OP_CALL => {
let function_name_index = self.read(chunk); let function_name_index = self.read(chunk);
let num_args = 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_DEF_F64: u16 = 40;
pub const OP_ASSIGN: u16 = 41; pub const OP_ASSIGN: u16 = 41;
pub const OP_LIST_GET: u16 = 42; pub const OP_LIST_GET: u16 = 42;
pub const OP_CALL_BUILTIN: u16 = 43;