diff --git a/src/builtins/globals.rs b/src/builtins/globals.rs new file mode 100644 index 0000000..f135dda --- /dev/null +++ b/src/builtins/globals.rs @@ -0,0 +1,20 @@ +use crate::builtins::{FunctionMap, Signature, add}; +use crate::compiler::tokens::TokenType::DateTime; +use crate::errors::RuntimeError; +use crate::value::Value; +use std::collections::HashMap; +use std::sync::LazyLock; + +pub(crate) static GLOBAL_FUNCTIONS: LazyLock = LazyLock::new(|| { + let mut global_functions: FunctionMap = HashMap::new(); + let functions = &mut global_functions; + add(functions, "now", Signature::new(vec![], DateTime, now)); + + global_functions +}); + +fn now(_self_val: Value, _args: Vec) -> Result { + Ok(Value::DateTime(Box::new(chrono::DateTime::from( + chrono::Utc::now(), + )))) +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 4650280..03dd602 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,5 +1,6 @@ mod string; mod list; +pub(crate) mod globals; use crate::builtins::string::string_functions; use crate::errors::{CompilerError, RuntimeError}; @@ -35,10 +36,12 @@ impl Signature { } pub(crate) type FunctionFn = fn(Value, Vec) -> Result; +/// maps function names to the signature pub(crate) type FunctionMap = HashMap; +/// maps receiver type name to a function map pub(crate) type FunctionTable = HashMap; -static METHODS: LazyLock = LazyLock::new(|| { +static FUNCTIONS: LazyLock = LazyLock::new(|| { let mut table: FunctionTable = HashMap::new(); table.insert("string".to_string(), string_functions()); table.insert("list".to_string(), list_functions()); @@ -51,7 +54,7 @@ pub(crate) fn add(m: &mut FunctionMap, name: &str, method: Signature) { } pub(crate) fn lookup(type_name: &str, method_name: &str) -> Result<&'static Signature, CompilerError> { - METHODS + FUNCTIONS .get(type_name) .and_then(|methods| methods.get(method_name)) .ok_or_else(|| CompilerError::FunctionNotFound(format!("{}.{}", type_name, method_name))) diff --git a/src/compiler/assembly_pass.rs b/src/compiler/assembly_pass.rs index 0727dca..56c21a0 100644 --- a/src/compiler/assembly_pass.rs +++ b/src/compiler/assembly_pass.rs @@ -1,5 +1,10 @@ +use crate::builtins::globals::GLOBAL_FUNCTIONS; use crate::builtins::lookup; -use crate::compiler::assembly_pass::Op::{Add, And, Assign, BitAnd, BitOr, BitXor, Call, CallBuiltin, Constant, DefList, DefMap, Divide, Dup, Equal, Get, Goto, GotoIf, GotoIfNot, Greater, GreaterEqual, Less, LessEqual, ListGet, Multiply, Negate, Not, NotEqual, Or, Pop, Print, Return, Shr, Subtract}; +use crate::compiler::assembly_pass::Op::{ + Add, And, Assign, BitAnd, BitOr, BitXor, Call, CallBuiltin, Constant, DefList, DefMap, Divide, + Dup, Equal, Get, Goto, GotoIf, GotoIfNot, Greater, GreaterEqual, Less, LessEqual, ListGet, + Multiply, Negate, Not, NotEqual, Or, Pop, Print, Return, Shr, Subtract, +}; use crate::compiler::ast_pass::Expression::NamedParameter; use crate::compiler::ast_pass::{Expression, Function, Parameter, Statement}; use crate::compiler::tokens::TokenType; @@ -221,7 +226,7 @@ impl AsmPass { self.emit(Pop); self.compile_statements(then_branch, symbols, registry, namespace)?; self.emit(Goto(0)); - let goto_addr2 = self.chunk.code.len() - 1;// placeholder + let goto_addr2 = self.chunk.code.len() - 1; // placeholder self.chunk.code[goto_addr1] = GotoIfNot(self.chunk.code.len()); if else_branch.is_some() { self.compile_statements( @@ -231,7 +236,7 @@ impl AsmPass { namespace, )?; } - self.chunk.code[goto_addr2]= Op::Goto(self.chunk.code.len()); + self.chunk.code[goto_addr2] = Op::Goto(self.chunk.code.len()); } Statement::ForStatement { loop_var, @@ -291,17 +296,24 @@ impl AsmPass { namespace, symbols, registry, arguments, parameters, )?; - self.emit(Call(name_index,arguments.len())); + self.emit(Call(name_index, arguments.len())); } // constructor function Some(Symbol::Object { fields, .. }) => { self.get_arguments_in_order( namespace, symbols, registry, arguments, fields, )?; - self.emit(Call(name_index,arguments.len())); + self.emit(Call(name_index, arguments.len())); } + // maybe global function _ => { - return Err(self.raise(CompilerError::FunctionNotFound(name.to_string()))); + if let Some(fun) = GLOBAL_FUNCTIONS.get(name) { + self.emit(Call(name_index, fun.arity())); + } else { + return Err( + self.raise(CompilerError::FunctionNotFound(name.to_string())) + ); + } } } } @@ -338,11 +350,7 @@ impl AsmPass { arguments, &signature.parameters, )?; - self.emit(CallBuiltin( - name_index, - type_index, - arguments.len(), - )); + self.emit(CallBuiltin(name_index, type_index, arguments.len())); } Expression::Variable { name, .. } => { let name_index = self.vars.get(name); @@ -513,7 +521,7 @@ pub enum Op { Negate, Print, Return, - Call(usize,usize), + Call(usize, usize), And, Or, Not, diff --git a/src/compiler/ast_pass.rs b/src/compiler/ast_pass.rs index a7d89b9..5a56476 100644 --- a/src/compiler/ast_pass.rs +++ b/src/compiler/ast_pass.rs @@ -1,11 +1,6 @@ use crate::compiler::ast_pass::Expression::{ Assignment, FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable, }; -use crate::errors::CompilerError::{ - self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable, -}; -use crate::errors::CompilerErrorAtLine; -use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::compiler::tokens::TokenType::{ Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For, Greater, GreaterEqual, GreaterGreater, Identifier, If, In, Indent, Integer, LeftBrace, @@ -14,8 +9,13 @@ use crate::compiler::tokens::TokenType::{ True, U32, U64, Unknown, }; use crate::compiler::tokens::{Token, TokenType}; +use crate::errors::CompilerError::{ + self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable, +}; +use crate::errors::CompilerErrorAtLine; +use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::value::Value; -use crate::{Expr, Stmt, SymbolTable}; +use crate::{DATE_FORMAT_TIMEZONE, Expr, Stmt, SymbolTable}; use log::debug; use std::collections::HashMap; @@ -663,12 +663,9 @@ impl AstCompiler { line: self.peek().line, literaltype: 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(), + chrono::DateTime::parse_from_str(&self.previous().lexeme, DATE_FORMAT_TIMEZONE) + .map_err(|_| self.raise(ParseError(self.previous().lexeme.clone())))? + .into(), )), } } else if self.match_token(&[LeftParen]) { diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index 56f4c37..a77ae46 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { + use crate::compiler::{compile, run}; use crate::errors::CompilerError::IllegalArgumentsException; use crate::errors::CompilerErrorAtLine; - use crate::errors::TipiLangError::{Compiler, Runtime}; use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds}; + use crate::errors::TipiLangError::{Compiler, Runtime}; use crate::value::{Value, string}; use chrono::DateTime; - use crate::compiler::{compile, run}; + use crate::DATE_FORMAT_TIMEZONE; #[test] fn literal_int() { @@ -241,8 +242,7 @@ m["name"]"#); date"#), 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" + "2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE ) .unwrap() .into() @@ -391,7 +391,7 @@ else: } #[test] - fn inline_comment(){ + fn inline_comment() { assert_eq!(run(r#"// this is a comment"#), Ok(Value::Void)); } @@ -408,6 +408,15 @@ sum ); } + #[test] + fn global_function_call() { + let value = run(r#"now()"#); + assert!(value.is_ok()); + let value = value.unwrap(); + let date_time_string = value.to_string(); + assert!(DateTime::parse_from_str(&date_time_string, DATE_FORMAT_TIMEZONE).is_ok()); + } + // #[test] // fn package() { // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); diff --git a/src/lib.rs b/src/lib.rs index bd1436b..ea2233c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,4 +19,5 @@ pub(crate) type Expr = Result; pub(crate) type Stmt = Result; pub(crate) type AsmRegistry = HashMap; -pub const TIPI_EXT: &str = ".tp"; \ No newline at end of file +pub const TIPI_EXT: &str = ".tp"; +pub const DATE_FORMAT_TIMEZONE: &str = "%Y-%m-%d %H:%M:%S%.3f %z"; \ No newline at end of file diff --git a/src/value.rs b/src/value.rs index 463f9e4..fa4c8eb 100644 --- a/src/value.rs +++ b/src/value.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub}; +use crate::DATE_FORMAT_TIMEZONE; #[derive(Debug, Clone)] pub struct Object { @@ -186,7 +187,7 @@ impl Display for Value { Value::F32(v) => write!(f, "{}", v), Value::F64(v) => write!(f, "{}", v), Value::Char(v) => write!(f, "{}", v), - Value::DateTime(v) => write!(f, "{}", v), + Value::DateTime(v) => write!(f, "{}", v.format(DATE_FORMAT_TIMEZONE)), Value::Enum => write!(f, "enum"), Value::ObjectType(o) => write!(f, "{}: {:?}", o.definition, o.fields), Value::List(v) => write!(f, "{:?}", v), diff --git a/src/vm.rs b/src/vm.rs index 1c168ad..6de8641 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,12 +1,13 @@ +use crate::AsmRegistry; +use crate::builtins::globals::GLOBAL_FUNCTIONS; use crate::compiler::assembly_pass::{AsmChunk, Op}; +use crate::compiler::tokens::TokenType; use crate::errors::{RuntimeError, ValueError}; use crate::value::{Object, Value}; -use crate::{AsmRegistry}; use arc_swap::Guard; use std::collections::HashMap; use std::sync::Arc; use tracing::debug; -use crate::compiler::tokens::TokenType; pub async fn interpret_async( registry: Guard>>, @@ -60,7 +61,11 @@ impl Vm { } } - fn run_function(&mut self, chunk: &AsmChunk, mut args: Vec) -> Result { + fn run_function( + &mut self, + chunk: &AsmChunk, + mut args: Vec, + ) -> Result { // arguments -> locals for (_, name) in chunk.vars.iter() { self.local_vars.insert(name.clone(), args.remove(0)); @@ -130,7 +135,7 @@ impl Vm { list.reverse(); self.push(Value::List(list)); } - Op::Assign(var_index) =>{ + Op::Assign(var_index) => { let (var_type, name) = chunk.vars.get(*var_index).unwrap(); let value = self.pop(); let value = number(var_type, value)?; @@ -172,7 +177,9 @@ impl Vm { crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?; self.push(return_value); } - Op::Pop => {self.pop();} + Op::Pop => { + self.pop(); + } Op::Call(function_name_index, num_args) => { let mut args = vec![]; for _ in 0..*num_args { @@ -182,38 +189,42 @@ impl Vm { args.reverse(); let function_name = chunk.constants[*function_name_index].to_string(); - let function_chunk = self - .registry - .get(&function_name) - .or_else(|| self.registry.get(&format!("{}/{}", context, function_name))); - - if function_chunk.is_none() { - 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).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)); - } + if let Some(fun) = GLOBAL_FUNCTIONS.get(&function_name) { + let return_value = (fun.function)(Value::Void, args)?; + self.push(return_value); } else { - let result = interpret_function(function_chunk.unwrap(), args)?; - self.push(result); + let function_chunk = self.registry.get(&function_name).or_else(|| { + self.registry.get(&format!("{}/{}", context, function_name)) + }); + + if function_chunk.is_none() { + 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).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); + } } } Op::GotoIfNot(goto_addr) => { @@ -231,7 +242,7 @@ impl Vm { Op::Goto(goto_addr) => { self.ip = *goto_addr; } - Op::Dup =>{ + Op::Dup => { let value = self.pop(); self.push(value.clone()); self.push(value); @@ -298,4 +309,4 @@ fn value_map(strings: HashMap) -> HashMap { .into_iter() .map(|(k, v)| (Value::String(k.to_string()), Value::String(v.to_string()))) .collect() -} \ No newline at end of file +}