LPC: adding support for default arguments (#1034)

* move argument_t to compiler.h

* checkpoint

* checkpoint

* switch to only use functional

* checkpoint

* LPC: adding argument default support

Add a syntax to allow declaring a function with default value for
arguments.

The default arguments needs to be specified as a closure/lambda
function, and will be evaluated in runtime, in callers's context.
This commit is contained in:
Yucong Sun 2023-12-26 02:02:25 -05:00 committed by GitHub
parent 1fd7f61df3
commit bcb8e91a53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1983 additions and 1395 deletions

View file

@ -386,6 +386,7 @@ set(SRC
"vm/internal/base/array.cc"
"vm/internal/base/buffer.cc"
"vm/internal/base/class.cc"
"vm/internal/base/debug.cc"
"vm/internal/base/function.cc"
"vm/internal/base/interpret.cc"
"vm/internal/base/mapping.cc"

View file

@ -18,6 +18,7 @@
#include "scratchpad.h"
#include "symbol.h"
#include <string>
#include <utility>
#include "vm/internal/base/machine.h" // for error(), FIXME
@ -42,7 +43,7 @@ extern object_t *simul_efun_ob;
extern svalue_t *safe_apply_master_ob(int, int);
static void clean_parser(void);
static void prolog(std::unique_ptr<LexStream>, char * /*name*/);
static void prolog(std::unique_ptr<LexStream>, const char * /*name*/);
static program_t *epilog(void);
static void show_overload_warnings(void);
@ -210,24 +211,26 @@ void pop_n_locals(int num) {
}
}
int add_local_name(const char *str, int type) {
int add_local_name(const char *str, int type, parse_node_t* optional_default_arg_value) {
auto max_local_variables = CFG_INT(__MAX_LOCAL_VARIABLES__);
if (max_num_locals == max_local_variables) {
yyerror("Too many local variables");
return 0;
} else {
ident_hash_elem_t *ihe;
symbol_record(OP_SYMBOL_NEW, current_file, current_line, str);
ihe = find_or_add_ident(str, FOA_NEEDS_MALLOC);
type_of_locals_ptr[max_num_locals] = type;
locals_ptr[current_number_of_locals].ihe = ihe;
locals_ptr[current_number_of_locals++].runtime_index = max_num_locals;
if (ihe->dn.local_num == -1) {
ihe->sem_value++;
}
return ihe->dn.local_num = max_num_locals++;
}
ident_hash_elem_t *ihe;
symbol_record(OP_SYMBOL_NEW, current_file, current_line, str);
ihe = find_or_add_ident(str, FOA_NEEDS_MALLOC);
type_of_locals_ptr[max_num_locals] = type;
auto idx = current_number_of_locals++;
locals_ptr[idx].ihe = ihe;
locals_ptr[idx].funcptr_default = optional_default_arg_value;
locals_ptr[idx].runtime_index = max_num_locals;
if (ihe->dn.local_num == -1) {
ihe->sem_value++;
}
return ihe->dn.local_num = max_num_locals++;
}
void reallocate_locals() {
@ -1049,7 +1052,8 @@ int define_new_function(const char *name, int num_arg, int num_local, int flags,
* 5. A "late" prototype has been encountered.
*/
if (funflags & FUNC_ALIAS) {
fatal("Inconsistent aliasing of functions!\n");
yyerror("Inconsistent aliasing of functions!\n");
return -1;
}
if (!(funflags & (FUNC_INHERITED | FUNC_PROTOTYPE | FUNC_UNDEFINED)) &&
@ -1151,6 +1155,7 @@ int define_new_function(const char *name, int num_arg, int num_local, int flags,
if (!funp) {
num = mem_block[A_FUNCTIONS].current_size / sizeof(function_t);
funp = reinterpret_cast<function_t *>(allocate_in_mem_block(A_FUNCTIONS, sizeof(function_t)));
memset(funp->default_args_findex, 0, sizeof(funp->default_args_findex));
funp->funcname = make_shared_string(name);
argument_start_index = INDEX_START_NONE;
add_to_mem_block(A_ARGUMENT_INDEX, (char *)&argument_start_index, sizeof argument_start_index);
@ -1181,11 +1186,14 @@ int define_new_function(const char *name, int num_arg, int num_local, int flags,
if (exact_types) {
flags |= FUNC_STRICT_TYPES;
}
DEBUG_CHECK(!(flags & DECL_ACCESS), "No access level for function!\n");
if(!(flags & DECL_ACCESS)) {
yyerror("No access level for function!\n");
}
newfunc->flags = flags;
funp->num_local = num_local;
funp->num_arg = num_arg;
funp->min_arg = num_arg;
funp->type = type;
funp->address = 0;
#ifdef PROFILE_FUNCTIONS
@ -1224,6 +1232,7 @@ int define_new_function(const char *name, int num_arg, int num_local, int flags,
if (flags & FUNC_PROTOTYPE) {
symbol_record(OP_SYMBOL_FUNC, current_file, current_line, name);
}
return newindex;
}
@ -1584,9 +1593,9 @@ int validate_function_call(int f, parse_node_t *args) {
if (num_var) {
yyerror("Illegal to pass a variable number of arguments to non-varargs function '%s'.",
funp->funcname);
} else if (funp->num_arg != num_arg) {
yyerror("Wrong number of arguments to '%s', expected: %d, got: %d.", funp->funcname,
funp->num_arg, num_arg);
} else if (funp->num_arg != num_arg && num_arg < funp->min_arg) {
yyerror("Wrong number of arguments to '%s', expected: %d, minimum: %d, got: %d.", funp->funcname,
funp->num_arg, funp->min_arg, num_arg);
}
}
/*
@ -1611,13 +1620,13 @@ int validate_function_call(int f, parse_node_t *args) {
if (arg_types) {
int arg, i, tmp;
parse_node_t *enode = args;
int fnarg = funp->num_arg;
int fnarg = funp->min_arg;
if (funflags & FUNC_TRUE_VARARGS) {
fnarg--;
}
for (i = 0; static_cast<unsigned>(i) < fnarg && i < num_arg; i++) {
for (i = 0; i < fnarg && i < num_arg; i++) {
if (enode->type & 1) {
break;
}
@ -1990,7 +1999,7 @@ void yywarn(const char *fmt, ...) {
/*
* Compile an LPC file.
*/
program_t *compile_file(std::unique_ptr<LexStream> stream, char *name) {
program_t *compile_file(std::unique_ptr<LexStream> stream, const char *name) {
int yyparse(void);
static int guard = 0;
program_t *prog;
@ -2114,6 +2123,7 @@ static void handle_functions() {
}
while (num_func && FUNC(func_index_map[num_func - 1])->address == ADDRESS_MAX) {
yywarn("Function %s(%d) dropped due to program size limit.\n", FUNC(func_index_map[num_func - 1])->funcname, num_func);
num_func--;
}
}
@ -2158,8 +2168,8 @@ static void handle_functions() {
final_index = comp_last_inherited + comp_sorted_funcs[cur_def->u.index];
}
if (cur_def->flags & FUNC_ALIAS) {
fatal("Aliasing difficulties!\n");
exit(1);
yyerror("Aliasing difficulties!\n");
return ;
}
comp_def_index_map[i] = final_index;
@ -2183,6 +2193,17 @@ static void handle_functions() {
}
}
// Fixup the default argument function index
for (int i = 0; i < num_func; i++) {
constexpr auto default_args_limit = sizeof(FUNC(i)->default_args_findex) / sizeof(FUNC(i)->default_args_findex[0]);
auto *func = FUNC(i);
for (int j = 0; j < default_args_limit; j++) {
if (func->default_args_findex[j] != 0) {
func->default_args_findex[j] = comp_sorted_funcs[func->default_args_findex[j]];
}
}
}
if (total_func) {
FREE((char *)comp_sorted_funcs);
}
@ -2248,9 +2269,13 @@ static program_t *epilog(void) {
current_tree = TREE_MAIN;
generate(comp_trees[TREE_MAIN]);
// DEBUG:
// dump_tree(comp_trees[TREE_MAIN]);
current_tree = TREE_INIT;
generate(comp_trees[TREE_INIT]);
// DEBUG:
// dump_tree(comp_trees[TREE_INIT]);
current_tree = TREE_MAIN;
@ -2460,7 +2485,7 @@ static program_t *epilog(void) {
/*
* Initialize the environment that the compiler needs.
*/
static void prolog(std::unique_ptr<LexStream> stream, char *name) {
static void prolog(std::unique_ptr<LexStream> stream, const char *name) {
int i;
function_context.num_parameters = -1;
@ -2719,7 +2744,7 @@ void save_file_info(int file_id, int lines) {
add_to_mem_block(A_FILE_INFO, (char *)&fi[0], sizeof(fi));
}
int add_program_file(char *name, int top) {
int add_program_file(const char *name, int top) {
if (!top) {
add_to_mem_block(A_INCLUDES, name, strlen(name) + 1);
}

View file

@ -10,6 +10,12 @@ class LexStream;
/* The end of a static buffer */
#define EndOf(x) (x + sizeof(x) / sizeof(x[0]))
/* structure for holding information about arguments for function prototype */
struct argument_t {
short num_arg;
char flags;
};
/*
* Information for allocating a block that can grow dynamically
* using realloc. That means that no pointers should be kept into such
@ -77,6 +83,7 @@ struct mem_block_t {
struct local_info_t {
int runtime_index;
parse_node_t *funcptr_default;
struct ident_hash_elem_t *ihe;
};
@ -190,7 +197,7 @@ char *get_type_name(char *, char *, int);
void init_locals(void);
void save_file_info(int, int);
int add_program_file(char *, int);
int add_program_file(const char *, int);
void yyerror(const char *fmt, ...);
void yywarn(const char *fmt, ...);
char *the_file_name(const char *);
@ -199,11 +206,11 @@ void pop_n_locals(int);
void reactivate_current_locals(void);
void clean_up_locals(void);
void deactivate_current_locals(void);
int add_local_name(const char *, int);
int add_local_name(const char *, int, parse_node_t* = nullptr);
void reallocate_locals(void);
void initialize_locals(void);
int get_id_number(void);
program_t *compile_file(std::unique_ptr<LexStream>, char *);
program_t *compile_file(std::unique_ptr<LexStream>, const char *);
void reset_function_blocks(void);
void copy_variables(program_t *, int);
void copy_structures(const program_t *);

View file

@ -6,6 +6,8 @@
#include "compiler/internal/lex.h"
#include "compiler/internal/icode.h"
#include <fmt/format.h>
static void disassemble(FILE *f /*f*/, char *code /*code*/, int /*start*/ start, int /*end*/ end,
program_t *prog /*prog*/);
static const char *disassem_string(const char * /*str*/);
@ -76,8 +78,8 @@ void dump_prog(program_t *prog, FILE *f, int flags) {
prog->inherit[i].function_index_offset, prog->inherit[i].variable_index_offset);
}
fprintf(f, "FUNCTIONS:\n");
fprintf(f, " name offset mods flags fio # locals # args\n");
fprintf(f, " --------------------- ------ ---- ------- --- -------- ------\n");
fprintf(f, " name offset mods flags fio # locals # args # def args\n");
fprintf(f, " --------------------- ------ ---- ------- --- -------- ------ ----------\n");
num_funcs_total = prog->last_inherited + prog->num_functions_defined;
for (i = 0; i < num_funcs_total; i++) {
@ -130,9 +132,17 @@ void dump_prog(program_t *prog, FILE *f, int flags) {
fprintf(f, "%4d: %-20s %6d %4s %7s %3d\n", i, func_entry->funcname, low, smods, sflags,
runtime_index - prog->inherit[low].function_index_offset);
} else {
fprintf(f, "%4d: %-20s %6d %4s %7s %7d %5d\n", i, func_entry->funcname,
runtime_index - prog->last_inherited, smods, sflags, func_entry->num_arg,
func_entry->num_local);
fprintf(f, "%4d: %-20s %6d %4s %7s %7d %5d %10d", i, func_entry->funcname,
runtime_index - prog->last_inherited, smods, sflags, func_entry->num_local,
func_entry->num_arg, func_entry->num_arg - func_entry->min_arg);
std::string default_arg_findex_map;
for(int j = 0; j < func_entry->num_arg; j++) {
if (func_entry->default_args_findex[j] != 0) {
default_arg_findex_map += fmt::format(FMT_STRING(" {}:{}"), j, func_entry->default_args_findex[j]);
}
}
fprintf(f, " %s\n", default_arg_findex_map.c_str());
}
}
@ -291,7 +301,7 @@ static void disassemble(FILE *f, char *code, int start, int end, program_t *prog
}
fflush(f);
fprintf(f, "%04tx: ", (pc - 1) - code);
fprintf(f, "%04tx: ", (pc - 1) - code); // Address
switch (instr) {
case F_PUSH: {
@ -414,15 +424,17 @@ static void disassemble(FILE *f, char *code, int start, int end, program_t *prog
break;
}
case F_CALL_FUNCTION_BY_ADDRESS:
case F_CALL_FUNCTION_BY_ADDRESS: {
COPY_SHORT(&sarg, pc);
pc += 3;
pc += sizeof(short);
const uint8_t args = EXTRACT_UCHAR(pc++);
if (sarg < NUM_FUNS) {
sprintf(buff, "%-12s %5d", function_name(prog, sarg), sarg);
sprintf(buff, "%s, pushed_args:%d", function_name(prog, sarg), args);
} else {
sprintf(buff, "<out of range %d>", sarg);
}
break;
}
break;
case F_CALL_INHERITED: {
program_t *newprog;
@ -533,10 +545,13 @@ static void disassemble(FILE *f, char *code, int start, int end, program_t *prog
}
break;
case FP_FUNCTIONAL:
case FP_FUNCTIONAL | FP_NOT_BINDABLE:
sprintf(buff, "<functional, %d args>\nCode:", pc[0]);
pc += 3;
case FP_FUNCTIONAL | FP_NOT_BINDABLE: {
uint8_t num_args = EXTRACT_UCHAR(pc++);
uint16_t size;
LOAD_SHORT(size, pc);
sprintf(buff, "<functional, %d args>: Code size: %d,", num_args, size);
break;
}
case FP_ANONYMOUS:
case FP_ANONYMOUS | FP_NOT_BINDABLE:
COPY_SHORT(&sarg, &pc[2]);
@ -674,9 +689,9 @@ static void disassemble(FILE *f, char *code, int start, int end, program_t *prog
while (saved_pc != pc) {
p += sprintf(p, "%02hhX ", *saved_pc++);
}
fprintf(f, " %-25s", tmp);
fprintf(f, " %-25s", tmp); // byte code in HEX
}
fprintf(f, " %-20s; %s\n", query_instr_name(instr), buff);
fprintf(f, " %-35s; %s\n", query_instr_name(instr), buff);
}
// print last line

View file

@ -649,7 +649,7 @@ void dump_tree(parse_node_t *expr) {
printf(")");
break;
case NODE_EFUN:
printf("(%s ", instrs[expr->v.number & ~NOVALUE_USED_FLAG].name);
printf("(efun %s ", instrs[expr->v.number & ~NOVALUE_USED_FLAG].name);
dump_expr_list(expr->r.expr);
printf(")");
break;

File diff suppressed because it is too large Load diff

View file

@ -35,8 +35,8 @@
especially those whose name start with YY_ or yy_. They are
private implementation details that can be changed or removed. */
#ifndef YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED
# define YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED
#ifndef YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_ASAN_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED
# define YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_ASAN_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED
/* Debug traces. */
#ifndef YYDEBUG
# define YYDEBUG 0
@ -137,7 +137,7 @@ union YYSTYPE
uint16_t save_exact_types;
} func_block; /* 8 */
#line 141 "/home/sunyc/src/fluffos/cmake-build-debug-wsl-clang/src/grammar.autogen.h"
#line 141 "/home/sunyc/src/fluffos/cmake-build-debug-asan-wsl-clang/src/grammar.autogen.h"
};
typedef union YYSTYPE YYSTYPE;
@ -152,4 +152,4 @@ extern YYSTYPE yylval;
int yyparse (void);
#endif /* !YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED */
#endif /* !YY_YY_HOME_SUNYC_SRC_FLUFFOS_CMAKE_BUILD_DEBUG_ASAN_WSL_CLANG_SRC_GRAMMAR_AUTOGEN_H_INCLUDED */

View file

@ -190,6 +190,7 @@ int yyparse (void);
%type <string> new_local_name
/* The following return a parse node */
%type <node> optional_default_arg_value
%type <node> number real string expr0 comma_expr for_expr sscanf catch
%type <node> parse_command time_expression expr_list expr_list2 expr_list3
%type <node> expr_list4 assoc_pair expr4 lvalue function_call lvalue_list
@ -260,9 +261,9 @@ identifier:
;
function:
type optional_star identifier { $1 = rule_func_type($1, $2, $3); }
'(' argument ')' { $<number>$ = rule_func_proto($1, $2, &$3, $6); }
block_or_semi { rule_func(&$$, $1, $2, $3, $6, &$<number>8, &$9); }
type optional_star identifier { $type = rule_func_type($type, $optional_star, $identifier); }
'(' argument ')' { $<number>$ = rule_func_proto($type, $optional_star, &$identifier, $argument); }
block_or_semi { rule_func(&$$, $type, $optional_star, $identifier, $argument, &$<number>8, &$block_or_semi); }
def:
@ -368,6 +369,27 @@ arg_type:
| basic_type ref { $$ = $1 | LOCAL_MOD_REF; }
;
optional_default_arg_value:
%empty { $$ = 0; }
| ':' L_FUNCTION_OPEN comma_expr ':' ')' {
if (CONFIG_INT(__RC_WOMBLES__)) {
if(*(outp-2) != ':') {
yyerror("End of functional not found");
}
}
if (current_function_context->num_locals)
yyerror("Illegal to use local variable in functional.");
if (current_function_context->values_list->r.expr)
current_function_context->values_list->r.expr->kind = current_function_context->values_list->kind;
$$ = new_node();
$$->kind = NODE_FUNCTION_CONSTRUCTOR;
$$->type = TYPE_FUNCTION;
$$->l.expr = $3;
$$->r.expr = nullptr; // no arguments
$$->v.number = FP_FUNCTIONAL + 0 /* args */;
}
new_arg:
arg_type optional_star
{
@ -375,11 +397,11 @@ new_arg:
if ($1 != TYPE_VOID)
add_local_name("", $1 | $2);
}
| arg_type optional_star new_local_name
| arg_type optional_star new_local_name optional_default_arg_value
{
if ($1 == TYPE_VOID)
yyerror("Illegal to declare argument of type void.");
add_local_name($3, $1 | $2);
add_local_name($3, $1 | $2, $optional_default_arg_value);
scratch_free($3);
$$ = $1 | $2;
}

View file

@ -8,6 +8,8 @@
#include "compiler/internal/generate.h"
#include "compiler/internal/grammar_rules.h"
#include <fmt/format.h>
extern int context; // FIXME
extern int func_present; // FIXME
@ -97,13 +99,13 @@ bool rule_inheritence(parse_node_t **$$, int $1, char *$3) {
return false;
}
LPC_INT rule_func_type(LPC_INT $1, LPC_INT $2, char *$3) {
LPC_INT rule_func_type(LPC_INT type, LPC_INT optional_star, char *identifier) {
int flags;
#ifdef SENSIBLE_MODIFIERS
int acc_mod;
#endif
func_present = 1;
flags = ($1 >> 16);
flags = (type >> 16);
flags |= global_modifiers;
@ -128,30 +130,30 @@ LPC_INT rule_func_type(LPC_INT $1, LPC_INT $2, char *$3) {
flags &= ~DECL_NOSAVE;
}
#endif
$1 = (flags << 16) | ($1 & 0xffff);
type = (flags << 16) | (type & 0xffff);
/* Handle type checking here so we know whether to typecheck
'argument' */
if ($1 & 0xffff) {
if (type & 0xffff) {
if (CONFIG_INT(__RC_OLD_TYPE_BEHAVIOR__)) {
exact_types = 0;
} else {
exact_types = ($1 & 0xffff) | $2;
exact_types = (type & 0xffff) | optional_star;
}
} else {
if (pragmas & PRAGMA_STRICT_TYPES) {
if (strcmp($3, "create") != 0)
if (strcmp(identifier, "create") != 0)
yyerror("\"#pragma strict_types\" requires type of function");
else
exact_types = TYPE_VOID; /* default for create() */
} else
exact_types = 0;
}
return $1;
return type;
}
LPC_INT rule_func_proto(LPC_INT $1, LPC_INT $2, char **$3, argument_t $5) {
char *p = *$3;
*$3 = (char *)make_shared_string(*$3);
LPC_INT rule_func_proto(LPC_INT type, LPC_INT optional_star, char **identifier, argument_t argument) {
char *p = *identifier;
*identifier = (char *)make_shared_string(*identifier);
scratch_free(p);
/* If we had nested functions, we would need to check */
@ -163,47 +165,90 @@ LPC_INT rule_func_proto(LPC_INT $1, LPC_INT $2, char **$3, argument_t $5) {
*/
LPC_INT func_types = FUNC_PROTOTYPE;
if ($5.flags & ARG_IS_VARARGS) {
if (argument.flags & ARG_IS_VARARGS) {
func_types |= (FUNC_TRUE_VARARGS | FUNC_VARARGS);
}
func_types |= ($1 >> 16);
func_types |= (type >> 16);
define_new_function(*$3, $5.num_arg, 0, func_types, ($1 & 0xffff) | $2);
define_new_function(*identifier, argument.num_arg, 0, func_types, (type & 0xffff) | optional_star);
/* This is safe since it is guaranteed to be in the
function table, so it can't be dangling */
free_string(*$3);
free_string(*identifier);
context = 0;
return func_types;
}
void rule_func(parse_node_t **$$, LPC_INT $1, LPC_INT $2, char *$3, argument_t $5, LPC_INT *$8,
parse_node_t **$9) {
void rule_func(parse_node_t **function, LPC_INT type, LPC_INT optional_star, char *identifier, argument_t argument, LPC_INT *func_types,
parse_node_t **block_or_semi) {
/* Either a prototype or a block */
if (*$9) {
if (*block_or_semi) {
int fun;
*$8 &= ~FUNC_PROTOTYPE;
if ((*$9)->kind != NODE_RETURN &&
((*$9)->kind != NODE_TWO_VALUES || (*$9)->r.expr->kind != NODE_RETURN)) {
*func_types &= ~FUNC_PROTOTYPE;
if ((*block_or_semi)->kind != NODE_RETURN &&
((*block_or_semi)->kind != NODE_TWO_VALUES || (*block_or_semi)->r.expr->kind != NODE_RETURN)) {
parse_node_t *replacement;
CREATE_STATEMENTS(replacement, *$9, 0);
CREATE_STATEMENTS(replacement, *block_or_semi, 0);
CREATE_RETURN(replacement->r.expr, 0);
*$9 = replacement;
*block_or_semi = replacement;
}
fun = define_new_function($3, $5.num_arg, max_num_locals - $5.num_arg, *$8, ($1 & 0xffff) | $2);
// Creating functions for argument defaults
fun = define_new_function(identifier, argument.num_arg, max_num_locals - argument.num_arg, *func_types, (type & 0xffff) | optional_star);
if (fun != -1) {
*$$ = new_node_no_line();
(*$$)->kind = NODE_FUNCTION;
(*$$)->v.number = fun;
(*$$)->l.number = max_num_locals;
(*$$)->r.expr = *$9;
*function = new_node_no_line();
(*function)->kind = NODE_FUNCTION;
(*function)->v.number = fun;
(*function)->l.number = max_num_locals;
(*function)->r.expr = *block_or_semi;
if (argument.num_arg) {
auto default_args_limit = sizeof(FUNC(fun)->default_args_findex) / sizeof(FUNC(fun)->default_args_findex[0]);
for (int i = 0; i < argument.num_arg; i++) {
auto local = locals_ptr[i];
if (local.funcptr_default) {
if (i > default_args_limit) {
yyerror("Functions with default arguments can only have %d args", default_args_limit);
return ;
}
FUNC(fun)->min_arg--;
auto funcname = fmt::format(FMT_STRING("__{}_{}"), identifier, local.ihe->name);
// the funcnum here will change in epilog(), see fixup in handle_functions()
auto funcnum = define_new_function(funcname.c_str(), 0, 0,
*func_types | DECL_NOMASK, // same access as origin function
type_of_locals_ptr[locals_ptr[i].runtime_index]);
FUNC(fun)->default_args_findex[i] = funcnum;
parse_node_t *node_return;
CREATE_RETURN(node_return, local.funcptr_default);
auto *node_func = new_node_no_line();
node_func->kind = NODE_FUNCTION;
node_func->v.number = funcnum;
node_func->l.number = 0;
node_func->r.expr = node_return;
auto *newnode = *function;
CREATE_TWO_VALUES(*function, 0, newnode, node_func);
} else {
if (i > 0) {
auto prev = FUNC(fun)->default_args_findex[i - 1];
if (prev != 0) {
yyerror("Function arguments with default value closure must be specified continuously.");
return ;
}
}
FUNC(fun)->default_args_findex[i] = 0;
}
}
}
} else
*$$ = 0;
*function = 0;
} else
*$$ = 0;
free_all_local_names(!!(*$9));
*function = 0;
free_all_local_names(!!(*block_or_semi));
}
ident_hash_elem_t *rule_define_class(LPC_INT *$$, char *$3) {

View file

@ -1,16 +1,14 @@
#include "vm/internal/base/number.h"
struct argument_t {
short num_arg;
char flags;
};
// in compiler.h
struct argument_t;
void rule_program(struct parse_node_t* node);
bool rule_inheritence(struct parse_node_t** $$, int $1, char* $3);
LPC_INT rule_func_type(LPC_INT $1, LPC_INT $2, char* $3);
LPC_INT rule_func_proto(LPC_INT $1, LPC_INT $2, char** $3, argument_t $5);
void rule_func(parse_node_t** $$, LPC_INT $1, LPC_INT $2, char* $3, argument_t $5, LPC_INT* $8,
parse_node_t** $9);
LPC_INT rule_func_type(LPC_INT type, LPC_INT optional_star, char* identifier);
LPC_INT rule_func_proto(LPC_INT, LPC_INT, char**, argument_t);
void rule_func(parse_node_t** function, LPC_INT type, LPC_INT optional_star, char* identifier, argument_t argument, LPC_INT* func_types,
parse_node_t** block_or_semi);
struct ident_hash_elem_t* rule_define_class(LPC_INT* $$, char* $3);
void rule_define_class_members(struct ident_hash_elem_t* $2, LPC_INT $5);

View file

@ -3364,9 +3364,12 @@ static void int_add_instr_name(const char *name, int n, short t) {
static void init_instrs() {
unsigned int i, n;
// operators
for (i = 0; i < EFUN_BASE; i++) {
instrs[i].ret_type = -1;
instrs[i].name = operator_names[i];
instrs[i].ret_type = T_ANY;
}
for (i = 0; i < size_of_predefs; i++) {
n = predefs[i].token;
if (n & F_ALIAS_FLAG) {
@ -3483,7 +3486,7 @@ static void init_instrs() {
add_instr_name("parse_command", "c_parse_command(%i);\n", F_PARSE_COMMAND, T_NUMBER);
add_instr_name("string", 0, F_STRING, T_STRING);
add_instr_name("short_string", 0, F_SHORT_STRING, T_STRING);
add_instr_name("call", "c_call(%i, %i);\n", F_CALL_FUNCTION_BY_ADDRESS, T_ANY);
add_instr_name("F_CALL_FUNCTION_BY_ADDRESS", "c_call(%i, %i);\n", F_CALL_FUNCTION_BY_ADDRESS, T_ANY);
add_instr_name("call_inherited", "c_call_inherited(%i, %i, %i);\n", F_CALL_INHERITED, T_ANY);
add_instr_name("aggregate_assoc", "C_AGGREGATE_ASSOC(%i);\n", F_AGGREGATE_ASSOC, T_MAPPING);
#ifdef DEBUG

View file

@ -97,7 +97,7 @@ typedef struct {
/*
* lex.c
*/
extern instr_t instrs[512];
extern instr_t instrs[MAX_INSTRS];
extern int current_line;
extern int current_line_base;
extern int current_line_saved;

View file

@ -147,13 +147,17 @@ typedef struct parse_node_block_s {
SAFE((vn) = new_node_no_line(); (vn)->kind = NODE_NUMBER; \
(vn)->type = (val ? TYPE_NUMBER : TYPE_ANY); (vn)->v.number = val;)
#define CREATE_STRING(vn, val) \
SAFE((vn) = new_node_no_line(); (vn)->kind = NODE_STRING; (vn)->type = TYPE_STRING; \
SAFE((vn) = new_node_no_line(); \
(vn)->kind = NODE_STRING; (vn)->type = TYPE_STRING; \
(vn)->v.number = store_prog_string(val);)
#define CREATE_EXPR_LIST(vn, pn) \
SAFE((vn) = new_node(); (vn)->v.number = (pn ? ((parse_node_t *)pn)->kind : 0); \
(vn)->l.expr = (pn ? ((parse_node_t *)pn)->l.expr : (vn)); (vn)->r.expr = pn;)
SAFE((vn) = new_node(); \
(vn)->v.number = (pn ? ((parse_node_t *)pn)->kind : 0); \
(vn)->l.expr = (pn ? ((parse_node_t *)pn)->l.expr : (vn)); \
(vn)->r.expr = pn;)
#define CREATE_EXPR_NODE(vn, pn, f) \
SAFE((vn) = new_node_no_line(); (vn)->v.expr = pn; (vn)->l.expr = vn; (vn)->r.expr = 0; \
SAFE((vn) = new_node_no_line(); \
(vn)->v.expr = pn; (vn)->l.expr = vn; (vn)->r.expr = 0; \
(vn)->type = f;)
#define CREATE_CATCH(vn, pn) \
SAFE((vn) = new_node(); (vn)->kind = NODE_CATCH; (vn)->type = TYPE_ANY; (vn)->r.expr = pn;)

View file

@ -5,10 +5,14 @@ if(${GTEST_FOUND})
include(GoogleTest)
enable_testing()
add_executable(lpc_tests test_lpc.cc test_ofile.cc)
add_executable(lpc_tests test_lpc.cc)
target_link_libraries(lpc_tests PRIVATE ${FLUFFOS_LINK} GTest::GTest GTest::Main)
target_compile_definitions(lpc_tests PRIVATE -DTESTSUITE_DIR="${CMAKE_SOURCE_DIR}/testsuite")
gtest_discover_tests(lpc_tests)
add_executable(ofile_tests test_ofile.cc)
target_link_libraries(ofile_tests PRIVATE ${FLUFFOS_LINK} GTest::GTest GTest::Main)
target_compile_definitions(ofile_tests PRIVATE -DTESTSUITE_DIR="${CMAKE_SOURCE_DIR}/testsuite")
gtest_discover_tests(lpc_tests ofile_tests)
endif()

View file

@ -52,8 +52,6 @@ TEST_F(DriverTest, TestInMemoryCompileFile) {
prog = compile_file(std::move(stream), "test");
ASSERT_NE(prog, nullptr);
dump_prog(prog, stdout, 1 | 2);
deallocate_program(prog);
}
@ -65,3 +63,68 @@ TEST_F(DriverTest, TestInMemoryCompileFileFail) {
ASSERT_EQ(prog, nullptr);
}
TEST_F(DriverTest, TestValidLPC_FunctionDeafultArgument) {
const char* source = R"(
// default case
void test1() {
}
// default case
void test2(int a, int b) {
ASSERT_EQ(a, 1);
ASSERT_EQ(b, 2);
}
// varargs
void test3(int a, int* b ...) {
ASSERT_EQ(a, 1);
ASSERT_EQ(b[0], 2);
ASSERT_EQ(b[1], 3);
ASSERT_EQ(b[2], 4);
ASSERT_EQ(b[3], 5);
}
// can have multiple trailing arguments with a FP for calculating default value
void test4(int a, string b: (: "str" :), int c: (: 0 :)) {
switch(a) {
case 1: {
ASSERT_EQ("str", b);
ASSERT_EQ(0, c);
break;
}
case 2: {
ASSERT_EQ("aaa", b);
ASSERT_EQ(0, c);
break;
}
case 3: {
ASSERT_EQ("bbb", b);
ASSERT_EQ(3, c);
break;
}
}
}
void do_tests() {
test1();
test2(1, 2);
test3(1, 2, 3, 4, 5);
// direct call
test4(1);
test4(2, "aaa");
test4(3, "bbb", 3);
// apply
this_object()->test4(1);
this_object()->test4(2, "aaa");
this_object()->test4(3, "bbb", 3);
}
)";
std::istringstream iss(source);
auto stream = std::make_unique<IStreamLexStream>(iss);
auto *prog = compile_file(std::move(stream), "test");
ASSERT_NE(prog, nullptr);
dump_prog(prog, stdout, 1 | 2);
deallocate_program(prog);
}

View file

@ -480,6 +480,14 @@ void make_efun_tables() {
fprintf(f, "#include \"base/std.h\"\n\n");
fprintf(f, "#include \"" EFUN_H "\"\n");
fprintf(f, "\n/* Operator names */\n");
fprintf(f, "const char* operator_names[] = {\n");
fprintf(f, " \"INVALID OPCODE\", // 0\n");
for (int i = 0; i < op_code; i++) {
fprintf(f, " \"%s\", // %s: %d \n ", oper_codes[i], oper_codes[i], i+1);
}
fprintf(f, "};\n");
fprintf(f, "\n// EFUN tables\n\n");
fprintf(f, "func_t efun_table[] = {\n");
for (int i = 0; i < efun_code; i++) {
@ -535,6 +543,7 @@ void make_efun_tables() {
fprintf(f, "#define %-30s %d\n", oper_codes[i], i + 1);
total_code++;
}
fprintf(f, "\n/* efuns */\n");
int efun_base = op_code + 1;
@ -551,7 +560,8 @@ void make_efun_tables() {
fprintf(f, "void f_%s (void);\n", efun_names[i]);
}
fprintf(f, "typedef void (*func_t) (void);\n\n");
fprintf(f, "extern func_t efun_table[];");
fprintf(f, "extern func_t efun_table[];\n");
fprintf(f, "extern const char* operator_names[];\n");
/* Now sort the main_list */
for (int i = 0; i < num_buff; i++) {

View file

@ -8,8 +8,10 @@
#include "base/internal/tracing.h"
#include "vm/internal/base/apply_cache.h"
#include "vm/internal/base/machine.h"
#include "vm/internal/base/debug.h"
#include "compiler/internal/compiler.h"
#include "compiler/internal/lex.h"
#include "compiler/internal/disassembler.h"
// global static result
svalue_t apply_ret_value;
@ -236,6 +238,59 @@ retry_for_shadow:
pop_n_elems(num_arg);
return 0;
}
/* setup default arguments if needed */
{
auto *progp = entry.progp;
auto *funcp = entry.funp;
if (!(funcp->type & FUNC_VARARGS) && funcp->min_arg != funcp->num_arg) {
if (num_arg < funcp->min_arg) {
// COMPAT: fluffos allow apply to call functions with fewer arguments than required, so we fix it up here
push_undefineds(funcp->min_arg - num_arg);
num_arg = funcp->min_arg;
}
// for functions with default argument values, we want to invoke the closure to
// fill in the arguments
if (num_arg != funcp->num_arg) {
auto *saved_fp = fp;
fp = sp; // leave the already pushed args on the stack
// NOTE: this assumes default arguments closure are always generated right after the function in order
for (int i = num_arg; i < funcp->num_arg; i++) {
auto current_sp = sp;
auto *default_funcp =progp->function_table + funcp->default_args_findex[i];
if(default_funcp->funcname[0]!='_') {
dump_vm_state();
dump_prog(progp, stdout, 1|2);
error("Illegal default argument function name %s in %s\n", default_funcp->funcname, progp->filename);
}
// notice we don't change current_object here, so the default arguments closure
// will be called in the context of the caller
fp = sp + 1; // zero args
push_control_stack(FRAME_FUNCTION);
caller_type = ORIGIN_LOCAL;
csp->pc = pc;
csp->num_local_variables = 0;
current_prog = progp;
call_program(progp, default_funcp->address);
// get the returned closure then evaluate for the real value
svalue_t sv_funcp;
assign_svalue_no_free(&sv_funcp, sp);
pop_stack();
// evaluate the closure in current context
push_svalue(call_function_pointer(sv_funcp.u.fp, 0));
free_svalue(&sv_funcp, "apply_low");
DEBUG_CHECK(sp - current_sp != 1 && dump_vm_state(), "Bad stack after default arguments call.");
}
fp = saved_fp;
num_arg = funcp->num_arg;
}
}
}
/* Check arguments */
if (!(funflags & FUNC_VARARGS)) {
check_co_args(num_arg, entry.progp, funp, findex);

View file

@ -0,0 +1,111 @@
#include "base/std.h"
#include "vm/internal/base/svalue.h"
#include "vm/internal/base/machine.h"
#include "vm/internal/base/interpret.h"
#include "compiler/internal/disassembler.h"
#include "vm/internal/trace.h"
#include <string>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
#include <fmt/format.h>
std::string framekind_name(int framekind) {
std::string result = "";
switch (framekind & FRAME_MASK) {
case FRAME_FUNCTION:
result = "FRAME_FUNCTION";
break;
case FRAME_FUNP:
result = "FRAME_FUNP";
break;
case FRAME_CATCH:
result = "FRAME_CATCH";
break;
case FRAME_FAKE:
result = "FRAME_FAKE";
break;
default:
result = "FRAME_UNKNOWN";
break;
}
if (framekind & FRAME_EXTERNAL) {
result += "| FRAME_EXTERNAL";
}
if (framekind & FRAME_OB_CHANGE) {
result += "| FRAME_OB_CHANGE";
}
if (framekind & FRAME_RETURNED_FROM_CATCH) {
result += "| FRAME_RETURNED_FROM_CATCH";
}
return result;
}
std::string print_object_ptr(object_t *ob) {
std::stringstream ss;
ss << (void*)ob << " (" << (ob ? ob->obname: "") << ")";
return ss.str();
}
std::string print_program_ptr(program_t *prog) {
std::stringstream ss;
ss << (void*)prog << " (" << (prog ? prog->filename: "") << ")";
return ss.str();
}
std::string print_pc(program_t *prog, char *pc) {
std::stringstream ss;
ss << (void*)pc << " (addr: " << fmt::format(FMT_STRING("{:04x}"), (pc - prog->program)) << ")";
return ss.str();
}
bool dump_vm_state() {
auto prefix = " ";
std::cout << "VM state:\n";
std::cout << prefix << "current_object = " << print_object_ptr(current_object) << ")\n";
std::cout << prefix << "current_interactive = " << current_interactive << "\n";
std::cout << prefix << "current_prog = " << print_program_ptr(current_prog) << "\n";
std::cout << prefix << "caller_type = " << caller_type << "\n";
std::cout << prefix << "pc = " << print_pc(current_prog, pc) << "\n";
std::cout << prefix << "fp = " << (void *)fp << " (sp - " << (sp - fp) << ")" << "\n";
std::cout << prefix << "sp = " << sp << "\n";
std::cout << prefix << "st_num_arg = " << st_num_arg << "\n";
// Dump current stack
std::cout << "current stack:\n";
for(auto *sv = csp->fp; sv < sp; sv++) {
std::cout << "sv " << (sv - csp->fp) << ":\n";
std::cout << prefix << "type = " << type_name(sv->type) << "\n";
std::cout << prefix << svalue_to_json_summary(sv, 0).dump(2) << "\n";
}
// Dump control stack
auto *p = csp;
int depth = 1;
while (p != control_stack) {
std::cout << "control stack: "<< -depth << "\n";
std::cout << prefix << "framekind = " << framekind_name(p->framekind) << "(" << p->framekind << ")\n";
std::cout << prefix << "ob = " << print_object_ptr(p->ob) << "\n";
std::cout << prefix << "prev_ob = " << print_object_ptr(p->prev_ob) << "\n";
std::cout << prefix << "prog = " << print_program_ptr(p->prog) << "\n";
std::cout << prefix << "pc = " << print_pc(p->prog, p->pc) << "\n";
std::cout << prefix << "fp = " << p->fp << "\n";
std::cout << prefix << "num_local_variables = " << p->num_local_variables << "\n";
std::cout << prefix << "function_index_offset = " << p->function_index_offset << "\n";
std::cout << prefix << "variable_index_offset = " << p->variable_index_offset << "\n";
std::cout << prefix << "caller_type = " << p->caller_type << "\n";
p--;
depth++;
}
// Dump the current program
std::cout << "current program:\n";
dump_prog(current_prog, stdout, 1 | 2);
// Dump current trace
dump_trace(1);
return true; // so we can use it in DEBUG_CHECK
}

View file

@ -0,0 +1 @@
bool dump_vm_state();

View file

@ -1,5 +1,7 @@
#include "base/std.h"
#include "vm/internal/base/interpret.h"
#include <algorithm>
#include <functional>
#include <memory>
@ -9,6 +11,7 @@
#include "base/internal/tracing.h"
#include "comm.h" // add_vmessage FIXME: reverse API
#include "thirdparty/scope_guard/scope_guard.hpp"
#include "vm/internal/base/debug.h"
#include "vm/internal/apply.h"
#include "vm/internal/base/apply_cache.h"
#include "vm/internal/base/machine.h"
@ -21,6 +24,7 @@
#include "packages/core/sprintf.h" // FIXME
#include "packages/core/regexp.h" // FIXME
#include "packages/ops/ops.h" // FIXME
#include "compiler/internal/disassembler.h"
extern int efun_arg_etypes[]; // in efuns.autogen.cc generated by make_func.y
@ -321,7 +325,7 @@ void push_undefined() {
*sp = const0u;
}
static void push_undefineds(int num) {
void push_undefineds(int num) {
CHECK_STACK_OVERFLOW(num);
while (num--) {
*++sp = const0u;
@ -1437,6 +1441,37 @@ void setup_varargs_variables(int actual, int local, int num_arg) {
fp = sp - (csp->num_local_variables = local + num_arg) + 1;
}
// Return function_t for findex, walking up the inheritance tree if necessary
// TODO: return nullptr if invalid findex
std::pair<program_t*, function_t*> get_function_at_index(program_t* prog, int findex) {
if(findex < 0 || findex > prog->last_inherited + prog->num_functions_defined) {
return std::make_pair(nullptr, nullptr);
}
/* Walk up the inheritance tree to the real definition */
if (prog->function_flags[findex] & FUNC_ALIAS) {
findex = prog->function_flags[findex] & ~FUNC_ALIAS;
}
while (prog->function_flags[findex] & FUNC_INHERITED) {
int low, high, mid;
low = 0;
high = prog->num_inherited - 1;
while (high > low) {
mid = (low + high + 1) >> 1;
if (prog->inherit[mid].function_index_offset > findex) {
high = mid - 1;
} else {
low = mid;
}
}
findex -= prog->inherit[low].function_index_offset;
prog = prog->inherit[low].prog;
}
findex -= prog->last_inherited;
return std::make_pair(prog, &prog->function_table[findex]);
}
function_t *setup_new_frame(int findex) {
function_t *func_entry;
int low, high, mid;
@ -2959,8 +2994,6 @@ void eval_instruction(char *p) {
break;
#endif
case F_CALL_FUNCTION_BY_ADDRESS: {
function_t *funp;
LOAD_SHORT(offset, pc);
offset += function_index_offset;
@ -2981,6 +3014,59 @@ void eval_instruction(char *p) {
if (current_object->prog->function_flags[offset] & (FUNC_PROTOTYPE | FUNC_UNDEFINED)) {
error("Undefined function called: %s\n", function_name(current_object->prog, offset));
}
auto pushed_args = EXTRACT_UCHAR(pc++) + num_varargs;
num_varargs = 0;
auto saved_pc = pc;
auto result = get_function_at_index(current_object->prog, offset);
auto *progp = result.first;
auto *funcp = result.second;
DEBUG_CHECK(!progp || !funcp, "BUG: Invalid Program or Illegal function index.");
if (!(funcp->type & FUNC_VARARGS) && funcp->min_arg != funcp->num_arg) {
if (pushed_args < funcp->min_arg) {
error("Not enough arguments to function %s, expected at least %d args, actual %d args.\n", funcp->funcname, funcp->min_arg, pushed_args);
}
// for functions with default argument values, we want to invoke the closure to
// fill in the arguments
if (pushed_args != funcp->num_arg) {
// NOTE: this assumes default arguments closure are always generated right after the function in order
for (int i = pushed_args; i < funcp->num_arg; i++) {
auto *current_sp = sp;
auto *default_funcp = progp->function_table + funcp->default_args_findex[i];
if (default_funcp->funcname[0]!='_') {
dump_vm_state();
dump_prog(progp, stdout, 1|2);
error("Illegal default argument function name %s in %s\n", default_funcp->funcname, progp->filename);
}
push_control_stack(FRAME_FUNCTION);
fp = sp + 1; // zero args
caller_type = ORIGIN_LOCAL;
csp->pc = saved_pc;
csp->num_local_variables = 0;
csp->fr.table_index = funcp->default_args_findex[i];
current_prog = progp;
call_program(progp, default_funcp->address);
// get the returned closure then evaluate for the real value
svalue_t sv_funcp = *sp--;
DEBUG_CHECK((sv_funcp.type != T_FUNCTION || sv_funcp.u.fp == nullptr) && dump_vm_state(),
"F_CALL_FUNCTION_BYADDRESS: default args closure returned null.");
// evaluate the closure in current context
push_svalue(call_function_pointer(sv_funcp.u.fp, 0));
free_svalue(&sv_funcp, "F_CALL_FUNCTION_BYADDRESS: default args closure");
DEBUG_CHECK(sp - current_sp != 1, "Bad stack after default arguments call.");
}
pushed_args = funcp->num_arg;
}
}
/* Save all important global stack machine registers */
push_control_stack(FRAME_FUNCTION);
@ -2991,12 +3077,8 @@ void eval_instruction(char *p) {
* If it is an inherited function, search for the real
* definition.
*/
csp->num_local_variables = EXTRACT_UCHAR(pc++) + num_varargs;
num_varargs = 0;
// if(offset > USHRT_MAX)
// error("Broken function table"); offset is a USHRT, so this just can't
// happen!
funp = setup_new_frame(offset);
csp->num_local_variables = pushed_args;
auto *funp = setup_new_frame(offset);
csp->pc = pc; /* The corrected return address */
pc = current_prog->program + funp->address;
if (Tracer::enabled()) {
@ -3881,9 +3963,9 @@ void eval_instruction(char *p) {
}
#endif
} /* switch (instruction) */
DEBUG_CHECK2(sp < fp + csp->num_local_variables - 1,
"Bad stack after evaluation. Instruction '%s' (%d) \n", instrs[instruction].name,
instruction);
DEBUG_CHECK2(sp < fp + csp->num_local_variables - 1 && dump_vm_state(),
"Bad stack after evaluation. Instruction '%s' (%d) \n",
instrs[instruction].name,instruction);
#if defined(DEBUG) && 0 // super slow
{
svalue_t *current_stack = sp;

View file

@ -162,7 +162,6 @@ svalue_t *safe_call_function_pointer(funptr_t *, int);
void call___INIT(object_t *);
array_t *call_all_other(array_t *, const char *, int);
const char *function_exists(const char *, object_t *, int);
void call_function(program_t *, int);
void mark_apply_low_cache(void);
void translate_absolute_line(int, unsigned short *, int *, int *);
char *add_slash(const char *const);
@ -225,4 +224,6 @@ inline const char *access_to_name(int mode) {
void get_explicit_line_number_info(char *, const program_t *, const char **, int *);
int last_instructions();
void push_undefineds(int num);
#endif /* _INTERPRET_H */

View file

@ -126,8 +126,9 @@
#define LOCAL_MOD_REF 0x0100
#define LOCAL_MOD_UNUSED 0x0200
#define LOCAL_MOD_DEFAULT 0x400
#define LOCAL_MODS (LOCAL_MOD_UNUSED | LOCAL_MOD_REF)
#define LOCAL_MODS (LOCAL_MOD_UNUSED | LOCAL_MOD_REF | LOCAL_MOD_DEFAULT)
typedef struct {
unsigned char num_arg;
@ -164,9 +165,16 @@ typedef struct {
struct function_t {
const char *funcname;
unsigned short type;
unsigned char num_arg;
uint8_t num_arg;
uint8_t min_arg;
unsigned char num_local;
ADDRESS_TYPE address;
// Default args can only be specified in a continuous trailing format
// and because their function is always generated after the original function
// the findex value can never be 0, we can use 0 as null value for easy initialization.
// The default args are stored in the order they are specified in the source code.
// TODO: this limits the function that uses default args to only have 16 args.
uint16_t default_args_findex[16];
#ifdef PROFILE_FUNCTIONS
unsigned long calls, self, children;
#endif

View file

@ -23,6 +23,7 @@
#include "interactive.h" // for interactive_t, FIXME
#include "vm/internal/apply.h"
#include "vm/internal/base/machine.h"
#include "vm/internal/base/debug.h"
#include "vm/internal/master.h"
#include "vm/internal/otable.h"
#include "vm/internal/simul_efun.h"
@ -1662,7 +1663,7 @@ void free_sentence(sentence_t *p) {
if (current_object) {
debug_message("(current object was /%s)\n", current_object->obname);
}
dump_vm_state();
dump_trace(1);
#ifdef PACKAGE_MUDLIB_STATS
save_stat_files();

View file

@ -12,7 +12,21 @@ public string clear_last_error() {
last_error = "";
}
// find stack right before __assert
private mapping* trace_to_last_assert() {
mapping *trace = dump_trace();
for (int i = 0; i < sizeof(trace); i++) {
if (trace[i]["function"][0..7] == "__assert") {
return trace[0..i];
}
}
return trace;
}
public string get_last_error() {
if (last_error == "") {
return sprintf("%O", trace_to_last_assert());
}
return last_error;
}

View file

@ -0,0 +1,65 @@
// default case
void test1() {
}
// default case
void test2(int a, int b) {
ASSERT_EQ(a, 1);
ASSERT_EQ(b, 2);
}
// varargs
void test3(int a, int* b ...) {
ASSERT_EQ(a, 1);
ASSERT_EQ(b[0], 2);
ASSERT_EQ(b[1], 3);
ASSERT_EQ(b[2], 4);
ASSERT_EQ(b[3], 5);
}
// can have multiple trailing arguments with a FP for calculating default value
void test4(int a, string b: (: "str" :), int c: (: -1 :)) {
int x = 111, y = 222; // two local variables.
switch(a) {
case 1: {
ASSERT_EQ("str", b);
ASSERT_EQ(-1, c);
break;
}
case 2: {
ASSERT_EQ("aaa", b);
ASSERT_EQ(-1, c);
break;
}
case 3: {
ASSERT_EQ("bbb", b);
ASSERT_EQ(1, c);
break;
}
}
ASSERT_EQ(111, x);
ASSERT_EQ(222, y);
}
object test5(object a: (: this_object() :)) {
return a;
}
void do_tests() {
test1();
test2(1, 2);
test3(1, 2, 3, 4, 5);
// direct call
test4(1);
test4(2, "aaa");
test4(3, "bbb", 1);
// apply
this_object()->test4(1);
this_object()->test4(2, "aaa");
this_object()->test4(3, "bbb", 1);
// making sure the default value is calculated in the caller's context
ASSERT_EQ(this_object(), test5());
ASSERT_EQ(this_object(), test5(this_object()));
// see call_other type of tests in function2.c
// see inherited type of tests in function3.c
}

View file

@ -0,0 +1,16 @@
#define FUNCTION_OBJ "/single/tests/compiler/function"
#define FUNCTION_OBJ_3 "/single/tests/compiler/function3"
void do_tests() {
FUNCTION_OBJ->test4(1);
FUNCTION_OBJ->test4(2, "aaa");
FUNCTION_OBJ->test4(3, "bbb", 1);
// making sure the default value is calculated in the caller's context
ASSERT_EQ(this_object(), FUNCTION_OBJ->test5());
FUNCTION_OBJ_3->test4(1);
FUNCTION_OBJ_3->test4(2, "aaa");
FUNCTION_OBJ_3->test4(3, "bbb", 1);
// making sure the default value is calculated in the caller's context
ASSERT_EQ(this_object(), FUNCTION_OBJ_3->test5());
}

View file

@ -0,0 +1,17 @@
#define FUNCTION_OBJ "/single/tests/compiler/function"
inherit FUNCTION_OBJ;
void do_tests() {
test1();
test2(1, 2);
test3(1, 2, 3, 4, 5);
// direct call
test4(1);
test4(2, "aaa");
test4(3, "bbb", 1);
// apply
this_object()->test4(1);
this_object()->test4(2, "aaa");
this_object()->test4(3, "bbb", 1);
}