summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-19 10:13:51 +0200
committerPaul Buetow <paul@buetow.org>2026-02-19 10:13:51 +0200
commit2e81f599a0323e2025883bc1375bf438d1406733 (patch)
tree8083216541adfb688e47d1c2dfbf8010ef34f23a
parent3ad7831662b8f4c6ffd4666169b8edebc1391e36 (diff)
Implement function named arguments, ret, and multiple return values
- Add FuncDef struct to symbol.h/symbol.c: holds body token list, strdup'd param names, and param count; replaces the bare List* that SYM_FUNCTION used to store; funcdef_delete frees param strings and body list (tokens are GC-managed) - Add CONTROL_RET to ControlType so ret can propagate cleanly through the interpreter's control-flow stack - Update _func_decl to parse optional (p1, p2, ...) param list and store a FuncDef* instead of a raw List* - Implement case TT_RET in _control: clears intermediate stack values, evaluates comma-separated return expressions, sets CONTROL_RET - Propagate CONTROL_RET in all loop bodies (while/until, loop, do) without clearing it; while/until rescues return values from the temporary condition stack before it is destroyed - Update _term call site to support parenthesised func(arg1, arg2) syntax alongside the existing no-parens style for backward compat - Modify interpret_subprocess to merge the sub's stack into the parent when CONTROL_RET is active so return values survive teardown - Update function_process_self_defined SYM_FUNCTION case to pop and bind named args, run the body, then clear CONTROL_RET - Add -D_POSIX_C_SOURCE=200809L to Makefile CFLAGS for strdup under -std=c99 -pedantic - Add examples/func_args_ret.fy covering zero-arg, single-arg, two-arg, conditional ret, multiple return values, and old-style no-parens backward compat Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--Makefile3
-rw-r--r--docs/help.txt2
-rw-r--r--docs/stats.txt6
-rw-r--r--docs/version.txt2
-rw-r--r--examples/all-examples.txt46
-rw-r--r--examples/func_args_ret.fy45
-rw-r--r--src/build.h2
-rw-r--r--src/core/functions.c33
-rw-r--r--src/core/interpret.c102
-rw-r--r--src/core/interpret.h1
-rw-r--r--src/core/symbol.c26
-rw-r--r--src/core/symbol.h12
-rw-r--r--tags2
13 files changed, 261 insertions, 21 deletions
diff --git a/Makefile b/Makefile
index 99875d3..e2fcb7e 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,8 @@ SRCS=$(shell find ./src -name '*.c')
OBJS=$(SRCS:.c=.o)
CC?=cc
DEBUG=-g3 -ggdb3
-CFLAGS+=-c -Wall -std=c99 -pedantic $(DEBUG)
+# Enable POSIX extensions (e.g. strdup) before any system headers are seen
+CFLAGS+=-c -Wall -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L $(DEBUG)
LDADD+=
HEADER?=docs/header.txt
# Detect the OS name for updating build.h
diff --git a/docs/help.txt b/docs/help.txt
index 37ccbb1..481c057 100644
--- a/docs/help.txt
+++ b/docs/help.txt
@@ -1,4 +1,4 @@
-Fype Superalpha Build 9673
+Fype Superalpha Build 9676
(c) Paul C. Buetow (2005 - 2008) <fype@dev.buetow.org>
-e Executes given code string (see synopses)
-h Prints this help
diff --git a/docs/stats.txt b/docs/stats.txt
index d0571bc..087de31 100644
--- a/docs/stats.txt
+++ b/docs/stats.txt
@@ -1,6 +1,6 @@
make[1]: Entering directory '/home/paul/git/fype'
===> Num of C source files : 46
-===> Num of C source lines : 8344
-===> Num of Fype source examples : 16
-===> Num of Fype source lines : 419
+===> Num of C source lines : 8490
+===> Num of Fype source examples : 17
+===> Num of Fype source lines : 464
make[1]: Leaving directory '/home/paul/git/fype'
diff --git a/docs/version.txt b/docs/version.txt
index c825cd1..aa5c59c 100644
--- a/docs/version.txt
+++ b/docs/version.txt
@@ -1 +1 @@
-Fype Superalpha Build 9673
+Fype Superalpha Build 9676
diff --git a/examples/all-examples.txt b/examples/all-examples.txt
index 966fc52..6e02967 100644
--- a/examples/all-examples.txt
+++ b/examples/all-examples.txt
@@ -202,6 +202,52 @@ ifnot pid {
say "I am the child process";
}
+# Test: function named arguments, explicit ret, and multiple return values
+
+# zero-arg function with explicit return
+func answer() {
+ ret 42;
+}
+assert 42 == say answer();
+
+# single-arg function — factorial with a while loop and ret
+func factorial(n) {
+ my result = 1;
+ while n > 1 {
+ result = result * n;
+ decr n;
+ }
+ ret result;
+}
+assert 120 == say factorial(5);
+
+# two-arg function
+func add(a, b) {
+ ret a + b;
+}
+assert 8 == say add(3, 5);
+
+# conditional return inside if
+func absval(n) {
+ if n < 0 { ret neg n; }
+ ret n;
+}
+assert 5 == say absval(5);
+assert 5 == say absval(neg 5);
+
+# multiple return values — both land on the caller's stack
+func minmax(a, b) {
+ if a < b { ret a, b; }
+ ret b, a;
+}
+say minmax(3, 7);
+
+# old-style zero-arg function without parens still works
+func greet {
+ say "hello";
+}
+greet;
+
#*
* Examples of how to use functions
*#
diff --git a/examples/func_args_ret.fy b/examples/func_args_ret.fy
new file mode 100644
index 0000000..54088e8
--- /dev/null
+++ b/examples/func_args_ret.fy
@@ -0,0 +1,45 @@
+# Test: function named arguments, explicit ret, and multiple return values
+
+# zero-arg function with explicit return
+func answer() {
+ ret 42;
+}
+assert 42 == say answer();
+
+# single-arg function — factorial with a while loop and ret
+func factorial(n) {
+ my result = 1;
+ while n > 1 {
+ result = result * n;
+ decr n;
+ }
+ ret result;
+}
+assert 120 == say factorial(5);
+
+# two-arg function
+func add(a, b) {
+ ret a + b;
+}
+assert 8 == say add(3, 5);
+
+# conditional return inside if
+func absval(n) {
+ if n < 0 { ret neg n; }
+ ret n;
+}
+assert 5 == say absval(5);
+assert 5 == say absval(neg 5);
+
+# multiple return values — both land on the caller's stack
+func minmax(a, b) {
+ if a < b { ret a, b; }
+ ret b, a;
+}
+say minmax(3, 7);
+
+# old-style zero-arg function without parens still works
+func greet {
+ say "hello";
+}
+greet;
diff --git a/src/build.h b/src/build.h
index aac60de..9125e92 100644
--- a/src/build.h
+++ b/src/build.h
@@ -36,7 +36,7 @@
#ifndef BUILD_H
#define BUILD_H
-#define BUILDNR 9675
+#define BUILDNR 9680
#define OS_LINUX
#endif
diff --git a/src/core/functions.c b/src/core/functions.c
index 792065a..76b4eed 100644
--- a/src/core/functions.c
+++ b/src/core/functions.c
@@ -971,9 +971,38 @@ function_process_self_defined(Interpret *p_interpret, Token *p_token_ident) {
break;
case SYM_FUNCTION:
{
- List *p_list_token = symbol_get_val(p_symbol);
+ FuncDef *p_funcdef = (FuncDef*) symbol_get_val(p_symbol);
scope_up(p_interpret->p_scope);
- interpret_subprocess(p_interpret, p_list_token);
+
+ /* Bind each named parameter to its argument value.
+ * Args were pushed left-to-right so the last arg is on top (LIFO).
+ * Reverse-fill a temp array so index 0 = first param = first arg. */
+ if (p_funcdef->i_nparams > 0) {
+ Token **pp_args = malloc(
+ sizeof(Token*) * p_funcdef->i_nparams);
+
+ for (int i = p_funcdef->i_nparams - 1; i >= 0; --i)
+ pp_args[i] = stack_pop(p_interpret->p_stack);
+
+ ListIterator *p_iter = listiterator_new(p_funcdef->p_params);
+
+ for (int i = 0; i < p_funcdef->i_nparams; ++i) {
+ char *c_name = (char*) listiterator_next(p_iter);
+ Symbol *p_sym = symbol_new(SYM_VARIABLE, pp_args[i]);
+ scope_newset(p_interpret->p_scope, c_name, p_sym);
+ }
+
+ listiterator_delete(p_iter);
+ free(pp_args);
+ }
+
+ interpret_subprocess(p_interpret, p_funcdef->p_body);
+
+ /* Consume CONTROL_RET here; return value(s) are already on the stack
+ * (merged by interpret_subprocess when CONTROL_RET was active). */
+ if (p_interpret->ct == CONTROL_RET)
+ p_interpret->ct = CONTROL_NONE;
+
scope_down(p_interpret->p_scope);
}
NO_DEFAULT;
diff --git a/src/core/interpret.c b/src/core/interpret.c
index f19dec4..0911ef7 100644
--- a/src/core/interpret.c
+++ b/src/core/interpret.c
@@ -414,18 +414,42 @@ _func_decl(Interpret *p_interpret) {
_INTERPRET_ERROR("Symbol already defined", p_token_ident);
}
- List *p_list_proc = list_new();
+ List *p_list_params = list_new();
+ int i_nparams = 0;
+
+ /* Optional comma-separated parameter list: func name(p1, p2) { body }
+ * Without parens, zero params — old-style syntax remains valid. */
+ if (p_interpret->tt == TT_PARANT_L) {
+ _NEXT /* past '(' */
+ while (p_interpret->tt != TT_PARANT_R
+ && p_interpret->tt != TT_NONE) {
+ if (p_interpret->tt != TT_IDENT)
+ _INTERPRET_ERROR("Expected parameter name",
+ p_interpret->p_token);
+ list_add_back(p_list_params,
+ strdup(token_get_val(p_interpret->p_token)));
+ ++i_nparams;
+ _NEXT
+ if (p_interpret->tt == TT_COMMA) _NEXT /* skip comma */
+ }
+ _NEXT /* past ')' */
+ }
- if (_block_get(p_interpret, p_list_proc)) {
+ List *p_list_body = list_new();
- Symbol *p_symbol = symbol_new(SYM_FUNCTION, p_list_proc);
+ if (_block_get(p_interpret, p_list_body)) {
+ FuncDef *p_funcdef = funcdef_new(p_list_body, p_list_params,
+ i_nparams);
+ Symbol *p_symbol = symbol_new(SYM_FUNCTION, p_funcdef);
scope_newset(p_interpret->p_scope, token_get_val(p_token_ident),
p_symbol);
-
return (1);
}
- list_delete(p_list_proc);
+ /* Block parse failed; clean up params and body */
+ list_delete(p_list_body);
+ list_iterate(p_list_params, free);
+ list_delete(p_list_params);
}
return (0);
@@ -498,6 +522,22 @@ _control(Interpret *p_interpret) {
Token *p_token = p_interpret->p_token;
switch (p_interpret->tt) {
+ /* ret; — no return value (stack unchanged after clearing intermediates)
+ * ret expr; — single return value pushed to stack
+ * ret a, b; — multiple return values; all pushed left-to-right */
+ case TT_RET:
+ _NEXT /* past 'ret' */
+ /* Clear any intermediate values accumulated during the function body
+ * so only the explicit return expressions remain on the stack. */
+ stack_clear(p_interpret->p_stack);
+ while (p_interpret->tt != TT_SEMICOLON
+ && p_interpret->tt != TT_NONE) {
+ _expression_(p_interpret);
+ if (p_interpret->tt == TT_COMMA)
+ _NEXT /* past ',' between multiple return values */
+ }
+ p_interpret->ct = CONTROL_RET;
+ return (1);
/* break; — set the break flag; the statement loop in _program() will
* stop and the flag propagates up to the enclosing while/until. */
case TT_BREAK:
@@ -598,15 +638,24 @@ _control(Interpret *p_interpret) {
}
}
- /* Act on any break/next flag set during loop body execution.
+ /* Act on any break/next/ret flag set during loop body execution.
* break clears the flag and stops iteration; next clears it
- * and lets the loop re-evaluate the condition naturally. */
+ * and lets the loop re-evaluate the condition naturally.
+ * ret does not clear the flag so it propagates to the caller;
+ * the return value is rescued from cond_stack to p_stack_backup
+ * before the cond_stack is deleted below. */
if (p_interpret->ct == CONTROL_BREAK) {
p_interpret->ct = CONTROL_NONE;
b_flag = false;
} else if (p_interpret->ct == CONTROL_NEXT) {
p_interpret->ct = CONTROL_NONE;
/* b_flag stays true; condition re-evaluated next iteration */
+ } else if (p_interpret->ct == CONTROL_RET) {
+ /* Rescue any return values from cond_stack into the outer
+ * stack before cond_stack is destroyed at the end of the
+ * do-while iteration. */
+ stack_merge(p_stack_backup, p_interpret->p_stack);
+ b_flag = false;
}
} else {
@@ -649,6 +698,10 @@ _control(Interpret *p_interpret) {
} else if (p_interpret->ct == CONTROL_NEXT) {
p_interpret->ct = CONTROL_NONE;
/* skip the rest of the body; re-run from the top */
+ } else if (p_interpret->ct == CONTROL_RET) {
+ /* ret inside loop: propagate CONTROL_RET up to the enclosing
+ * function without clearing it; return values are on the stack. */
+ break;
}
}
@@ -693,7 +746,9 @@ _control(Interpret *p_interpret) {
interpret_subprocess(p_interpret, p_list_block);
scope_down(p_interpret->p_scope);
- /* Handle break/next before re-evaluating the condition */
+ /* Handle break/next/ret before re-evaluating the condition.
+ * For ret: stop the loop without touching the return value on
+ * p_interpret->p_stack; skip condition eval via continue. */
if (p_interpret->ct == CONTROL_BREAK) {
p_interpret->ct = CONTROL_NONE;
b_flag = false;
@@ -701,6 +756,11 @@ _control(Interpret *p_interpret) {
} else if (p_interpret->ct == CONTROL_NEXT) {
p_interpret->ct = CONTROL_NONE;
/* fall through to condition check */
+ } else if (p_interpret->ct == CONTROL_RET) {
+ /* ret inside do-loop: propagate CONTROL_RET; skip condition
+ * re-eval to avoid corrupting the stack with cond results. */
+ b_flag = false;
+ continue;
}
/* Re-evaluate condition using a temp stack/iterator over
@@ -980,8 +1040,21 @@ _term(Interpret *p_interpret) {
Stack *p_stack = p_interpret->p_stack;
p_interpret->p_stack = stack_new();
- _NEXT
- if (_expression_(p_interpret));
+ _NEXT /* advance past function name */
+ if (p_interpret->tt == TT_PARANT_L) {
+ /* Parenthesised call: func(arg1, arg2) — collect args */
+ _NEXT /* past '(' */
+ while (p_interpret->tt != TT_PARANT_R
+ && p_interpret->tt != TT_NONE) {
+ _expression_(p_interpret);
+ if (p_interpret->tt == TT_COMMA)
+ _NEXT /* past ',' between arguments */
+ }
+ _NEXT /* past ')' */
+ } else {
+ /* Old-style call without parens (procedures; zero-arg funcs) */
+ if (_expression_(p_interpret));
+ }
function_process_self_defined(p_interpret, p_token);
@@ -1158,14 +1231,19 @@ interpret_process(Interpret *p_interpret) {
int
interpret_subprocess(Interpret *p_interpret, List *p_list_token) {
- Interpret *p_interpret_sub = interpret_new(p_list_token,
- NULL);
+ Interpret *p_interpret_sub = interpret_new(p_list_token, NULL);
p_interpret_sub->p_scope = p_interpret->p_scope;
int i_ret = interpret_process(p_interpret_sub);
p_interpret->ct = p_interpret_sub->ct;
+ /* When a 'ret' fired, move return values from the sub's stack into
+ * the parent's stack so they survive subprocess teardown. The merge
+ * empties p_interpret_sub->p_stack, so interpret_delete is safe. */
+ if (p_interpret->ct == CONTROL_RET)
+ stack_merge(p_interpret->p_stack, p_interpret_sub->p_stack);
+
interpret_delete(p_interpret_sub);
return (i_ret);
diff --git a/src/core/interpret.h b/src/core/interpret.h
index f31d100..972c49d 100644
--- a/src/core/interpret.h
+++ b/src/core/interpret.h
@@ -50,6 +50,7 @@ typedef enum {
CONTROL_NONE,
CONTROL_NEXT,
CONTROL_BREAK,
+ CONTROL_RET, /* explicit return from a function */
} ControlType;
typedef struct {
diff --git a/src/core/symbol.c b/src/core/symbol.c
index 2a3c865..0195107 100644
--- a/src/core/symbol.c
+++ b/src/core/symbol.c
@@ -38,6 +38,26 @@
#include "../data/list.h"
#include "token.h"
+FuncDef*
+funcdef_new(List *p_body, List *p_params, int i_nparams) {
+ FuncDef *p_fd = malloc(sizeof(FuncDef));
+ p_fd->p_body = p_body;
+ p_fd->p_params = p_params;
+ p_fd->i_nparams = i_nparams;
+ return (p_fd);
+}
+
+void
+funcdef_delete(FuncDef *p_funcdef) {
+ /* list_delete only frees list/node structs, not the token pointers
+ * inside p_body — those tokens are GC-managed by the scanner. */
+ list_delete(p_funcdef->p_body);
+ /* Each param name was strdup'd at declaration time; free each one. */
+ list_iterate(p_funcdef->p_params, free);
+ list_delete(p_funcdef->p_params);
+ free(p_funcdef);
+}
+
Symbol*
symbol_new(SymbolType sym, void *p_val) {
Symbol *p_symbol = malloc(sizeof(Symbol));
@@ -61,6 +81,12 @@ symbol_delete(Symbol *p_symbol) {
case SYM_ARRAY:
symbol_delete(symbol_get_val(p_symbol));
break;
+ case SYM_FUNCTION:
+ {
+ FuncDef *p_funcdef = (FuncDef*) symbol_get_val(p_symbol);
+ funcdef_delete(p_funcdef);
+ }
+ break;
NO_DEFAULT;
}
free(p_symbol);
diff --git a/src/core/symbol.h b/src/core/symbol.h
index b3b3b29..e5a331c 100644
--- a/src/core/symbol.h
+++ b/src/core/symbol.h
@@ -37,6 +37,7 @@
#define SYMBOL_H
#include "../defines.h"
+#include "../data/list.h"
#define symbol_set_val(s,v) s->p_val = v
#define symbol_set_sym(s,st) s->sym = st
@@ -55,6 +56,17 @@ typedef enum {
SYM_VARIABLE,
} SymbolType;
+/* Wraps a function's body token list and its named parameter names.
+ * Every SYM_FUNCTION symbol stores a FuncDef* as its p_val. */
+typedef struct {
+ List *p_body; /* function body token list */
+ List *p_params; /* list of strdup'd char* param names */
+ int i_nparams; /* cached count of p_params */
+} FuncDef;
+
+FuncDef* funcdef_new(List *p_body, List *p_params, int i_nparams);
+void funcdef_delete(FuncDef *p_funcdef);
+
typedef struct {
SymbolType sym;
void *p_val;
diff --git a/tags b/tags
index 2ed6274..d29941d 100644
--- a/tags
+++ b/tags
@@ -160,6 +160,8 @@ datiter_new ./src/data/dat.c /^datiter_new(Dat *p_dat) {$/;" f typeref:typename:
datiter_next ./src/data/dat.c /^datiter_next(DatIter *p_iter) {$/;" f typeref:typename:void *
datiter_next_t ./src/data/dat.c /^datiter_next_t(DatIter *p_iter, TYPE *p_type) {$/;" f typeref:typename:void *
datiter_skip ./src/data/dat.c /^datiter_skip(DatIter *p_iter, unsigned i_num) {$/;" f typeref:typename:void
+funcdef_delete ./src/core/symbol.c /^funcdef_delete(FuncDef *p_funcdef) {$/;" f typeref:typename:void
+funcdef_new ./src/core/symbol.c /^funcdef_new(List *p_body, List *p_params, int i_nparams) {$/;" f typeref:typename:FuncDef *
function_delete ./src/core/function.c /^function_delete(Function *p_function) {$/;" f typeref:typename:void
function_is_buildin ./src/core/functions.c /^function_is_buildin(Token *p_token_ident) {$/;" f typeref:typename:_Bool
function_is_self_defined ./src/core/functions.c /^function_is_self_defined(Interpret *p_interpret) {$/;" f typeref:typename:_Bool