mirror of
https://github.com/fluffos/fluffos
synced 2026-08-12 18:26:06 -04:00
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:
parent
1fd7f61df3
commit
bcb8e91a53
27 changed files with 1983 additions and 1395 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
111
src/vm/internal/base/debug.cc
Normal file
111
src/vm/internal/base/debug.cc
Normal 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
|
||||
}
|
||||
1
src/vm/internal/base/debug.h
Normal file
1
src/vm/internal/base/debug.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
bool dump_vm_state();
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
65
testsuite/single/tests/compiler/function.c
Normal file
65
testsuite/single/tests/compiler/function.c
Normal 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
|
||||
}
|
||||
16
testsuite/single/tests/compiler/function2.c
Normal file
16
testsuite/single/tests/compiler/function2.c
Normal 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());
|
||||
}
|
||||
17
testsuite/single/tests/compiler/function3.c
Normal file
17
testsuite/single/tests/compiler/function3.c
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue