From 0945d385d0fe6afeecae19dbc76f6df198a9f7f1 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 10 Nov 2025 15:10:36 +0100 Subject: [PATCH] object instantiation --- src/ast_compiler.rs | 70 +++++++++++++------------------------ src/bytecode_compiler.rs | 75 ++++++++++++++++++++++++++-------------- src/chunk.rs | 2 +- src/compiler_tests.rs | 37 ++++++++++---------- src/errors.rs | 2 ++ src/file_watch.rs | 2 -- src/lib.rs | 1 + src/symbol_builder.rs | 16 ++++----- src/value.rs | 8 ++--- src/vm.rs | 23 +++++++++--- 10 files changed, 127 insertions(+), 109 deletions(-) diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 15f3c8e..b500014 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -2,17 +2,15 @@ use crate::ast_compiler::Expression::{ FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable, }; use crate::errors::CompilerError::{ - self, Expected, ParseError, TooManyParameters, UndeclaredVariable, UnexpectedIndent, - UninitializedVariable, + self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable, }; use crate::errors::CompilerErrorAtLine; use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::tokens::TokenType::{ - Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn, - Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, - LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, - Print, RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, - StringType, True, U32, U64, Unknown, UnsignedInteger, + Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, + GreaterEqual, GreaterGreater, Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen, + Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, + RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, Unknown, }; use crate::tokens::{Token, TokenType}; use crate::value::Value; @@ -125,7 +123,7 @@ impl AstCompiler { } else if self.match_token(vec![Let]) { self.let_declaration(symbol_table) } else if self.match_token(vec![Object]) { - self.object_declaration() + self.object_declaration(symbol_table) } else if self.match_token(vec![TokenType::Pipe]) { self.guard_declaration(symbol_table) } else { @@ -194,7 +192,10 @@ impl AstCompiler { Err(self.raise(Expected("unimplemented"))) } - fn object_declaration(&mut self) -> Result { + fn object_declaration( + &mut self, + symbol_table: &mut HashMap, + ) -> Result { let type_name = self.consume(Identifier, Expected("object name."))?; self.consume(Colon, Expected("':' after object name."))?; self.consume(Eol, Expected("end of line."))?; @@ -202,7 +203,6 @@ impl AstCompiler { let mut fields = vec![]; let expected_indent = self.indent.last().unwrap() + 1; - // self.indent.push(expected_indent); let mut done = false; while !done && !self.match_token(vec![Eof]) { for _ in 0..expected_indent { @@ -228,6 +228,16 @@ impl AstCompiler { } } self.consume(Eol, Expected("end of line."))?; + + let type_name_as_str = type_name.lexeme.clone(); + symbol_table.insert( + type_name_as_str.clone(), + Symbol::Object { + name: type_name_as_str.clone(), + fields: fields.clone(), + }, + ); // name name name + Ok(Statement::ObjectStmt { name: type_name, fields, @@ -518,7 +528,7 @@ impl AstCompiler { operand: Expression, index: Expression, ) -> Result { - let get = (match &operand { + let get = match &operand { Expression::Map { .. } => MapGet { map: Box::new(operand), key: Box::new(index), @@ -541,7 +551,7 @@ impl AstCompiler { } }, _ => return Err(self.raise(CompilerError::IllegalTypeToIndex("Unknown".to_string()))), - }); + }; self.consume(RightBracket, Expected("']' after index."))?; Ok(get) } @@ -634,14 +644,14 @@ impl AstCompiler { Expression::Literal { line: self.peek().line, literaltype: DateTime, - value: Value::DateTime( + value: Value::DateTime(Box::new( chrono::DateTime::parse_from_str( &self.previous().lexeme, "%Y-%m-%d %H:%M:%S%.3f %z", ) .map_err(|_| self.raise(ParseError(self.previous().lexeme.clone())))? .into(), - ), + )), } } else if self.match_token(vec![LeftParen]) { let expr = self.expression(symbol_table)?; @@ -653,38 +663,11 @@ impl AstCompiler { } else { let token = self.advance().clone(); debug!("{:?}", token); - // function call? if self.match_token(vec![LeftParen]) { self.function_call(token.clone(), symbol_table)? } else if self.match_token(vec![Colon]) { self.named_parameter(&token, symbol_table)? } else { - // } else if self.check(Dot) { - // chain of variable or function lookups? - // let mut name = "/".to_string(); - // name.push_str(&self.previous().lexeme); - // while self.match_token(vec![Dot]) { - // name.push_str("/"); - // name.push_str(&self.peek().lexeme); - // self.advance(); - // } - // chained function call? - // if self.match_token(vec![LeftParen]) { - // self.function_call(name())? - // } else { - // empty line - // return if self.match_token(vec![Eol, Eof]) { - // Ok(Expression::Literal { - // value: Value::Void, - // literaltype: Object, - // line: token.line, - // }) - // } else { - // Err(self.raise(UndeclaredVariable(token.lexeme.clone()))) - // }; - // } - // } else { - // none of the above, must be a variable lookup self.variable_lookup(&token, symbol_table)? } }) @@ -939,10 +922,6 @@ pub enum Expression { Stop { line: usize, }, - // PathMatch { - // line: usize, - // condition: Box, - // }, NamedParameter { line: usize, name: Token, @@ -973,7 +952,6 @@ impl Expression { Variable { line, .. } => *line, FunctionCall { line, .. } => *line, Stop { line } => *line, - // Expression::PathMatch { line, .. } => *line, NamedParameter { line, .. } => *line, MapGet { .. } => 0, ListGet { .. } => 0, diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index 79fb24d..796cb00 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -1,5 +1,5 @@ use crate::ast_compiler::Expression::NamedParameter; -use crate::ast_compiler::{Expression, Function, Statement}; +use crate::ast_compiler::{Expression, Function, Parameter, Statement}; use crate::chunk::Chunk; use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::symbol_builder::{Symbol, calculate_type, infer_type}; @@ -183,29 +183,29 @@ impl Compiler { .find_constant(&name) .unwrap_or_else(|| self.chunk.add_constant(Value::String(name.to_string()))); let function = symbols.get(name); - if let Some(Symbol::Function { parameters, .. }) = function { - for parameter in parameters { - for argument in arguments { - if let NamedParameter { name, .. } = argument { - if name.lexeme == parameter.name.lexeme { - self.compile_expression( - namespace, argument, symbols, registry, - )?; - break; - } - } else { - self.compile_expression(namespace, argument, symbols, registry)?; - break; - } - } + match function { + Some(Symbol::Function { parameters, .. }) => { + self.get_arguments_in_order( + namespace, symbols, registry, arguments, parameters, + )?; + + self.emit_bytes(OP_CALL, name_index as u16); + self.emit_byte(arguments.len() as u16); + } + // constructor function + Some(Symbol::Object { fields, .. }) => { + self.get_arguments_in_order( + namespace, symbols, registry, arguments, fields, + )?; + self.emit_bytes(OP_CALL, name_index as u16); + self.emit_byte(arguments.len() as u16); + } + _ => { + return Err(CompilerErrorAtLine::raise( + CompilerError::FunctionNotFound(name.to_string()), + 0, + )); } - self.emit_bytes(OP_CALL, name_index as u16); - self.emit_byte(arguments.len() as u16); - } else { - return Err(CompilerErrorAtLine::raise( - CompilerError::FunctionNotFound(name.to_string()), - 0, - )); } } Expression::Variable { name, line, .. } => { @@ -281,8 +281,7 @@ impl Compiler { } } Expression::Stop { .. } => {} - // Expression::PathMatch { line, .. } => {} - NamedParameter { .. } => {} + NamedParameter { value,.. } => {self.compile_expression(namespace, value, symbols, registry)?} Expression::ListGet { index, list } => { self.compile_expression(namespace, list, symbols, registry)?; self.compile_expression(namespace, index, symbols, registry)?; @@ -294,6 +293,32 @@ impl Compiler { Ok(()) } + // any unnamed parameters must be passed in order + // named parameters do not have to be passed in order, but they do need to be evaluated in the order of the called function/constructor + fn get_arguments_in_order( + &mut self, + namespace: &str, + symbols: &HashMap, + registry: &mut HashMap, + arguments: &Vec, + parameters: &Vec, + ) -> Result<(), CompilerErrorAtLine> { + for parameter in parameters { + for argument in arguments { + if let NamedParameter { name, .. } = argument { + if name.lexeme == parameter.name.lexeme { + self.compile_expression(namespace, argument, symbols, registry)?; + break; + } + } else { + self.compile_expression(namespace, argument, symbols, registry)?; + break; + } + } + } + Ok(()) + } + fn emit_byte(&mut self, byte: u16) { self.chunk.add(byte, self.current_line); } diff --git a/src/chunk.rs b/src/chunk.rs index 7cafa00..4c3a98d 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -15,7 +15,7 @@ pub struct Chunk { pub code: Vec, pub constants: Vec, lines: Vec, - object_defs: HashMap>, + pub(crate) object_defs: HashMap>, pub(crate) function_parameters: Vec, pub vars: Vec<(TokenType, String)> } diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index 03c1271..e225127 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -115,10 +115,9 @@ a"#), #[test] fn call_fn_with_args_returns_value() { assert_eq!( - run(r#" -fn add_hello(name: string) -> string: + run(r#"fn add_hello(name: string) -> string: "Hello " + name -add_hello("world")"#,), +add_hello("world")"#), Ok(Value::String("Hello world".to_string())) ); } @@ -133,20 +132,20 @@ object Person: assert!(r.is_ok()); // does nothing runtime } - // #[test] - // fn object_() { - // let r = run( - // r#" - // object Person: - // name: string - // - // let p = Person(name: "Sander") - // print p - // "#, - // ); - // println!("{:?}", r); - // assert!(r.is_ok()); - // } + #[test] + fn declare_and_instantiate_object() { + let r = run(r#" +object Person: + name: string + +let p = Person(name: "Sander") +p"#); + assert!(r.is_ok()); + assert_eq!( + r#"Person: [("name", String("Sander"))]"#, + format!("{}", r.unwrap().to_string()) + ); + } #[test] fn literal_map() { @@ -233,14 +232,14 @@ m["name"]"#); assert_eq!( run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100" date"#), - Ok(Value::DateTime( + Ok(Value::DateTime(Box::new( DateTime::parse_from_str( "2025-11-09 16:44:28.000 +0100", "%Y-%m-%d %H:%M:%S%.3f %z" ) .unwrap() .into() - )) + ))) ); } diff --git a/src/errors.rs b/src/errors.rs index 24eceee..a7c1e92 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -81,6 +81,8 @@ pub enum RuntimeError { Expected(&'static str, &'static str), #[error("Function {0} not found")] FunctionNotFound(String), + #[error("The number of of arguments for {0} is not correct. Should be {1}, got {2}")] + IllegalArgumentsException(String,usize,usize), } #[derive(Error, Debug, PartialEq)] diff --git a/src/file_watch.rs b/src/file_watch.rs index 956ef1c..fbe5971 100644 --- a/src/file_watch.rs +++ b/src/file_watch.rs @@ -1,9 +1,7 @@ use crate::chunk::Chunk; use crate::compile_sourcedir; -use log4rs::append::Append; use notify::{RecursiveMode, Watcher}; use std::collections::HashMap; -use std::hash::Hash; use std::path::Path; use std::sync::Arc; use std::sync::mpsc::channel; diff --git a/src/lib.rs b/src/lib.rs index 7ba6a6e..9988bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ pub fn compile(src: &str) -> Result, CrudLangError> { Ok(registry) } +#[cfg(test)] pub(crate) fn run(src: &str) -> Result { let tokens = scan(src)?; let mut symbol_table = HashMap::new(); diff --git a/src/symbol_builder.rs b/src/symbol_builder.rs index 018ae80..ddd130c 100644 --- a/src/symbol_builder.rs +++ b/src/symbol_builder.rs @@ -1,10 +1,10 @@ use crate::ast_compiler::{Expression, Parameter, Statement}; use crate::errors::CompilerError; -use crate::errors::CompilerError::{IncompatibleTypes, TypeError}; +use crate::errors::CompilerError::IncompatibleTypes; use crate::tokens::TokenType::{ Bool, DateTime, F32, F64, FloatingPoint, Greater, GreaterEqual, I32, I64, Integer, Less, - LessEqual, ListType, MapType, Minus, Object, 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; @@ -138,7 +138,7 @@ pub fn calculate_type( DateTime => DateTime, ListType => ListType, MapType => MapType, - Object => Object, + ObjectType(p) => ObjectType(p.clone()), _ => return Err(CompilerError::UnexpectedType(inferred_type.clone())), } }) @@ -219,10 +219,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap) -> Token Expression::Variable { var_type, .. } => var_type.clone(), Expression::FunctionCall { name, .. } => { let symbol = symbols.get(name); - if let Some(Symbol::Function { return_type, .. }) = symbol { - return_type.clone() - } else { - Unknown + match symbol { + Some(Symbol::Function { return_type, .. }) => return_type.clone(), + Some(Symbol::Object { name, .. }) => ObjectType(name.clone()), + _ => Unknown, } } Expression::Stop { .. } => TokenType::Unknown, diff --git a/src/value.rs b/src/value.rs index 518c416..99911a3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,8 +8,8 @@ use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub}; #[derive(Debug, Clone)] pub struct Object { - definition: String, - fields: Vec, + pub(crate) definition: String, + pub(crate) fields: Vec<(String,Value)>, } #[derive(Debug, Clone)] @@ -23,7 +23,7 @@ pub enum Value { String(String), Char(char), Bool(bool), - DateTime(DateTime), + DateTime(Box>), Enum, List(Vec), Map(HashMap), @@ -156,7 +156,7 @@ impl Into for bool { impl Into for DateTime { fn into(self) -> Value { - Value::DateTime(self) + Value::DateTime(Box::new(self)) } } diff --git a/src/vm.rs b/src/vm.rs index df76563..e515b34 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -2,10 +2,8 @@ use crate::chunk::Chunk; use crate::errors::RuntimeError::Something; use crate::errors::{RuntimeError, ValueError}; use crate::tokens::TokenType; -use crate::value::Value; +use crate::value::{Object, Value}; use arc_swap::Guard; -use axum::http::Uri; -use reqwest::get; use std::collections::HashMap; use std::sync::Arc; use tracing::debug; @@ -216,8 +214,25 @@ impl Vm { .registry .get(&function_name) .or_else(|| self.registry.get(&format!("{}/{}", context, function_name))); + if function_chunk.is_none() { - return Err(RuntimeError::FunctionNotFound(function_name)); + let constructor = chunk.object_defs.get(&function_name); + + if let Some(params) = constructor { + if params.len() != args.len() { + return Err(RuntimeError::IllegalArgumentsException(function_name, params.len(), args.len())); + } + + let mut fields = vec![]; + params.iter().zip(args.into_iter()).for_each(|(param, arg)| {fields.push((param.name.lexeme.clone(), arg))}); + let new_instance = Value::ObjectType(Box::new(Object { + definition: function_name, + fields, + })); + self.push(new_instance); + } else { + return Err(RuntimeError::FunctionNotFound(function_name)); + } } else { let result = interpret_function(function_chunk.unwrap(), args)?; self.push(result);