From 72933f4bf6cbc91acb391073fa8bf1f4eb2691d9 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Fri, 24 Oct 2025 00:03:43 +0800 Subject: [PATCH] :sparkles: finish caller Signed-off-by: ValKmjolnir --- src/nasal_parse.cpp | 2344 +++++++++++++++++++-------------------- src/nasal_parse.h | 338 +++--- src/nasal_type.cpp | 580 +++++----- src/nasal_type.h | 764 +++++++------ src/nasal_vm.cpp | 1656 ++++++++++++++------------- src/nasal_vm.h | 21 +- src/natives/builtin.cpp | 1749 ++++++++++++++--------------- test/caller.nas | 24 + test/coroutine.nas | 4 +- 9 files changed, 3759 insertions(+), 3721 deletions(-) create mode 100644 test/caller.nas diff --git a/src/nasal_parse.cpp b/src/nasal_parse.cpp index 4a9bb80..e1c67d6 100644 --- a/src/nasal_parse.cpp +++ b/src/nasal_parse.cpp @@ -1,1172 +1,1172 @@ -#include "nasal_ast.h" -#include "nasal_parse.h" -#include "util/util.h" - -namespace nasal { - -const error& parse::compile(const lexer& lexer) { - toks = lexer.result().data(); - ptr = in_func_depth = in_loop_depth = 0; - - root = new code_block(toks[0].loc); - - while (!lookahead(tok::tk_eof)) { - root->add_expression(expression()); - if (lookahead(tok::tk_semi)) { - match(tok::tk_semi); - } else if (need_semi_check(root->get_expressions().back()) && - !lookahead(tok::tk_eof)) { - // the last expression can be recognized without semi - die(prevspan, "expected \";\" after this token"); - } - } - update_location(root); - return err; -} - -void parse::easter_egg() { - // do you remember this text drawing in old versions? - std::clog - << " _,,,_ \n" - << " .' `'. \n" - << " / ____ \\ Fucking Nasal Parser \n" - << " | .-'_ _\\/ / \n" - << " \\_/ a a| / \n" - << " (,` \\ | .----. \n" - << " | -' | /| '--. \n" - << " \\ '= / || ]| `-. \n" - << " /`-.__.' || ]| ::| \n" - << " .-'`-.__ \\__ || ]| ::| \n" - << " / `` `. || ]| ::| \n" - << " _ | \\ \\ \\ \\| ]| .-' \n" - << " / \\| \\ | \\ L.__ .--'( \n" - << " | |\\ `. | \\ ,---|_ \\---------, \n" - << " | | '. './\\ \\/ .--._|=- |_ /| \n" - << " | \\ '. `'.'. /`\\/ .-' '. / | \n" - << " | | `'. `;-:-;`)| |-./ | \n" - << " | /_ `'--./_ ` )/'-------------')/) | \n" - << " \\ | `\"\"\"\"----\"`\\//`\"\"`/,===..'`````````/ ( |\n" - << " | | / `---` `===' / ) | \n" - << " / \\ / / ( | \n" - << " | '------. |'--------------------'| ) | \n" - << " \\ `-| | / | \n" - << " `--...,______| | ( | \n" - << " | | | | ) ,| \n" - << " | | | | ( /|| \n" - << " | | | | )/ `\" \n" - << " / \\ | | (/ \n" - << " .' /I\\ '.| | /) \n" - << " .-'_.'/ \\'. | | / \n" - << " ``` `\"\"\"` `| .-------------------.|| \n" - << " `\"` `\"` \n"; -} - -void parse::die(const span& loc, const std::string& info) { - err.err("parse", loc, info); -} - -void parse::next() { - if (lookahead(tok::tk_eof)) { - return; - } - ++ptr; -} - -void parse::match(tok type, const char* info) { - if (!lookahead(type)) { - if (info) { - die(thisspan, info); - return; - } - switch(type) { - case tok::tk_num: die(thisspan, "expected number"); break; - case tok::tk_str: die(thisspan, "expected string"); break; - case tok::tk_id: die(thisspan, "expected identifier"); break; - default: - die(thisspan, - "expected \"" + token_name_mapper.at(type)+"\"" - ); - break; - } - return; - } - next(); -} - -bool parse::lookahead(tok type) { - return toks[ptr].type == type; -} - -bool parse::lookahead_expression() { - switch (toks[ptr].type) { - case tok::tk_nil: - case tok::tk_num: - case tok::tk_str: - case tok::tk_id: - case tok::tk_true: - case tok::tk_false: - case tok::tk_func: - case tok::tk_lbracket: - case tok::tk_lbrace: - case tok::tk_sub: - case tok::tk_floater: - case tok::tk_not: - case tok::tk_var: - case tok::tk_lcurve: - case tok::tk_for: - case tok::tk_forindex: - case tok::tk_foreach: - case tok::tk_while: - case tok::tk_if: - case tok::tk_cont: - case tok::tk_brk: - case tok::tk_ret: return true; - default: return false; - } - return false; -} - -bool parse::is_call(tok type) { - return type==tok::tk_lcurve || type==tok::tk_lbracket || - type==tok::tk_dot || type==tok::tk_quesdot; -} - -bool parse::check_comma(const tok* panic_set) { - for (u32 i = 0; panic_set[i]!=tok::tk_null; ++i) { - if (lookahead(panic_set[i])) { - die(prevspan, "expected \",\" between scalars"); - return true; - } - } - return false; -} - -bool parse::check_tuple() { - u64 check_ptr = ptr, curve = 1, bracket = 0, brace = 0; - while (toks[++check_ptr].type!=tok::tk_eof && curve) { - switch(toks[check_ptr].type) { - case tok::tk_lcurve: ++curve; break; - case tok::tk_lbracket: ++bracket; break; - case tok::tk_lbrace: ++brace; break; - case tok::tk_rcurve: --curve; break; - case tok::tk_rbracket: --bracket; break; - case tok::tk_rbrace: --brace; break; - default: break; - } - if (curve==1 && !bracket && !brace && - toks[check_ptr].type==tok::tk_comma) { - return true; - } - } - return false; -} - -bool parse::check_func_end(expr* node) { - // avoid error parse caused nullptr return value - if (!node) { - return true; - } - const auto type = node->get_type(); - if (type==expr_type::ast_func) { - return true; - } else if (type==expr_type::ast_def) { - return check_func_end( - reinterpret_cast(node)->get_value() - ); - } else if (type==expr_type::ast_assign) { - return check_func_end( - reinterpret_cast(node)->get_right() - ); - } - return false; -} - -bool parse::check_in_curve_multi_definition() { - // we do not allow syntax like: - // func {}(var a, b, c) - // but we still allow syntax like: - // func {}(var a = 1) - // in fact, this syntax is not recommended - if (!lookahead(tok::tk_lcurve) || toks[ptr+1].type!=tok::tk_var) { - return false; - } - return toks[ptr+2].type==tok::tk_id && toks[ptr+3].type==tok::tk_comma; -} - -bool parse::check_special_call() { - // special call means like this: function_name(a:1, b:2, c:3); - u64 check_ptr = ptr, curve = 1, bracket = 0, brace = 0; - while (toks[++check_ptr].type!=tok::tk_eof && curve) { - switch(toks[check_ptr].type) { - case tok::tk_lcurve: ++curve; break; - case tok::tk_lbracket: ++bracket;break; - case tok::tk_lbrace: ++brace; break; - case tok::tk_rcurve: --curve; break; - case tok::tk_rbracket: --bracket;break; - case tok::tk_rbrace: --brace; break; - default: break; - } - // m?1:0 will be recognized as normal parameter - if (curve==1 && !bracket && !brace && - toks[check_ptr].type==tok::tk_quesmark) { - return false; - } - if (curve==1 && !bracket && !brace && - toks[check_ptr].type==tok::tk_colon) { - return true; - } - } - return false; -} - -bool parse::need_semi_check(expr* node) { - // avoid error parse caused nullptr return value - if (!node) { - return true; - } - auto type = node->get_type(); - if (type==expr_type::ast_for || - type==expr_type::ast_forei || - type==expr_type::ast_while || - type==expr_type::ast_cond) { - return false; - } - return !check_func_end(node); -} - -void parse::update_location(expr* node) { - if (!ptr) { - return; - } - node->update_location(toks[ptr-1].loc); -} - -use_stmt* parse::use_stmt_gen() { - auto node = new use_stmt(toks[ptr].loc); - match(tok::tk_use); - node->add_path(id()); - while (lookahead(tok::tk_dot)) { - match(tok::tk_dot); - node->add_path(id()); - } - update_location(node); - return node; -} - -null_expr* parse::null() { - return new null_expr(toks[ptr].loc); -} - -nil_expr* parse::nil() { - return new nil_expr(toks[ptr].loc); -} - -number_literal* parse::num() { - auto node = new number_literal( - toks[ptr].loc, - util::str_to_num(toks[ptr].str.c_str()), - toks[ptr].str - ); - match(tok::tk_num); - return node; -} - -string_literal* parse::str() { - auto node = new string_literal(toks[ptr].loc, toks[ptr].str); - match(tok::tk_str); - return node; -} - -identifier* parse::id() { - auto node = new identifier(toks[ptr].loc, toks[ptr].str); - match(tok::tk_id); - return node; -} - -bool_literal* parse::bools() { - auto node = new bool_literal(toks[ptr].loc, toks[ptr].str=="true"); - if (lookahead(tok::tk_true)) { - match(tok::tk_true); - } else { - match(tok::tk_false); - } - return node; -} - -vector_expr* parse::vec() { - // panic set for this token is not ',' - // this is the FIRST set of calculation - // array end with tok::tk_null = 0 - const tok panic[] = { - tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, - tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, - tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, - tok::tk_lbrace, tok::tk_lbracket, tok::tk_null - }; - auto node = new vector_expr(toks[ptr].loc); - match(tok::tk_lbracket); - while (!lookahead(tok::tk_rbracket)) { - node->add_element(calc()); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_eof)) { - break; - } else if (!lookahead(tok::tk_rbracket) && !check_comma(panic)) { - break; - } - } - update_location(node); - match(tok::tk_rbracket, "expected \"]\" when generating vector"); - return node; -} - -hash_expr* parse::hash() { - auto node = new hash_expr(toks[ptr].loc); - match(tok::tk_lbrace); - while (!lookahead(tok::tk_rbrace)) { - node->add_member(pair()); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_id) || lookahead(tok::tk_str)) { - // first set of hashmember - die(prevspan, "expected \",\" between hash members"); - } else { - break; - } - } - update_location(node); - match(tok::tk_rbrace, "expected \"}\" when generating hash"); - return node; -} - -hash_pair* parse::pair() { - auto node = new hash_pair(toks[ptr].loc); - if (lookahead(tok::tk_id)) { - node->set_name(toks[ptr].str); - match(tok::tk_id); - } else if (lookahead(tok::tk_str)) { - node->set_name(toks[ptr].str); - match(tok::tk_str); - } else { - match(tok::tk_id, "expected hashmap key"); - } - match(tok::tk_colon); - node->set_value(calc()); - update_location(node); - return node; -} - -function* parse::func() { - ++in_func_depth; - auto node = new function(toks[ptr].loc); - match(tok::tk_func); - if (lookahead(tok::tk_lcurve)) { - params(node); - } - node->set_code_block(expression_block()); - --in_func_depth; - update_location(node); - return node; -} - -void parse::params(function* func_node) { - match(tok::tk_lcurve); - while (!lookahead(tok::tk_rcurve)) { - auto param = new parameter(toks[ptr].loc); - param->set_parameter_name(toks[ptr].str); - match(tok::tk_id); - if (lookahead(tok::tk_eq)) { - match(tok::tk_eq); - param->set_parameter_type(parameter::kind::default_parameter); - param->set_default_value(calc()); - } else if (lookahead(tok::tk_ellipsis)) { - match(tok::tk_ellipsis); - param->set_parameter_type(parameter::kind::dynamic_parameter); - } else { - param->set_parameter_type(parameter::kind::normal_parameter); - } - update_location(param); - func_node->add_parameter(param); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_id)) { // first set of identifier - die(prevspan, "expected \",\" between identifiers"); - } else { - break; - } - } - update_location(func_node); - match(tok::tk_rcurve, "expected \")\" after parameter list"); - return; -} - -expr* parse::lcurve_expr() { - if (toks[ptr+1].type==tok::tk_var) - return definition(); - return check_tuple()? multi_assignment():calc(); -} - -expr* parse::expression() { - tok type = toks[ptr].type; - if ((type == tok::tk_brk || type == tok::tk_cont) && !in_loop_depth) { - die(thisspan, "must use break/continue in loops"); - } - switch (type) { - case tok::tk_use: return use_stmt_gen(); - case tok::tk_nil: - case tok::tk_num: - case tok::tk_str: - case tok::tk_id: - case tok::tk_true: - case tok::tk_false: - case tok::tk_func: - case tok::tk_lbracket: - case tok::tk_lbrace: - case tok::tk_sub: - case tok::tk_floater: - case tok::tk_not: return calc(); - case tok::tk_var: return definition(); - case tok::tk_lcurve: return lcurve_expr(); - case tok::tk_for: - case tok::tk_forindex: - case tok::tk_foreach: - case tok::tk_while: return loop(); - case tok::tk_if: return cond(); - case tok::tk_cont: return continue_expression(); - case tok::tk_brk: return break_expression(); - case tok::tk_ret: return return_expression(); - case tok::tk_semi: break; - default: - die(thisspan, "incorrect token <"+toks[ptr].str+">"); - next(); - break; - } - - // unreachable - return new null_expr(toks[ptr].loc); -} - -code_block* parse::expression_block() { - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected expression block"); - return new code_block(toks[ptr].loc); - } - auto node = new code_block(toks[ptr].loc); - if (lookahead(tok::tk_lbrace)) { - match(tok::tk_lbrace); - while (!lookahead(tok::tk_rbrace) && !lookahead(tok::tk_eof)) { - node->add_expression(expression()); - if (lookahead(tok::tk_semi)) { - match(tok::tk_semi); - } else if (need_semi_check(node->get_expressions().back()) && - !lookahead(tok::tk_rbrace)) { - // the last expression can be recognized without semi - die(prevspan, "expected \";\" after this token"); - } - } - match(tok::tk_rbrace, "expected \"}\" when generating expressions"); - } else if (lookahead_expression()) { - node->add_expression(expression()); - if (lookahead(tok::tk_semi)) { - match(tok::tk_semi); - } - } - update_location(node); - return node; -} - -expr* parse::calc() { - auto node = bitwise_or(); - if (lookahead(tok::tk_quesmark)) { - // trinocular calculation - auto tmp = new ternary_operator(toks[ptr].loc); - match(tok::tk_quesmark); - tmp->set_condition(node); - tmp->set_left(calc()); - match(tok::tk_colon); - tmp->set_right(calc()); - node = tmp; - } else if (tok::tk_eq<=toks[ptr].type && toks[ptr].type<=tok::tk_lnkeq) { - auto tmp = new assignment_expr(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_eq: tmp->set_assignment_type(assignment_expr::kind::equal); break; - case tok::tk_addeq: tmp->set_assignment_type(assignment_expr::kind::add_equal); break; - case tok::tk_subeq: tmp->set_assignment_type(assignment_expr::kind::sub_equal); break; - case tok::tk_multeq: tmp->set_assignment_type(assignment_expr::kind::mult_equal); break; - case tok::tk_diveq: tmp->set_assignment_type(assignment_expr::kind::div_equal); break; - case tok::tk_lnkeq: tmp->set_assignment_type(assignment_expr::kind::concat_equal); break; - default: break; - } - tmp->set_left(node); - match(toks[ptr].type); - tmp->set_right(calc()); - node = tmp; - } else if (toks[ptr].type==tok::tk_btandeq || - toks[ptr].type==tok::tk_btoreq || - toks[ptr].type==tok::tk_btxoreq) { - auto tmp = new assignment_expr(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_btandeq: tmp->set_assignment_type(assignment_expr::kind::bitwise_and_equal); break; - case tok::tk_btoreq: tmp->set_assignment_type(assignment_expr::kind::bitwise_or_equal); break; - case tok::tk_btxoreq: tmp->set_assignment_type(assignment_expr::kind::bitwise_xor_equal); break; - default: break; - } - tmp->set_left(node); - match(toks[ptr].type); - tmp->set_right(calc()); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::bitwise_or() { - auto node = bitwise_xor(); - while (lookahead(tok::tk_btor)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::bitwise_or); - tmp->set_left(node); - match(tok::tk_btor); - tmp->set_right(bitwise_xor()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::bitwise_xor() { - auto node = bitwise_and(); - while (lookahead(tok::tk_btxor)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::bitwise_xor); - tmp->set_left(node); - match(tok::tk_btxor); - tmp->set_right(bitwise_and()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::bitwise_and() { - auto node = or_expr(); - while (lookahead(tok::tk_btand)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::bitwise_and); - tmp->set_left(node); - match(tok::tk_btand); - tmp->set_right(or_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::or_expr() { - auto node = and_expr(); - while (lookahead(tok::tk_or)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::condition_or); - tmp->set_left(node); - match(tok::tk_or); - tmp->set_right(and_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::and_expr() { - auto node = cmp_expr(); - while (lookahead(tok::tk_and)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::condition_and); - tmp->set_left(node); - match(tok::tk_and); - tmp->set_right(cmp_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::cmp_expr() { - auto node = null_chain_expr(); - while (tok::tk_cmpeq<=toks[ptr].type && toks[ptr].type<=tok::tk_geq) { - auto tmp = new binary_operator(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_cmpeq: tmp->set_operator_type(binary_operator::kind::cmpeq); break; - case tok::tk_neq: tmp->set_operator_type(binary_operator::kind::cmpneq); break; - case tok::tk_less: tmp->set_operator_type(binary_operator::kind::less); break; - case tok::tk_leq: tmp->set_operator_type(binary_operator::kind::leq); break; - case tok::tk_grt: tmp->set_operator_type(binary_operator::kind::grt); break; - case tok::tk_geq: tmp->set_operator_type(binary_operator::kind::geq); break; - default: break; - } - tmp->set_left(node); - match(toks[ptr].type); - tmp->set_right(null_chain_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::null_chain_expr() { - auto node = additive_expr(); - while (lookahead(tok::tk_quesques)) { - auto tmp = new binary_operator(toks[ptr].loc); - tmp->set_operator_type(binary_operator::kind::null_chain); - tmp->set_left(node); - match(tok::tk_quesques); - tmp->set_right(additive_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::additive_expr() { - auto node = multive_expr(); - while (lookahead(tok::tk_add) || - lookahead(tok::tk_sub) || - lookahead(tok::tk_floater)) { - auto tmp = new binary_operator(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_add: tmp->set_operator_type(binary_operator::kind::add); break; - case tok::tk_sub: tmp->set_operator_type(binary_operator::kind::sub); break; - case tok::tk_floater: tmp->set_operator_type(binary_operator::kind::concat); break; - default: break; - } - tmp->set_left(node); - match(toks[ptr].type); - tmp->set_right(multive_expr()); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -expr* parse::multive_expr() { - expr* node=(lookahead(tok::tk_sub) || - lookahead(tok::tk_not) || - lookahead(tok::tk_floater))? unary():scalar(); - while (lookahead(tok::tk_mult) || lookahead(tok::tk_div)) { - auto tmp = new binary_operator(toks[ptr].loc); - if (lookahead(tok::tk_mult)) { - tmp->set_operator_type(binary_operator::kind::mult); - } else { - tmp->set_operator_type(binary_operator::kind::div); - } - tmp->set_left(node); - match(toks[ptr].type); - tmp->set_right( - (lookahead(tok::tk_sub) || - lookahead(tok::tk_not) || - lookahead(tok::tk_floater))? unary():scalar() - ); - update_location(tmp); - node = tmp; - } - update_location(node); - return node; -} - -unary_operator* parse::unary() { - auto node = new unary_operator(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_sub: - node->set_operator_type(unary_operator::kind::negative); - match(tok::tk_sub); - break; - case tok::tk_not: - node->set_operator_type(unary_operator::kind::logical_not); - match(tok::tk_not); - break; - case tok::tk_floater: - node->set_operator_type(unary_operator::kind::bitwise_not); - match(tok::tk_floater); - break; - default: break; - } - node->set_value( - (lookahead(tok::tk_sub) || - lookahead(tok::tk_not) || - lookahead(tok::tk_floater))? unary():scalar() - ); - update_location(node); - return node; -} - -expr* parse::scalar() { - expr* node = nullptr; - if (lookahead(tok::tk_nil)) { - node = nil(); - match(tok::tk_nil); - } else if (lookahead(tok::tk_num)) { - node = num(); - } else if (lookahead(tok::tk_str)) { - node = str(); - } else if (lookahead(tok::tk_id)) { - node = id(); - } else if (lookahead(tok::tk_true) || lookahead(tok::tk_false)) { - node = bools(); - } else if (lookahead(tok::tk_func)) { - node = func(); - } else if (lookahead(tok::tk_lbracket)) { - node = vec(); - } else if (lookahead(tok::tk_lbrace)) { - node = hash(); - } else if (lookahead(tok::tk_lcurve)) { - const auto& loc = toks[ptr].loc; - match(tok::tk_lcurve); - node = calc(); - node->set_begin(loc.begin_line, loc.begin_column); - update_location(node); - match(tok::tk_rcurve); - } else if (lookahead(tok::tk_var)) { - match(tok::tk_var); - auto def_node = new definition_expr(toks[ptr].loc); - def_node->set_identifier(id()); - match(tok::tk_eq); - def_node->set_value(calc()); - node = def_node; - } else { - die(thisspan, "expected scalar"); - return null(); - } - // check call and avoid ambiguous syntax: - // var f = func() {} - // (var a, b, c) = (1, 2, 3); - // will be incorrectly recognized like: - // var f = func() {}(var a, b, c) - if (is_call(toks[ptr].type) && !check_in_curve_multi_definition()) { - auto call_node = new call_expr(toks[ptr].loc); - call_node->set_first(node); - while (is_call(toks[ptr].type)) { - call_node->add_call(call_scalar()); - } - node = call_node; - } - update_location(node); - return node; -} - -call* parse::call_scalar() { - switch(toks[ptr].type) { - case tok::tk_lcurve: return callf(); break; - case tok::tk_lbracket: return callv(); break; - case tok::tk_dot: return callh(); break; - case tok::tk_quesdot: return null_access_call(); break; - default: break; - } - // unreachable - return new call(toks[ptr].loc, expr_type::ast_null); -} - -call_hash* parse::callh() { - const auto& begin_loc = toks[ptr].loc; - match(tok::tk_dot); - auto node = new call_hash(begin_loc, toks[ptr].str); - update_location(node); - match(tok::tk_id, "expected hashmap key"); // get key - return node; -} - -null_access* parse::null_access_call() { - const auto& begin_loc = toks[ptr].loc; - match(tok::tk_quesdot); - auto node = new null_access(begin_loc, toks[ptr].str); - update_location(node); - match(tok::tk_id, "expected hashmap key"); // get key - return node; -} - -call_vector* parse::callv() { - // panic set for this token is not ',' - // this is the FIRST set of subvec - // array end with tok::tk_null = 0 - const tok panic[] = { - tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, - tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, - tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, - tok::tk_lbrace, tok::tk_lbracket, tok::tk_colon, tok::tk_null - }; - auto node = new call_vector(toks[ptr].loc); - match(tok::tk_lbracket); - while (!lookahead(tok::tk_rbracket)) { - node->add_slice(subvec()); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_eof)) { - break; - } else if (!lookahead(tok::tk_rbracket) && !check_comma(panic)) { - break; - } - } - if (node->get_slices().size()==0) { - die(thisspan, "expected index value"); - } - update_location(node); - match(tok::tk_rbracket, "expected \"]\" when calling vector"); - return node; -} - -call_function* parse::callf() { - // panic set for this token is not ',' - // this is the FIRST set of calculation/hashmember - // array end with tok::tk_null = 0 - const tok panic[] = { - tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, - tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, - tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, - tok::tk_lbrace, tok::tk_lbracket, tok::tk_null - }; - auto node = new call_function(toks[ptr].loc); - bool special_call=check_special_call(); - match(tok::tk_lcurve); - while (!lookahead(tok::tk_rcurve)) { - node->add_argument(special_call?pair():calc()); - if (lookahead(tok::tk_comma)) - match(tok::tk_comma); - else if (lookahead(tok::tk_eof)) - break; - else if (!lookahead(tok::tk_rcurve) && !check_comma(panic)) - break; - } - update_location(node); - match(tok::tk_rcurve, "expected \")\" when calling function"); - return node; -} - -slice_vector* parse::subvec() { - auto node = new slice_vector(toks[ptr].loc); - node->set_begin(lookahead(tok::tk_colon)?nil():calc()); - if (lookahead(tok::tk_colon)) { - match(tok::tk_colon); - node->set_end( - (lookahead(tok::tk_comma) || lookahead(tok::tk_rbracket))? - nil(): - calc() - ); - } - update_location(node); - return node; -} - -expr* parse::definition() { - auto node = new definition_expr(toks[ptr].loc); - if (lookahead(tok::tk_var)) { - match(tok::tk_var); - switch(toks[ptr].type) { - case tok::tk_id: node->set_identifier(id());break; - case tok::tk_lcurve: node->set_multi_define(outcurve_def());break; - default: die(thisspan, "expected identifier");break; - } - } else if (lookahead(tok::tk_lcurve)) { - node->set_multi_define(incurve_def()); - } - match(tok::tk_eq); - if (lookahead(tok::tk_lcurve)) { - check_tuple()? - node->set_tuple(multi_scalar()): - node->set_value(calc()); - } else { - node->set_value(calc()); - } - update_location(node); - return node; -} - -multi_identifier* parse::incurve_def() { - const auto& loc = toks[ptr].loc; - match(tok::tk_lcurve); - match(tok::tk_var); - auto node = multi_id(); - update_location(node); - match(tok::tk_rcurve); - node->set_begin(loc.begin_line, loc.begin_column); - return node; -} - -multi_identifier* parse::outcurve_def() { - const auto& loc = toks[ptr].loc; - match(tok::tk_lcurve); - auto node = multi_id(); - update_location(node); - match(tok::tk_rcurve); - node->set_begin(loc.begin_line, loc.begin_column); - return node; -} - -multi_identifier* parse::multi_id() { - auto node = new multi_identifier(toks[ptr].loc); - while (!lookahead(tok::tk_eof)) { - // only identifier is allowed here - node->add_var(id()); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_id)) { // first set of identifier - die(prevspan, "expected \",\" between identifiers"); - } else { - break; - } - } - update_location(node); - return node; -} - -tuple_expr* parse::multi_scalar() { - // if check_call_memory is true, - // we will check if value called here can reach a memory space - const tok panic[] = { - tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, - tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, - tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, - tok::tk_lbrace, tok::tk_lbracket, tok::tk_null - }; - auto node = new tuple_expr(toks[ptr].loc); - match(tok::tk_lcurve); - while (!lookahead(tok::tk_rcurve)) { - node->add_element(calc()); - if (lookahead(tok::tk_comma)) { - match(tok::tk_comma); - } else if (lookahead(tok::tk_eof)) { - break; - } else if (!lookahead(tok::tk_rcurve) && !check_comma(panic)) { - break; - } - } - update_location(node); - match(tok::tk_rcurve, "expected \")\" after multi-scalar"); - return node; -} - -multi_assign* parse::multi_assignment() { - auto node = new multi_assign(toks[ptr].loc); - node->set_tuple(multi_scalar()); - match(tok::tk_eq); - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected value list"); - return node; - } - if (lookahead(tok::tk_lcurve)) { - node->set_value(check_tuple()?multi_scalar():calc()); - } else { - node->set_value(calc()); - } - update_location(node); - return node; -} - -expr* parse::loop() { - ++in_loop_depth; - expr* node = nullptr; - switch(toks[ptr].type) { - case tok::tk_while: node = while_loop(); break; - case tok::tk_for: node = for_loop(); break; - case tok::tk_forindex: - case tok::tk_foreach: node = forei_loop(); break; - default: - die(thisspan, "unreachable"); - node = null(); - break; - } - --in_loop_depth; - return node; -} - -while_expr* parse::while_loop() { - auto node = new while_expr(toks[ptr].loc); - match(tok::tk_while); - match(tok::tk_lcurve); - node->set_condition(calc()); - match(tok::tk_rcurve); - node->set_code_block(expression_block()); - update_location(node); - return node; -} - -for_expr* parse::for_loop() { - auto node = new for_expr(toks[ptr].loc); - match(tok::tk_for); - match(tok::tk_lcurve); - - // first expression - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected definition"); - } - if (lookahead(tok::tk_semi)) { - node->set_initial(null()); - } else if (lookahead(tok::tk_var)) { - node->set_initial(definition()); - } else if (lookahead(tok::tk_lcurve)) { - node->set_initial(lcurve_expr()); - } else { - node->set_initial(calc()); - } - match(tok::tk_semi, "expected \";\" in for (;;)"); - - // conditional expression - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected conditional expr"); - } - if (lookahead(tok::tk_semi)) { - node->set_condition(null()); - } else { - node->set_condition(calc()); - } - match(tok::tk_semi, "expected \";\" in for (;;)"); - - //after loop expression - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected calculation"); - } - if (lookahead(tok::tk_rcurve)) { - node->set_step(null()); - } else { - node->set_step(calc()); - } - match(tok::tk_rcurve); - node->set_code_block(expression_block()); - update_location(node); - return node; -} - -forei_expr* parse::forei_loop() { - auto node = new forei_expr(toks[ptr].loc); - switch(toks[ptr].type) { - case tok::tk_forindex: - node->set_loop_type(forei_expr::kind::forindex); - match(tok::tk_forindex); - break; - case tok::tk_foreach: - node->set_loop_type(forei_expr::kind::foreach); - match(tok::tk_foreach); - break; - default: break; - } - match(tok::tk_lcurve); - // first expression - // foreach/forindex must have an iterator to loop through - if (!lookahead(tok::tk_var) && !lookahead(tok::tk_id)) { - die(thisspan, "expected iterator"); - } - node->set_iterator(iter_gen()); - match(tok::tk_semi, "expected \";\" in foreach/forindex(iter;vector)"); - if (lookahead(tok::tk_eof)) { - die(thisspan, "expected vector"); - } - node->set_value(calc()); - match(tok::tk_rcurve); - node->set_code_block(expression_block()); - update_location(node); - return node; -} - -iter_expr* parse::iter_gen() { - auto node = new iter_expr(toks[ptr].loc); - // definition - if (lookahead(tok::tk_var)) { - match(tok::tk_var); - node->set_name(id()); - node->set_is_definition(true); - update_location(node); - return node; - } - - // single symbol call - auto id_node = id(); - if (!is_call(toks[ptr].type)) { - node->set_name(id_node); - update_location(node); - return node; - } - - // call expression - auto tmp = new call_expr(id_node->get_location()); - tmp->set_first(id_node); - while (is_call(toks[ptr].type)) { - tmp->add_call(call_scalar()); - } - node->set_call(tmp); - update_location(node); - return node; -} - -condition_expr* parse::cond() { - auto node = new condition_expr(toks[ptr].loc); - - // generate if - auto ifnode = new if_expr(toks[ptr].loc); - match(tok::tk_if); - match(tok::tk_lcurve); - ifnode->set_condition(calc()); - match(tok::tk_rcurve); - ifnode->set_code_block(expression_block()); - update_location(ifnode); - node->set_if_statement(ifnode); - - // generate elsif - while (lookahead(tok::tk_elsif)) { - auto elsifnode = new if_expr(toks[ptr].loc); - match(tok::tk_elsif); - match(tok::tk_lcurve); - elsifnode->set_condition(calc()); - match(tok::tk_rcurve); - elsifnode->set_code_block(expression_block()); - update_location(elsifnode); - node->add_elsif_statement(elsifnode); - } - - // generate else - if (lookahead(tok::tk_else)) { - auto elsenode = new if_expr(toks[ptr].loc); - match(tok::tk_else); - elsenode->set_code_block(expression_block()); - update_location(elsenode); - node->set_else_statement(elsenode); - } - update_location(node); - return node; -} - -continue_expr* parse::continue_expression() { - auto node = new continue_expr(toks[ptr].loc); - match(tok::tk_cont); - return node; -} - -break_expr* parse::break_expression() { - auto node = new break_expr(toks[ptr].loc); - match(tok::tk_brk); - return node; -} - -return_expr* parse::return_expression() { - auto node = new return_expr(toks[ptr].loc); - match(tok::tk_ret); - tok type = toks[ptr].type; - if (type==tok::tk_nil || type==tok::tk_num || - type==tok::tk_str || type==tok::tk_id || - type==tok::tk_func || type==tok::tk_sub || - type==tok::tk_not || type==tok::tk_lcurve || - type==tok::tk_lbracket || type==tok::tk_lbrace || - type==tok::tk_true || type==tok::tk_false) { - node->set_value(calc()); - } else { - node->set_value(nil()); - } - update_location(node); - return node; -} - -} +#include "nasal_ast.h" +#include "nasal_parse.h" +#include "util/util.h" + +namespace nasal { + +const error& parse::compile(const lexer& lexer) { + toks = lexer.result().data(); + ptr = in_func_depth = in_loop_depth = 0; + + root = new code_block(toks[0].loc); + + while (!lookahead(tok::tk_eof)) { + root->add_expression(expression()); + if (lookahead(tok::tk_semi)) { + match(tok::tk_semi); + } else if (need_semi_check(root->get_expressions().back()) && + !lookahead(tok::tk_eof)) { + // the last expression can be recognized without semi + die(prevspan, "expected \";\" after this token"); + } + } + update_location(root); + return err; +} + +void parse::easter_egg() { + // do you remember this text drawing in old versions? + std::clog + << " _,,,_ \n" + << " .' `'. \n" + << " / ____ \\ Fucking Nasal Parser \n" + << " | .-'_ _\\/ / \n" + << " \\_/ a a| / \n" + << " (,` \\ | .----. \n" + << " | -' | /| '--. \n" + << " \\ '= / || ]| `-. \n" + << " /`-.__.' || ]| ::| \n" + << " .-'`-.__ \\__ || ]| ::| \n" + << " / `` `. || ]| ::| \n" + << " _ | \\ \\ \\ \\| ]| .-' \n" + << " / \\| \\ | \\ L.__ .--'( \n" + << " | |\\ `. | \\ ,---|_ \\---------, \n" + << " | | '. './\\ \\/ .--._|=- |_ /| \n" + << " | \\ '. `'.'. /`\\/ .-' '. / | \n" + << " | | `'. `;-:-;`)| |-./ | \n" + << " | /_ `'--./_ ` )/'-------------')/) | \n" + << " \\ | `\"\"\"\"----\"`\\//`\"\"`/,===..'`````````/ ( |\n" + << " | | / `---` `===' / ) | \n" + << " / \\ / / ( | \n" + << " | '------. |'--------------------'| ) | \n" + << " \\ `-| | / | \n" + << " `--...,______| | ( | \n" + << " | | | | ) ,| \n" + << " | | | | ( /|| \n" + << " | | | | )/ `\" \n" + << " / \\ | | (/ \n" + << " .' /I\\ '.| | /) \n" + << " .-'_.'/ \\'. | | / \n" + << " ``` `\"\"\"` `| .-------------------.|| \n" + << " `\"` `\"` \n"; +} + +void parse::die(const span& loc, const std::string& info) { + err.err("parse", loc, info); +} + +void parse::next() { + if (lookahead(tok::tk_eof)) { + return; + } + ++ptr; +} + +void parse::match(tok type, const char* info) { + if (!lookahead(type)) { + if (info) { + die(thisspan, info); + return; + } + switch(type) { + case tok::tk_num: die(thisspan, "expected number"); break; + case tok::tk_str: die(thisspan, "expected string"); break; + case tok::tk_id: die(thisspan, "expected identifier"); break; + default: + die(thisspan, + "expected \"" + token_name_mapper.at(type)+"\"" + ); + break; + } + return; + } + next(); +} + +bool parse::lookahead(tok type) { + return toks[ptr].type == type; +} + +bool parse::lookahead_expression() { + switch (toks[ptr].type) { + case tok::tk_nil: + case tok::tk_num: + case tok::tk_str: + case tok::tk_id: + case tok::tk_true: + case tok::tk_false: + case tok::tk_func: + case tok::tk_lbracket: + case tok::tk_lbrace: + case tok::tk_sub: + case tok::tk_floater: + case tok::tk_not: + case tok::tk_var: + case tok::tk_lcurve: + case tok::tk_for: + case tok::tk_forindex: + case tok::tk_foreach: + case tok::tk_while: + case tok::tk_if: + case tok::tk_cont: + case tok::tk_brk: + case tok::tk_ret: return true; + default: return false; + } + return false; +} + +bool parse::is_call(tok type) { + return type==tok::tk_lcurve || type==tok::tk_lbracket || + type==tok::tk_dot || type==tok::tk_quesdot; +} + +bool parse::check_comma(const tok* panic_set) { + for (u32 i = 0; panic_set[i]!=tok::tk_null; ++i) { + if (lookahead(panic_set[i])) { + die(prevspan, "expected \",\" between scalars"); + return true; + } + } + return false; +} + +bool parse::check_tuple() { + u64 check_ptr = ptr, curve = 1, bracket = 0, brace = 0; + while (toks[++check_ptr].type!=tok::tk_eof && curve) { + switch(toks[check_ptr].type) { + case tok::tk_lcurve: ++curve; break; + case tok::tk_lbracket: ++bracket; break; + case tok::tk_lbrace: ++brace; break; + case tok::tk_rcurve: --curve; break; + case tok::tk_rbracket: --bracket; break; + case tok::tk_rbrace: --brace; break; + default: break; + } + if (curve==1 && !bracket && !brace && + toks[check_ptr].type==tok::tk_comma) { + return true; + } + } + return false; +} + +bool parse::check_func_end(expr* node) { + // avoid error parse caused nullptr return value + if (!node) { + return true; + } + const auto type = node->get_type(); + if (type==expr_type::ast_func) { + return true; + } else if (type==expr_type::ast_def) { + return check_func_end( + reinterpret_cast(node)->get_value() + ); + } else if (type==expr_type::ast_assign) { + return check_func_end( + reinterpret_cast(node)->get_right() + ); + } + return false; +} + +bool parse::check_in_curve_multi_definition() { + // we do not allow syntax like: + // func {}(var a, b, c) + // but we still allow syntax like: + // func {}(var a = 1) + // in fact, this syntax is not recommended + if (!lookahead(tok::tk_lcurve) || toks[ptr+1].type!=tok::tk_var) { + return false; + } + return toks[ptr+2].type==tok::tk_id && toks[ptr+3].type==tok::tk_comma; +} + +bool parse::check_special_call() { + // special call means like this: function_name(a:1, b:2, c:3); + u64 check_ptr = ptr, curve = 1, bracket = 0, brace = 0; + while (toks[++check_ptr].type!=tok::tk_eof && curve) { + switch(toks[check_ptr].type) { + case tok::tk_lcurve: ++curve; break; + case tok::tk_lbracket: ++bracket;break; + case tok::tk_lbrace: ++brace; break; + case tok::tk_rcurve: --curve; break; + case tok::tk_rbracket: --bracket;break; + case tok::tk_rbrace: --brace; break; + default: break; + } + // m?1:0 will be recognized as normal parameter + if (curve==1 && !bracket && !brace && + toks[check_ptr].type==tok::tk_quesmark) { + return false; + } + if (curve==1 && !bracket && !brace && + toks[check_ptr].type==tok::tk_colon) { + return true; + } + } + return false; +} + +bool parse::need_semi_check(expr* node) { + // avoid error parse caused nullptr return value + if (!node) { + return true; + } + auto type = node->get_type(); + if (type==expr_type::ast_for || + type==expr_type::ast_forei || + type==expr_type::ast_while || + type==expr_type::ast_cond) { + return false; + } + return !check_func_end(node); +} + +void parse::update_location(expr* node) { + if (!ptr) { + return; + } + node->update_location(toks[ptr-1].loc); +} + +use_stmt* parse::use_stmt_gen() { + auto node = new use_stmt(toks[ptr].loc); + match(tok::tk_use); + node->add_path(id()); + while (lookahead(tok::tk_dot)) { + match(tok::tk_dot); + node->add_path(id()); + } + update_location(node); + return node; +} + +null_expr* parse::null() { + return new null_expr(toks[ptr].loc); +} + +nil_expr* parse::nil() { + return new nil_expr(toks[ptr].loc); +} + +number_literal* parse::num() { + auto node = new number_literal( + toks[ptr].loc, + util::str_to_num(toks[ptr].str.c_str()), + toks[ptr].str + ); + match(tok::tk_num); + return node; +} + +string_literal* parse::str() { + auto node = new string_literal(toks[ptr].loc, toks[ptr].str); + match(tok::tk_str); + return node; +} + +identifier* parse::id() { + auto node = new identifier(toks[ptr].loc, toks[ptr].str); + match(tok::tk_id); + return node; +} + +bool_literal* parse::bools() { + auto node = new bool_literal(toks[ptr].loc, toks[ptr].str=="true"); + if (lookahead(tok::tk_true)) { + match(tok::tk_true); + } else { + match(tok::tk_false); + } + return node; +} + +vector_expr* parse::vec() { + // panic set for this token is not ',' + // this is the FIRST set of calculation + // array end with tok::tk_null = 0 + const tok panic[] = { + tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, + tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, + tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, + tok::tk_lbrace, tok::tk_lbracket, tok::tk_null + }; + auto node = new vector_expr(toks[ptr].loc); + match(tok::tk_lbracket); + while (!lookahead(tok::tk_rbracket)) { + node->add_element(calc()); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_eof)) { + break; + } else if (!lookahead(tok::tk_rbracket) && !check_comma(panic)) { + break; + } + } + update_location(node); + match(tok::tk_rbracket, "expected \"]\" when generating vector"); + return node; +} + +hash_expr* parse::hash() { + auto node = new hash_expr(toks[ptr].loc); + match(tok::tk_lbrace); + while (!lookahead(tok::tk_rbrace)) { + node->add_member(pair()); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_id) || lookahead(tok::tk_str)) { + // first set of hashmember + die(prevspan, "expected \",\" between hash members"); + } else { + break; + } + } + update_location(node); + match(tok::tk_rbrace, "expected \"}\" when generating hash"); + return node; +} + +hash_pair* parse::pair() { + auto node = new hash_pair(toks[ptr].loc); + if (lookahead(tok::tk_id)) { + node->set_name(toks[ptr].str); + match(tok::tk_id); + } else if (lookahead(tok::tk_str)) { + node->set_name(toks[ptr].str); + match(tok::tk_str); + } else { + match(tok::tk_id, "expected hashmap key"); + } + match(tok::tk_colon); + node->set_value(calc()); + update_location(node); + return node; +} + +function* parse::func() { + ++in_func_depth; + auto node = new function(toks[ptr].loc); + match(tok::tk_func); + if (lookahead(tok::tk_lcurve)) { + params(node); + } + node->set_code_block(expression_block()); + --in_func_depth; + update_location(node); + return node; +} + +void parse::params(function* func_node) { + match(tok::tk_lcurve); + while (!lookahead(tok::tk_rcurve)) { + auto param = new parameter(toks[ptr].loc); + param->set_parameter_name(toks[ptr].str); + match(tok::tk_id); + if (lookahead(tok::tk_eq)) { + match(tok::tk_eq); + param->set_parameter_type(parameter::kind::default_parameter); + param->set_default_value(calc()); + } else if (lookahead(tok::tk_ellipsis)) { + match(tok::tk_ellipsis); + param->set_parameter_type(parameter::kind::dynamic_parameter); + } else { + param->set_parameter_type(parameter::kind::normal_parameter); + } + update_location(param); + func_node->add_parameter(param); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_id)) { // first set of identifier + die(prevspan, "expected \",\" between identifiers"); + } else { + break; + } + } + update_location(func_node); + match(tok::tk_rcurve, "expected \")\" after parameter list"); + return; +} + +expr* parse::lcurve_expr() { + if (toks[ptr+1].type==tok::tk_var) + return definition(); + return check_tuple()? multi_assignment():calc(); +} + +expr* parse::expression() { + tok type = toks[ptr].type; + if ((type == tok::tk_brk || type == tok::tk_cont) && !in_loop_depth) { + die(thisspan, "must use break/continue in loops"); + } + switch (type) { + case tok::tk_use: return use_stmt_gen(); + case tok::tk_nil: + case tok::tk_num: + case tok::tk_str: + case tok::tk_id: + case tok::tk_true: + case tok::tk_false: + case tok::tk_func: + case tok::tk_lbracket: + case tok::tk_lbrace: + case tok::tk_sub: + case tok::tk_floater: + case tok::tk_not: return calc(); + case tok::tk_var: return definition(); + case tok::tk_lcurve: return lcurve_expr(); + case tok::tk_for: + case tok::tk_forindex: + case tok::tk_foreach: + case tok::tk_while: return loop(); + case tok::tk_if: return cond(); + case tok::tk_cont: return continue_expression(); + case tok::tk_brk: return break_expression(); + case tok::tk_ret: return return_expression(); + case tok::tk_semi: break; + default: + die(thisspan, "incorrect token <"+toks[ptr].str+">"); + next(); + break; + } + + // unreachable + return new null_expr(toks[ptr].loc); +} + +code_block* parse::expression_block() { + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected expression block"); + return new code_block(toks[ptr].loc); + } + auto node = new code_block(toks[ptr].loc); + if (lookahead(tok::tk_lbrace)) { + match(tok::tk_lbrace); + while (!lookahead(tok::tk_rbrace) && !lookahead(tok::tk_eof)) { + node->add_expression(expression()); + if (lookahead(tok::tk_semi)) { + match(tok::tk_semi); + } else if (need_semi_check(node->get_expressions().back()) && + !lookahead(tok::tk_rbrace)) { + // the last expression can be recognized without semi + die(prevspan, "expected \";\" after this token"); + } + } + match(tok::tk_rbrace, "expected \"}\" when generating expressions"); + } else if (lookahead_expression()) { + node->add_expression(expression()); + if (lookahead(tok::tk_semi)) { + match(tok::tk_semi); + } + } + update_location(node); + return node; +} + +expr* parse::calc() { + auto node = bitwise_or(); + if (lookahead(tok::tk_quesmark)) { + // trinocular calculation + auto tmp = new ternary_operator(toks[ptr].loc); + match(tok::tk_quesmark); + tmp->set_condition(node); + tmp->set_left(calc()); + match(tok::tk_colon); + tmp->set_right(calc()); + node = tmp; + } else if (tok::tk_eq<=toks[ptr].type && toks[ptr].type<=tok::tk_lnkeq) { + auto tmp = new assignment_expr(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_eq: tmp->set_assignment_type(assignment_expr::kind::equal); break; + case tok::tk_addeq: tmp->set_assignment_type(assignment_expr::kind::add_equal); break; + case tok::tk_subeq: tmp->set_assignment_type(assignment_expr::kind::sub_equal); break; + case tok::tk_multeq: tmp->set_assignment_type(assignment_expr::kind::mult_equal); break; + case tok::tk_diveq: tmp->set_assignment_type(assignment_expr::kind::div_equal); break; + case tok::tk_lnkeq: tmp->set_assignment_type(assignment_expr::kind::concat_equal); break; + default: break; + } + tmp->set_left(node); + match(toks[ptr].type); + tmp->set_right(calc()); + node = tmp; + } else if (toks[ptr].type==tok::tk_btandeq || + toks[ptr].type==tok::tk_btoreq || + toks[ptr].type==tok::tk_btxoreq) { + auto tmp = new assignment_expr(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_btandeq: tmp->set_assignment_type(assignment_expr::kind::bitwise_and_equal); break; + case tok::tk_btoreq: tmp->set_assignment_type(assignment_expr::kind::bitwise_or_equal); break; + case tok::tk_btxoreq: tmp->set_assignment_type(assignment_expr::kind::bitwise_xor_equal); break; + default: break; + } + tmp->set_left(node); + match(toks[ptr].type); + tmp->set_right(calc()); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::bitwise_or() { + auto node = bitwise_xor(); + while (lookahead(tok::tk_btor)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::bitwise_or); + tmp->set_left(node); + match(tok::tk_btor); + tmp->set_right(bitwise_xor()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::bitwise_xor() { + auto node = bitwise_and(); + while (lookahead(tok::tk_btxor)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::bitwise_xor); + tmp->set_left(node); + match(tok::tk_btxor); + tmp->set_right(bitwise_and()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::bitwise_and() { + auto node = or_expr(); + while (lookahead(tok::tk_btand)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::bitwise_and); + tmp->set_left(node); + match(tok::tk_btand); + tmp->set_right(or_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::or_expr() { + auto node = and_expr(); + while (lookahead(tok::tk_or)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::condition_or); + tmp->set_left(node); + match(tok::tk_or); + tmp->set_right(and_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::and_expr() { + auto node = cmp_expr(); + while (lookahead(tok::tk_and)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::condition_and); + tmp->set_left(node); + match(tok::tk_and); + tmp->set_right(cmp_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::cmp_expr() { + auto node = null_chain_expr(); + while (tok::tk_cmpeq<=toks[ptr].type && toks[ptr].type<=tok::tk_geq) { + auto tmp = new binary_operator(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_cmpeq: tmp->set_operator_type(binary_operator::kind::cmpeq); break; + case tok::tk_neq: tmp->set_operator_type(binary_operator::kind::cmpneq); break; + case tok::tk_less: tmp->set_operator_type(binary_operator::kind::less); break; + case tok::tk_leq: tmp->set_operator_type(binary_operator::kind::leq); break; + case tok::tk_grt: tmp->set_operator_type(binary_operator::kind::grt); break; + case tok::tk_geq: tmp->set_operator_type(binary_operator::kind::geq); break; + default: break; + } + tmp->set_left(node); + match(toks[ptr].type); + tmp->set_right(null_chain_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::null_chain_expr() { + auto node = additive_expr(); + while (lookahead(tok::tk_quesques)) { + auto tmp = new binary_operator(toks[ptr].loc); + tmp->set_operator_type(binary_operator::kind::null_chain); + tmp->set_left(node); + match(tok::tk_quesques); + tmp->set_right(additive_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::additive_expr() { + auto node = multive_expr(); + while (lookahead(tok::tk_add) || + lookahead(tok::tk_sub) || + lookahead(tok::tk_floater)) { + auto tmp = new binary_operator(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_add: tmp->set_operator_type(binary_operator::kind::add); break; + case tok::tk_sub: tmp->set_operator_type(binary_operator::kind::sub); break; + case tok::tk_floater: tmp->set_operator_type(binary_operator::kind::concat); break; + default: break; + } + tmp->set_left(node); + match(toks[ptr].type); + tmp->set_right(multive_expr()); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +expr* parse::multive_expr() { + expr* node=(lookahead(tok::tk_sub) || + lookahead(tok::tk_not) || + lookahead(tok::tk_floater))? unary():scalar(); + while (lookahead(tok::tk_mult) || lookahead(tok::tk_div)) { + auto tmp = new binary_operator(toks[ptr].loc); + if (lookahead(tok::tk_mult)) { + tmp->set_operator_type(binary_operator::kind::mult); + } else { + tmp->set_operator_type(binary_operator::kind::div); + } + tmp->set_left(node); + match(toks[ptr].type); + tmp->set_right( + (lookahead(tok::tk_sub) || + lookahead(tok::tk_not) || + lookahead(tok::tk_floater))? unary():scalar() + ); + update_location(tmp); + node = tmp; + } + update_location(node); + return node; +} + +unary_operator* parse::unary() { + auto node = new unary_operator(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_sub: + node->set_operator_type(unary_operator::kind::negative); + match(tok::tk_sub); + break; + case tok::tk_not: + node->set_operator_type(unary_operator::kind::logical_not); + match(tok::tk_not); + break; + case tok::tk_floater: + node->set_operator_type(unary_operator::kind::bitwise_not); + match(tok::tk_floater); + break; + default: break; + } + node->set_value( + (lookahead(tok::tk_sub) || + lookahead(tok::tk_not) || + lookahead(tok::tk_floater))? unary():scalar() + ); + update_location(node); + return node; +} + +expr* parse::scalar() { + expr* node = nullptr; + if (lookahead(tok::tk_nil)) { + node = nil(); + match(tok::tk_nil); + } else if (lookahead(tok::tk_num)) { + node = num(); + } else if (lookahead(tok::tk_str)) { + node = str(); + } else if (lookahead(tok::tk_id)) { + node = id(); + } else if (lookahead(tok::tk_true) || lookahead(tok::tk_false)) { + node = bools(); + } else if (lookahead(tok::tk_func)) { + node = func(); + } else if (lookahead(tok::tk_lbracket)) { + node = vec(); + } else if (lookahead(tok::tk_lbrace)) { + node = hash(); + } else if (lookahead(tok::tk_lcurve)) { + const auto& loc = toks[ptr].loc; + match(tok::tk_lcurve); + node = calc(); + node->set_begin(loc.begin_line, loc.begin_column); + update_location(node); + match(tok::tk_rcurve); + } else if (lookahead(tok::tk_var)) { + match(tok::tk_var); + auto def_node = new definition_expr(toks[ptr].loc); + def_node->set_identifier(id()); + match(tok::tk_eq); + def_node->set_value(calc()); + node = def_node; + } else { + die(thisspan, "expected scalar"); + return null(); + } + // check call and avoid ambiguous syntax: + // var f = func() {} + // (var a, b, c) = (1, 2, 3); + // will be incorrectly recognized like: + // var f = func() {}(var a, b, c) + if (is_call(toks[ptr].type) && !check_in_curve_multi_definition()) { + auto call_node = new call_expr(toks[ptr].loc); + call_node->set_first(node); + while (is_call(toks[ptr].type)) { + call_node->add_call(call_scalar()); + } + node = call_node; + } + update_location(node); + return node; +} + +call* parse::call_scalar() { + switch(toks[ptr].type) { + case tok::tk_lcurve: return callf(); break; + case tok::tk_lbracket: return callv(); break; + case tok::tk_dot: return callh(); break; + case tok::tk_quesdot: return null_access_call(); break; + default: break; + } + // unreachable + return new call(toks[ptr].loc, expr_type::ast_null); +} + +call_hash* parse::callh() { + const auto& begin_loc = toks[ptr].loc; + match(tok::tk_dot); + auto node = new call_hash(begin_loc, toks[ptr].str); + update_location(node); + match(tok::tk_id, "expected hashmap key"); // get key + return node; +} + +null_access* parse::null_access_call() { + const auto& begin_loc = toks[ptr].loc; + match(tok::tk_quesdot); + auto node = new null_access(begin_loc, toks[ptr].str); + update_location(node); + match(tok::tk_id, "expected hashmap key"); // get key + return node; +} + +call_vector* parse::callv() { + // panic set for this token is not ',' + // this is the FIRST set of subvec + // array end with tok::tk_null = 0 + const tok panic[] = { + tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, + tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, + tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, + tok::tk_lbrace, tok::tk_lbracket, tok::tk_colon, tok::tk_null + }; + auto node = new call_vector(toks[ptr].loc); + match(tok::tk_lbracket); + while (!lookahead(tok::tk_rbracket)) { + node->add_slice(subvec()); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_eof)) { + break; + } else if (!lookahead(tok::tk_rbracket) && !check_comma(panic)) { + break; + } + } + if (node->get_slices().size()==0) { + die(thisspan, "expected index value"); + } + update_location(node); + match(tok::tk_rbracket, "expected \"]\" when calling vector"); + return node; +} + +call_function* parse::callf() { + // panic set for this token is not ',' + // this is the FIRST set of calculation/hashmember + // array end with tok::tk_null = 0 + const tok panic[] = { + tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, + tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, + tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, + tok::tk_lbrace, tok::tk_lbracket, tok::tk_null + }; + auto node = new call_function(toks[ptr].loc); + bool special_call=check_special_call(); + match(tok::tk_lcurve); + while (!lookahead(tok::tk_rcurve)) { + node->add_argument(special_call?pair():calc()); + if (lookahead(tok::tk_comma)) + match(tok::tk_comma); + else if (lookahead(tok::tk_eof)) + break; + else if (!lookahead(tok::tk_rcurve) && !check_comma(panic)) + break; + } + update_location(node); + match(tok::tk_rcurve, "expected \")\" when calling function"); + return node; +} + +slice_vector* parse::subvec() { + auto node = new slice_vector(toks[ptr].loc); + node->set_begin(lookahead(tok::tk_colon)?nil():calc()); + if (lookahead(tok::tk_colon)) { + match(tok::tk_colon); + node->set_end( + (lookahead(tok::tk_comma) || lookahead(tok::tk_rbracket))? + nil(): + calc() + ); + } + update_location(node); + return node; +} + +expr* parse::definition() { + auto node = new definition_expr(toks[ptr].loc); + if (lookahead(tok::tk_var)) { + match(tok::tk_var); + switch(toks[ptr].type) { + case tok::tk_id: node->set_identifier(id());break; + case tok::tk_lcurve: node->set_multi_define(outcurve_def());break; + default: die(thisspan, "expected identifier");break; + } + } else if (lookahead(tok::tk_lcurve)) { + node->set_multi_define(incurve_def()); + } + match(tok::tk_eq); + if (lookahead(tok::tk_lcurve)) { + check_tuple()? + node->set_tuple(multi_scalar()): + node->set_value(calc()); + } else { + node->set_value(calc()); + } + update_location(node); + return node; +} + +multi_identifier* parse::incurve_def() { + const auto& loc = toks[ptr].loc; + match(tok::tk_lcurve); + match(tok::tk_var); + auto node = multi_id(); + update_location(node); + match(tok::tk_rcurve); + node->set_begin(loc.begin_line, loc.begin_column); + return node; +} + +multi_identifier* parse::outcurve_def() { + const auto& loc = toks[ptr].loc; + match(tok::tk_lcurve); + auto node = multi_id(); + update_location(node); + match(tok::tk_rcurve); + node->set_begin(loc.begin_line, loc.begin_column); + return node; +} + +multi_identifier* parse::multi_id() { + auto node = new multi_identifier(toks[ptr].loc); + while (!lookahead(tok::tk_eof)) { + // only identifier is allowed here + node->add_var(id()); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_id)) { // first set of identifier + die(prevspan, "expected \",\" between identifiers"); + } else { + break; + } + } + update_location(node); + return node; +} + +tuple_expr* parse::multi_scalar() { + // if check_call_memory is true, + // we will check if value called here can reach a memory space + const tok panic[] = { + tok::tk_id, tok::tk_str, tok::tk_num, tok::tk_true, + tok::tk_false, tok::tk_not, tok::tk_sub, tok::tk_nil, + tok::tk_func, tok::tk_var, tok::tk_lcurve, tok::tk_floater, + tok::tk_lbrace, tok::tk_lbracket, tok::tk_null + }; + auto node = new tuple_expr(toks[ptr].loc); + match(tok::tk_lcurve); + while (!lookahead(tok::tk_rcurve)) { + node->add_element(calc()); + if (lookahead(tok::tk_comma)) { + match(tok::tk_comma); + } else if (lookahead(tok::tk_eof)) { + break; + } else if (!lookahead(tok::tk_rcurve) && !check_comma(panic)) { + break; + } + } + update_location(node); + match(tok::tk_rcurve, "expected \")\" after multi-scalar"); + return node; +} + +multi_assign* parse::multi_assignment() { + auto node = new multi_assign(toks[ptr].loc); + node->set_tuple(multi_scalar()); + match(tok::tk_eq); + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected value list"); + return node; + } + if (lookahead(tok::tk_lcurve)) { + node->set_value(check_tuple()?multi_scalar():calc()); + } else { + node->set_value(calc()); + } + update_location(node); + return node; +} + +expr* parse::loop() { + ++in_loop_depth; + expr* node = nullptr; + switch(toks[ptr].type) { + case tok::tk_while: node = while_loop(); break; + case tok::tk_for: node = for_loop(); break; + case tok::tk_forindex: + case tok::tk_foreach: node = forei_loop(); break; + default: + die(thisspan, "unreachable"); + node = null(); + break; + } + --in_loop_depth; + return node; +} + +while_expr* parse::while_loop() { + auto node = new while_expr(toks[ptr].loc); + match(tok::tk_while); + match(tok::tk_lcurve); + node->set_condition(calc()); + match(tok::tk_rcurve); + node->set_code_block(expression_block()); + update_location(node); + return node; +} + +for_expr* parse::for_loop() { + auto node = new for_expr(toks[ptr].loc); + match(tok::tk_for); + match(tok::tk_lcurve); + + // first expression + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected definition"); + } + if (lookahead(tok::tk_semi)) { + node->set_initial(null()); + } else if (lookahead(tok::tk_var)) { + node->set_initial(definition()); + } else if (lookahead(tok::tk_lcurve)) { + node->set_initial(lcurve_expr()); + } else { + node->set_initial(calc()); + } + match(tok::tk_semi, "expected \";\" in for (;;)"); + + // conditional expression + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected conditional expr"); + } + if (lookahead(tok::tk_semi)) { + node->set_condition(null()); + } else { + node->set_condition(calc()); + } + match(tok::tk_semi, "expected \";\" in for (;;)"); + + //after loop expression + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected calculation"); + } + if (lookahead(tok::tk_rcurve)) { + node->set_step(null()); + } else { + node->set_step(calc()); + } + match(tok::tk_rcurve); + node->set_code_block(expression_block()); + update_location(node); + return node; +} + +forei_expr* parse::forei_loop() { + auto node = new forei_expr(toks[ptr].loc); + switch(toks[ptr].type) { + case tok::tk_forindex: + node->set_loop_type(forei_expr::kind::forindex); + match(tok::tk_forindex); + break; + case tok::tk_foreach: + node->set_loop_type(forei_expr::kind::foreach); + match(tok::tk_foreach); + break; + default: break; + } + match(tok::tk_lcurve); + // first expression + // foreach/forindex must have an iterator to loop through + if (!lookahead(tok::tk_var) && !lookahead(tok::tk_id)) { + die(thisspan, "expected iterator"); + } + node->set_iterator(iter_gen()); + match(tok::tk_semi, "expected \";\" in foreach/forindex(iter;vector)"); + if (lookahead(tok::tk_eof)) { + die(thisspan, "expected vector"); + } + node->set_value(calc()); + match(tok::tk_rcurve); + node->set_code_block(expression_block()); + update_location(node); + return node; +} + +iter_expr* parse::iter_gen() { + auto node = new iter_expr(toks[ptr].loc); + // definition + if (lookahead(tok::tk_var)) { + match(tok::tk_var); + node->set_name(id()); + node->set_is_definition(true); + update_location(node); + return node; + } + + // single symbol call + auto id_node = id(); + if (!is_call(toks[ptr].type)) { + node->set_name(id_node); + update_location(node); + return node; + } + + // call expression + auto tmp = new call_expr(id_node->get_location()); + tmp->set_first(id_node); + while (is_call(toks[ptr].type)) { + tmp->add_call(call_scalar()); + } + node->set_call(tmp); + update_location(node); + return node; +} + +condition_expr* parse::cond() { + auto node = new condition_expr(toks[ptr].loc); + + // generate if + auto ifnode = new if_expr(toks[ptr].loc); + match(tok::tk_if); + match(tok::tk_lcurve); + ifnode->set_condition(calc()); + match(tok::tk_rcurve); + ifnode->set_code_block(expression_block()); + update_location(ifnode); + node->set_if_statement(ifnode); + + // generate elsif + while (lookahead(tok::tk_elsif)) { + auto elsifnode = new if_expr(toks[ptr].loc); + match(tok::tk_elsif); + match(tok::tk_lcurve); + elsifnode->set_condition(calc()); + match(tok::tk_rcurve); + elsifnode->set_code_block(expression_block()); + update_location(elsifnode); + node->add_elsif_statement(elsifnode); + } + + // generate else + if (lookahead(tok::tk_else)) { + auto elsenode = new if_expr(toks[ptr].loc); + match(tok::tk_else); + elsenode->set_code_block(expression_block()); + update_location(elsenode); + node->set_else_statement(elsenode); + } + update_location(node); + return node; +} + +continue_expr* parse::continue_expression() { + auto node = new continue_expr(toks[ptr].loc); + match(tok::tk_cont); + return node; +} + +break_expr* parse::break_expression() { + auto node = new break_expr(toks[ptr].loc); + match(tok::tk_brk); + return node; +} + +return_expr* parse::return_expression() { + auto node = new return_expr(toks[ptr].loc); + match(tok::tk_ret); + tok type = toks[ptr].type; + if (type==tok::tk_nil || type==tok::tk_num || + type==tok::tk_str || type==tok::tk_id || + type==tok::tk_func || type==tok::tk_sub || + type==tok::tk_not || type==tok::tk_lcurve || + type==tok::tk_lbracket || type==tok::tk_lbrace || + type==tok::tk_true || type==tok::tk_false) { + node->set_value(calc()); + } else { + node->set_value(nil()); + } + update_location(node); + return node; +} + +} diff --git a/src/nasal_parse.h b/src/nasal_parse.h index 2baa441..682a266 100644 --- a/src/nasal_parse.h +++ b/src/nasal_parse.h @@ -1,169 +1,169 @@ -#pragma once - -#include - -#include "nasal.h" -#include "nasal_ast.h" -#include "nasal_lexer.h" -#include "nasal_err.h" - -namespace nasal { - -class parse { - -#define thisspan (toks[ptr].loc) -#define prevspan (ptr!=0? toks[ptr-1].loc:toks[ptr].loc) - -private: - u64 ptr; - u64 in_func_depth; // count function block - u64 in_loop_depth; // count loop block - const token* toks; - code_block* root; - error err; - -private: - const std::unordered_map token_name_mapper = { - {tok::tk_true , "true" }, - {tok::tk_false , "false" }, - {tok::tk_use , "use" }, - {tok::tk_for , "for" }, - {tok::tk_forindex, "forindex"}, - {tok::tk_foreach , "foreach" }, - {tok::tk_while , "while" }, - {tok::tk_var , "var" }, - {tok::tk_func , "func" }, - {tok::tk_brk , "break" }, - {tok::tk_cont , "continue"}, - {tok::tk_ret , "return" }, - {tok::tk_if , "if" }, - {tok::tk_elsif , "elsif" }, - {tok::tk_else , "else" }, - {tok::tk_nil , "nil" }, - {tok::tk_lcurve , "(" }, - {tok::tk_rcurve , ")" }, - {tok::tk_lbracket, "[" }, - {tok::tk_rbracket, "]" }, - {tok::tk_lbrace , "{" }, - {tok::tk_rbrace , "}" }, - {tok::tk_semi , ";" }, - {tok::tk_and , "and" }, - {tok::tk_or , "or" }, - {tok::tk_comma , "," }, - {tok::tk_dot , "." }, - {tok::tk_ellipsis, "..." }, - {tok::tk_quesmark, "?" }, - {tok::tk_quesques, "??" }, - {tok::tk_quesdot , "?." }, - {tok::tk_colon , ":" }, - {tok::tk_add , "+" }, - {tok::tk_sub , "-" }, - {tok::tk_mult , "*" }, - {tok::tk_div , "/" }, - {tok::tk_floater , "~" }, - {tok::tk_btand , "&" }, - {tok::tk_btor , "|" }, - {tok::tk_btxor , "^" }, - {tok::tk_not , "!" }, - {tok::tk_eq , "=" }, - {tok::tk_addeq , "+=" }, - {tok::tk_subeq , "-=" }, - {tok::tk_multeq , "*=" }, - {tok::tk_diveq , "/=" }, - {tok::tk_lnkeq , "~=" }, - {tok::tk_btandeq , "&=" }, - {tok::tk_btoreq , "|=" }, - {tok::tk_btxoreq , "^=" }, - {tok::tk_cmpeq , "==" }, - {tok::tk_neq , "!=" }, - {tok::tk_less , "<" }, - {tok::tk_leq , "<=" }, - {tok::tk_grt , ">" }, - {tok::tk_geq , ">=" } - }; - -private: - void die(const span&, const std::string&); - void next(); - void match(tok, const char* info = nullptr); - bool lookahead(tok); - bool lookahead_expression(); - bool is_call(tok); - bool check_comma(const tok*); - bool check_tuple(); - bool check_func_end(expr*); - bool check_in_curve_multi_definition(); - bool check_special_call(); - bool need_semi_check(expr*); - void update_location(expr*); - -private: - use_stmt* use_stmt_gen(); - null_expr* null(); - nil_expr* nil(); - number_literal* num(); - string_literal* str(); - identifier* id(); - bool_literal* bools(); - vector_expr* vec(); - hash_expr* hash(); - hash_pair* pair(); - function* func(); - void params(function*); - expr* lcurve_expr(); - expr* expression(); - code_block* expression_block(); - expr* calc(); - expr* bitwise_or(); - expr* bitwise_xor(); - expr* bitwise_and(); - expr* or_expr(); - expr* and_expr(); - expr* cmp_expr(); - expr* null_chain_expr(); - expr* additive_expr(); - expr* multive_expr(); - unary_operator* unary(); - expr* scalar(); - call* call_scalar(); - call_hash* callh(); - null_access* null_access_call(); - call_vector* callv(); - call_function* callf(); - slice_vector* subvec(); - expr* definition(); - multi_identifier* incurve_def(); - multi_identifier* outcurve_def(); - multi_identifier* multi_id(); - tuple_expr* multi_scalar(); - multi_assign* multi_assignment(); - expr* loop(); - while_expr* while_loop(); - for_expr* for_loop(); - forei_expr* forei_loop(); - iter_expr* iter_gen(); - condition_expr* cond(); - continue_expr* continue_expression(); - break_expr* break_expression(); - return_expr* return_expression(); - -public: - code_block* tree() {return root;} - - // swap root pointer with another pointer(maybe nullptr) - code_block* swap(code_block* another) { - auto res = root; - root = another; - return res; - } - -public: - parse(): ptr(0), in_func_depth(0), - in_loop_depth(0), toks(nullptr), - root(nullptr) {} - ~parse() {delete root;} - const error& compile(const lexer&); - static void easter_egg(); -}; - -} +#pragma once + +#include + +#include "nasal.h" +#include "nasal_ast.h" +#include "nasal_lexer.h" +#include "nasal_err.h" + +namespace nasal { + +class parse { + +#define thisspan (toks[ptr].loc) +#define prevspan (ptr!=0? toks[ptr-1].loc:toks[ptr].loc) + +private: + u64 ptr; + u64 in_func_depth; // count function block + u64 in_loop_depth; // count loop block + const token* toks; + code_block* root; + error err; + +private: + const std::unordered_map token_name_mapper = { + {tok::tk_true , "true" }, + {tok::tk_false , "false" }, + {tok::tk_use , "use" }, + {tok::tk_for , "for" }, + {tok::tk_forindex, "forindex"}, + {tok::tk_foreach , "foreach" }, + {tok::tk_while , "while" }, + {tok::tk_var , "var" }, + {tok::tk_func , "func" }, + {tok::tk_brk , "break" }, + {tok::tk_cont , "continue"}, + {tok::tk_ret , "return" }, + {tok::tk_if , "if" }, + {tok::tk_elsif , "elsif" }, + {tok::tk_else , "else" }, + {tok::tk_nil , "nil" }, + {tok::tk_lcurve , "(" }, + {tok::tk_rcurve , ")" }, + {tok::tk_lbracket, "[" }, + {tok::tk_rbracket, "]" }, + {tok::tk_lbrace , "{" }, + {tok::tk_rbrace , "}" }, + {tok::tk_semi , ";" }, + {tok::tk_and , "and" }, + {tok::tk_or , "or" }, + {tok::tk_comma , "," }, + {tok::tk_dot , "." }, + {tok::tk_ellipsis, "..." }, + {tok::tk_quesmark, "?" }, + {tok::tk_quesques, "??" }, + {tok::tk_quesdot , "?." }, + {tok::tk_colon , ":" }, + {tok::tk_add , "+" }, + {tok::tk_sub , "-" }, + {tok::tk_mult , "*" }, + {tok::tk_div , "/" }, + {tok::tk_floater , "~" }, + {tok::tk_btand , "&" }, + {tok::tk_btor , "|" }, + {tok::tk_btxor , "^" }, + {tok::tk_not , "!" }, + {tok::tk_eq , "=" }, + {tok::tk_addeq , "+=" }, + {tok::tk_subeq , "-=" }, + {tok::tk_multeq , "*=" }, + {tok::tk_diveq , "/=" }, + {tok::tk_lnkeq , "~=" }, + {tok::tk_btandeq , "&=" }, + {tok::tk_btoreq , "|=" }, + {tok::tk_btxoreq , "^=" }, + {tok::tk_cmpeq , "==" }, + {tok::tk_neq , "!=" }, + {tok::tk_less , "<" }, + {tok::tk_leq , "<=" }, + {tok::tk_grt , ">" }, + {tok::tk_geq , ">=" } + }; + +private: + void die(const span&, const std::string&); + void next(); + void match(tok, const char* info = nullptr); + bool lookahead(tok); + bool lookahead_expression(); + bool is_call(tok); + bool check_comma(const tok*); + bool check_tuple(); + bool check_func_end(expr*); + bool check_in_curve_multi_definition(); + bool check_special_call(); + bool need_semi_check(expr*); + void update_location(expr*); + +private: + use_stmt* use_stmt_gen(); + null_expr* null(); + nil_expr* nil(); + number_literal* num(); + string_literal* str(); + identifier* id(); + bool_literal* bools(); + vector_expr* vec(); + hash_expr* hash(); + hash_pair* pair(); + function* func(); + void params(function*); + expr* lcurve_expr(); + expr* expression(); + code_block* expression_block(); + expr* calc(); + expr* bitwise_or(); + expr* bitwise_xor(); + expr* bitwise_and(); + expr* or_expr(); + expr* and_expr(); + expr* cmp_expr(); + expr* null_chain_expr(); + expr* additive_expr(); + expr* multive_expr(); + unary_operator* unary(); + expr* scalar(); + call* call_scalar(); + call_hash* callh(); + null_access* null_access_call(); + call_vector* callv(); + call_function* callf(); + slice_vector* subvec(); + expr* definition(); + multi_identifier* incurve_def(); + multi_identifier* outcurve_def(); + multi_identifier* multi_id(); + tuple_expr* multi_scalar(); + multi_assign* multi_assignment(); + expr* loop(); + while_expr* while_loop(); + for_expr* for_loop(); + forei_expr* forei_loop(); + iter_expr* iter_gen(); + condition_expr* cond(); + continue_expr* continue_expression(); + break_expr* break_expression(); + return_expr* return_expression(); + +public: + code_block* tree() {return root;} + + // swap root pointer with another pointer(maybe nullptr) + code_block* swap(code_block* another) { + auto res = root; + root = another; + return res; + } + +public: + parse(): ptr(0), in_func_depth(0), + in_loop_depth(0), toks(nullptr), + root(nullptr) {} + ~parse() {delete root;} + const error& compile(const lexer&); + static void easter_egg(); +}; + +} diff --git a/src/nasal_type.cpp b/src/nasal_type.cpp index c019c3e..8c28def 100644 --- a/src/nasal_type.cpp +++ b/src/nasal_type.cpp @@ -1,301 +1,281 @@ -#include "nasal_type.h" -#include "util/util.h" - -#include -#include - -namespace nasal { - -std::ostream& operator<<(std::ostream& out, nas_vec& vec) { - if (!vec.elems.size() || vec.printed) { - out << (vec.elems.size()? "[..]":"[]"); - return out; - } - vec.printed = true; - usize iter = 0, size = vec.elems.size(); - out << "["; - for (auto& i:vec.elems) { - out << i << ",]"[(++iter)==size]; - } - vec.printed = false; - return out; -} - -var nas_hash::get_value(const std::string& key) { - if (elems.count(key)) { - return elems.at(key); - } - if (!elems.count("parents")) { - return var::none(); - } - - auto ret = var::none(); - auto& val = elems.at("parents"); - if (!val.is_vec()) { - return ret; - } - for (auto& i : val.vec().elems) { - if (i.is_hash()) { - ret = i.hash().get_value(key); - } - if (!ret.is_none()) { - return ret; - } - } - return ret; -} - -var* nas_hash::get_memory(const std::string& key) { - if (elems.count(key)) { - return &elems.at(key); - } - if (!elems.count("parents")) { - return nullptr; - } - - var* addr = nullptr; - var& val = elems.at("parents"); - if (!val.is_vec()) { - return addr; - } - for (auto& i : val.vec().elems) { - // recursively search key in `parents` - if (i.is_hash()) { - addr = i.hash().get_memory(key); - } - if (addr) { - return addr; - } - } - return addr; -} - -std::ostream& operator<<(std::ostream& out, nas_hash& hash) { - if (!hash.elems.size() || hash.printed) { - out << (hash.elems.size()? "{..}":"{}"); - return out; - } - - // mark print, to avoid infinite recursion - hash.printed = true; - - static const char* sep[] = {", ", "}"}; - usize iter = 0, size = hash.elems.size(); - out << "{"; - for (auto& i : hash.elems) { - out << i.first << ": " << i.second << sep[(++iter)==size]; - } - - // restore flag - hash.printed = false; - return out; -} - -std::ostream& operator<<(std::ostream& out, nas_func& func) { - out << "func("; - - std::vector argument_list = {}; - argument_list.resize(func.keys.size()); - for (const auto& key : func.keys) { - argument_list[key.second-1] = key.first; - } - - for (const auto& key : argument_list) { - out << key; - if (key != argument_list.back()) { - out << ", "; - } - } - if (func.dynamic_parameter_index>=0) { - out << (argument_list.size()? ", ":""); - out << func.dynamic_parameter_name << "..."; - } - - out << ") {..}"; - return out; -} - -void nas_func::clear() { - dynamic_parameter_index = -1; - local.clear(); - upval.clear(); - keys.clear(); -} - -void nas_ghost::set(const std::string& ghost_type_name, - destructor destructor_pointer, - marker gc_marker_pointer, - void* ghost_pointer) { - type_name = ghost_type_name; - destructor_function = destructor_pointer; - gc_mark_function = gc_marker_pointer; - pointer = ghost_pointer; -} - -void nas_ghost::clear() { - // do nothing if pointer is null - if (!pointer) { - return; - } - - // do clear pointer if destructor function pointer is null - if (!destructor_function) { - type_name = ""; - pointer = nullptr; - return; - } - - // do destruction - destructor_function(pointer); - type_name = ""; - pointer = nullptr; - destructor_function = nullptr; - gc_mark_function = nullptr; -} - -std::ostream& operator<<(std::ostream& out, const nas_ghost& ghost) { - out << "<" << ghost.get_ghost_name(); - out << "@0x" << std::hex << ghost.convert() << std::dec << ">"; - return out; -} - -void nas_co::clear() { - if (!ctx.stack) { - return; - } - for (u32 i = 0; i < VM_STACK_DEPTH; ++i) { - ctx.stack[i] = var::nil(); - } - - ctx.pc = 0; - ctx.localr = nullptr; - ctx.memr = nullptr; - ctx.canary = ctx.stack+VM_STACK_DEPTH-1; - ctx.top = ctx.stack; - ctx.func_top = ctx.func_stack; - ctx.funcr = var::nil(); - ctx.upvalr = var::nil(); - - status = status::suspended; -} - -std::ostream& operator<<(std::ostream& out, const nas_co& co) { - out << "(&co) << std::dec << ">"; - return out; -} - -var nas_map::get_value(const std::string& key) { - if (mapper.count(key)) { - return *mapper.at(key); - } - return var::none(); -} - -var* nas_map::get_memory(const std::string& key) { - if (mapper.count(key)) { - return mapper.at(key); - } - return nullptr; -} - -std::ostream& operator<<(std::ostream& out, nas_map& mp) { - if (!mp.mapper.size() || mp.printed) { - out << (mp.mapper.size()? "{..}":"{}"); - return out; - } - - // mark print, to avoid infinite recursion - mp.printed = true; - - static const char* sep[] = {", ", "}"}; - usize iter = 0, size = mp.mapper.size(); - out << "{"; - for (auto& i : mp.mapper) { - out << i.first << ": " << *i.second << sep[(++iter)==size]; - } - - // restore flag - mp.printed = false; - return out; -} - -nas_val::nas_val(vm_type val_type) { - mark = gc_status::collected; - type = val_type; - immutable = 0; - switch(val_type) { - case vm_type::vm_str: ptr.str = new std::string; break; - case vm_type::vm_vec: ptr.vec = new nas_vec; break; - case vm_type::vm_hash: ptr.hash = new nas_hash; break; - case vm_type::vm_func: ptr.func = new nas_func; break; - case vm_type::vm_upval: ptr.upval = new nas_upval; break; - case vm_type::vm_ghost: ptr.obj = new nas_ghost; break; - case vm_type::vm_co: ptr.co = new nas_co; break; - case vm_type::vm_map: ptr.map = new nas_map; break; - default: break; - } -} - -nas_val::~nas_val() { - switch(type) { - case vm_type::vm_str: delete ptr.str; break; - case vm_type::vm_vec: delete ptr.vec; break; - case vm_type::vm_hash: delete ptr.hash; break; - case vm_type::vm_func: delete ptr.func; break; - case vm_type::vm_upval: delete ptr.upval; break; - case vm_type::vm_ghost: delete ptr.obj; break; - case vm_type::vm_co: delete ptr.co; break; - case vm_type::vm_map: delete ptr.map; break; - default: break; - } - type = vm_type::vm_nil; -} - -void nas_val::clear() { - switch(type) { - case vm_type::vm_str: ptr.str->clear(); break; - case vm_type::vm_vec: ptr.vec->elems.clear(); break; - case vm_type::vm_hash: ptr.hash->elems.clear(); break; - case vm_type::vm_func: ptr.func->clear(); break; - case vm_type::vm_upval: ptr.upval->clear(); break; - case vm_type::vm_ghost: ptr.obj->clear(); break; - case vm_type::vm_co: ptr.co->clear(); break; - case vm_type::vm_map: ptr.map->clear(); break; - default: break; - } -} - -std::string var::to_str() { - if (type==vm_type::vm_str) { - return str(); - } else if (type==vm_type::vm_num) { - auto tmp = std::to_string(num()); - tmp.erase(tmp.find_last_not_of('0') + 1, std::string::npos); - tmp.erase(tmp.find_last_not_of('.') + 1, std::string::npos); - return tmp; - } - - std::stringstream ss; - ss << *this; - return ss.str(); -} - -std::ostream& operator<<(std::ostream& out, var& ref) { - switch(ref.type) { - case vm_type::vm_none: out << "undefined"; break; - case vm_type::vm_nil: out << "nil"; break; - case vm_type::vm_num: out << ref.val.num; break; - case vm_type::vm_str: out << ref.str(); break; - case vm_type::vm_vec: out << ref.vec(); break; - case vm_type::vm_hash: out << ref.hash(); break; - case vm_type::vm_func: out << ref.func(); break; - case vm_type::vm_ghost: out << ref.ghost(); break; - case vm_type::vm_co: out << ref.co(); break; - case vm_type::vm_map: out << ref.map(); break; - default: break; - } - return out; -} - +#include "nasal_type.h" +#include "util/util.h" + +#include +#include + +namespace nasal { + +std::ostream& operator<<(std::ostream& out, nas_vec& vec) { + if (!vec.elems.size() || vec.printed) { + out << (vec.elems.size()? "[..]":"[]"); + return out; + } + vec.printed = true; + usize iter = 0, size = vec.elems.size(); + out << "["; + for (auto& i:vec.elems) { + out << i << ",]"[(++iter)==size]; + } + vec.printed = false; + return out; +} + +var nas_hash::get_value(const std::string& key) { + if (elems.count(key)) { + return elems.at(key); + } + if (!elems.count("parents")) { + return var::none(); + } + + auto ret = var::none(); + auto& val = elems.at("parents"); + if (!val.is_vec()) { + return ret; + } + for (auto& i : val.vec().elems) { + if (i.is_hash()) { + ret = i.hash().get_value(key); + } + if (!ret.is_none()) { + return ret; + } + } + return ret; +} + +var* nas_hash::get_memory(const std::string& key) { + if (elems.count(key)) { + return &elems.at(key); + } + if (!elems.count("parents")) { + return nullptr; + } + + var* addr = nullptr; + var& val = elems.at("parents"); + if (!val.is_vec()) { + return addr; + } + for (auto& i : val.vec().elems) { + // recursively search key in `parents` + if (i.is_hash()) { + addr = i.hash().get_memory(key); + } + if (addr) { + return addr; + } + } + return addr; +} + +std::ostream& operator<<(std::ostream& out, nas_hash& hash) { + if (!hash.elems.size() || hash.printed) { + out << (hash.elems.size()? "{..}":"{}"); + return out; + } + + // mark print, to avoid infinite recursion + hash.printed = true; + + static const char* sep[] = {", ", "}"}; + usize iter = 0, size = hash.elems.size(); + out << "{"; + for (auto& i : hash.elems) { + out << i.first << ": " << i.second << sep[(++iter)==size]; + } + + // restore flag + hash.printed = false; + return out; +} + +std::ostream& operator<<(std::ostream& out, nas_func& func) { + out << "func("; + + std::vector argument_list = {}; + argument_list.resize(func.keys.size()); + for (const auto& key : func.keys) { + argument_list[key.second-1] = key.first; + } + + for (const auto& key : argument_list) { + out << key; + if (key != argument_list.back()) { + out << ", "; + } + } + if (func.dynamic_parameter_index>=0) { + out << (argument_list.size()? ", ":""); + out << func.dynamic_parameter_name << "..."; + } + + out << ") {..}"; + return out; +} + +void nas_func::clear() { + dynamic_parameter_index = -1; + local.clear(); + upval.clear(); + keys.clear(); +} + +void nas_ghost::set(const std::string& ghost_type_name, + destructor destructor_pointer, + marker gc_marker_pointer, + void* ghost_pointer) { + type_name = ghost_type_name; + destructor_function = destructor_pointer; + gc_mark_function = gc_marker_pointer; + pointer = ghost_pointer; +} + +void nas_ghost::clear() { + // do nothing if pointer is null + if (!pointer) { + return; + } + + // do clear pointer if destructor function pointer is null + if (!destructor_function) { + type_name = ""; + pointer = nullptr; + return; + } + + // do destruction + destructor_function(pointer); + type_name = ""; + pointer = nullptr; + destructor_function = nullptr; + gc_mark_function = nullptr; +} + +std::ostream& operator<<(std::ostream& out, const nas_ghost& ghost) { + out << "<" << ghost.get_ghost_name(); + out << "@0x" << std::hex << ghost.convert() << std::dec << ">"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const nas_co& co) { + out << "(&co) << std::dec << ">"; + return out; +} + +var nas_map::get_value(const std::string& key) { + if (mapper.count(key)) { + return *mapper.at(key); + } + return var::none(); +} + +var* nas_map::get_memory(const std::string& key) { + if (mapper.count(key)) { + return mapper.at(key); + } + return nullptr; +} + +std::ostream& operator<<(std::ostream& out, nas_map& mp) { + if (!mp.mapper.size() || mp.printed) { + out << (mp.mapper.size()? "{..}":"{}"); + return out; + } + + // mark print, to avoid infinite recursion + mp.printed = true; + + static const char* sep[] = {", ", "}"}; + usize iter = 0, size = mp.mapper.size(); + out << "{"; + for (auto& i : mp.mapper) { + out << i.first << ": " << *i.second << sep[(++iter)==size]; + } + + // restore flag + mp.printed = false; + return out; +} + +nas_val::nas_val(vm_type val_type) { + mark = gc_status::collected; + type = val_type; + immutable = 0; + switch(val_type) { + case vm_type::vm_str: ptr.str = new std::string; break; + case vm_type::vm_vec: ptr.vec = new nas_vec; break; + case vm_type::vm_hash: ptr.hash = new nas_hash; break; + case vm_type::vm_func: ptr.func = new nas_func; break; + case vm_type::vm_upval: ptr.upval = new nas_upval; break; + case vm_type::vm_ghost: ptr.obj = new nas_ghost; break; + case vm_type::vm_co: ptr.co = new nas_co; break; + case vm_type::vm_map: ptr.map = new nas_map; break; + default: break; + } +} + +nas_val::~nas_val() { + switch(type) { + case vm_type::vm_str: delete ptr.str; break; + case vm_type::vm_vec: delete ptr.vec; break; + case vm_type::vm_hash: delete ptr.hash; break; + case vm_type::vm_func: delete ptr.func; break; + case vm_type::vm_upval: delete ptr.upval; break; + case vm_type::vm_ghost: delete ptr.obj; break; + case vm_type::vm_co: delete ptr.co; break; + case vm_type::vm_map: delete ptr.map; break; + default: break; + } + type = vm_type::vm_nil; +} + +void nas_val::clear() { + switch(type) { + case vm_type::vm_str: ptr.str->clear(); break; + case vm_type::vm_vec: ptr.vec->elems.clear(); break; + case vm_type::vm_hash: ptr.hash->elems.clear(); break; + case vm_type::vm_func: ptr.func->clear(); break; + case vm_type::vm_upval: ptr.upval->clear(); break; + case vm_type::vm_ghost: ptr.obj->clear(); break; + case vm_type::vm_co: ptr.co->clear(); break; + case vm_type::vm_map: ptr.map->clear(); break; + default: break; + } +} + +std::string var::to_str() { + if (type==vm_type::vm_str) { + return str(); + } else if (type==vm_type::vm_num) { + auto tmp = std::to_string(num()); + tmp.erase(tmp.find_last_not_of('0') + 1, std::string::npos); + tmp.erase(tmp.find_last_not_of('.') + 1, std::string::npos); + return tmp; + } + + std::stringstream ss; + ss << *this; + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, var& ref) { + switch(ref.type) { + case vm_type::vm_none: out << "undefined"; break; + case vm_type::vm_nil: out << "nil"; break; + case vm_type::vm_num: out << ref.val.num; break; + case vm_type::vm_str: out << ref.str(); break; + case vm_type::vm_vec: out << ref.vec(); break; + case vm_type::vm_hash: out << ref.hash(); break; + case vm_type::vm_func: out << ref.func(); break; + case vm_type::vm_ghost: out << ref.ghost(); break; + case vm_type::vm_co: out << ref.co(); break; + case vm_type::vm_map: out << ref.map(); break; + default: break; + } + return out; +} + } \ No newline at end of file diff --git a/src/nasal_type.h b/src/nasal_type.h index e1bbb08..8f27753 100644 --- a/src/nasal_type.h +++ b/src/nasal_type.h @@ -1,368 +1,398 @@ -#pragma once - -#include "nasal.h" -#include "util/util.h" - -#include -#include -#include -#include -#include - -namespace nasal { - -enum class vm_type: u8 { - /* none-gc object */ - vm_none = 0, // error type - vm_cnt, // counter for forindex/foreach loop - vm_addr, // var* address - vm_ret, // return addres(program counter) - vm_nil, // nil - vm_num, // number - - /* gc object */ - vm_str, // string - vm_vec, // vector - vm_hash, // hashmap(dict) - vm_func, // function(lambda) - vm_upval, // upvalue - vm_ghost, // ghost type - vm_co, // coroutine - vm_map, // for globals and namespaces - - /* mark type range */ - vm_type_size_max -}; - -// size of gc object type -const u32 GC_TYPE_SIZE = - static_cast(vm_type::vm_type_size_max) - - static_cast(vm_type::vm_str); - -// basic types -struct nas_vec; // vector -struct nas_hash; // hashmap(dict) -struct nas_func; // function(lambda) -struct nas_upval; // upvalue -struct nas_ghost; // objects -struct nas_co; // coroutine -struct nas_map; // mapper - -// nas_val includes gc-managed types -struct nas_val { - enum class gc_status: u8 { - uncollected = 0, - collected, - found - }; - - gc_status mark; - vm_type type; // value type - u8 immutable; // used to mark if a string is immutable - union elem { - std::string* str; - nas_vec* vec; - nas_hash* hash; - nas_func* func; - nas_upval* upval; - nas_ghost* obj; - nas_co* co; - nas_map* map; - } ptr; - - nas_val(vm_type); - ~nas_val(); - void clear(); -}; - -struct var { -public: - vm_type type = vm_type::vm_none; - union { - u64 ret; - i64 cnt; - f64 num; - var* addr; - nas_val* gcobj; - } val; - -private: - var(vm_type t, u64 pc) { type = t; val.ret = pc; } - var(vm_type t, i64 ct) { type = t; val.cnt = ct; } - var(vm_type t, f64 n) { type = t; val.num = n; } - var(vm_type t, var* p) { type = t; val.addr = p; } - var(vm_type t, nas_val* p) { type = t; val.gcobj = p; } - -public: - var() = default; - var(const var&) = default; - bool operator==(const var& nr) const { - return type == nr.type && val.gcobj == nr.val.gcobj; - } - bool operator!=(const var& nr) const { - return type != nr.type || val.gcobj != nr.val.gcobj; - } - -public: - // create new var object - static var none() { - return var(vm_type::vm_none, static_cast(0)); - } - static var nil() { - return var(vm_type::vm_nil, static_cast(0)); - } - static var ret(u64 pc) { - return var(vm_type::vm_ret, pc); - } - static var cnt(i64 n) { - return var(vm_type::vm_cnt, n); - } - static var num(f64 n) { - return var(vm_type::vm_num, n); - } - static var gcobj(nas_val* p) { - return var(p->type, p); - } - static var addr(var* p) { - return var(vm_type::vm_addr, p); - } - -public: - // get value - var* addr() const { return val.addr; } - u64 ret() const { return val.ret; } - i64& cnt() { return val.cnt; } - f64 num() const { return val.num; } - -public: - // get gc object - std::string& str() { return *val.gcobj->ptr.str; } - nas_vec& vec() { return *val.gcobj->ptr.vec; } - nas_hash& hash() { return *val.gcobj->ptr.hash; } - nas_func& func() { return *val.gcobj->ptr.func; } - nas_upval& upval() { return *val.gcobj->ptr.upval; } - nas_ghost& ghost() { return *val.gcobj->ptr.obj; } - nas_co& co() { return *val.gcobj->ptr.co; } - nas_map& map() { return *val.gcobj->ptr.map; } - - -public: - // get const gc object - const std::string& str() const { return *val.gcobj->ptr.str; } - const nas_vec& vec() const { return *val.gcobj->ptr.vec; } - const nas_hash& hash() const { return *val.gcobj->ptr.hash; } - const nas_func& func() const { return *val.gcobj->ptr.func; } - const nas_upval& upval() const { return *val.gcobj->ptr.upval; } - const nas_ghost& ghost() const { return *val.gcobj->ptr.obj; } - const nas_co& co() const { return *val.gcobj->ptr.co; } - const nas_map& map() const { return *val.gcobj->ptr.map; } - -public: - bool is_none() const { return type == vm_type::vm_none; } - bool is_cnt() const { return type == vm_type::vm_cnt; } - bool is_addr() const { return type == vm_type::vm_addr; } - bool is_ret() const { return type == vm_type::vm_ret; } - bool is_nil() const { return type == vm_type::vm_nil; } - bool is_num() const { return type == vm_type::vm_num; } - bool is_str() const { return type == vm_type::vm_str; } - bool is_vec() const { return type == vm_type::vm_vec; } - bool is_hash() const { return type == vm_type::vm_hash; } - bool is_func() const { return type == vm_type::vm_func; } - bool is_upval() const { return type == vm_type::vm_upval; } - bool is_ghost() const { return type == vm_type::vm_ghost; } - bool is_coroutine() const { return type == vm_type::vm_co; } - bool is_map() const { return type == vm_type::vm_map; } - -public: - // convert to number - f64 to_num() const { - return type != vm_type::vm_str - ? val.num - : util::str_to_num(str().c_str()); - } - // convert to string - std::string to_str(); - inline bool object_check(const std::string&) const; - friend std::ostream& operator<<(std::ostream&, var&); -}; - -struct nas_vec { - std::vector elems; - - // mark if this is printed, avoid stack overflow - bool printed = false; - - auto size() const { return elems.size(); } - var get_value(const i32 index) { - i32 size = elems.size(); - if (index < -size || index >= size) { - return var::none(); - } - return elems[index >= 0 ? index : index + size]; - } - var* get_memory(const i32 index) { - i32 size = elems.size(); - if (index < -size || index >= size) { - return nullptr; - } - return &elems[index >= 0 ? index : index + size]; - } - friend std::ostream& operator<<(std::ostream&, nas_vec&); -}; - -struct nas_hash { - std::unordered_map elems; - - // mark if this is printed, avoid stack overflow - bool printed = false; - - auto size() const { return elems.size(); } - var get_value(const std::string&); - var* get_memory(const std::string&); - friend std::ostream& operator<<(std::ostream&, nas_hash&); -}; - -struct nas_func { - i64 dynamic_parameter_index; // dynamic parameter name index in hash. - u64 entry; // pc will set to entry-1 to call this function - u32 parameter_size; // used to load default parameters to a new function - u64 local_size; // used to expand memory space for local values on stack - std::vector local; // local scope with default value(var) - std::vector upval; // closure - - // parameter table, u32 begins from 1 - std::unordered_map keys; - - // dynamic parameter name - std::string dynamic_parameter_name; - - nas_func(): - dynamic_parameter_index(-1), entry(0), - parameter_size(0), local_size(0), - dynamic_parameter_name("") {} - void clear(); - friend std::ostream& operator<<(std::ostream&, nas_func&); -}; - -struct nas_upval { -public: - /* on stack, use these variables */ - bool on_stack; - u64 size; - var* stack_frame_offset; - - /* not on stack, use this */ - std::vector elems; - -public: - nas_upval(): on_stack(true), size(0), stack_frame_offset(nullptr) {} - - var& operator[](usize n) { - return on_stack? stack_frame_offset[n] : elems[n]; - } - - void clear() { - on_stack = true; - elems.clear(); - size = 0; - } -}; - -struct nas_ghost { -private: - using destructor = void (*)(void*); - using marker = void (*)(void*, std::vector*); - -public: - std::string type_name; - destructor destructor_function; - marker gc_mark_function; - void* pointer; - -public: - nas_ghost(): - type_name(""), destructor_function(nullptr), - gc_mark_function(nullptr), pointer(nullptr) {} - ~nas_ghost() { clear(); } - void set(const std::string&, destructor, marker, void*); - void clear(); - friend std::ostream& operator<<(std::ostream&, const nas_ghost&); - -public: - const auto& get_ghost_name() const { return type_name; } - -public: - template - T* get() { return static_cast(pointer); } - template - T convert() const { return reinterpret_cast(pointer); } -}; - -struct context { - u64 pc = 0; - var* localr = nullptr; - var* memr = nullptr; - var funcr = var::nil(); - var upvalr = var::nil(); - var* canary = nullptr; - - var* stack = nullptr; - var* top = nullptr; - - var* func_stack = nullptr; - var* func_top = nullptr; -}; - -struct nas_co { - enum class status:u32 { - suspended, - running, - dead - }; - - context ctx; - status status; - - nas_co() { - ctx.stack = new var[VM_STACK_DEPTH]; - ctx.func_stack = new var[VM_STACK_DEPTH]; - clear(); - } - ~nas_co() { - delete[] ctx.stack; - delete[] ctx.func_stack; - } - void clear(); - friend std::ostream& operator<<(std::ostream&, const nas_co&); -}; - -struct nas_map { - bool printed = false; - std::unordered_map mapper; - -public: - void clear() { - mapper.clear(); - } - auto size() const { return mapper.size(); } - - var get_value(const std::string&); - var* get_memory(const std::string&); - friend std::ostream& operator<<(std::ostream&, nas_map&); -}; - -const var zero = var::num(0); -const var one = var::num(1); -const var nil = var::nil(); - -inline bool var::object_check(const std::string& name) const { - return is_ghost() && ghost().type_name == name && ghost().pointer; -} - -// use to print error log and return error value -static var nas_err(const std::string& func, const std::string& info) { - std::cerr << "[vm] " << func << ": " << info << "\n"; - return var::none(); -} - +#pragma once + +#include "nasal.h" +#include "util/util.h" + +#include +#include +#include +#include +#include + +namespace nasal { + +enum class vm_type: u8 { + /* none-gc object */ + vm_none = 0, // error type + vm_cnt, // counter for forindex/foreach loop + vm_addr, // var* address + vm_ret, // return addres(program counter) + vm_nil, // nil + vm_num, // number + + /* gc object */ + vm_str, // string + vm_vec, // vector + vm_hash, // hashmap(dict) + vm_func, // function(lambda) + vm_upval, // upvalue + vm_ghost, // ghost type + vm_co, // coroutine + vm_map, // for globals and namespaces + + /* mark type range */ + vm_type_size_max +}; + +// size of gc object type +const u32 GC_TYPE_SIZE = + static_cast(vm_type::vm_type_size_max) - + static_cast(vm_type::vm_str); + +// basic types +struct nas_vec; // vector +struct nas_hash; // hashmap(dict) +struct nas_func; // function(lambda) +struct nas_upval; // upvalue +struct nas_ghost; // objects +struct nas_co; // coroutine +struct nas_map; // mapper + +// nas_val includes gc-managed types +struct nas_val { + enum class gc_status: u8 { + uncollected = 0, + collected, + found + }; + + gc_status mark; + vm_type type; // value type + u8 immutable; // used to mark if a string is immutable + union elem { + std::string* str; + nas_vec* vec; + nas_hash* hash; + nas_func* func; + nas_upval* upval; + nas_ghost* obj; + nas_co* co; + nas_map* map; + } ptr; + + nas_val(vm_type); + ~nas_val(); + void clear(); +}; + +struct var { +public: + vm_type type = vm_type::vm_none; + union { + u64 ret; + i64 cnt; + f64 num; + var* addr; + nas_val* gcobj; + } val; + +private: + var(vm_type t, u64 pc) { type = t; val.ret = pc; } + var(vm_type t, i64 ct) { type = t; val.cnt = ct; } + var(vm_type t, f64 n) { type = t; val.num = n; } + var(vm_type t, var* p) { type = t; val.addr = p; } + var(vm_type t, nas_val* p) { type = t; val.gcobj = p; } + +public: + var() = default; + var(const var&) = default; + bool operator==(const var& nr) const { + return type == nr.type && val.gcobj == nr.val.gcobj; + } + bool operator!=(const var& nr) const { + return type != nr.type || val.gcobj != nr.val.gcobj; + } + +public: + // create new var object + static var none() { + return var(vm_type::vm_none, static_cast(0)); + } + static var nil() { + return var(vm_type::vm_nil, static_cast(0)); + } + static var ret(u64 pc) { + return var(vm_type::vm_ret, pc); + } + static var cnt(i64 n) { + return var(vm_type::vm_cnt, n); + } + static var num(f64 n) { + return var(vm_type::vm_num, n); + } + static var gcobj(nas_val* p) { + return var(p->type, p); + } + static var addr(var* p) { + return var(vm_type::vm_addr, p); + } + +public: + // get value + var* addr() const { return val.addr; } + u64 ret() const { return val.ret; } + i64& cnt() { return val.cnt; } + f64 num() const { return val.num; } + +public: + // get gc object + std::string& str() { return *val.gcobj->ptr.str; } + nas_vec& vec() { return *val.gcobj->ptr.vec; } + nas_hash& hash() { return *val.gcobj->ptr.hash; } + nas_func& func() { return *val.gcobj->ptr.func; } + nas_upval& upval() { return *val.gcobj->ptr.upval; } + nas_ghost& ghost() { return *val.gcobj->ptr.obj; } + nas_co& co() { return *val.gcobj->ptr.co; } + nas_map& map() { return *val.gcobj->ptr.map; } + + +public: + // get const gc object + const std::string& str() const { return *val.gcobj->ptr.str; } + const nas_vec& vec() const { return *val.gcobj->ptr.vec; } + const nas_hash& hash() const { return *val.gcobj->ptr.hash; } + const nas_func& func() const { return *val.gcobj->ptr.func; } + const nas_upval& upval() const { return *val.gcobj->ptr.upval; } + const nas_ghost& ghost() const { return *val.gcobj->ptr.obj; } + const nas_co& co() const { return *val.gcobj->ptr.co; } + const nas_map& map() const { return *val.gcobj->ptr.map; } + +public: + bool is_none() const { return type == vm_type::vm_none; } + bool is_cnt() const { return type == vm_type::vm_cnt; } + bool is_addr() const { return type == vm_type::vm_addr; } + bool is_ret() const { return type == vm_type::vm_ret; } + bool is_nil() const { return type == vm_type::vm_nil; } + bool is_num() const { return type == vm_type::vm_num; } + bool is_str() const { return type == vm_type::vm_str; } + bool is_vec() const { return type == vm_type::vm_vec; } + bool is_hash() const { return type == vm_type::vm_hash; } + bool is_func() const { return type == vm_type::vm_func; } + bool is_upval() const { return type == vm_type::vm_upval; } + bool is_ghost() const { return type == vm_type::vm_ghost; } + bool is_coroutine() const { return type == vm_type::vm_co; } + bool is_map() const { return type == vm_type::vm_map; } + +public: + // convert to number + f64 to_num() const { + return type != vm_type::vm_str + ? val.num + : util::str_to_num(str().c_str()); + } + // convert to string + std::string to_str(); + inline bool object_check(const std::string&) const; + friend std::ostream& operator<<(std::ostream&, var&); +}; + +struct nas_vec { + std::vector elems; + + // mark if this is printed, avoid stack overflow + bool printed = false; + + auto size() const { return elems.size(); } + var get_value(const i32 index) { + i32 size = elems.size(); + if (index < -size || index >= size) { + return var::none(); + } + return elems[index >= 0 ? index : index + size]; + } + var* get_memory(const i32 index) { + i32 size = elems.size(); + if (index < -size || index >= size) { + return nullptr; + } + return &elems[index >= 0 ? index : index + size]; + } + friend std::ostream& operator<<(std::ostream&, nas_vec&); +}; + +struct nas_hash { + std::unordered_map elems; + + // mark if this is printed, avoid stack overflow + bool printed = false; + + auto size() const { return elems.size(); } + var get_value(const std::string&); + var* get_memory(const std::string&); + friend std::ostream& operator<<(std::ostream&, nas_hash&); +}; + +struct nas_func { + i64 dynamic_parameter_index; // dynamic parameter name index in hash. + u64 entry; // pc will set to entry-1 to call this function + u32 parameter_size; // used to load default parameters to a new function + u64 local_size; // used to expand memory space for local values on stack + std::vector local; // local scope with default value(var) + std::vector upval; // closure + + // parameter table, u32 begins from 1 + std::unordered_map keys; + + // dynamic parameter name + std::string dynamic_parameter_name; + + nas_func(): + dynamic_parameter_index(-1), entry(0), + parameter_size(0), local_size(0), + dynamic_parameter_name("") {} + void clear(); + friend std::ostream& operator<<(std::ostream&, nas_func&); +}; + +struct nas_upval { +public: + /* on stack, use these variables */ + bool on_stack; + u64 size; + var* stack_frame_offset; + + /* not on stack, use this */ + std::vector elems; + +public: + nas_upval(): on_stack(true), size(0), stack_frame_offset(nullptr) {} + + var& operator[](usize n) { + return on_stack? stack_frame_offset[n] : elems[n]; + } + + void clear() { + on_stack = true; + elems.clear(); + size = 0; + } +}; + +struct nas_ghost { +private: + using destructor = void (*)(void*); + using marker = void (*)(void*, std::vector*); + +public: + std::string type_name; + destructor destructor_function; + marker gc_mark_function; + void* pointer; + +public: + nas_ghost(): + type_name(""), destructor_function(nullptr), + gc_mark_function(nullptr), pointer(nullptr) {} + ~nas_ghost() { clear(); } + void set(const std::string&, destructor, marker, void*); + void clear(); + friend std::ostream& operator<<(std::ostream&, const nas_ghost&); + +public: + const auto& get_ghost_name() const { return type_name; } + +public: + template + T* get() { return static_cast(pointer); } + template + T convert() const { return reinterpret_cast(pointer); } +}; + +struct callsite { + var caller; + u64 file_index = 0; + u64 line = 0; +}; + +struct context { + u64 pc = 0; + var* localr = nullptr; + var* memr = nullptr; + var funcr = var::nil(); + var upvalr = var::nil(); + var* canary = nullptr; + + var* stack = nullptr; + var* top = nullptr; + + callsite* func_stack = nullptr; + callsite* func_top = nullptr; + + const std::string* files = nullptr; + + void ctor() { + stack = new var[VM_STACK_DEPTH]; + func_stack = new callsite[VM_STACK_DEPTH]; + } + void dtor() { + delete[] stack; + delete[] func_stack; + } + void clear() { + /* set canary and program counter */ + pc = 0; + localr = nullptr; + memr = nullptr; + funcr = var::nil(); + upvalr = var::nil(); + + /* set canary = stack[VM_STACK_DEPTH-1] */ + canary = stack + VM_STACK_DEPTH - 1; + + /* nothing is on stack */ + top = stack; + func_top = func_stack - 1; + + /* clear main stack */ + for (u32 i = 0; i < VM_STACK_DEPTH; ++i) { + stack[i] = var::nil(); + } + } +}; + +struct nas_co { + enum class status:u32 { + suspended, + running, + dead + }; + + context ctx; + status status; + + nas_co() { ctx.ctor(); } + ~nas_co() { ctx.dtor(); } + void clear() { ctx.clear(); status = status::suspended; } + friend std::ostream& operator<<(std::ostream&, const nas_co&); +}; + +struct nas_map { + bool printed = false; + std::unordered_map mapper; + +public: + void clear() { + mapper.clear(); + } + auto size() const { return mapper.size(); } + + var get_value(const std::string&); + var* get_memory(const std::string&); + friend std::ostream& operator<<(std::ostream&, nas_map&); +}; + +const var zero = var::num(0); +const var one = var::num(1); +const var nil = var::nil(); + +inline bool var::object_check(const std::string& name) const { + return is_ghost() && ghost().type_name == name && ghost().pointer; +} + +// use to print error log and return error value +static var nas_err(const std::string& func, const std::string& info) { + std::cerr << "[vm] " << func << ": " << info << "\n"; + return var::none(); +} + } \ No newline at end of file diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index 4f7623e..2dbaf9c 100644 --- a/src/nasal_vm.cpp +++ b/src/nasal_vm.cpp @@ -1,834 +1,822 @@ -#include "nasal_vm.h" -#include "util/util.h" - -namespace nasal { - -void vm::vm_init_enrty(const std::vector& strs, - const std::vector& nums, - const std::vector& natives, - const std::vector& code, - const std::unordered_map& global_symbol, - const std::vector& filenames, - const std::vector& argv) { - const_number = nums.data(); - const_string = strs.data(); - bytecode = code.data(); - files = filenames.data(); - global_size = global_symbol.size(); - - /* set native functions */ - native_function = natives; - - /* set context and global */ - if (!is_repl_mode || first_exec_flag) { - context_and_global_init(); - first_exec_flag = false; - } - - /* init gc */ - ngc.set(&ctx, global, global_size); - ngc.init(strs, argv); - - /* init vm globals */ - auto map_instance = ngc.alloc(vm_type::vm_map); - global_symbol_name.resize(global_symbol.size()); - global[global_symbol.at("globals")] = map_instance; - for (const auto& i : global_symbol) { - map_instance.map().mapper[i.first] = global + i.second; - global_symbol_name[i.second] = i.first; - } - - /* init vm arg */ - auto arg_instance = ngc.alloc(vm_type::vm_vec); - global[global_symbol.at("arg")] = arg_instance; - arg_instance.vec().elems = ngc.env_argv; -} - -void vm::context_and_global_init() { - /* set canary and program counter */ - ctx.pc = 0; - ctx.localr = nullptr; - ctx.memr = nullptr; - ctx.funcr = nil; - ctx.upvalr = nil; - - /* set canary = stack[VM_STACK_DEPTH-1] */ - ctx.canary = ctx.stack+VM_STACK_DEPTH-1; - - /* nothing is on stack */ - ctx.top = ctx.stack - 1; - ctx.func_top = ctx.func_stack - 1; - - /* clear main stack and global */ - for (u32 i = 0; i < VM_STACK_DEPTH; ++i) { - ctx.stack[i] = nil; - global[i] = nil; - } -} - -void vm::return_address_info(const var& val) { - std::clog << "0x"; - std::clog << std::hex << val.ret() << std::dec; -} - -void vm::memory_address_info(const var& val) { - std::clog << "0x"; - std::clog << std::hex << reinterpret_cast(val.addr()) << std::dec; -} - -void vm::raw_string_info(var& val) { - std::clog << "\"" << util::rawstr(val.str(), 24) << "\""; -} - -void vm::upvalue_info(var& val) { - std::clog << "[" << val.upval().size << " val] "; - if (val.upval().on_stack) { - std::clog << "offset:0x" << std::hex; - std::clog << reinterpret_cast(val.upval().stack_frame_offset); - std::clog << std::dec; - } -} - -void vm::vector_value_info(var& val) { - std::clog << "[" << val.vec().size() << " val]"; -} - -void vm::hash_value_info(var& val, const usize max_show_elems) { - std::clog << "{"; - usize count = 0; - for (const auto& i : val.hash().elems) { - ++count; - if (count>max_show_elems) { - break; - } - - std::clog << i.first; - if (count!=val.hash().size()) { - std::clog << ", "; - } - } - if (val.hash().size()>max_show_elems) { - std::clog << "..."; - } - std::clog << "}"; -} - -void vm::coroutine_value_info(var& val) { - std::clog << "[ "; - switch(val.co().status) { - case nas_co::status::dead: std::clog << "dead"; break; - case nas_co::status::running: std::clog << "running"; break; - case nas_co::status::suspended: std::clog << "suspended"; break; - } - std::clog << " ] @0x"; - std::clog << std::hex << reinterpret_cast(val.val.gcobj) << std::dec; -} - -void vm::namespace_value_info(var& val, const usize max_show_elems) { - std::clog << "{"; - usize count = 0; - for (const auto& i : val.map().mapper) { - ++count; - if (count>max_show_elems) { - break; - } - - std::clog << i.first; - if (count!=val.map().size()) { - std::clog << ", "; - } - } - if (val.map().size()>max_show_elems) { - std::clog << "..."; - } - std::clog << "}"; -} - -void vm::value_name_form(const var& val) { - std::clog << "| "; - switch(val.type) { - case vm_type::vm_none: std::clog << "null "; break; - case vm_type::vm_ret: std::clog << "ret "; break; - case vm_type::vm_addr: std::clog << "addr "; break; - case vm_type::vm_cnt: std::clog << "cnt "; break; - case vm_type::vm_nil: std::clog << "nil "; break; - case vm_type::vm_num: std::clog << "num "; break; - case vm_type::vm_str: std::clog << "str "; break; - case vm_type::vm_func: std::clog << "func "; break; - case vm_type::vm_upval: std::clog << "upval"; break; - case vm_type::vm_vec: std::clog << "vec "; break; - case vm_type::vm_hash: std::clog << "hash "; break; - case vm_type::vm_ghost: std::clog << "ghost"; break; - case vm_type::vm_co: std::clog << "co "; break; - case vm_type::vm_map: std::clog << "map "; break; - default: std::clog << "err "; break; - } - std::clog << " | "; -} - -void vm::value_info(var& val) { - value_name_form(val); - - switch(val.type) { - case vm_type::vm_none: break; - case vm_type::vm_ret: return_address_info(val); break; - case vm_type::vm_addr: memory_address_info(val); break; - case vm_type::vm_cnt: std::clog << val.cnt(); break; - case vm_type::vm_nil: break; - case vm_type::vm_num: std::clog << val.num(); break; - case vm_type::vm_str: raw_string_info(val); break; - case vm_type::vm_func: std::clog << val.func(); break; - case vm_type::vm_upval: upvalue_info(val); break; - case vm_type::vm_vec: vector_value_info(val); break; - case vm_type::vm_hash: hash_value_info(val, 4); break; - case vm_type::vm_ghost: std::clog << val.ghost(); break; - case vm_type::vm_co: coroutine_value_info(val); break; - case vm_type::vm_map: namespace_value_info(val, 4); break; - default: std::clog << "unknown"; break; - } - std::clog << "\n"; -} - -void vm::function_detail_info(const nas_func& func) { - std::clog << "func "; - - std::vector argument_list = {}; - argument_list.resize(func.keys.size()); - for (const auto& key : func.keys) { - argument_list[key.second-1] = key.first; - } - - std::clog << "("; - for (const auto& key : argument_list) { - std::clog << key; - if (key != argument_list.back()) { - std::clog << ", "; - } - } - if (func.dynamic_parameter_index>=0) { - std::clog << (argument_list.size()? ", ":""); - std::clog << func.dynamic_parameter_name << "..."; - } - std::clog << ") "; - const auto& code = bytecode[func.entry]; - std::clog << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; -} - -void vm::function_call_trace() { - // no function is called when error ocurred - if (!ctx.funcr.is_func()) { - return; - } - - util::windows_code_page_manager cp; - cp.set_utf8_output(); - - var* bottom = ctx.stack; - var* top = ctx.top; - - // generate trace back - std::vector functions; - std::vector callsite; - - var* prev_func = &ctx.funcr; - functions.push_back(&prev_func->func()); - for (var* i = top; i >= bottom; i--) { - // +-------+------------------+ - // | ret | 0x3bf | <-- i + 1 (should not be 0, except coroutine) - // +-------+------------------+ - // | addr | 0x7ff5f61ae020 | <-- i - // +-------+------------------+ - // | upval | ... | <-- i - 1 - // +-------+------------------+ - // | locals| ... | - // +-------+------------------+ - // | func | function | <-- i - 1 - prev_func->local_size - 1 - // +-------+------------------+ - if (i + 1 <= top && i[0].is_addr() && i[1].is_ret()) { - auto r_addr = i[1].ret(); - callsite.push_back(r_addr); - i--; - i -= prev_func->func().local_size; - i--; - if (i >= bottom && i[0].is_func()) { - prev_func = i; - functions.push_back(&prev_func->func()); - } - } - } - - std::clog << "\ncall trace "; - std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; - std::clog << " crash occurred at\n "; - function_detail_info(ctx.funcr.func()); - std::clog << " at " << files[bytecode[ctx.pc].fidx] << ":"; - std::clog << bytecode[ctx.pc].line << "\n"; - - if (callsite.empty()) { - cp.restore_code_page(); - return; - } - - const nas_func* prev = nullptr; - u64 prev_addr = 0; - u64 same_call_count = 0; - for (int i = 0; i < functions.size(); ++i) { - if (functions[i] == prev && callsite[i] == prev_addr) { - same_call_count++; - continue; - } else if (same_call_count) { - std::clog << " `--> " << same_call_count << " same call(s)\n"; - same_call_count = 0; - } - - // in coroutine - if (callsite[i] == 0 && ngc.cort) { - std::clog << " call by coroutine\n"; - break; - } - - std::clog << " call "; - function_detail_info(*functions[i]); - auto r_addr = callsite[i]; - std::clog << " from " << files[bytecode[r_addr].fidx] << ":"; - std::clog << bytecode[r_addr].line << "\n"; - - prev = functions[i]; - prev_addr = r_addr; - } - if (same_call_count) { - std::clog << " `--> " << same_call_count << " same call(s)\n"; - } - - cp.restore_code_page(); -} - -void vm::trace_back() { - // generate trace back - std::stack ret; - for (var* i = ctx.stack; i<=ctx.top; ++i) { - if (i->is_ret() && i->ret()!=0) { - ret.push(i->ret()); - } - } - - // store the position program crashed - ret.push(ctx.pc); - - std::clog << "\nback trace "; - std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; - codestream::set( - const_number, - const_string, - global_symbol_name, - native_function.data(), - files - ); - - for (u64 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) { - if ((p = ret.top())==prev) { - ++same; - continue; - } else if (same) { - std::clog << " 0x" << std::hex - << std::setw(8) << std::setfill('0') - << prev << std::dec << " " - << same << " same call(s)\n"; - same = 0; - } - std::clog << " " << codestream(bytecode[p], p) << "\n"; - } - // the first called place has no same calls -} - -void vm::stack_info(const u64 limit) { - var* top = ctx.top; - var* bottom = ctx.stack; - const auto stack_address = reinterpret_cast(bottom); - - std::clog << "\nstack (0x" << std::hex << stack_address << std::dec; - std::clog << ", limit " << limit << ", total "; - std::clog << (top(top-bottom+1)) << ")\n"; - - for (u32 i = 0; i=bottom; ++i, --top) { - std::clog << " 0x" << std::hex - << std::setw(8) << std::setfill('0') - << static_cast(top-bottom) << std::dec - << " "; - value_info(top[0]); - } -} - -void vm::register_info() { - std::clog << "\nregister (" << (ngc.cort? "coroutine":"main") << ")\n"; - std::clog << std::hex - << " [ pc ] | pc | 0x" << ctx.pc << "\n" - << " [ global ] | addr | 0x" - << reinterpret_cast(global) << "\n" - << " [ local ] | addr | 0x" - << reinterpret_cast(ctx.localr) << "\n" - << " [ memr ] | addr | 0x" - << reinterpret_cast(ctx.memr) << "\n" - << " [ canary ] | addr | 0x" - << reinterpret_cast(ctx.canary) << "\n" - << " [ top ] | addr | 0x" - << reinterpret_cast(ctx.top) << "\n" - << std::dec; - std::clog << " [ funcr ] "; value_info(ctx.funcr); - std::clog << " [ upval ] "; value_info(ctx.upvalr); -} - -void vm::global_state() { - if (!global_size || global[0].is_none()) { - return; - } - std::clog << "\nglobal (0x" << std::hex - << reinterpret_cast(global) << ")\n" << std::dec; - for (usize i = 0; i(i) << std::dec - << " "; - auto name = global_symbol_name[i]; - if (name.length()>=10) { - name = name.substr(0, 7) + "..."; - } else { - - } - std::clog << "| " << std::left << std::setw(10) - << std::setfill(' ') << name << " " - << std::internal; - value_info(global[i]); - } -} - -void vm::local_state() { - if (!ctx.localr || !ctx.funcr.func().local_size) { - return; - } - const u32 lsize = ctx.funcr.func().local_size; - std::clog << "\nlocal (0x" << std::hex << reinterpret_cast(ctx.localr) - << " <+" << static_cast(ctx.localr-ctx.stack) - << ">)\n" << std::dec; - for (u32 i = 0; i upval[" << i << "]:\n"; - auto& uv = upval[i].upval(); - for (u32 j = 0; j argument_list = {}; - argument_list.resize(func.keys.size()); - for (const auto& i : func.keys) { - argument_list[i.second-1] = i.first; - } - for (u32 i = 0; i=0) { - result += argument_list.size()? ", ":""; - result += const_string[func.dynamic_parameter_index] + "[dynamic]"; - } - result += ") "; - std::stringstream out; - const auto& code = bytecode[func.entry]; - out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; - out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; - return result + out.str(); -} - -std::string vm::report_special_call_lack_arguments(var* local, - const nas_func& func) const { - auto result = std::string("lack argument(s) when calling function:\n func("); - std::vector argument_list = {}; - argument_list.resize(func.keys.size()); - for (const auto& i : func.keys) { - argument_list[i.second-1] = i.first; - } - for (const auto& key : argument_list) { - if (local[func.keys.at(key)].is_none()) { - result += key + ", "; - } else { - result += key + "[get], "; - } - } - result = result.substr(0, result.length()-2); - result += ") "; - std::stringstream out; - const auto& code = bytecode[func.entry]; - out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; - out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; - return result + out.str(); -} - -std::string vm::report_key_not_found(const std::string& not_found, - const nas_hash& hash) const { - auto result = "member \"" + not_found + "\" doesn't exist in hash {"; - for (const auto& i : hash.elems) { - result += i.first + ", "; - } - if (hash.elems.size()) { - result = result.substr(0, result.length()-2); - } - result += "}"; - return result; -} - -std::string vm::report_out_of_range(f64 index, usize real_size) const { - auto result = "index out of range: " + std::to_string(index); - result += " but max size is " + std::to_string(real_size); - if (!real_size) { - return result; - } - result += ", index range is -" + std::to_string(real_size); - result += "~" + std::to_string(real_size-1); - return result; -} - -std::string vm::type_name_string(const var& value) const { - switch(value.type) { - case vm_type::vm_none: return "none"; - case vm_type::vm_cnt: return "counter"; - case vm_type::vm_addr: return "address"; - case vm_type::vm_ret: return "program counter"; - case vm_type::vm_nil: return "nil"; - case vm_type::vm_num: return "number"; - case vm_type::vm_str: return "string"; - case vm_type::vm_vec: return "vector"; - case vm_type::vm_hash: return "hash"; - case vm_type::vm_func: return "function"; - case vm_type::vm_upval: return "upvalue"; - case vm_type::vm_ghost: return "ghost type"; - case vm_type::vm_co: return "coroutine"; - case vm_type::vm_map: return "namespace"; - default: break; - } - return "unknown"; -} - -void vm::die(const std::string& str) { - const auto& file = files[bytecode[ctx.pc].fidx]; - const auto line = bytecode[ctx.pc].line; - std::cerr << "[vm] error occurred at " << file << ":" << line << ": "; - std::cerr << str << "\n"; - function_call_trace(); - - // trace back contains bytecode info, dump in verbose mode - if (verbose) { - trace_back(); - } - - // verbose will dump more values on stack - if (verbose) { - stack_info(64); - } - - // show verbose crash info - if (verbose) { - all_state_detail(); - } - - if (!ngc.cort) { - if (!verbose) { - std::cerr << "\n[vm] use <-d> for detailed crash info.\n\n"; - } - // in main context, exit directly - std::exit(1); - } - - // in coroutine, shut down the coroutine and return to main context - ctx.pc = 0; // mark coroutine 'dead' - ngc.context_reserve(); // switch context to main - ctx.top[0] = nil; // generate return value 'nil' -} - -void vm::run(const codegen& gen, - const linker& linker, - const std::vector& argv) { - vm_init_enrty( - gen.strs(), - gen.nums(), - gen.natives(), - gen.codes(), - gen.globals(), - linker.get_file_list(), - argv - ); - -#ifndef _MSC_VER - -// interrupt check macro for computed goto mode. -#define CHECK_INTERRUPT { \ - if (interrupt_ptr && interrupt_ptr->load()) { \ - throw std::runtime_error("VM execution interrupted by timeout"); \ - } \ -} - - // using labels as values/computed goto - const void* oprs[] = { - &&vmexit, - &&repl, - &&intl, - &&loadg, - &&loadl, - &&loadu, - &&dup, - &&pnum, - &&pnil, - &&pstr, - &&newv, - &&newh, - &&newf, - &&happ, - &¶, - &&deft, - &&dyn, - &&lnot, - &&usub, - &&bnot, - &&btor, - &&btxor, - &&btand, - &&add, - &&sub, - &&mul, - &&div, - &&lnk, - &&addc, - &&subc, - &&mulc, - &&divc, - &&lnkc, - &&addeq, - &&subeq, - &&muleq, - &&diveq, - &&lnkeq, - &&bandeq, - &&boreq, - &&bxoreq, - &&addeqc, - &&subeqc, - &&muleqc, - &&diveqc, - &&lnkeqc, - &&addecp, - &&subecp, - &&mulecp, - &&divecp, - &&lnkecp, - &&meq, - &&eq, - &&neq, - &&less, - &&leq, - &&grt, - &&geq, - &&lessc, - &&leqc, - &&grtc, - &&geqc, - &&pop, - &&jmp, - &&jt, - &&jf, - &&cnt, - &&findex, - &&feach, - &&callg, - &&calll, - &&upval, - &&callv, - &&callvi, - &&callh, - &&callfv, - &&callfh, - &&callb, - &&slcbeg, - &&slcend, - &&slc, - &&slc2, - &&mcallg, - &&mcalll, - &&mupval, - &&mcallv, - &&mcallh, - &&ret - }; - std::vector code; - for (const auto& i : gen.codes()) { - code.push_back(oprs[i.op]); - imm.push_back(i.num); - } - CHECK_INTERRUPT; - // goto the first operand - goto *code[ctx.pc]; -#else - std::vector code; - for (const auto& i : gen.codes()) { - code.push_back(operand_function[i.op]); - imm.push_back(i.num); - } - while (code[ctx.pc]) { - if (interrupt_ptr && interrupt_ptr->load()) { - throw std::runtime_error("VM execution interrupted by timeout"); - } - (this->*code[ctx.pc])(); - if (ctx.top>=ctx.canary) { - die("stack overflow"); - } - ++ctx.pc; - } -#endif -// all nasal programs should end here -vmexit: - if (verbose) { - ngc.status.dump_info(); - } - imm.clear(); - if (!is_repl_mode) { - ngc.clear(); - } - return; - -#ifndef _MSC_VER -// IR which may cause stackoverflow -#define exec_check(op) {\ - op();\ - CHECK_INTERRUPT;\ - if (ctx.top& strs, + const std::vector& nums, + const std::vector& natives, + const std::vector& code, + const std::unordered_map& global_symbol, + const std::vector& filenames, + const std::vector& argv) { + const_number = nums.data(); + const_string = strs.data(); + bytecode = code.data(); + files = filenames.data(); + global_size = global_symbol.size(); + + /* set native functions */ + native_function = natives; + + /* set context and global */ + if (!is_repl_mode || first_exec_flag) { + context_and_global_init(); + first_exec_flag = false; + } + + /* init gc */ + ngc.set(&ctx, global, global_size); + ngc.init(strs, argv); + + /* init vm globals */ + auto map_instance = ngc.alloc(vm_type::vm_map); + global_symbol_name.resize(global_symbol.size()); + global[global_symbol.at("globals")] = map_instance; + for (const auto& i : global_symbol) { + map_instance.map().mapper[i.first] = global + i.second; + global_symbol_name[i.second] = i.first; + } + + /* init vm arg */ + auto arg_instance = ngc.alloc(vm_type::vm_vec); + global[global_symbol.at("arg")] = arg_instance; + arg_instance.vec().elems = ngc.env_argv; +} + +void vm::context_and_global_init() { + /* clear context status */ + ctx.clear(); + + /* clear main stack and global */ + for (u32 i = 0; i < VM_STACK_DEPTH; ++i) { + global[i] = nil; + } +} + +void vm::return_address_info(const var& val) { + std::clog << "0x"; + std::clog << std::hex << val.ret() << std::dec; +} + +void vm::memory_address_info(const var& val) { + std::clog << "0x"; + std::clog << std::hex << reinterpret_cast(val.addr()) << std::dec; +} + +void vm::raw_string_info(var& val) { + std::clog << "\"" << util::rawstr(val.str(), 24) << "\""; +} + +void vm::upvalue_info(var& val) { + std::clog << "[" << val.upval().size << " val] "; + if (val.upval().on_stack) { + std::clog << "offset:0x" << std::hex; + std::clog << reinterpret_cast(val.upval().stack_frame_offset); + std::clog << std::dec; + } +} + +void vm::vector_value_info(var& val) { + std::clog << "[" << val.vec().size() << " val]"; +} + +void vm::hash_value_info(var& val, const usize max_show_elems) { + std::clog << "{"; + usize count = 0; + for (const auto& i : val.hash().elems) { + ++count; + if (count>max_show_elems) { + break; + } + + std::clog << i.first; + if (count!=val.hash().size()) { + std::clog << ", "; + } + } + if (val.hash().size()>max_show_elems) { + std::clog << "..."; + } + std::clog << "}"; +} + +void vm::coroutine_value_info(var& val) { + std::clog << "[ "; + switch(val.co().status) { + case nas_co::status::dead: std::clog << "dead"; break; + case nas_co::status::running: std::clog << "running"; break; + case nas_co::status::suspended: std::clog << "suspended"; break; + } + std::clog << " ] @0x"; + std::clog << std::hex << reinterpret_cast(val.val.gcobj) << std::dec; +} + +void vm::namespace_value_info(var& val, const usize max_show_elems) { + std::clog << "{"; + usize count = 0; + for (const auto& i : val.map().mapper) { + ++count; + if (count>max_show_elems) { + break; + } + + std::clog << i.first; + if (count!=val.map().size()) { + std::clog << ", "; + } + } + if (val.map().size()>max_show_elems) { + std::clog << "..."; + } + std::clog << "}"; +} + +void vm::value_name_form(const var& val) { + std::clog << "| "; + switch(val.type) { + case vm_type::vm_none: std::clog << "null "; break; + case vm_type::vm_ret: std::clog << "ret "; break; + case vm_type::vm_addr: std::clog << "addr "; break; + case vm_type::vm_cnt: std::clog << "cnt "; break; + case vm_type::vm_nil: std::clog << "nil "; break; + case vm_type::vm_num: std::clog << "num "; break; + case vm_type::vm_str: std::clog << "str "; break; + case vm_type::vm_func: std::clog << "func "; break; + case vm_type::vm_upval: std::clog << "upval"; break; + case vm_type::vm_vec: std::clog << "vec "; break; + case vm_type::vm_hash: std::clog << "hash "; break; + case vm_type::vm_ghost: std::clog << "ghost"; break; + case vm_type::vm_co: std::clog << "co "; break; + case vm_type::vm_map: std::clog << "map "; break; + default: std::clog << "err "; break; + } + std::clog << " | "; +} + +void vm::value_info(var& val) { + value_name_form(val); + + switch(val.type) { + case vm_type::vm_none: break; + case vm_type::vm_ret: return_address_info(val); break; + case vm_type::vm_addr: memory_address_info(val); break; + case vm_type::vm_cnt: std::clog << val.cnt(); break; + case vm_type::vm_nil: break; + case vm_type::vm_num: std::clog << val.num(); break; + case vm_type::vm_str: raw_string_info(val); break; + case vm_type::vm_func: std::clog << val.func(); break; + case vm_type::vm_upval: upvalue_info(val); break; + case vm_type::vm_vec: vector_value_info(val); break; + case vm_type::vm_hash: hash_value_info(val, 4); break; + case vm_type::vm_ghost: std::clog << val.ghost(); break; + case vm_type::vm_co: coroutine_value_info(val); break; + case vm_type::vm_map: namespace_value_info(val, 4); break; + default: std::clog << "unknown"; break; + } + std::clog << "\n"; +} + +void vm::function_detail_info(const nas_func& func) { + std::clog << "func "; + + std::vector argument_list = {}; + argument_list.resize(func.keys.size()); + for (const auto& key : func.keys) { + argument_list[key.second-1] = key.first; + } + + std::clog << "("; + for (const auto& key : argument_list) { + std::clog << key; + if (key != argument_list.back()) { + std::clog << ", "; + } + } + if (func.dynamic_parameter_index>=0) { + std::clog << (argument_list.size()? ", ":""); + std::clog << func.dynamic_parameter_name << "..."; + } + std::clog << ") "; + const auto& code = bytecode[func.entry]; + std::clog << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; +} + +void vm::function_call_trace() { + // no function is called when error ocurred + if (!ctx.funcr.is_func()) { + return; + } + + util::windows_code_page_manager cp; + cp.set_utf8_output(); + + var* bottom = ctx.stack; + var* top = ctx.top; + + // generate trace back + std::vector functions; + std::vector callsite; + + var* prev_func = &ctx.funcr; + functions.push_back(&prev_func->func()); + for (var* i = top; i >= bottom; i--) { + // +-------+------------------+ + // | ret | 0x3bf | <-- i + 1 (should not be 0, except coroutine) + // +-------+------------------+ + // | addr | 0x7ff5f61ae020 | <-- i + // +-------+------------------+ + // | upval | ... | <-- i - 1 + // +-------+------------------+ + // | locals| ... | + // +-------+------------------+ + // | func | function | <-- i - 1 - prev_func->local_size - 1 + // +-------+------------------+ + if (i + 1 <= top && i[0].is_addr() && i[1].is_ret()) { + auto r_addr = i[1].ret(); + callsite.push_back(r_addr); + i--; + i -= prev_func->func().local_size; + i--; + if (i >= bottom && i[0].is_func()) { + prev_func = i; + functions.push_back(&prev_func->func()); + } + } + } + + std::clog << "\ncall trace "; + std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; + std::clog << " crash occurred at\n "; + function_detail_info(ctx.funcr.func()); + std::clog << " at " << files[bytecode[ctx.pc].fidx] << ":"; + std::clog << bytecode[ctx.pc].line << "\n"; + + if (callsite.empty()) { + cp.restore_code_page(); + return; + } + + const nas_func* prev = nullptr; + u64 prev_addr = 0; + u64 same_call_count = 0; + for (int i = 0; i < functions.size(); ++i) { + if (functions[i] == prev && callsite[i] == prev_addr) { + same_call_count++; + continue; + } else if (same_call_count) { + std::clog << " `--> " << same_call_count << " same call(s)\n"; + same_call_count = 0; + } + + // in coroutine + if (callsite[i] == 0 && ngc.cort) { + std::clog << " call by coroutine\n"; + break; + } + + std::clog << " call "; + function_detail_info(*functions[i]); + auto r_addr = callsite[i]; + std::clog << " from " << files[bytecode[r_addr].fidx] << ":"; + std::clog << bytecode[r_addr].line << "\n"; + + prev = functions[i]; + prev_addr = r_addr; + } + if (same_call_count) { + std::clog << " `--> " << same_call_count << " same call(s)\n"; + } + + cp.restore_code_page(); +} + +void vm::trace_back() { + // generate trace back + std::stack ret; + for (var* i = ctx.stack; i<=ctx.top; ++i) { + if (i->is_ret() && i->ret()!=0) { + ret.push(i->ret()); + } + } + + // store the position program crashed + ret.push(ctx.pc); + + std::clog << "\nback trace "; + std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; + codestream::set( + const_number, + const_string, + global_symbol_name, + native_function.data(), + files + ); + + for (u64 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) { + if ((p = ret.top())==prev) { + ++same; + continue; + } else if (same) { + std::clog << " 0x" << std::hex + << std::setw(8) << std::setfill('0') + << prev << std::dec << " " + << same << " same call(s)\n"; + same = 0; + } + std::clog << " " << codestream(bytecode[p], p) << "\n"; + } + // the first called place has no same calls +} + +void vm::stack_info(const u64 limit) { + var* top = ctx.top; + var* bottom = ctx.stack; + const auto stack_address = reinterpret_cast(bottom); + + std::clog << "\nstack (0x" << std::hex << stack_address << std::dec; + std::clog << ", limit " << limit << ", total "; + std::clog << (top(top-bottom+1)) << ")\n"; + + for (u32 i = 0; i=bottom; ++i, --top) { + std::clog << " 0x" << std::hex + << std::setw(8) << std::setfill('0') + << static_cast(top-bottom) << std::dec + << " "; + value_info(top[0]); + } +} + +void vm::register_info() { + std::clog << "\nregister (" << (ngc.cort? "coroutine":"main") << ")\n"; + std::clog << std::hex + << " [ pc ] | pc | 0x" << ctx.pc << "\n" + << " [ global ] | addr | 0x" + << reinterpret_cast(global) << "\n" + << " [ local ] | addr | 0x" + << reinterpret_cast(ctx.localr) << "\n" + << " [ memr ] | addr | 0x" + << reinterpret_cast(ctx.memr) << "\n" + << " [ canary ] | addr | 0x" + << reinterpret_cast(ctx.canary) << "\n" + << " [ top ] | addr | 0x" + << reinterpret_cast(ctx.top) << "\n" + << std::dec; + std::clog << " [ funcr ] "; value_info(ctx.funcr); + std::clog << " [ upval ] "; value_info(ctx.upvalr); +} + +void vm::global_state() { + if (!global_size || global[0].is_none()) { + return; + } + std::clog << "\nglobal (0x" << std::hex + << reinterpret_cast(global) << ")\n" << std::dec; + for (usize i = 0; i(i) << std::dec + << " "; + auto name = global_symbol_name[i]; + if (name.length()>=10) { + name = name.substr(0, 7) + "..."; + } else { + + } + std::clog << "| " << std::left << std::setw(10) + << std::setfill(' ') << name << " " + << std::internal; + value_info(global[i]); + } +} + +void vm::local_state() { + if (!ctx.localr || !ctx.funcr.func().local_size) { + return; + } + const u32 lsize = ctx.funcr.func().local_size; + std::clog << "\nlocal (0x" << std::hex << reinterpret_cast(ctx.localr) + << " <+" << static_cast(ctx.localr-ctx.stack) + << ">)\n" << std::dec; + for (u32 i = 0; i upval[" << i << "]:\n"; + auto& uv = upval[i].upval(); + for (u32 j = 0; j argument_list = {}; + argument_list.resize(func.keys.size()); + for (const auto& i : func.keys) { + argument_list[i.second-1] = i.first; + } + for (u32 i = 0; i=0) { + result += argument_list.size()? ", ":""; + result += const_string[func.dynamic_parameter_index] + "[dynamic]"; + } + result += ") "; + std::stringstream out; + const auto& code = bytecode[func.entry]; + out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; + out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; + return result + out.str(); +} + +std::string vm::report_special_call_lack_arguments(var* local, + const nas_func& func) const { + auto result = std::string("lack argument(s) when calling function:\n func("); + std::vector argument_list = {}; + argument_list.resize(func.keys.size()); + for (const auto& i : func.keys) { + argument_list[i.second-1] = i.first; + } + for (const auto& key : argument_list) { + if (local[func.keys.at(key)].is_none()) { + result += key + ", "; + } else { + result += key + "[get], "; + } + } + result = result.substr(0, result.length()-2); + result += ") "; + std::stringstream out; + const auto& code = bytecode[func.entry]; + out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; + out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; + return result + out.str(); +} + +std::string vm::report_key_not_found(const std::string& not_found, + const nas_hash& hash) const { + auto result = "member \"" + not_found + "\" doesn't exist in hash {"; + for (const auto& i : hash.elems) { + result += i.first + ", "; + } + if (hash.elems.size()) { + result = result.substr(0, result.length()-2); + } + result += "}"; + return result; +} + +std::string vm::report_out_of_range(f64 index, usize real_size) const { + auto result = "index out of range: " + std::to_string(index); + result += " but max size is " + std::to_string(real_size); + if (!real_size) { + return result; + } + result += ", index range is -" + std::to_string(real_size); + result += "~" + std::to_string(real_size-1); + return result; +} + +std::string vm::type_name_string(const var& value) const { + switch(value.type) { + case vm_type::vm_none: return "none"; + case vm_type::vm_cnt: return "counter"; + case vm_type::vm_addr: return "address"; + case vm_type::vm_ret: return "program counter"; + case vm_type::vm_nil: return "nil"; + case vm_type::vm_num: return "number"; + case vm_type::vm_str: return "string"; + case vm_type::vm_vec: return "vector"; + case vm_type::vm_hash: return "hash"; + case vm_type::vm_func: return "function"; + case vm_type::vm_upval: return "upvalue"; + case vm_type::vm_ghost: return "ghost type"; + case vm_type::vm_co: return "coroutine"; + case vm_type::vm_map: return "namespace"; + default: break; + } + return "unknown"; +} + +void vm::die(const std::string& str) { + const auto& file = files[bytecode[ctx.pc].fidx]; + const auto line = bytecode[ctx.pc].line; + std::cerr << "[vm] error occurred at " << file << ":" << line << ": "; + std::cerr << str << "\n"; + function_call_trace(); + + // trace back contains bytecode info, dump in verbose mode + if (verbose) { + trace_back(); + } + + // verbose will dump more values on stack + if (verbose) { + stack_info(64); + } + + // show verbose crash info + if (verbose) { + all_state_detail(); + } + + if (!ngc.cort) { + if (!verbose) { + std::cerr << "\n[vm] use <-d> for detailed crash info.\n\n"; + } + // in main context, exit directly + std::exit(1); + } + + // in coroutine, shut down the coroutine and return to main context + ctx.pc = 0; // mark coroutine 'dead' + ngc.context_reserve(); // switch context to main + ctx.top[0] = nil; // generate return value 'nil' +} + +void vm::run(const codegen& gen, + const linker& linker, + const std::vector& argv) { + vm_init_enrty( + gen.strs(), + gen.nums(), + gen.natives(), + gen.codes(), + gen.globals(), + linker.get_file_list(), + argv + ); + +#ifndef _MSC_VER + +// interrupt check macro for computed goto mode. +#define CHECK_INTERRUPT { \ + if (interrupt_ptr && interrupt_ptr->load()) { \ + throw std::runtime_error("VM execution interrupted by timeout"); \ + } \ +} + + // using labels as values/computed goto + const void* oprs[] = { + &&vmexit, + &&repl, + &&intl, + &&loadg, + &&loadl, + &&loadu, + &&dup, + &&pnum, + &&pnil, + &&pstr, + &&newv, + &&newh, + &&newf, + &&happ, + &¶, + &&deft, + &&dyn, + &&lnot, + &&usub, + &&bnot, + &&btor, + &&btxor, + &&btand, + &&add, + &&sub, + &&mul, + &&div, + &&lnk, + &&addc, + &&subc, + &&mulc, + &&divc, + &&lnkc, + &&addeq, + &&subeq, + &&muleq, + &&diveq, + &&lnkeq, + &&bandeq, + &&boreq, + &&bxoreq, + &&addeqc, + &&subeqc, + &&muleqc, + &&diveqc, + &&lnkeqc, + &&addecp, + &&subecp, + &&mulecp, + &&divecp, + &&lnkecp, + &&meq, + &&eq, + &&neq, + &&less, + &&leq, + &&grt, + &&geq, + &&lessc, + &&leqc, + &&grtc, + &&geqc, + &&pop, + &&jmp, + &&jt, + &&jf, + &&cnt, + &&findex, + &&feach, + &&callg, + &&calll, + &&upval, + &&callv, + &&callvi, + &&callh, + &&callfv, + &&callfh, + &&callb, + &&slcbeg, + &&slcend, + &&slc, + &&slc2, + &&mcallg, + &&mcalll, + &&mupval, + &&mcallv, + &&mcallh, + &&ret + }; + std::vector code; + for (const auto& i : gen.codes()) { + code.push_back(oprs[i.op]); + imm.push_back(i.num); + } + CHECK_INTERRUPT; + // goto the first operand + goto *code[ctx.pc]; +#else + std::vector code; + for (const auto& i : gen.codes()) { + code.push_back(operand_function[i.op]); + imm.push_back(i.num); + } + while (code[ctx.pc]) { + if (interrupt_ptr && interrupt_ptr->load()) { + throw std::runtime_error("VM execution interrupted by timeout"); + } + (this->*code[ctx.pc])(); + if (ctx.top>=ctx.canary) { + die("stack overflow"); + } + ++ctx.pc; + } +#endif +// all nasal programs should end here +vmexit: + if (verbose) { + ngc.status.dump_info(); + } + imm.clear(); + if (!is_repl_mode) { + ngc.clear(); + } + return; + +#ifndef _MSC_VER +// IR which may cause stackoverflow +#define exec_check(op) {\ + op();\ + CHECK_INTERRUPT;\ + if (ctx.top=ctx.canary) { @@ -972,7 +974,11 @@ inline void vm::o_callfh() { var tmp = ctx.top[-1]; ctx.top[-1] = ctx.funcr; ctx.funcr = tmp; - (++ctx.func_top)[0] = tmp; + (++ctx.func_top)[0] = { + tmp, + bytecode[ctx.pc].fidx, + bytecode[ctx.pc].line + }; // top -1(hash) +lsize(local) +1(old pc) +1(old localr) +1(old upvalr) if (ctx.top+func.local_size+2>= ctx.canary) { @@ -1015,6 +1021,9 @@ inline void vm::o_callb() { // this code is written for coroutine (++ctx.top)[0] = nil; + // set file list into ctx + ctx.files = files; + // if running a native function about coroutine // (top) will be set to another context.top, instead of main_context.top auto function_pointer = native_function[imm[ctx.pc]].func; diff --git a/src/natives/builtin.cpp b/src/natives/builtin.cpp index 0d5cce9..ec865ee 100644 --- a/src/natives/builtin.cpp +++ b/src/natives/builtin.cpp @@ -1,871 +1,878 @@ -#include "natives/builtin.h" -#include "util/util.h" - -#include - -#ifdef _WIN32 -#include -#else -#include -#include -#endif - -namespace nasal { - -var builtin_unsafe(context* ctx, gc* ngc) { - return nas_err( - "unsafe_redirect", - "you are using unsafe system api under limited mode!" - ); -} - -var builtin_print(context* ctx, gc* ngc) { - for (auto& i : ctx->localr[1].vec().elems) { - std::cout << i; - } - std::cout << std::flush; - return nil; -} - -var builtin_println(context* ctx, gc* ngc) { - for (auto& i : ctx->localr[1].vec().elems) { - std::cout << i; - } - std::cout << std::endl; - return nil; -} - -var builtin_exit(context* ctx, gc* ngc) { - std::exit(ctx->localr[1].num()); - return nil; -} - -var builtin_abort(context* ctx, gc* ngc) { - std::abort(); - return nil; -} - -var builtin_append(context* ctx, gc* ngc) { - auto local = ctx->localr; - var vec = local[1]; - var elem = local[2]; - if (!vec.is_vec()) { - return nas_err("native::append", "\"vec\" must be vector"); - } - auto& v = vec.vec().elems; - for (auto& i : elem.vec().elems) { - v.push_back(i); - } - return nil; -} - -var builtin_setsize(context* ctx, gc* ngc) { - auto local = ctx->localr; - var vec = local[1]; - var size = local[2]; - if (!vec.is_vec()) { - return nas_err("native::setsize", "\"vec\" must be vector"); - } - if (!size.is_num() || size.num()<0) { - return nil; - } - vec.vec().elems.resize(static_cast(size.num()), nil); - return nil; -} - -var builtin_system(context* ctx, gc* ngc) { - auto str = ctx->localr[1]; - if (!str.is_str()) { - return var::num(-1); - } - return var::num(static_cast(system(str.str().c_str()))); -} - -var builtin_input(context* ctx, gc* ngc) { - auto local = ctx->localr; - var end = local[1]; - var ret = ngc->alloc(vm_type::vm_str); - if (!end.is_str() || end.str().length()>1 || !end.str().length()) { - std::cin >> ret.str(); - } else { - std::getline(std::cin, ret.str(), end.str()[0]); - } - return ret; -} - -var builtin_split(context* ctx, gc* ngc) { - auto local = ctx->localr; - var separator = local[1]; - var str = local[2]; - if (!separator.is_str()) { - return nas_err("native::split", "\"separator\" must be string"); - } - if (!str.is_str()) { - return nas_err("native::split", "\"str\" must be string"); - } - const auto& sep = separator.str(); - const auto& s = str.str(); - - // avoid being sweeped - auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); - auto& vec = res.vec().elems; - - // empty separator means split every char - if (!sep.length()) { - for (auto i : s) { - vec.push_back(ngc->newstr(i)); - } - ngc->temp = nil; - return res; - } - - usize last = 0; - usize pos = s.find(sep, 0); - while (pos!=std::string::npos) { - if (pos>last) { - vec.push_back(ngc->newstr(s.substr(last, pos-last))); - } - last = pos + sep.length(); - pos = s.find(sep, last); - } - if (last!=s.length()) { - vec.push_back(ngc->newstr(s.substr(last))); - } - ngc->temp = nil; - return res; -} - -var builtin_split_with_empty_substr(context* ctx, gc* ngc) { - auto local = ctx->localr; - var separator = local[1]; - var str = local[2]; - if (!separator.is_str()) { - return nas_err( - "native::split_with_empty_substr", - "\"separator\" must be string" - ); - } - if (!str.is_str()) { - return nas_err( - "native::split_with_empty_substr", - "\"str\" must be string" - ); - } - const auto& sep = separator.str(); - const auto& s = str.str(); - - // avoid being sweeped - auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); - auto& vec = res.vec().elems; - - // empty separator means split every char - if (!sep.length()) { - for (auto i : s) { - vec.push_back(ngc->newstr(i)); - } - ngc->temp = nil; - return res; - } - - usize last = 0; - usize pos = s.find(sep, 0); - while (pos!=std::string::npos) { - if (pos>=last) { - vec.push_back(ngc->newstr(s.substr(last, pos-last))); - } - last = pos + sep.length(); - pos = s.find(sep, last); - } - if (last<=s.length()) { - vec.push_back(ngc->newstr(s.substr(last))); - } - ngc->temp = nil; - return res; -} - -var builtin_rand(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (!val.is_num() && !val.is_nil()) { - return nas_err("native::rand", "\"seed\" must be nil or number"); - } - if (val.is_num()) { - srand(static_cast(val.num())); - return nil; - } - f64 num = 0; - for (u32 i = 0; i<5; ++i) { - num = (num+rand())*(1.0/(RAND_MAX+1.0)); - } - return var::num(num); -} - -var builtin_id(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - std::stringstream ss; - ss << "0"; - if (val.type>vm_type::vm_num) { - ss << "x" << std::hex; - ss << reinterpret_cast(val.val.gcobj) << std::dec; - } - return ngc->newstr(ss.str()); -} - -var builtin_int(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (!val.is_num() && !val.is_str()) { - return nil; - } - return var::num(static_cast(static_cast(val.to_num()))); -} - -var builtin_floor(context* ctx, gc* ngc) { - auto value = ctx->localr[1]; - return var::num(std::floor(value.num())); -} - -var builtin_ceil(context* ctx, gc* ngc) { - auto value = ctx->localr[1]; - return var::num(std::ceil(value.num())); -} - -var builtin_num(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (val.is_num()) { - return val; - } - if (!val.is_str()) { - return nil; - } - auto res = val.to_num(); - if (std::isnan(res)) { - return nil; - } - return var::num(res); -} - -var builtin_pop(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (!val.is_vec()) { - return nas_err("native::pop", "\"vec\" must be vector"); - } - auto& vec = val.vec().elems; - if (vec.size()) { - auto tmp = vec.back(); - vec.pop_back(); - return tmp; - } - return nil; -} - -var builtin_str(context* ctx, gc* ngc) { - return ngc->newstr(ctx->localr[1].to_str()); -} - -var builtin_size(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - - usize num = 0; - switch(val.type) { - case vm_type::vm_num: return val; - case vm_type::vm_str: num = val.str().length(); break; - case vm_type::vm_vec: num = val.vec().size(); break; - case vm_type::vm_hash: num = val.hash().size(); break; - case vm_type::vm_map: num = val.map().mapper.size(); break; - default: break; - } - return var::num(static_cast(num)); -} - -var builtin_time(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (!val.is_num()) { - return nas_err("native::time", "\"begin\" must be number"); - } - auto begin = static_cast(val.num()); - return var::num(static_cast(time(&begin))); -} - -var builtin_contains(context* ctx, gc* ngc) { - auto local = ctx->localr; - var hash = local[1]; - var key = local[2]; - if (!hash.is_hash() || !key.is_str()) { - return zero; - } - return hash.hash().elems.count(key.str())? one:zero; -} - -var builtin_delete(context* ctx, gc* ngc) { - auto local = ctx->localr; - var hash = local[1]; - var key = local[2]; - if (!hash.is_hash()) { - return nas_err("native::delete", "\"hash\" must be hash"); - } - if (!key.is_str()) { - return nil; - } - if (hash.hash().elems.count(key.str())) { - hash.hash().elems.erase(key.str()); - } - return nil; -} - -var builtin_keys(context* ctx, gc* ngc) { - auto hash = ctx->localr[1]; - if (!hash.is_hash() && !hash.is_map()) { - return nas_err("native::keys", "\"hash\" must be hash"); - } - // avoid being sweeped - auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); - auto& vec = res.vec().elems; - if (hash.is_hash()) { - for (const auto& iter : hash.hash().elems) { - vec.push_back(ngc->newstr(iter.first)); - } - } else { - for (const auto& iter : hash.map().mapper) { - vec.push_back(ngc->newstr(iter.first)); - } - } - ngc->temp = nil; - return res; -} - -var builtin_die(context* ctx, gc* ngc) { - return nas_err("native::error", ctx->localr[1].to_str()); -} - -var builtin_find(context* ctx, gc* ngc) { - auto local = ctx->localr; - var needle = local[1]; - var haystack = local[2]; - usize pos = haystack.to_str().find(needle.to_str()); - if (pos==std::string::npos) { - return var::num(-1.0); - } - return var::num(static_cast(pos)); -} - -var builtin_type(context* ctx, gc* ngc) { - switch(ctx->localr[1].type) { - case vm_type::vm_none: return ngc->newstr("undefined"); - case vm_type::vm_nil: return ngc->newstr("nil"); - case vm_type::vm_num: return ngc->newstr("num"); - case vm_type::vm_str: return ngc->newstr("str"); - case vm_type::vm_vec: return ngc->newstr("vec"); - case vm_type::vm_hash: return ngc->newstr("hash"); - case vm_type::vm_func: return ngc->newstr("func"); - case vm_type::vm_ghost: return ngc->newstr("ghost"); - case vm_type::vm_co: return ngc->newstr("coroutine"); - case vm_type::vm_map: return ngc->newstr("namespace"); - default: break; - } - return nil; -} - -var builtin_substr(context* ctx, gc* ngc) { - auto local = ctx->localr; - var str = local[1]; - var beg = local[2]; - var len = local[3]; - if (!str.is_str()) { - return nas_err("native::substr", "\"str\" must be string"); - } - if (!beg.is_num() || beg.num()<0) { - return nas_err("native::substr", "\"begin\" should be number >= 0"); - } - if (!len.is_num() || len.num()<0) { - return nas_err("native::substr", "\"length\" should be number >= 0"); - } - auto begin = static_cast(beg.num()); - auto length = static_cast(len.num()); - if (begin >= str.str().length()) { - return nas_err( - "native::susbtr", - "begin index out of range: " + std::to_string(begin) - ); - } - return ngc->newstr(str.str().substr(begin, length)); -} - -var builtin_streq(context* ctx, gc* ngc) { - auto local = ctx->localr; - var a = local[1]; - var b = local[2]; - return var::num(static_cast( - (!a.is_str() || !b.is_str())? 0:(a.str()==b.str()) - )); -} - -var builtin_left(context* ctx, gc* ngc) { - auto local = ctx->localr; - var str = local[1]; - var len = local[2]; - - if (!str.is_str()) { - return nas_err("native::left", "\"string\" must be string"); - } - if (!len.is_num()) { - return nas_err("native::left", "\"length\" must be number"); - } - if (len.num() < 0) { - return ngc->newstr(""); - } - return ngc->newstr(str.str().substr(0, len.num())); -} - -var builtin_right(context* ctx, gc* ngc) { - auto local = ctx->localr; - var str = local[1]; - var len = local[2]; - - if (!str.is_str()) { - return nas_err("native::right", "\"string\" must be string"); - } - if (!len.is_num()) { - return nas_err("native::right", "\"length\" must be number"); - } - - i32 length = static_cast(len.num()); - i32 srclen = static_cast(str.str().length()); - if (length > srclen) { - length = srclen; - } - if (length < 0) { - length = 0; - } - - return ngc->newstr(str.str().substr(srclen - length, srclen)); -} - -var builtin_cmp(context* ctx, gc* ngc) { - auto local = ctx->localr; - var a = local[1]; - var b = local[2]; - if (!a.is_str() || !b.is_str()) { - return nas_err("native::cmp", "\"a\" and \"b\" must be string"); - } - return var::num(static_cast(strcmp( - a.str().c_str(), - b.str().c_str() - ))); -} - -var builtin_chr(context* ctx, gc* ngc) { - const char* extend[] = { - "€", " ", "‚", "ƒ", "„", "…", "†", "‡", - "ˆ", "‰", "Š", "‹", "Œ", " ", "Ž", " ", - " ", "‘", "’", "“", "”", "•", "–", "—", - "˜", "™", "š", "›", "œ", " ", "ž", "Ÿ", - " ", "¡", "¢", "£", "¤", "¥", "¦", "§", - "¨", "©", "ª", "«", "¬", " ", "®", "¯", - "°", "±", "²", "³", "´", "µ", "¶", "·", - "¸", "¹", "º", "»", "¼", "½", "¾", "¿", - "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", - "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", - "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", - "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", - "à", "á", "â", "ã", "ä", "å", "æ", "ç", - "è", "é", "ê", "ë", "ì", "í", "î", "ï", - "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", - "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ" - }; - auto num = static_cast(ctx->localr[1].num()); - if (0<=num && num<128) { - return ngc->newstr(static_cast(num)); - } else if (128<=num && num<256) { - return ngc->newstr(extend[num-128]); - } - return ngc->newstr(" "); -} - -var builtin_char(context* ctx, gc* ngc) { - return ngc->newstr(static_cast(ctx->localr[1].num())); -} - -var builtin_values(context* ctx, gc* ngc) { - auto hash = ctx->localr[1]; - if (!hash.is_hash() && !hash.is_map()) { - return nas_err("native::values", "\"hash\" must be hash or namespace"); - } - auto vec = ngc->alloc(vm_type::vm_vec); - auto& v = vec.vec().elems; - if (hash.is_hash()) { - for (auto& i : hash.hash().elems) { - v.push_back(i.second); - } - } else { - for (auto& i : hash.map().mapper) { - v.push_back(*i.second); - } - } - return vec; -} - -var builtin_sleep(context* ctx, gc* ngc) { - auto val = ctx->localr[1]; - if (!val.is_num()) { - return nil; - } -#if defined(_WIN32) && !defined(_GLIBCXX_HAS_GTHREADS) - // mingw-w64 win32 thread model has no std::this_thread - // also msvc will use this - Sleep(static_cast(val.num()*1e3)); -#else - std::this_thread::sleep_for ( - std::chrono::microseconds(static_cast(val.num()*1e6)) - ); -#endif - return nil; -} - -var builtin_platform(context* ctx, gc* ngc) { - return ngc->newstr(util::get_platform()); -} - -var builtin_version(context* ctx, gc* ngc) { - return ngc->newstr(__nasver__); -} - -var builtin_caller(context* ctx, gc* ngc) { - auto level = ctx->localr[1]; - if (!level.is_num()) { - return nil; - } - - auto level_num = static_cast(level.num()); - if (ctx->func_top - level_num - 1 < ctx->func_stack) { - return nil; - } - return ctx->func_top[-level_num - 1]; -} - -var builtin_arch(context* ctx, gc* ngc) { - return ngc->newstr(util::get_arch()); -} - -// md5 related functions -std::string tohex(u32 num) { - const char str16[] = "0123456789abcdef"; - std::string str = ""; - for (u32 i = 0; i<4; i++, num >>= 8) { - std::string tmp = ""; - for (u32 j = 0, b = num&0xff; j<2; j++, b >>= 4) { - tmp.insert(0, 1, str16[b&0xf]); - } - str += tmp; - } - return str; -} - -std::string md5(const std::string& src) { - std::vector buff; - usize num = ((src.length()+8)>>6)+1; - usize buffsize = num<<4; - buff.resize(buffsize, 0); - for (usize i = 0; i>2] |= (static_cast(src[i]))<<((i&0x3)<<3); - } - buff[src.length()>>2] |= 0x80<<(((src.length()%4))<<3); - buff[buffsize-2] = (src.length()<<3)&0xffffffff; - buff[buffsize-1] = ((src.length()<<3)>>32)&0xffffffff; - - // u32(abs(sin(i+1))*(2pow32)) - const u32 k[] = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 - }; - - // left shift bits - const u32 s[] = { - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 - }; - - // index - const u32 idx[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // g=i - 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, // g=(5*i+1)%16; - 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, // g=(3*i+5)%16; - 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 // g=(7*i)%16; - }; - -#define shift(x, n) (((x)<<(n))|((x)>>(32-(n)))) // cycle left shift -#define md5f(x, y, z) (((x)&(y))|((~x)&(z))) -#define md5g(x, y, z) (((x)&(z))|((y)&(~z))) -#define md5h(x, y, z) ((x)^(y)^(z)) -#define md5i(x, y, z) ((y)^((x)|(~z))) - - u32 atmp = 0x67452301, btmp = 0xefcdab89; - u32 ctmp = 0x98badcfe, dtmp = 0x10325476; - for (u32 i = 0; ilocalr[1]; - if (!str.is_str()) { - return nas_err("native::md5", "\"str\" must be string"); - } - return ngc->newstr(md5(str.str())); -} - -class time_stamp { -private: - std::chrono::high_resolution_clock::time_point stamp; - -public: - time_stamp() { - stamp = std::chrono::high_resolution_clock::now(); - } - - void make_stamp() { - stamp = std::chrono::high_resolution_clock::now(); - } - - auto elapsed_milliseconds() const { - const auto duration = std::chrono::high_resolution_clock::now() - stamp; - return std::chrono::duration_cast(duration).count(); - } - - auto elapsed_microseconds() const { - const auto duration = std::chrono::high_resolution_clock::now() - stamp; - return std::chrono::duration_cast(duration).count(); - } -}; - -void time_stamp_destructor(void* ptr) { - delete static_cast(ptr); -} - -var builtin_maketimestamp(context* ctx, gc* ngc) { - auto res = ngc->alloc(vm_type::vm_ghost); - res.ghost().set( - "nasal-time-stamp", - time_stamp_destructor, - nullptr, - new time_stamp - ); - return res; -} - -var builtin_time_stamp(context* ctx, gc* ngc) { - auto object = ctx->localr[1]; - if (!object.object_check("nasal-time-stamp")) { - return nil; - } - auto stamp = object.ghost().get(); - stamp->make_stamp(); - return nil; -} - -var builtin_elapsed_millisecond(context* ctx, gc* ngc) { - auto object = ctx->localr[1]; - if (!object.object_check("nasal-time-stamp")) { - return var::num(-1); - } - auto stamp = object.ghost().get(); - return var::num(static_cast(stamp->elapsed_milliseconds())); -} - -var builtin_elapsed_microsecond(context* ctx, gc* ngc) { - auto object = ctx->localr[1]; - if (!object.object_check("nasal-time-stamp")) { - return var::num(-1); - } - auto stamp = object.ghost().get(); - return var::num(static_cast(stamp->elapsed_microseconds())); -} - -var builtin_gcextend(context* ctx, gc* ngc) { - auto type = ctx->localr[1]; - if (!type.is_str()) { - return nil; - } - const auto& s = type.str(); - if (s=="str") { - ngc->extend(vm_type::vm_str); - } else if (s=="vec") { - ngc->extend(vm_type::vm_vec); - } else if (s=="hash") { - ngc->extend(vm_type::vm_hash); - } else if (s=="func") { - ngc->extend(vm_type::vm_func); - } else if (s=="upval") { - ngc->extend(vm_type::vm_upval); - } else if (s=="ghost") { - ngc->extend(vm_type::vm_ghost); - } else if (s=="co") { - ngc->extend(vm_type::vm_co); - } - return nil; -} - -var builtin_gcinfo(context* ctx, gc* ngc) { - var res = ngc->alloc(vm_type::vm_hash); - auto& map = res.hash().elems; - // using ms - map["total"] = var::num(ngc->status.gc_time_ms()); - map["average"] = var::num(ngc->status.avg_time_ms()); - map["mark_count"] = var::num(ngc->status.total_mark_count); - map["sweep_count"] = var::num(ngc->status.total_sweep_count); - map["avg_mark"] = var::num(ngc->status.avg_mark_time_ms()); - map["avg_sweep"] = var::num(ngc->status.avg_sweep_time_ms()); - map["max_mark"] = var::num(ngc->status.max_mark_time_ms()); - map["max_sweep"] = var::num(ngc->status.max_sweep_time_ms()); - return res; -} - -var builtin_logtime(context* ctx, gc* ngc) { - time_t t = time(nullptr); - tm* tm_t = localtime(&t); - char s[64]; - snprintf( - s, 64, "%d-%.2d-%.2d %.2d:%.2d:%.2d", - tm_t->tm_year+1900, - tm_t->tm_mon+1, - tm_t->tm_mday, - tm_t->tm_hour, - tm_t->tm_min, - tm_t->tm_sec - ); - return ngc->newstr(s); -} - -var builtin_ghosttype(context* ctx, gc* ngc) { - auto arg = ctx->localr[1]; - if (!arg.is_ghost()) { - return nas_err("native::ghosttype", "this is not a ghost object."); - } - - const auto& name = arg.ghost().get_ghost_name(); - - // https://wiki.flightgear.org/Nasal_library#ghosttype() - // tolds us if no name has been set, - // return a unique id (the pointer to the instance) - if (!name.length()) { - std::stringstream ss; - ss << "0x" << std::hex; - ss << arg.ghost().convert() << std::dec; - return ngc->newstr(ss.str()); - } - return ngc->newstr(name); -} - -var builtin_set_utf8_output(context* ctx, gc* ngc) { -#ifdef _WIN32 - // allow 65001 code page - SetConsoleOutputCP(CP_UTF8); -#endif - // do nothing on other platforms - return nil; -} - -var builtin_terminal_size(context* ctx, gc* ngc) { - var res = ngc->alloc(vm_type::vm_hash); -#ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO csbi; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { - auto rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; - auto cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; - res.hash().elems["rows"] = var::num(rows); - res.hash().elems["cols"] = var::num(cols); - } -#else - struct winsize w; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - res.hash().elems["rows"] = var::num(w.ws_row); - res.hash().elems["cols"] = var::num(w.ws_col); -#endif - return res; -} - -nasal_builtin_table builtin[] = { - {"__print", builtin_print}, - {"__println", builtin_println}, - {"__exit", builtin_exit}, - {"__abort", builtin_abort}, - {"__append", builtin_append}, - {"__setsize", builtin_setsize}, - {"__system", builtin_system}, - {"__input", builtin_input}, - {"__split", builtin_split}, - {"__split_with_empty_substr", builtin_split_with_empty_substr}, - {"__rand", builtin_rand}, - {"__id", builtin_id}, - {"__int", builtin_int}, - {"__floor", builtin_floor}, - {"__ceil", builtin_ceil}, - {"__num", builtin_num}, - {"__pop", builtin_pop}, - {"__str", builtin_str}, - {"__size", builtin_size}, - {"__time", builtin_time}, - {"__contains", builtin_contains}, - {"__delete", builtin_delete}, - {"__keys", builtin_keys}, - {"__die", builtin_die}, - {"__find", builtin_find}, - {"__type", builtin_type}, - {"__substr", builtin_substr}, - {"__streq", builtin_streq}, - {"__left", builtin_left}, - {"__right", builtin_right}, - {"__cmp", builtin_cmp}, - {"__chr", builtin_chr}, - {"__char", builtin_char}, - {"__values", builtin_values}, - {"__sleep", builtin_sleep}, - {"__platform", builtin_platform}, - {"__arch", builtin_arch}, - {"__version", builtin_version}, - {"__caller", builtin_caller}, - {"__md5", builtin_md5}, - {"__maketimestamp", builtin_maketimestamp}, - {"__time_stamp", builtin_time_stamp}, - {"__elapsed_millisecond", builtin_elapsed_millisecond}, - {"__elapsed_microsecond", builtin_elapsed_microsecond}, - {"__gcextd", builtin_gcextend}, - {"__gcinfo", builtin_gcinfo}, - {"__logtime", builtin_logtime}, - {"__ghosttype", builtin_ghosttype}, - {"__set_utf8_output", builtin_set_utf8_output}, - {"__terminal_size", builtin_terminal_size}, - {nullptr, nullptr} -}; - -} +#include "natives/builtin.h" +#include "util/util.h" + +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +namespace nasal { + +var builtin_unsafe(context* ctx, gc* ngc) { + return nas_err( + "unsafe_redirect", + "you are using unsafe system api under limited mode!" + ); +} + +var builtin_print(context* ctx, gc* ngc) { + for (auto& i : ctx->localr[1].vec().elems) { + std::cout << i; + } + std::cout << std::flush; + return nil; +} + +var builtin_println(context* ctx, gc* ngc) { + for (auto& i : ctx->localr[1].vec().elems) { + std::cout << i; + } + std::cout << std::endl; + return nil; +} + +var builtin_exit(context* ctx, gc* ngc) { + std::exit(ctx->localr[1].num()); + return nil; +} + +var builtin_abort(context* ctx, gc* ngc) { + std::abort(); + return nil; +} + +var builtin_append(context* ctx, gc* ngc) { + auto local = ctx->localr; + var vec = local[1]; + var elem = local[2]; + if (!vec.is_vec()) { + return nas_err("native::append", "\"vec\" must be vector"); + } + auto& v = vec.vec().elems; + for (auto& i : elem.vec().elems) { + v.push_back(i); + } + return nil; +} + +var builtin_setsize(context* ctx, gc* ngc) { + auto local = ctx->localr; + var vec = local[1]; + var size = local[2]; + if (!vec.is_vec()) { + return nas_err("native::setsize", "\"vec\" must be vector"); + } + if (!size.is_num() || size.num()<0) { + return nil; + } + vec.vec().elems.resize(static_cast(size.num()), nil); + return nil; +} + +var builtin_system(context* ctx, gc* ngc) { + auto str = ctx->localr[1]; + if (!str.is_str()) { + return var::num(-1); + } + return var::num(static_cast(system(str.str().c_str()))); +} + +var builtin_input(context* ctx, gc* ngc) { + auto local = ctx->localr; + var end = local[1]; + var ret = ngc->alloc(vm_type::vm_str); + if (!end.is_str() || end.str().length()>1 || !end.str().length()) { + std::cin >> ret.str(); + } else { + std::getline(std::cin, ret.str(), end.str()[0]); + } + return ret; +} + +var builtin_split(context* ctx, gc* ngc) { + auto local = ctx->localr; + var separator = local[1]; + var str = local[2]; + if (!separator.is_str()) { + return nas_err("native::split", "\"separator\" must be string"); + } + if (!str.is_str()) { + return nas_err("native::split", "\"str\" must be string"); + } + const auto& sep = separator.str(); + const auto& s = str.str(); + + // avoid being sweeped + auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); + auto& vec = res.vec().elems; + + // empty separator means split every char + if (!sep.length()) { + for (auto i : s) { + vec.push_back(ngc->newstr(i)); + } + ngc->temp = nil; + return res; + } + + usize last = 0; + usize pos = s.find(sep, 0); + while (pos!=std::string::npos) { + if (pos>last) { + vec.push_back(ngc->newstr(s.substr(last, pos-last))); + } + last = pos + sep.length(); + pos = s.find(sep, last); + } + if (last!=s.length()) { + vec.push_back(ngc->newstr(s.substr(last))); + } + ngc->temp = nil; + return res; +} + +var builtin_split_with_empty_substr(context* ctx, gc* ngc) { + auto local = ctx->localr; + var separator = local[1]; + var str = local[2]; + if (!separator.is_str()) { + return nas_err( + "native::split_with_empty_substr", + "\"separator\" must be string" + ); + } + if (!str.is_str()) { + return nas_err( + "native::split_with_empty_substr", + "\"str\" must be string" + ); + } + const auto& sep = separator.str(); + const auto& s = str.str(); + + // avoid being sweeped + auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); + auto& vec = res.vec().elems; + + // empty separator means split every char + if (!sep.length()) { + for (auto i : s) { + vec.push_back(ngc->newstr(i)); + } + ngc->temp = nil; + return res; + } + + usize last = 0; + usize pos = s.find(sep, 0); + while (pos!=std::string::npos) { + if (pos>=last) { + vec.push_back(ngc->newstr(s.substr(last, pos-last))); + } + last = pos + sep.length(); + pos = s.find(sep, last); + } + if (last<=s.length()) { + vec.push_back(ngc->newstr(s.substr(last))); + } + ngc->temp = nil; + return res; +} + +var builtin_rand(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (!val.is_num() && !val.is_nil()) { + return nas_err("native::rand", "\"seed\" must be nil or number"); + } + if (val.is_num()) { + srand(static_cast(val.num())); + return nil; + } + f64 num = 0; + for (u32 i = 0; i<5; ++i) { + num = (num+rand())*(1.0/(RAND_MAX+1.0)); + } + return var::num(num); +} + +var builtin_id(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + std::stringstream ss; + ss << "0"; + if (val.type>vm_type::vm_num) { + ss << "x" << std::hex; + ss << reinterpret_cast(val.val.gcobj) << std::dec; + } + return ngc->newstr(ss.str()); +} + +var builtin_int(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (!val.is_num() && !val.is_str()) { + return nil; + } + return var::num(static_cast(static_cast(val.to_num()))); +} + +var builtin_floor(context* ctx, gc* ngc) { + auto value = ctx->localr[1]; + return var::num(std::floor(value.num())); +} + +var builtin_ceil(context* ctx, gc* ngc) { + auto value = ctx->localr[1]; + return var::num(std::ceil(value.num())); +} + +var builtin_num(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (val.is_num()) { + return val; + } + if (!val.is_str()) { + return nil; + } + auto res = val.to_num(); + if (std::isnan(res)) { + return nil; + } + return var::num(res); +} + +var builtin_pop(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (!val.is_vec()) { + return nas_err("native::pop", "\"vec\" must be vector"); + } + auto& vec = val.vec().elems; + if (vec.size()) { + auto tmp = vec.back(); + vec.pop_back(); + return tmp; + } + return nil; +} + +var builtin_str(context* ctx, gc* ngc) { + return ngc->newstr(ctx->localr[1].to_str()); +} + +var builtin_size(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + + usize num = 0; + switch(val.type) { + case vm_type::vm_num: return val; + case vm_type::vm_str: num = val.str().length(); break; + case vm_type::vm_vec: num = val.vec().size(); break; + case vm_type::vm_hash: num = val.hash().size(); break; + case vm_type::vm_map: num = val.map().mapper.size(); break; + default: break; + } + return var::num(static_cast(num)); +} + +var builtin_time(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (!val.is_num()) { + return nas_err("native::time", "\"begin\" must be number"); + } + auto begin = static_cast(val.num()); + return var::num(static_cast(time(&begin))); +} + +var builtin_contains(context* ctx, gc* ngc) { + auto local = ctx->localr; + var hash = local[1]; + var key = local[2]; + if (!hash.is_hash() || !key.is_str()) { + return zero; + } + return hash.hash().elems.count(key.str())? one:zero; +} + +var builtin_delete(context* ctx, gc* ngc) { + auto local = ctx->localr; + var hash = local[1]; + var key = local[2]; + if (!hash.is_hash()) { + return nas_err("native::delete", "\"hash\" must be hash"); + } + if (!key.is_str()) { + return nil; + } + if (hash.hash().elems.count(key.str())) { + hash.hash().elems.erase(key.str()); + } + return nil; +} + +var builtin_keys(context* ctx, gc* ngc) { + auto hash = ctx->localr[1]; + if (!hash.is_hash() && !hash.is_map()) { + return nas_err("native::keys", "\"hash\" must be hash"); + } + // avoid being sweeped + auto res = ngc->temp = ngc->alloc(vm_type::vm_vec); + auto& vec = res.vec().elems; + if (hash.is_hash()) { + for (const auto& iter : hash.hash().elems) { + vec.push_back(ngc->newstr(iter.first)); + } + } else { + for (const auto& iter : hash.map().mapper) { + vec.push_back(ngc->newstr(iter.first)); + } + } + ngc->temp = nil; + return res; +} + +var builtin_die(context* ctx, gc* ngc) { + return nas_err("native::error", ctx->localr[1].to_str()); +} + +var builtin_find(context* ctx, gc* ngc) { + auto local = ctx->localr; + var needle = local[1]; + var haystack = local[2]; + usize pos = haystack.to_str().find(needle.to_str()); + if (pos==std::string::npos) { + return var::num(-1.0); + } + return var::num(static_cast(pos)); +} + +var builtin_type(context* ctx, gc* ngc) { + switch(ctx->localr[1].type) { + case vm_type::vm_none: return ngc->newstr("undefined"); + case vm_type::vm_nil: return ngc->newstr("nil"); + case vm_type::vm_num: return ngc->newstr("num"); + case vm_type::vm_str: return ngc->newstr("str"); + case vm_type::vm_vec: return ngc->newstr("vec"); + case vm_type::vm_hash: return ngc->newstr("hash"); + case vm_type::vm_func: return ngc->newstr("func"); + case vm_type::vm_ghost: return ngc->newstr("ghost"); + case vm_type::vm_co: return ngc->newstr("coroutine"); + case vm_type::vm_map: return ngc->newstr("namespace"); + default: break; + } + return nil; +} + +var builtin_substr(context* ctx, gc* ngc) { + auto local = ctx->localr; + var str = local[1]; + var beg = local[2]; + var len = local[3]; + if (!str.is_str()) { + return nas_err("native::substr", "\"str\" must be string"); + } + if (!beg.is_num() || beg.num()<0) { + return nas_err("native::substr", "\"begin\" should be number >= 0"); + } + if (!len.is_num() || len.num()<0) { + return nas_err("native::substr", "\"length\" should be number >= 0"); + } + auto begin = static_cast(beg.num()); + auto length = static_cast(len.num()); + if (begin >= str.str().length()) { + return nas_err( + "native::susbtr", + "begin index out of range: " + std::to_string(begin) + ); + } + return ngc->newstr(str.str().substr(begin, length)); +} + +var builtin_streq(context* ctx, gc* ngc) { + auto local = ctx->localr; + var a = local[1]; + var b = local[2]; + return var::num(static_cast( + (!a.is_str() || !b.is_str())? 0:(a.str()==b.str()) + )); +} + +var builtin_left(context* ctx, gc* ngc) { + auto local = ctx->localr; + var str = local[1]; + var len = local[2]; + + if (!str.is_str()) { + return nas_err("native::left", "\"string\" must be string"); + } + if (!len.is_num()) { + return nas_err("native::left", "\"length\" must be number"); + } + if (len.num() < 0) { + return ngc->newstr(""); + } + return ngc->newstr(str.str().substr(0, len.num())); +} + +var builtin_right(context* ctx, gc* ngc) { + auto local = ctx->localr; + var str = local[1]; + var len = local[2]; + + if (!str.is_str()) { + return nas_err("native::right", "\"string\" must be string"); + } + if (!len.is_num()) { + return nas_err("native::right", "\"length\" must be number"); + } + + i32 length = static_cast(len.num()); + i32 srclen = static_cast(str.str().length()); + if (length > srclen) { + length = srclen; + } + if (length < 0) { + length = 0; + } + + return ngc->newstr(str.str().substr(srclen - length, srclen)); +} + +var builtin_cmp(context* ctx, gc* ngc) { + auto local = ctx->localr; + var a = local[1]; + var b = local[2]; + if (!a.is_str() || !b.is_str()) { + return nas_err("native::cmp", "\"a\" and \"b\" must be string"); + } + return var::num(static_cast(strcmp( + a.str().c_str(), + b.str().c_str() + ))); +} + +var builtin_chr(context* ctx, gc* ngc) { + const char* extend[] = { + "€", " ", "‚", "ƒ", "„", "…", "†", "‡", + "ˆ", "‰", "Š", "‹", "Œ", " ", "Ž", " ", + " ", "‘", "’", "“", "”", "•", "–", "—", + "˜", "™", "š", "›", "œ", " ", "ž", "Ÿ", + " ", "¡", "¢", "£", "¤", "¥", "¦", "§", + "¨", "©", "ª", "«", "¬", " ", "®", "¯", + "°", "±", "²", "³", "´", "µ", "¶", "·", + "¸", "¹", "º", "»", "¼", "½", "¾", "¿", + "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", + "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", + "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", + "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", + "à", "á", "â", "ã", "ä", "å", "æ", "ç", + "è", "é", "ê", "ë", "ì", "í", "î", "ï", + "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", + "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ" + }; + auto num = static_cast(ctx->localr[1].num()); + if (0<=num && num<128) { + return ngc->newstr(static_cast(num)); + } else if (128<=num && num<256) { + return ngc->newstr(extend[num-128]); + } + return ngc->newstr(" "); +} + +var builtin_char(context* ctx, gc* ngc) { + return ngc->newstr(static_cast(ctx->localr[1].num())); +} + +var builtin_values(context* ctx, gc* ngc) { + auto hash = ctx->localr[1]; + if (!hash.is_hash() && !hash.is_map()) { + return nas_err("native::values", "\"hash\" must be hash or namespace"); + } + auto vec = ngc->alloc(vm_type::vm_vec); + auto& v = vec.vec().elems; + if (hash.is_hash()) { + for (auto& i : hash.hash().elems) { + v.push_back(i.second); + } + } else { + for (auto& i : hash.map().mapper) { + v.push_back(*i.second); + } + } + return vec; +} + +var builtin_sleep(context* ctx, gc* ngc) { + auto val = ctx->localr[1]; + if (!val.is_num()) { + return nil; + } +#if defined(_WIN32) && !defined(_GLIBCXX_HAS_GTHREADS) + // mingw-w64 win32 thread model has no std::this_thread + // also msvc will use this + Sleep(static_cast(val.num()*1e3)); +#else + std::this_thread::sleep_for ( + std::chrono::microseconds(static_cast(val.num()*1e6)) + ); +#endif + return nil; +} + +var builtin_platform(context* ctx, gc* ngc) { + return ngc->newstr(util::get_platform()); +} + +var builtin_version(context* ctx, gc* ngc) { + return ngc->newstr(__nasver__); +} + +var builtin_caller(context* ctx, gc* ngc) { + auto level = ctx->localr[1]; + if (!level.is_num()) { + return nil; + } + + auto level_num = static_cast(level.num()); + if (ctx->func_top - level_num - 1 < ctx->func_stack) { + return nil; + } + const auto& cs = ctx->func_top[-level_num - 1]; + var res = ngc->temp = ngc->alloc(vm_type::vm_vec); + res.vec().elems.push_back(ngc->alloc(vm_type::vm_hash)); + res.vec().elems.push_back(cs.caller); + res.vec().elems.push_back(ngc->newstr(ctx->files[cs.file_index])); + res.vec().elems.push_back(var::num(cs.line)); + ngc->temp = nil; + return res; +} + +var builtin_arch(context* ctx, gc* ngc) { + return ngc->newstr(util::get_arch()); +} + +// md5 related functions +std::string tohex(u32 num) { + const char str16[] = "0123456789abcdef"; + std::string str = ""; + for (u32 i = 0; i<4; i++, num >>= 8) { + std::string tmp = ""; + for (u32 j = 0, b = num&0xff; j<2; j++, b >>= 4) { + tmp.insert(0, 1, str16[b&0xf]); + } + str += tmp; + } + return str; +} + +std::string md5(const std::string& src) { + std::vector buff; + usize num = ((src.length()+8)>>6)+1; + usize buffsize = num<<4; + buff.resize(buffsize, 0); + for (usize i = 0; i>2] |= (static_cast(src[i]))<<((i&0x3)<<3); + } + buff[src.length()>>2] |= 0x80<<(((src.length()%4))<<3); + buff[buffsize-2] = (src.length()<<3)&0xffffffff; + buff[buffsize-1] = ((src.length()<<3)>>32)&0xffffffff; + + // u32(abs(sin(i+1))*(2pow32)) + const u32 k[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + // left shift bits + const u32 s[] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + + // index + const u32 idx[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // g=i + 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, // g=(5*i+1)%16; + 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, // g=(3*i+5)%16; + 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 // g=(7*i)%16; + }; + +#define shift(x, n) (((x)<<(n))|((x)>>(32-(n)))) // cycle left shift +#define md5f(x, y, z) (((x)&(y))|((~x)&(z))) +#define md5g(x, y, z) (((x)&(z))|((y)&(~z))) +#define md5h(x, y, z) ((x)^(y)^(z)) +#define md5i(x, y, z) ((y)^((x)|(~z))) + + u32 atmp = 0x67452301, btmp = 0xefcdab89; + u32 ctmp = 0x98badcfe, dtmp = 0x10325476; + for (u32 i = 0; ilocalr[1]; + if (!str.is_str()) { + return nas_err("native::md5", "\"str\" must be string"); + } + return ngc->newstr(md5(str.str())); +} + +class time_stamp { +private: + std::chrono::high_resolution_clock::time_point stamp; + +public: + time_stamp() { + stamp = std::chrono::high_resolution_clock::now(); + } + + void make_stamp() { + stamp = std::chrono::high_resolution_clock::now(); + } + + auto elapsed_milliseconds() const { + const auto duration = std::chrono::high_resolution_clock::now() - stamp; + return std::chrono::duration_cast(duration).count(); + } + + auto elapsed_microseconds() const { + const auto duration = std::chrono::high_resolution_clock::now() - stamp; + return std::chrono::duration_cast(duration).count(); + } +}; + +void time_stamp_destructor(void* ptr) { + delete static_cast(ptr); +} + +var builtin_maketimestamp(context* ctx, gc* ngc) { + auto res = ngc->alloc(vm_type::vm_ghost); + res.ghost().set( + "nasal-time-stamp", + time_stamp_destructor, + nullptr, + new time_stamp + ); + return res; +} + +var builtin_time_stamp(context* ctx, gc* ngc) { + auto object = ctx->localr[1]; + if (!object.object_check("nasal-time-stamp")) { + return nil; + } + auto stamp = object.ghost().get(); + stamp->make_stamp(); + return nil; +} + +var builtin_elapsed_millisecond(context* ctx, gc* ngc) { + auto object = ctx->localr[1]; + if (!object.object_check("nasal-time-stamp")) { + return var::num(-1); + } + auto stamp = object.ghost().get(); + return var::num(static_cast(stamp->elapsed_milliseconds())); +} + +var builtin_elapsed_microsecond(context* ctx, gc* ngc) { + auto object = ctx->localr[1]; + if (!object.object_check("nasal-time-stamp")) { + return var::num(-1); + } + auto stamp = object.ghost().get(); + return var::num(static_cast(stamp->elapsed_microseconds())); +} + +var builtin_gcextend(context* ctx, gc* ngc) { + auto type = ctx->localr[1]; + if (!type.is_str()) { + return nil; + } + const auto& s = type.str(); + if (s=="str") { + ngc->extend(vm_type::vm_str); + } else if (s=="vec") { + ngc->extend(vm_type::vm_vec); + } else if (s=="hash") { + ngc->extend(vm_type::vm_hash); + } else if (s=="func") { + ngc->extend(vm_type::vm_func); + } else if (s=="upval") { + ngc->extend(vm_type::vm_upval); + } else if (s=="ghost") { + ngc->extend(vm_type::vm_ghost); + } else if (s=="co") { + ngc->extend(vm_type::vm_co); + } + return nil; +} + +var builtin_gcinfo(context* ctx, gc* ngc) { + var res = ngc->alloc(vm_type::vm_hash); + auto& map = res.hash().elems; + // using ms + map["total"] = var::num(ngc->status.gc_time_ms()); + map["average"] = var::num(ngc->status.avg_time_ms()); + map["mark_count"] = var::num(ngc->status.total_mark_count); + map["sweep_count"] = var::num(ngc->status.total_sweep_count); + map["avg_mark"] = var::num(ngc->status.avg_mark_time_ms()); + map["avg_sweep"] = var::num(ngc->status.avg_sweep_time_ms()); + map["max_mark"] = var::num(ngc->status.max_mark_time_ms()); + map["max_sweep"] = var::num(ngc->status.max_sweep_time_ms()); + return res; +} + +var builtin_logtime(context* ctx, gc* ngc) { + time_t t = time(nullptr); + tm* tm_t = localtime(&t); + char s[64]; + snprintf( + s, 64, "%d-%.2d-%.2d %.2d:%.2d:%.2d", + tm_t->tm_year+1900, + tm_t->tm_mon+1, + tm_t->tm_mday, + tm_t->tm_hour, + tm_t->tm_min, + tm_t->tm_sec + ); + return ngc->newstr(s); +} + +var builtin_ghosttype(context* ctx, gc* ngc) { + auto arg = ctx->localr[1]; + if (!arg.is_ghost()) { + return nas_err("native::ghosttype", "this is not a ghost object."); + } + + const auto& name = arg.ghost().get_ghost_name(); + + // https://wiki.flightgear.org/Nasal_library#ghosttype() + // tolds us if no name has been set, + // return a unique id (the pointer to the instance) + if (!name.length()) { + std::stringstream ss; + ss << "0x" << std::hex; + ss << arg.ghost().convert() << std::dec; + return ngc->newstr(ss.str()); + } + return ngc->newstr(name); +} + +var builtin_set_utf8_output(context* ctx, gc* ngc) { +#ifdef _WIN32 + // allow 65001 code page + SetConsoleOutputCP(CP_UTF8); +#endif + // do nothing on other platforms + return nil; +} + +var builtin_terminal_size(context* ctx, gc* ngc) { + var res = ngc->alloc(vm_type::vm_hash); +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { + auto rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + auto cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + res.hash().elems["rows"] = var::num(rows); + res.hash().elems["cols"] = var::num(cols); + } +#else + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + res.hash().elems["rows"] = var::num(w.ws_row); + res.hash().elems["cols"] = var::num(w.ws_col); +#endif + return res; +} + +nasal_builtin_table builtin[] = { + {"__print", builtin_print}, + {"__println", builtin_println}, + {"__exit", builtin_exit}, + {"__abort", builtin_abort}, + {"__append", builtin_append}, + {"__setsize", builtin_setsize}, + {"__system", builtin_system}, + {"__input", builtin_input}, + {"__split", builtin_split}, + {"__split_with_empty_substr", builtin_split_with_empty_substr}, + {"__rand", builtin_rand}, + {"__id", builtin_id}, + {"__int", builtin_int}, + {"__floor", builtin_floor}, + {"__ceil", builtin_ceil}, + {"__num", builtin_num}, + {"__pop", builtin_pop}, + {"__str", builtin_str}, + {"__size", builtin_size}, + {"__time", builtin_time}, + {"__contains", builtin_contains}, + {"__delete", builtin_delete}, + {"__keys", builtin_keys}, + {"__die", builtin_die}, + {"__find", builtin_find}, + {"__type", builtin_type}, + {"__substr", builtin_substr}, + {"__streq", builtin_streq}, + {"__left", builtin_left}, + {"__right", builtin_right}, + {"__cmp", builtin_cmp}, + {"__chr", builtin_chr}, + {"__char", builtin_char}, + {"__values", builtin_values}, + {"__sleep", builtin_sleep}, + {"__platform", builtin_platform}, + {"__arch", builtin_arch}, + {"__version", builtin_version}, + {"__caller", builtin_caller}, + {"__md5", builtin_md5}, + {"__maketimestamp", builtin_maketimestamp}, + {"__time_stamp", builtin_time_stamp}, + {"__elapsed_millisecond", builtin_elapsed_millisecond}, + {"__elapsed_microsecond", builtin_elapsed_microsecond}, + {"__gcextd", builtin_gcextend}, + {"__gcinfo", builtin_gcinfo}, + {"__logtime", builtin_logtime}, + {"__ghosttype", builtin_ghosttype}, + {"__set_utf8_output", builtin_set_utf8_output}, + {"__terminal_size", builtin_terminal_size}, + {nullptr, nullptr} +}; + +} diff --git a/test/caller.nas b/test/caller.nas new file mode 100644 index 0000000..c6bf8ba --- /dev/null +++ b/test/caller.nas @@ -0,0 +1,24 @@ +var a = func(x, y, z) { + for (var i = 0; i < 20; i += 1) { + var cl = caller(i); + if (cl == nil) { + return; + } + print("[", i, "]\t", cl[1], "\t -> called from ", cl[2], ":", cl[3], "\n"); + } +} + +var b = func(x, y) { + a(1, 2, 3); +} + +var c = func(x) b(1, 2); +var d = func c(1); +var e = func d(); +var f = func e(); +var g = func f(); +var h = func g(); +var i = func h(); +var j = func i(); + +j(); \ No newline at end of file diff --git a/test/coroutine.nas b/test/coroutine.nas index 9934a8c..a1f847d 100644 --- a/test/coroutine.nas +++ b/test/coroutine.nas @@ -109,7 +109,7 @@ for (var t = 0; t < 10; t += 1) { counter += 1; for (var i = 0; i < t + 1; i += 1) coroutine.resume(co); - if (counter - int(counter / 1000) * 1000 == 0) { + if (counter - int(counter / 2500) * 2500 == 0) { var rate = counter / 2e5; print(" ", bar.bar(rate), " ", padding.leftpad(str(int(rate*100)),3), "% | ", @@ -120,7 +120,7 @@ for (var t = 0; t < 10; t += 1) { } tm.stamp(); - for (var i = 0; i < 1e5; i += 1) + for (var i = 0; i < 2e5; i += 1) consumer(); println(" ", bar.bar(1), " 100% | ", str(int(1e3 * counter / tm.elapsedMSec())),