object instantiation

This commit is contained in:
Shautvast 2025-11-10 15:10:36 +01:00
parent 647ba186b1
commit 0945d385d0
10 changed files with 127 additions and 109 deletions

View file

@ -2,17 +2,15 @@ use crate::ast_compiler::Expression::{
FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable, FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable,
}; };
use crate::errors::CompilerError::{ use crate::errors::CompilerError::{
self, Expected, ParseError, TooManyParameters, UndeclaredVariable, UnexpectedIndent, self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable,
UninitializedVariable,
}; };
use crate::errors::CompilerErrorAtLine; use crate::errors::CompilerErrorAtLine;
use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::symbol_builder::{Symbol, calculate_type, infer_type};
use crate::tokens::TokenType::{ use crate::tokens::TokenType::{
Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater,
Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, GreaterEqual, GreaterGreater, Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen,
LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace,
Print, RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, Unknown,
StringType, True, U32, U64, Unknown, UnsignedInteger,
}; };
use crate::tokens::{Token, TokenType}; use crate::tokens::{Token, TokenType};
use crate::value::Value; use crate::value::Value;
@ -125,7 +123,7 @@ impl AstCompiler {
} else if self.match_token(vec![Let]) { } else if self.match_token(vec![Let]) {
self.let_declaration(symbol_table) self.let_declaration(symbol_table)
} else if self.match_token(vec![Object]) { } else if self.match_token(vec![Object]) {
self.object_declaration() self.object_declaration(symbol_table)
} else if self.match_token(vec![TokenType::Pipe]) { } else if self.match_token(vec![TokenType::Pipe]) {
self.guard_declaration(symbol_table) self.guard_declaration(symbol_table)
} else { } else {
@ -194,7 +192,10 @@ impl AstCompiler {
Err(self.raise(Expected("unimplemented"))) Err(self.raise(Expected("unimplemented")))
} }
fn object_declaration(&mut self) -> Result<Statement, CompilerErrorAtLine> { fn object_declaration(
&mut self,
symbol_table: &mut HashMap<String, Symbol>,
) -> Result<Statement, CompilerErrorAtLine> {
let type_name = self.consume(Identifier, Expected("object name."))?; let type_name = self.consume(Identifier, Expected("object name."))?;
self.consume(Colon, Expected("':' after object name."))?; self.consume(Colon, Expected("':' after object name."))?;
self.consume(Eol, Expected("end of line."))?; self.consume(Eol, Expected("end of line."))?;
@ -202,7 +203,6 @@ impl AstCompiler {
let mut fields = vec![]; let mut fields = vec![];
let expected_indent = self.indent.last().unwrap() + 1; let expected_indent = self.indent.last().unwrap() + 1;
// self.indent.push(expected_indent);
let mut done = false; let mut done = false;
while !done && !self.match_token(vec![Eof]) { while !done && !self.match_token(vec![Eof]) {
for _ in 0..expected_indent { for _ in 0..expected_indent {
@ -228,6 +228,16 @@ impl AstCompiler {
} }
} }
self.consume(Eol, Expected("end of line."))?; 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 { Ok(Statement::ObjectStmt {
name: type_name, name: type_name,
fields, fields,
@ -518,7 +528,7 @@ impl AstCompiler {
operand: Expression, operand: Expression,
index: Expression, index: Expression,
) -> Result<Expression, CompilerErrorAtLine> { ) -> Result<Expression, CompilerErrorAtLine> {
let get = (match &operand { let get = match &operand {
Expression::Map { .. } => MapGet { Expression::Map { .. } => MapGet {
map: Box::new(operand), map: Box::new(operand),
key: Box::new(index), key: Box::new(index),
@ -541,7 +551,7 @@ impl AstCompiler {
} }
}, },
_ => return Err(self.raise(CompilerError::IllegalTypeToIndex("Unknown".to_string()))), _ => return Err(self.raise(CompilerError::IllegalTypeToIndex("Unknown".to_string()))),
}); };
self.consume(RightBracket, Expected("']' after index."))?; self.consume(RightBracket, Expected("']' after index."))?;
Ok(get) Ok(get)
} }
@ -634,14 +644,14 @@ impl AstCompiler {
Expression::Literal { Expression::Literal {
line: self.peek().line, line: self.peek().line,
literaltype: DateTime, literaltype: DateTime,
value: Value::DateTime( value: Value::DateTime(Box::new(
chrono::DateTime::parse_from_str( chrono::DateTime::parse_from_str(
&self.previous().lexeme, &self.previous().lexeme,
"%Y-%m-%d %H:%M:%S%.3f %z", "%Y-%m-%d %H:%M:%S%.3f %z",
) )
.map_err(|_| self.raise(ParseError(self.previous().lexeme.clone())))? .map_err(|_| self.raise(ParseError(self.previous().lexeme.clone())))?
.into(), .into(),
), )),
} }
} else if self.match_token(vec![LeftParen]) { } else if self.match_token(vec![LeftParen]) {
let expr = self.expression(symbol_table)?; let expr = self.expression(symbol_table)?;
@ -653,38 +663,11 @@ impl AstCompiler {
} else { } else {
let token = self.advance().clone(); let token = self.advance().clone();
debug!("{:?}", token); debug!("{:?}", token);
// function call?
if self.match_token(vec![LeftParen]) { if self.match_token(vec![LeftParen]) {
self.function_call(token.clone(), symbol_table)? self.function_call(token.clone(), symbol_table)?
} else if self.match_token(vec![Colon]) { } else if self.match_token(vec![Colon]) {
self.named_parameter(&token, symbol_table)? self.named_parameter(&token, symbol_table)?
} else { } 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)? self.variable_lookup(&token, symbol_table)?
} }
}) })
@ -939,10 +922,6 @@ pub enum Expression {
Stop { Stop {
line: usize, line: usize,
}, },
// PathMatch {
// line: usize,
// condition: Box<Expression>,
// },
NamedParameter { NamedParameter {
line: usize, line: usize,
name: Token, name: Token,
@ -973,7 +952,6 @@ impl Expression {
Variable { line, .. } => *line, Variable { line, .. } => *line,
FunctionCall { line, .. } => *line, FunctionCall { line, .. } => *line,
Stop { line } => *line, Stop { line } => *line,
// Expression::PathMatch { line, .. } => *line,
NamedParameter { line, .. } => *line, NamedParameter { line, .. } => *line,
MapGet { .. } => 0, MapGet { .. } => 0,
ListGet { .. } => 0, ListGet { .. } => 0,

View file

@ -1,5 +1,5 @@
use crate::ast_compiler::Expression::NamedParameter; 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::chunk::Chunk;
use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::errors::{CompilerError, CompilerErrorAtLine};
use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::symbol_builder::{Symbol, calculate_type, infer_type};
@ -183,29 +183,29 @@ impl Compiler {
.find_constant(&name) .find_constant(&name)
.unwrap_or_else(|| self.chunk.add_constant(Value::String(name.to_string()))); .unwrap_or_else(|| self.chunk.add_constant(Value::String(name.to_string())));
let function = symbols.get(name); let function = symbols.get(name);
if let Some(Symbol::Function { parameters, .. }) = function { match function {
for parameter in parameters { Some(Symbol::Function { parameters, .. }) => {
for argument in arguments { self.get_arguments_in_order(
if let NamedParameter { name, .. } = argument { namespace, symbols, registry, arguments, parameters,
if name.lexeme == parameter.name.lexeme { )?;
self.compile_expression(
namespace, argument, symbols, registry, self.emit_bytes(OP_CALL, name_index as u16);
)?; self.emit_byte(arguments.len() as u16);
break; }
} // constructor function
} else { Some(Symbol::Object { fields, .. }) => {
self.compile_expression(namespace, argument, symbols, registry)?; self.get_arguments_in_order(
break; 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, .. } => { Expression::Variable { name, line, .. } => {
@ -281,8 +281,7 @@ impl Compiler {
} }
} }
Expression::Stop { .. } => {} Expression::Stop { .. } => {}
// Expression::PathMatch { line, .. } => {} NamedParameter { value,.. } => {self.compile_expression(namespace, value, symbols, registry)?}
NamedParameter { .. } => {}
Expression::ListGet { index, list } => { Expression::ListGet { index, list } => {
self.compile_expression(namespace, list, symbols, registry)?; self.compile_expression(namespace, list, symbols, registry)?;
self.compile_expression(namespace, index, symbols, registry)?; self.compile_expression(namespace, index, symbols, registry)?;
@ -294,6 +293,32 @@ impl Compiler {
Ok(()) 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<String, Symbol>,
registry: &mut HashMap<String, Chunk>,
arguments: &Vec<Expression>,
parameters: &Vec<Parameter>,
) -> 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) { fn emit_byte(&mut self, byte: u16) {
self.chunk.add(byte, self.current_line); self.chunk.add(byte, self.current_line);
} }

View file

@ -15,7 +15,7 @@ pub struct Chunk {
pub code: Vec<u16>, pub code: Vec<u16>,
pub constants: Vec<Value>, pub constants: Vec<Value>,
lines: Vec<usize>, lines: Vec<usize>,
object_defs: HashMap<String, Vec<Parameter>>, pub(crate) object_defs: HashMap<String, Vec<Parameter>>,
pub(crate) function_parameters: Vec<Parameter>, pub(crate) function_parameters: Vec<Parameter>,
pub vars: Vec<(TokenType, String)> pub vars: Vec<(TokenType, String)>
} }

View file

@ -115,10 +115,9 @@ a"#),
#[test] #[test]
fn call_fn_with_args_returns_value() { fn call_fn_with_args_returns_value() {
assert_eq!( assert_eq!(
run(r#" run(r#"fn add_hello(name: string) -> string:
fn add_hello(name: string) -> string:
"Hello " + name "Hello " + name
add_hello("world")"#,), add_hello("world")"#),
Ok(Value::String("Hello world".to_string())) Ok(Value::String("Hello world".to_string()))
); );
} }
@ -133,20 +132,20 @@ object Person:
assert!(r.is_ok()); // does nothing runtime assert!(r.is_ok()); // does nothing runtime
} }
// #[test] #[test]
// fn object_() { fn declare_and_instantiate_object() {
// let r = run( let r = run(r#"
// r#" object Person:
// object Person: name: string
// name: string
// let p = Person(name: "Sander")
// let p = Person(name: "Sander") p"#);
// print p assert!(r.is_ok());
// "#, assert_eq!(
// ); r#"Person: [("name", String("Sander"))]"#,
// println!("{:?}", r); format!("{}", r.unwrap().to_string())
// assert!(r.is_ok()); );
// } }
#[test] #[test]
fn literal_map() { fn literal_map() {
@ -233,14 +232,14 @@ m["name"]"#);
assert_eq!( assert_eq!(
run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100" run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100"
date"#), date"#),
Ok(Value::DateTime( Ok(Value::DateTime(Box::new(
DateTime::parse_from_str( DateTime::parse_from_str(
"2025-11-09 16:44:28.000 +0100", "2025-11-09 16:44:28.000 +0100",
"%Y-%m-%d %H:%M:%S%.3f %z" "%Y-%m-%d %H:%M:%S%.3f %z"
) )
.unwrap() .unwrap()
.into() .into()
)) )))
); );
} }

View file

@ -81,6 +81,8 @@ pub enum RuntimeError {
Expected(&'static str, &'static str), Expected(&'static str, &'static str),
#[error("Function {0} not found")] #[error("Function {0} not found")]
FunctionNotFound(String), 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)] #[derive(Error, Debug, PartialEq)]

View file

@ -1,9 +1,7 @@
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::compile_sourcedir; use crate::compile_sourcedir;
use log4rs::append::Append;
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;

View file

@ -80,6 +80,7 @@ pub fn compile(src: &str) -> Result<HashMap<String, Chunk>, CrudLangError> {
Ok(registry) Ok(registry)
} }
#[cfg(test)]
pub(crate) fn run(src: &str) -> Result<Value, CrudLangError> { pub(crate) fn run(src: &str) -> Result<Value, CrudLangError> {
let tokens = scan(src)?; let tokens = scan(src)?;
let mut symbol_table = HashMap::new(); let mut symbol_table = HashMap::new();

View file

@ -1,10 +1,10 @@
use crate::ast_compiler::{Expression, Parameter, Statement}; use crate::ast_compiler::{Expression, Parameter, Statement};
use crate::errors::CompilerError; use crate::errors::CompilerError;
use crate::errors::CompilerError::{IncompatibleTypes, TypeError}; 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, Object, Plus, SignedInteger, StringType, U32, U64, LessEqual, ListType, MapType, Minus, ObjectType, Plus, SignedInteger, StringType, U32,
Unknown, UnsignedInteger, U64, Unknown, UnsignedInteger,
}; };
use crate::tokens::{Token, TokenType}; use crate::tokens::{Token, TokenType};
use log::debug; use log::debug;
@ -138,7 +138,7 @@ pub fn calculate_type(
DateTime => DateTime, DateTime => DateTime,
ListType => ListType, ListType => ListType,
MapType => MapType, MapType => MapType,
Object => Object, ObjectType(p) => ObjectType(p.clone()),
_ => return Err(CompilerError::UnexpectedType(inferred_type.clone())), _ => return Err(CompilerError::UnexpectedType(inferred_type.clone())),
} }
}) })
@ -219,10 +219,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> Token
Expression::Variable { var_type, .. } => var_type.clone(), Expression::Variable { var_type, .. } => var_type.clone(),
Expression::FunctionCall { name, .. } => { Expression::FunctionCall { name, .. } => {
let symbol = symbols.get(name); let symbol = symbols.get(name);
if let Some(Symbol::Function { return_type, .. }) = symbol { match symbol {
return_type.clone() Some(Symbol::Function { return_type, .. }) => return_type.clone(),
} else { Some(Symbol::Object { name, .. }) => ObjectType(name.clone()),
Unknown _ => Unknown,
} }
} }
Expression::Stop { .. } => TokenType::Unknown, Expression::Stop { .. } => TokenType::Unknown,

View file

@ -8,8 +8,8 @@ use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Object { pub struct Object {
definition: String, pub(crate) definition: String,
fields: Vec<Value>, pub(crate) fields: Vec<(String,Value)>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -23,7 +23,7 @@ pub enum Value {
String(String), String(String),
Char(char), Char(char),
Bool(bool), Bool(bool),
DateTime(DateTime<Utc>), DateTime(Box<DateTime<Utc>>),
Enum, Enum,
List(Vec<Value>), List(Vec<Value>),
Map(HashMap<Value, Value>), Map(HashMap<Value, Value>),
@ -156,7 +156,7 @@ impl Into<Value> for bool {
impl Into<Value> for DateTime<Utc> { impl Into<Value> for DateTime<Utc> {
fn into(self) -> Value { fn into(self) -> Value {
Value::DateTime(self) Value::DateTime(Box::new(self))
} }
} }

View file

@ -2,10 +2,8 @@ use crate::chunk::Chunk;
use crate::errors::RuntimeError::Something; use crate::errors::RuntimeError::Something;
use crate::errors::{RuntimeError, ValueError}; use crate::errors::{RuntimeError, ValueError};
use crate::tokens::TokenType; use crate::tokens::TokenType;
use crate::value::Value; use crate::value::{Object, Value};
use arc_swap::Guard; use arc_swap::Guard;
use axum::http::Uri;
use reqwest::get;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use tracing::debug; use tracing::debug;
@ -216,8 +214,25 @@ impl Vm {
.registry .registry
.get(&function_name) .get(&function_name)
.or_else(|| self.registry.get(&format!("{}/{}", context, function_name))); .or_else(|| self.registry.get(&format!("{}/{}", context, function_name)));
if function_chunk.is_none() { 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 { } else {
let result = interpret_function(function_chunk.unwrap(), args)?; let result = interpret_function(function_chunk.unwrap(), args)?;
self.push(result); self.push(result);