From 599b645ef00f25476ae984e354d4b409e9ba5288 Mon Sep 17 00:00:00 2001 From: cassowarii Date: Sun, 12 Jul 2026 13:33:00 -0700 Subject: [PATCH] I had a cool idea for method call semantics. voila, custom methods! --- sample/project_euler/pe03.sa | 2 +- sample/project_euler/pe04.sa | 3 ++- sample/project_euler/pe06.sa | 6 +---- src/compile/analyze.c | 1 + src/compile/ir.c | 30 +++++++++++++++++------ src/lib/lib.c | 58 +++++++++++++++++++++++++++++++++++++++----- src/lib/module.h | 3 +++ src/lib/module/global.c | 9 ++----- src/lib/module/string.c | 51 ++++++++++++++++++++++++++++++++++++++ src/parse/ast.h | 2 +- src/parse/lexer.c | 50 +------------------------------------- src/parse/parser.c | 23 +++--------------- src/vm/exec.c | 6 +++++ src/vm/exec.h | 3 +++ 14 files changed, 150 insertions(+), 97 deletions(-) create mode 100644 src/lib/module/string.c diff --git a/sample/project_euler/pe03.sa b/sample/project_euler/pe03.sa index 2c4c002..295bdfc 100644 --- a/sample/project_euler/pe03.sa +++ b/sample/project_euler/pe03.sa @@ -20,7 +20,7 @@ def factors num { } def prime? num { - factors(num).length == 0 + factors(num).length() == 0 } println max_positive factors(600851475143).filter => num { prime? num } diff --git a/sample/project_euler/pe04.sa b/sample/project_euler/pe04.sa index f9f8418..48bd320 100644 --- a/sample/project_euler/pe04.sa +++ b/sample/project_euler/pe04.sa @@ -1,5 +1,6 @@ def is_palindrome num { - num.to_string == num.to_string.split.reverse.join + let string_num = num | string::from + string_num == string_num.split().reverse().join() } let max = 0 diff --git a/sample/project_euler/pe06.sa b/sample/project_euler/pe06.sa index 51aad0e..8e2715c 100644 --- a/sample/project_euler/pe06.sa +++ b/sample/project_euler/pe06.sa @@ -10,12 +10,8 @@ def square num { num * num } -def map f { - => l { l.map(f) } -} - def sum_squares list { - list.map(square) | sum + list | _.map(square) | sum } def square_sum list { diff --git a/src/compile/analyze.c b/src/compile/analyze.c index a83a507..7eac78a 100644 --- a/src/compile/analyze.c +++ b/src/compile/analyze.c @@ -20,6 +20,7 @@ i32 sbAst_count_pipe_underscores(sbAst node) { case AST_NODE_NEXT: case AST_NODE_MULTIVAL: case AST_NODE_FUNCCALL: + case AST_NODE_DOT: case AST_NODE_HASHENTRY: case AST_NODE_WHILE: case AST_NODE_REPEAT: diff --git a/src/compile/ir.c b/src/compile/ir.c index 31e96a8..88807fe 100644 --- a/src/compile/ir.c +++ b/src/compile/ir.c @@ -903,7 +903,7 @@ static sbIrVariable *compile_ast_var(hIrChunk ck, sbAst node) { } } -static sbIrExpr *compile_ast_list(hIrChunk ck, sbAst node) { +static sbIrExpr *compile_ast_list_after(hIrChunk ck, sbAst node, sbIrExpr *eol) { sbAst considering = node; sbIrExpr *list = IR_EMPTY_LIST; sbIrExpr **place_here = &list; @@ -914,9 +914,16 @@ static sbIrExpr *compile_ast_list(hIrChunk ck, sbAst node) { place_here = &(*place_here)->list.next; considering = considering->seq.left; } + + *place_here = eol; + return list; } +static sbIrExpr *compile_ast_list(hIrChunk ck, sbAst node) { + return compile_ast_list_after(ck, node, IR_EMPTY_LIST); +} + static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) { sbAst considering = node; sbIrExpr *list = NULL; @@ -1006,13 +1013,22 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) { if (node == NO_NODE) return NULL; if (node->type == AST_NODE_FUNCCALL) { - sbIrExpr *called = compile_ast_expr(ck, node->seq.left, FALSE); - sbIrExpr *params = compile_ast_list(ck, node->seq.right); - return expr_call(ck, called, params); - } else if (node->type == AST_NODE_METHODCALL) { + if (node->seq.left->type == AST_NODE_DOT) { + /* For a.b(c,d,e), we can optimize a bit to avoid allocating closures + * for built-in types that get methods called on them */ + sbIrExpr *called = compile_ast_expr(ck, node->seq.left->seq.left, FALSE); + sbIrExpr *method = expr_list(ck, compile_ast_expr(ck, node->seq.left->seq.right, FALSE)); + sbIrExpr *params = compile_ast_list_after(ck, node->seq.right, method); + return expr_send(ck, called, params); + } else { + sbIrExpr *called = compile_ast_expr(ck, node->seq.left, FALSE); + sbIrExpr *params = compile_ast_list(ck, node->seq.right); + return expr_call(ck, called, params); + } + } else if (node->type == AST_NODE_DOT) { sbIrExpr *target = compile_ast_expr(ck, node->seq.left, FALSE); - sbIrExpr *message = compile_ast_list(ck, node->seq.right); - return expr_send(ck, target, message); + sbIrExpr *param = expr_list(ck, compile_ast_expr(ck, node->seq.right, FALSE)); + return expr_call(ck, target, param); } else if (node->type == AST_VAL_FUNC) { sbIrChunk *func = compile_ast_function(ck->program, node->seq.left, node->seq.right); return expr_func(ck, func); diff --git a/src/lib/lib.c b/src/lib/lib.c index 23e0f48..2f293d2 100644 --- a/src/lib/lib.c +++ b/src/lib/lib.c @@ -7,6 +7,8 @@ #include "lib/method.h" #include "lib/module.h" +sbCFuncStatus please_call_again(hVm vm, flag init); + void sbLib_sys_init() { sbLib_create_sentinels(); @@ -38,6 +40,7 @@ void sbLib_resolve_method(hVm vm) { /* TODO this may become not true */ PANIC("method name must be symbol!"); } + hSymbol method_sym = method_name_val->symbol; /* subtract 1 because the method name is itself a param */ usize num_params = argc->integer - 1; @@ -55,15 +58,32 @@ void sbLib_resolve_method(hVm vm) { case IT_FLOAT: table_to_use = &g_float_methods; break; - default: - PANIC("Have not implemented this method table yet! (%lld)", (long long)target->type); } - sbLibMethod f = sbLibTable_find_method(table_to_use, method_name_val->symbol); - if (f) { - f(vm, target, num_params); + if (table_to_use == NULL) { + /* did not find built in method table */ + if (target->type <= 0) { + /* for built in type, just fail, it's not done yet */ + PANIC("Have not implemented this method table yet! (%lld)", (long long)target->type); + } else { + /* for a user defined type, try to retrieve its method: we'll need to jump back into VM for this */ + /* convert stack position ...params :methodname (argc+1) target --> ...params argc :methodname 1 target, + * and then call twice */ + sbVm_push_immediate(vm, &HVINT(num_params)); + sbVm_push_immediate(vm, &HVSYM(method_sym)); + sbVm_push_immediate(vm, target); /* if target points to xstack, need to push before it gets overwritten */ + sbVm_push_immediate(vm, &HVINT(1)); + sbVm_swap(vm); /* now put target on top of stack */ + sbVm_call_c_func(vm, please_call_again); + } } else { - PANIC("invalid method name for type %lld: %s", (long long)target->type, sbSymbol_name(method_name_val->symbol)); + /* built in type that has a method table defined: find method */ + sbLibMethod f = sbLibTable_find_method(table_to_use, method_name_val->symbol); + if (f) { + f(vm, target, num_params); + } else { + PANIC("invalid method name for type %lld: %s", (long long)target->type, sbSymbol_name(method_name_val->symbol)); + } } } @@ -79,3 +99,29 @@ void sbLib_resolve_global(hVm vm) { PANIC("name '%s' is not defined", sbSymbol_name(target->symbol)); } } + +/* --- */ + +/* function that resolves methods from BC_SEND for non intrinsic types (aka functions) */ +/* we have something like a.b(c,d,e), we need to call twice to get result of a(:b)(c,d,e) */ +/* resolve_method should have set us up so we can just call in twice */ +sbCFuncStatus please_call_again(hVm vm, flag init) { + hV *target = sbVm_pop(vm); + if (target->type <= 0) { + PANIC("i have not implemented partial methods for builtin types yet! i'll get to it"); + } + + if (init) { + /* first time around: call to dispatch method */ + sbVm_call_func(vm, target); + + /* please call us again if you have any questions*/ + return CFUNC_NEXT; + } else { + /* second time around: call method on its parameters */ + sbVm_transfer_to_func(vm, target); + + /* please forget we ever existed */ + return CFUNC_TAILCALL; + } +} diff --git a/src/lib/module.h b/src/lib/module.h index 65d2e3e..1b824cf 100644 --- a/src/lib/module.h +++ b/src/lib/module.h @@ -1,6 +1,9 @@ extern sbLibTable g_global_module; +extern sbLibTable g_math_module; extern sbLibTable g_list_module; +extern sbLibTable g_string_module; void sbLib_loadmodule_global(); void sbLib_loadmodule_math(); void sbLib_loadmodule_list(); +void sbLib_loadmodule_string(); diff --git a/src/lib/module/global.c b/src/lib/module/global.c index 52922e3..9600aee 100644 --- a/src/lib/module/global.c +++ b/src/lib/module/global.c @@ -13,10 +13,6 @@ sbCFuncStatus println_cfunc(hVm vm, flag init); sbLibTable g_global_module; -sbLibTable g_string_namespace; - -extern sbLibTable g_math_module; - static void print(hVm vm, usize argc) { sbVm_push_immediate(vm, &HVINT(argc)); sbVm_call_c_func(vm, print_cfunc); @@ -32,9 +28,8 @@ void sbLib_loadmodule_global() { REGISTER_VALUE(&g_global_module, "print", &HVBUILTIN(print)); REGISTER_VALUE(&g_global_module, "println", &HVBUILTIN(println)); - sbLibTable_initialize(&g_string_namespace, 16, FALSE); - REGISTER_VALUE(&g_string_namespace, "convert", &HVSYM(S_OP_TO_STRING)); - REGISTER_VALUE(&g_global_module, "string", &HVMODULE(&g_string_namespace)); + sbLib_loadmodule_string(); + REGISTER_VALUE(&g_global_module, "string", &HVMODULE(&g_string_module)); sbLib_loadmodule_list(); REGISTER_VALUE(&g_global_module, "list", &HVMODULE(&g_list_module)); diff --git a/src/lib/module/string.c b/src/lib/module/string.c new file mode 100644 index 0000000..7a7750d --- /dev/null +++ b/src/lib/module/string.c @@ -0,0 +1,51 @@ +#include "common.h" + +#include "lib/table.h" +#include "lib/sentinel.h" +#include "data/list.h" +#include "data/string.h" +#include "data/symbol.h" +#include "data/integer.h" +#include "vm/exec.h" + +sbCFuncStatus from_cfunc(hVm vm, flag init); + +sbLibTable g_string_module; + +static void from(hVm vm, usize argc) { + if (argc != 1) { + PANIC("string::from takes 1 argument"); + } + + sbVm_call_c_func(vm, from_cfunc); +} + +void sbLib_loadmodule_string() { + sbLibTable_initialize(&g_string_module, 16, FALSE); + REGISTER_VALUE(&g_string_module, "from", &HVBUILTIN(from)); + REGISTER_VALUE(&g_string_module, "convert", &HVSYM(S_OP_TO_STRING)); +} + +/* --- */ + +sbCFuncStatus from_cfunc(hVm vm, flag init) { + if (!init) { + /* get value of previous to_string */ + hV *value = sbVm_peek(vm, 0); + /* TODO: We should probably have some kind of 'implicit convert to string' + * thing that checks that it's really a string and throws if not, that we + * can call from multiple places */ + /* also this should have some kind of like default value if it doesn't work */ + if (value->type != IT_STRING) { + PANIC("string::convert needs to return a string"); + } + return CFUNC_END; + } else { + sbVm_push_immediate(vm, &HVSYM(S_OP_TO_STRING)); /* ... target string::convert */ + sbVm_swap(vm); /* ... string::convert target */ + sbVm_push_immediate(vm, &HVINT(1)); /* ... string::convert target 1 */ + sbVm_swap(vm); /* ... string::convert 1 target */ + sbLib_resolve_method(vm); + return CFUNC_NEXT; + } +} diff --git a/src/parse/ast.h b/src/parse/ast.h index 8ad0257..c870e7a 100644 --- a/src/parse/ast.h +++ b/src/parse/ast.h @@ -37,7 +37,7 @@ typedef enum sbAstType { AST_NODE_NEXT, AST_NODE_MULTIVAL, AST_NODE_FUNCCALL, - AST_NODE_METHODCALL, + AST_NODE_DOT, AST_NODE_SEND, AST_NODE_RETURN, AST_NODE_ELLIPSIS, diff --git a/src/parse/lexer.c b/src/parse/lexer.c index 268042c..db364ae 100644 --- a/src/parse/lexer.c +++ b/src/parse/lexer.c @@ -291,17 +291,6 @@ static void stack_token(hLexer lx, sbLexToken token) { } } -static void unstack_one_invisible_parenthesis(hLexer lx) { - if (lx->brackets_stack.size == 0) return; - - char top = brackets_stack_top(lx); - if (top == 'G' || top == 'H') { - sbLexToken invisible_rparen = { .type = T_RPAREN, .invisible = 1 }; - enqueue_output_token(lx, invisible_rparen); - brackets_stack_pop(lx); - } -} - static void unstack_invisible_parentheses_of_type(hLexer lx, char type) { /* this happens when a real bracket closes, and also at the end of a line */ if (lx->brackets_stack.size == 0) return; @@ -451,44 +440,7 @@ static void compute_next_token(hLexer lx) { space_offset = 1; } - if (token.type == T_BACKSQUIGARROW) { - /* <~ always gets a ( after it to include its parameters, unless there's a ( immediately - * after it with no space. (*with* a space, we accept stuff like a <~ (b), c, d which - * seems likely to be more common than things like a <~(b, c, d) which is kinda wonky - * to begin with (probably people should just write (a <~ b, c, d) but idk) */ - if (input_peek_ahead(lx, 0).type != T_LPAREN) { - enqueue_output_token(lx, invisible_lparen); - } - } else if (lx->last_token_seen.type == T_DOT && token.type == T_IDENTIFIER) { - /* an identifier after a dot always gets an invisible parentheses after it, - * unless there is a visible parentheses immediately after it. */ - if (input_peek_ahead(lx, 0).type != T_LPAREN) { - enqueue_output_token(lx, invisible_lparen); - - sbTokenType next_type = input_peek_ahead(lx, space_offset).type; - sbTokenType next_next_type = input_peek_ahead(lx, space_offset + 1).type; - - if (maybe_can_start_expression(next_type)) { - /* this is something like 'a.b( +c' or 'a.b( + c'. if there is a space - * after the operator, or not before the operator, add an invisible ), - * otherwise don't because that's the 'a.b( +c' case */ - if (next_next_type == T_SPACE || space_offset == 0) { - unstack_one_invisible_parenthesis(lx); - } - } else if (!can_only_start_expression(next_type, is_in_brace_terminated_state(lx))) { - /* ok, so this means that we just inserted a ( in some situation like - * 'a.b(.c' or 'a.b(, c' or 'a.b( % 3' -- in this situation, we need to also - * add a matching invisible right parenthesis immediately. */ - - /* In brace-terminated state, { doesn't count as can_only_start_expression. - * So this applies before { as well: normally 'a.b {' --> 'a.b({', but - * inside block headers 'a.b {' --> 'a.b() {' */ - unstack_one_invisible_parenthesis(lx); - } - /* if can_only_start_expression(next_type), then we don't want to add an invisible - * right parenthesis because it must be a parameter. */ - } - } else if (input_peek_ahead(lx, 0).type == T_SPACE || insert_paren_no_space) { + if (input_peek_ahead(lx, 0).type == T_SPACE || insert_paren_no_space) { /* suspicious... tell me more */ if (can_only_start_expression(input_peek_ahead(lx, space_offset).type, is_in_brace_terminated_state(lx))) { /* ah ! yes. insert a magic ( into the stream. */ diff --git a/src/parse/parser.c b/src/parse/parser.c index bc47632..5774da3 100644 --- a/src/parse/parser.c +++ b/src/parse/parser.c @@ -616,30 +616,13 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) { /* . and -> parse an identifier to their right as a symbol that will * name the method to be called. to call a method whose name isn't * a symbol, you need a.[expr] or a->[expr] */ - sbAst method_name = NO_NODE; - if (expect(pr, T_LBRACKET)) { - method_name = parse_expr(pr, 0); - if (!expect(pr, T_RBRACKET)) return syntax_error(pr); - } else { - method_name = parse_name_as_sym(pr); - } - if (method_name == NO_NODE) return syntax_error(pr); - rhs = seq_node(pr, AST_NODE_MULTIVAL, NO_NODE, method_name); - if (expect(pr, T_LPAREN)) { - rhs = parse_comma_exprs(pr, rhs); - if (!expect(pr, T_RPAREN)) return syntax_error(pr); - } - ast_type = AST_NODE_METHODCALL; + rhs = parse_name_as_sym(pr); + if (rhs == NO_NODE) return syntax_error(pr); + ast_type = AST_NODE_DOT; if (op.type == T_ARROW) { /* (whatever)->x is rewritten as (*whatever).x */ lhs = unop_node(pr, AST_OP_DEREF, lhs); } - } else if (op.type == T_BACKSQUIGARROW) { - /* a <~ b, c, d can have multiple comma things on the right side */ - if (!expect(pr, T_LPAREN)) return syntax_error(pr); - ast_type = AST_NODE_METHODCALL; - rhs = parse_comma_exprs(pr, NULL); - if (!expect(pr, T_RPAREN)) return syntax_error(pr); } else if (op.type == T_PAAMAYIM_NEKUDOTAYIM) { /* :: is similar to . and -> above, will parse the thing to the right * as a symbol unless using the syntax a::[expr] */ diff --git a/src/vm/exec.c b/src/vm/exec.c index bc2a549..9f8639a 100644 --- a/src/vm/exec.c +++ b/src/vm/exec.c @@ -82,6 +82,12 @@ void sbVm_call_func(hVm vm, hV *func) { call_block(vm, func->type, func->closure); } +void sbVm_transfer_to_func(hVm vm, hV *func) { + /* tail call */ + return_from_block(vm); + call_block(vm, func->type, func->closure); +} + void sbVm_call_c_func(hVm vm, sbRuntimeCFunc func) { sbVmStackFrame frame = { .return_addr = vm->ip, diff --git a/src/vm/exec.h b/src/vm/exec.h index 7229c9b..6041238 100644 --- a/src/vm/exec.h +++ b/src/vm/exec.h @@ -39,6 +39,7 @@ typedef enum { CFUNC_END, CFUNC_NEXT, + CFUNC_TAILCALL, } sbCFuncStatus; typedef sbCFuncStatus (*sbRuntimeCFunc)(hVm, flag init); @@ -102,6 +103,8 @@ void sbVm_swap(hVm vm); void sbVm_call_func(hVm vm, hV *func); +void sbVm_transfer_to_func(hVm vm, hV *func); + void sbVm_call_c_func(hVm vm, sbRuntimeCFunc func); void sbVm_request_var_space(hVm vm, usize amount); -- 1.8.3.1