}
def prime? num {
- factors(num).length == 0
+ factors(num).length() == 0
}
println max_positive factors(600851475143).filter => num { prime? num }
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
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 {
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:
}
}
-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;
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;
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);
#include "lib/method.h"
#include "lib/module.h"
+sbCFuncStatus please_call_again(hVm vm, flag init);
+
void sbLib_sys_init() {
sbLib_create_sentinels();
/* 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;
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));
+ }
}
}
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;
+ }
+}
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();
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);
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));
--- /dev/null
+#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;
+ }
+}
AST_NODE_NEXT,
AST_NODE_MULTIVAL,
AST_NODE_FUNCCALL,
- AST_NODE_METHODCALL,
+ AST_NODE_DOT,
AST_NODE_SEND,
AST_NODE_RETURN,
AST_NODE_ELLIPSIS,
}
}
-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;
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. */
/* . 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] */
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,
typedef enum {
CFUNC_END,
CFUNC_NEXT,
+ CFUNC_TAILCALL,
} sbCFuncStatus;
typedef sbCFuncStatus (*sbRuntimeCFunc)(hVm, flag init);
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);