calling functions in other source files

This commit is contained in:
Shautvast 2026-01-09 20:42:33 +01:00
parent 05d241b97a
commit f563a8048a
12 changed files with 101 additions and 77 deletions

View file

@ -1,2 +1,2 @@
fn get_all() -> list:
fn get_all() -> string:
db::get_all()

View file

@ -1,9 +1,2 @@
fn get() -> list:
service::get_all()
fn post(customer: Customer):
service.add(customer)
fn put(customer: Customer):
service.update(customer)

View file

@ -39,7 +39,7 @@ impl Signature {
}
pub(crate) fn has_varargs(&self) -> bool {
self.parameters.last().unwrap().name.lexeme == "varargs"
!self.parameters.is_empty() && self.parameters.last().unwrap().name.lexeme == "varargs"
}
}

View file

@ -16,6 +16,7 @@ use crate::value::Value;
use crate::{AsmRegistry, SymbolTable};
use std::collections::HashMap;
use std::ops::Deref;
use crate::compiler::namespace::Namespace;
pub fn compile(
qualified_name: Option<&str>,
@ -30,7 +31,7 @@ pub fn compile_function(
function: &Function,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
namespace: &str,
namespace: &Namespace,
) -> Result<AsmChunk, CompilerErrorAtLine> {
let fn_name = &function.name.lexeme;
let mut compiler = AsmPass::new(fn_name);
@ -53,7 +54,7 @@ pub fn compile_in_namespace(
) -> Result<(), CompilerErrorAtLine> {
let name = namespace.unwrap_or("main");
let mut compiler = AsmPass::new(name);
let chunk = compiler.compile(ast, symbols, registry, name)?;
let chunk = compiler.compile(ast, symbols, registry, &Namespace::new(name))?;
registry.insert(name.to_string(), chunk);
Ok(())
}
@ -136,7 +137,7 @@ impl AsmPass {
ast: &Vec<Statement>,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
namespace: &str,
namespace: &Namespace,
) -> Result<AsmChunk, CompilerErrorAtLine> {
self.compile_statements(ast, symbols, registry, namespace)?;
self.emit(Return);
@ -151,7 +152,7 @@ impl AsmPass {
ast: &Vec<Statement>,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
namespace: &str,
namespace: &Namespace,
) -> Result<(), CompilerErrorAtLine> {
for statement in ast {
self.compile_statement(statement, symbols, registry, namespace)?;
@ -165,7 +166,7 @@ impl AsmPass {
statement: &Statement,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
namespace: &str,
namespace: &Namespace,
) -> Result<(), CompilerErrorAtLine> {
self.current_line = statement.line();
match statement {
@ -193,7 +194,7 @@ impl AsmPass {
fn compile_expression(
&mut self,
namespace: &str,
namespace: &Namespace,
expression: &Expression,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
@ -258,8 +259,8 @@ impl AsmPass {
Expression::FunctionCall {
name, arguments, ..
} => {
let qname = format!("{}/{}", namespace, name);
let function = symbols.get(&qname).or(symbols.get(name));
let qname = format!("{}/{}", namespace.pop(), name.replace("::","/"));
let function = symbols.get(&qname);
match function {
Some(Symbol::Function { parameters, .. }) => {
let name_index = self.chunk.find_constant(&qname).unwrap_or_else(|| {
@ -496,7 +497,7 @@ impl AsmPass {
// 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,
namespace: &Namespace,
symbols: &SymbolTable,
registry: &mut AsmRegistry,
arguments: &[Expression],

View file

@ -1,8 +1,4 @@
use crate::builtins::globals::GLOBAL_FUNCTIONS;
use crate::compiler::ast_pass::Expression::{
Assignment, FieldGet, FunctionCall, IfElseExpression, IfExpression, LetExpression, ListGet,
MapGet, MethodCall, NamedParameter, Stop, Variable,
};
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,
@ -151,7 +147,7 @@ impl AstCompiler {
Err(self.error_at_line(Expected("-> or ?")))
};
}
Ok(Stop {
Ok(Expression::Stop {
line: self.peek().line,
})
}
@ -162,7 +158,7 @@ impl AstCompiler {
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
Ok(query_params)
} else {
Ok(Stop {
Ok(Expression::Stop {
line: self.peek().line,
})
}
@ -174,7 +170,7 @@ impl AstCompiler {
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
Ok(path_params)
} else {
Ok(Stop {
Ok(Expression::Stop {
line: self.peek().line,
})
}
@ -347,7 +343,7 @@ impl AstCompiler {
},
);
Ok(LetExpression {
Ok(Expression::LetExpression {
name: name_token,
var_type,
initializer: Box::new(initializer),
@ -391,8 +387,8 @@ impl AstCompiler {
if self.match_token(&[Equal]) {
let operator = self.previous().clone();
let right = self.comparison(symbol_table)?;
if let Variable { name, .. } = expr {
Ok(Assignment {
if let Expression::Variable { name, .. } = expr {
Ok(Expression::Assignment {
line: operator.line,
variable_name: name.to_string(),
value: Box::new(right),
@ -499,13 +495,13 @@ impl AstCompiler {
self.inc_indent();
Ok(IfElseExpression {
Ok(Expression::IfElseExpression {
condition: Box::new(condition),
then_branch,
else_branch: self.compile(symbol_table)?,
})
} else {
Ok(IfExpression {
Ok(Expression::IfExpression {
condition: Box::new(condition),
then_branch,
})
@ -534,22 +530,23 @@ impl AstCompiler {
}
}
// index into list
fn index(&mut self, operand: Expression, index: Expression) -> Expr {
let get = match &operand {
Expression::Map { .. } => MapGet {
Expression::Map { .. } => Expression::MapGet {
map: Box::new(operand),
key: Box::new(index),
},
Expression::List { .. } => ListGet {
Expression::List { .. } => Expression::ListGet {
list: Box::new(operand),
index: Box::new(index),
},
Variable { var_type, .. } => match var_type {
ListType => ListGet {
Expression::Variable { var_type, .. } => match var_type {
ListType => Expression::ListGet {
list: Box::new(operand),
index: Box::new(index),
},
MapType => MapGet {
MapType => Expression::MapGet {
map: Box::new(operand),
key: Box::new(index),
},
@ -578,7 +575,7 @@ impl AstCompiler {
) -> Expr {
if self.match_token(&[LeftParen]) {
let arguments = self.arguments(symbol_table)?;
Ok(MethodCall {
Ok(Expression::MethodCall {
receiver: Box::new(receiver.clone()),
method_name: op.lexeme,
arguments,
@ -586,7 +583,7 @@ impl AstCompiler {
})
} else {
// no test yet
Ok(FieldGet {
Ok(Expression::FieldGet {
receiver: Box::new(receiver.clone()),
field: op.lexeme.clone(),
})
@ -698,7 +695,7 @@ impl AstCompiler {
fn named_parameter(&mut self, name: &Token, symbol_table: &mut SymbolTable) -> Expr {
let value = self.expression(symbol_table)?;
let line = name.line;
Ok(NamedParameter {
Ok(Expression::NamedParameter {
name: name.clone(),
value: Box::new(value),
line,
@ -751,7 +748,7 @@ impl AstCompiler {
} else {
&Unknown
};
Ok(Variable {
Ok(Expression::Variable {
name: name.lexeme.to_string(),
var_type: var_type.clone(),
line: name.line,
@ -760,7 +757,7 @@ impl AstCompiler {
fn function_call(&mut self, name: Token, symbol_table: &mut SymbolTable) -> Expr {
let arguments = self.arguments(symbol_table)?;
Ok(FunctionCall {
Ok(Expression::FunctionCall {
line: self.peek().line,
name: name.lexeme.to_string(),
arguments,
@ -1020,19 +1017,19 @@ impl Expression {
Self::Range { line, .. } => *line,
Self::List { line, .. } => *line,
Self::Map { line, .. } => *line,
Variable { line, .. } => *line,
Assignment { line, .. } => *line,
FunctionCall { line, .. } => *line,
MethodCall { line, .. } => *line,
Stop { line } => *line,
NamedParameter { line, .. } => *line,
MapGet { .. } => 0,
ListGet { .. } => 0,
FieldGet { .. } => 0,
IfExpression { condition, .. } => condition.line(),
IfElseExpression { condition, .. } => condition.line(),
LetExpression { name, .. } => name.line,
Expression::ForStatement { loop_var, .. } => loop_var.line,
Self::Variable { line, .. } => *line,
Self::Assignment { line, .. } => *line,
Self::FunctionCall { line, .. } => *line,
Self::MethodCall { line, .. } => *line,
Self::Stop { line } => *line,
Self::NamedParameter { line, .. } => *line,
Self::MapGet { .. } => 0,
Self::ListGet { .. } => 0,
Self::FieldGet { .. } => 0,
Self::IfExpression { condition, .. } => condition.line(),
Self::IfElseExpression { condition, .. } => condition.line(),
Self::LetExpression { name, .. } => name.line,
Self::ForStatement { loop_var, .. } => loop_var.line,
}
}
}

View file

@ -2,7 +2,7 @@
mod tests {
use crate::DATE_FORMAT_TIMEZONE;
use crate::compiler::{compile, run};
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName};
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName, UndeclaredVariable};
use crate::errors::CompilerErrorAtLine;
use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds, NotSortable};
use crate::errors::TipiLangError::{Compiler, Runtime};
@ -474,20 +474,21 @@ else:
a"#)
.unwrap();
}
// #[test]
// fn package() {
// assert_eq!(run(r#"a::b.c()"#), Ok(Value::U32(48)));
// }
#[test]
fn guards() {
assert_eq!(
run(r#"fn get_all_users() -> list:
| /{uuid} -> service.get_by_uuid(uuid)?"#),
Ok(Value::Void)
);
fn package() {
assert_eq!(run(r#"a::b.c()"#), Err(Compiler(CompilerErrorAtLine {error: UndeclaredVariable("a::b".to_string()), line: 1 })));
}
// #[test]
// fn guards() {
// assert_eq!(
// run(r#"fn get_all_users() -> list:
// | /{uuid} -> service.get_by_uuid(uuid)?"#),
// Ok(Value::Void)
// );
// }
#[test]
fn query() {
let result =

View file

@ -11,9 +11,11 @@ pub mod scan_pass;
pub mod ast_pass;
pub mod tokens;
pub mod assembly_pass;
pub(crate) mod namespace;
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, AsmChunk>, TipiLangError> {
let mut asm_registry = AsmRegistry::new();
let mut symbol_table = HashMap::new();
for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path().to_str().unwrap();
@ -21,7 +23,6 @@ pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, AsmChunk>,
println!("-- Compiling {} -- ", path);
let source = fs::read_to_string(path).map_err(map_underlying())?;
let tokens = scan_pass::scan(&source)?;
let mut symbol_table = HashMap::new();
match ast_pass::compile(Some(path), tokens, &mut symbol_table) {
Ok(statements) => {
let path = path.strip_prefix(source_dir).unwrap().replace(TIPI_EXT, "");

27
src/compiler/namespace.rs Normal file
View file

@ -0,0 +1,27 @@
use std::fmt::Display;
pub(crate) struct Namespace {
elements: Vec<String>,
}
impl Namespace {
pub(crate) fn new(path: &str) -> Self {
Self {
elements: path.split('/').map(|path| path.to_string()).collect(),
}
}
pub(crate) fn pop(&self) -> Self{
let mut copy = self.elements.to_vec();
copy.pop();
Self{
elements: copy,
}
}
}
impl Display for Namespace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.elements.join("/"))
}
}

View file

@ -88,12 +88,9 @@ impl Scanner {
'#' => self.add_token(TokenType::Hash),
'+' => self.add_token(TokenType::Plus),
':' => {
let t = if self.match_next(':') {
TokenType::ColonColon
} else {
TokenType::Colon
};
self.add_token(t);
if !self.match_next(':') {
self.add_token(TokenType::Colon);
}
}
';' => println!("Warning: Ignoring semicolon at line {}", self.line),
'*' => self.add_token(TokenType::Star),
@ -189,7 +186,13 @@ impl Scanner {
}
fn identifier(&mut self) {
while is_alphanumeric(self.peek()) {
while is_alphanumeric(self.peek())
|| self.peek() == '_'
|| (self.peek() == ':' && self.peek_next() == ':')
{
if self.peek() == ':' && self.peek_next() == ':' {
self.advance();
}
self.advance();
}
let value: String = self.chars[self.start..self.current].iter().collect();

View file

@ -10,6 +10,7 @@ use std::io;
use std::io::Write;
use std::ops::Deref;
use std::sync::Arc;
use crate::compiler::namespace::Namespace;
pub fn start(registry: Arc<ArcSwap<HashMap<String, AsmChunk>>>) -> Result<(), TipiLangError> {
println!("REPL started -- Type ctrl-c to exit (both the repl and the server)");
@ -44,7 +45,7 @@ pub fn start(registry: Arc<ArcSwap<HashMap<String, AsmChunk>>>) -> Result<(), Ti
};
symbol_builder::build("", &ast, &mut symbol_table);
match asm_pass.compile(&ast, &symbol_table, &mut registry_copy, "") {
match asm_pass.compile(&ast, &symbol_table, &mut registry_copy, &Namespace::new("")) {
Ok(chunk) => {
registry_copy.insert("main".to_string(), chunk);
registry.store(Arc::new(registry_copy));

View file

@ -33,7 +33,7 @@ fn make_qname(path: &str, name: &Token) -> String {
if path.is_empty() {
name.lexeme.to_string()
} else {
format!("{}.{}", path, name.lexeme)
format!("{}/{}", path, name.lexeme)
}
}

View file

@ -13,7 +13,7 @@
"languages": [{
"id": "tipi",
"aliases": ["tipi-lang", "tipi"],
"extensions": [".tipi"],
"extensions": [".tp"],
"configuration": "./language-configuration.json"
}],
"grammars": [{