diff --git a/src/main.cpp b/src/main.cpp index d9e7588..16435b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,7 @@ const u32 VM_SYMINFO = 1<<7; const u32 VM_PROFILE = 1<<8; const u32 VM_PROF_ALL = 1<<9; const u32 VM_REF_FILE = 1<<10; +const u32 VM_LIMIT = 1<<11; std::ostream& help(std::ostream& out) { out @@ -59,6 +60,7 @@ std::ostream& help(std::ostream& out) { << " --prof | show profiling result, available in debug mode.\n" << " --prof-all | show profiling result of all files," << "available under debug mode.\n" + << " --limit | use limited execution mode." << "file:\n" << " | execute file.\n" << "argv:\n" @@ -75,13 +77,18 @@ std::ostream& logo(std::ostream& out) { << " / \\/ / _` / __|/ _` | |\n" << " / /\\ / (_| \\__ \\ (_| | |\n" << " \\_\\ \\/ \\__,_|___/\\__,_|_|\n" - << "ver : " << __nasver << " (" << __DATE__ << " " << __TIME__ << ")\n" + << "\n" + << "ver : " << __nasver__ + << " " << nasal::get_platform() << " " << nasal::get_arch() + << " (" << __DATE__ << " " << __TIME__ << ")\n" << "std : c++ " << __cplusplus << "\n" << "core : " << std::thread::hardware_concurrency() << " core(s)\n" << "repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n" << "repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n" << "wiki : https://wiki.flightgear.org/Nasal_scripting_language\n" << "\n" + << "presented by fgprc members - http://fgprc.org.cn\n" + << "\n" << "input to get help .\n\n"; return out; } @@ -95,7 +102,8 @@ std::ostream& version(std::ostream& out) { if (num<0.01) { nasal::parse::easter_egg(); } - out << "nasal interpreter version " << __nasver; + out << "nasal interpreter version " << __nasver__; + out << " " << nasal::get_platform() << " " << nasal::get_arch(); out << " (" << __DATE__ << " " << __TIME__ << ")\n"; return out; } @@ -127,12 +135,11 @@ void execute( // parser gets lexer's token list to compile parse.compile(lex).chkerr(); if (cmd&VM_RAW_AST) { - auto dumper = std::unique_ptr(new nasal::ast_dumper); - dumper->dump(parse.tree()); + nasal::ast_dumper().dump(parse.tree()); } // linker gets parser's ast and load import files to this ast - ld.link(parse, file, cmd&VM_DETAIL).chkerr(); + ld.link(parse, cmd&VM_DETAIL).chkerr(); if (cmd&VM_REF_FILE) { if (ld.get_file_list().size()) { std::cout << "referenced file(s):\n"; @@ -146,12 +153,11 @@ void execute( auto opt = std::unique_ptr(new nasal::optimizer); opt->do_optimization(parse.tree()); if (cmd&VM_AST) { - auto dumper = std::unique_ptr(new nasal::ast_dumper); - dumper->dump(parse.tree()); + nasal::ast_dumper().dump(parse.tree()); } // code generator gets parser's ast and import file list to generate code - gen.compile(parse, ld, false).chkerr(); + gen.compile(parse, ld, false, cmd&VM_LIMIT).chkerr(); if (cmd&VM_CODE) { gen.print(std::cout); } @@ -167,6 +173,7 @@ void execute( } else if (cmd&VM_TIME || cmd&VM_EXEC) { auto runtime = std::unique_ptr(new nasal::vm); runtime->set_detail_report_info(cmd&VM_DETAIL); + runtime->set_limit_mode_flag(cmd&VM_LIMIT); runtime->run(gen, ld, argv); } @@ -223,7 +230,8 @@ i32 main(i32 argc, const char* argv[]) { {"--prof", VM_PROFILE}, {"--prof-all", VM_PROF_ALL}, {"-f", VM_REF_FILE}, - {"--ref-file", VM_REF_FILE} + {"--ref-file", VM_REF_FILE}, + {"--limit", VM_LIMIT|VM_EXEC} }; u32 cmd = 0; std::string filename = ""; diff --git a/src/nasal.h b/src/nasal.h index ccfd9b7..e458572 100644 --- a/src/nasal.h +++ b/src/nasal.h @@ -1,7 +1,7 @@ #pragma once -#ifndef __nasver -#define __nasver "11.1" +#ifndef __nasver__ +#define __nasver__ "11.1" #endif #include @@ -34,7 +34,8 @@ bool is_aarch64(); bool is_ia64(); bool is_powerpc(); bool is_superh(); - +const char* get_platform(); +const char* get_arch(); // virtual machine stack depth, both global depth and value stack depth const u32 STACK_DEPTH = 4096; diff --git a/src/nasal_builtin.cpp b/src/nasal_builtin.cpp index e3bc834..58aee78 100644 --- a/src/nasal_builtin.cpp +++ b/src/nasal_builtin.cpp @@ -3,6 +3,13 @@ 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; @@ -447,35 +454,11 @@ var builtin_sleep(context* ctx, gc* ngc) { } var builtin_platform(context* ctx, gc* ngc) { - if (is_windows()) { - return ngc->newstr("windows"); - } else if (is_linux()) { - return ngc->newstr("linux"); - } else if (is_macos()) { - return ngc->newstr("macOS"); - } - return ngc->newstr("unknown"); + return ngc->newstr(get_platform()); } var builtin_arch(context* ctx, gc* ngc) { - if (is_x86()) { - return ngc->newstr("x86"); - } else if (is_x86_64()) { - return ngc->newstr("x86-64"); - } else if (is_amd64()) { - return ngc->newstr("amd64"); - } else if (is_arm()) { - return ngc->newstr("arm"); - } else if (is_aarch64()) { - return ngc->newstr("aarch64"); - } else if (is_ia64()) { - return ngc->newstr("ia64"); - } else if (is_powerpc()) { - return ngc->newstr("powerpc"); - } else if (is_superh()) { - return ngc->newstr("superh"); - } - return ngc->newstr("unknown"); + return ngc->newstr(get_arch()); } // md5 related functions @@ -683,7 +666,7 @@ var builtin_logtime(context* ctx, gc* ngc) { tm* tm_t = localtime(&t); char s[64]; snprintf( - s,64,"%d-%.2d-%.2d %.2d:%.2d:%.2d", + s, 64, "%d-%.2d-%.2d %.2d:%.2d:%.2d", tm_t->tm_year+1900, tm_t->tm_mon+1, tm_t->tm_mday, diff --git a/src/nasal_builtin.h b/src/nasal_builtin.h index d366879..9ca7503 100644 --- a/src/nasal_builtin.h +++ b/src/nasal_builtin.h @@ -29,6 +29,7 @@ namespace nasal { +var builtin_unsafe(context*, gc*); var builtin_print(context*, gc*); var builtin_println(context*, gc*); var builtin_exit(context*, gc*); diff --git a/src/nasal_codegen.cpp b/src/nasal_codegen.cpp index 95a135e..c6328a0 100644 --- a/src/nasal_codegen.cpp +++ b/src/nasal_codegen.cpp @@ -11,12 +11,18 @@ void codegen::init_file_map(const std::vector& file_list) { void codegen::load_native_function_table(nasal_builtin_table* table) { for(usize i = 0; table[i].func; ++i) { + // check confliction if (native_function_mapper.count(table[i].name)) { err.err("code", "\"" + std::string(table[i].name) + "\" conflicts."); continue; } - native_function.push_back(table[i]); + if (flag_limited_mode && unsafe_system_api.count(table[i].name)) { + native_function.push_back({"__unsafe_redirect", builtin_unsafe}); + } else { + native_function.push_back(table[i]); + } auto index = native_function_mapper.size(); + // insert into mapper native_function_mapper[table[i].name] = index; } } @@ -1302,10 +1308,14 @@ void codegen::ret_gen(return_expr* node) { emit(op_ret, 0, node->get_location()); } -const error& codegen::compile(parse& parse, linker& import, bool repl_flag) { +const error& codegen::compile(parse& parse, + linker& import, + bool repl_flag, + bool limit_mode) { + need_repl_output = repl_flag; + flag_limited_mode = limit_mode; init_native_function(); init_file_map(import.get_file_list()); - need_repl_output = repl_flag; in_foreach_loop_level.push_back(0); diff --git a/src/nasal_codegen.h b/src/nasal_codegen.h index 221a0d3..9d8ec7f 100644 --- a/src/nasal_codegen.h +++ b/src/nasal_codegen.h @@ -37,6 +37,24 @@ private: // repl output flag, will generate op_repl to output stack top value if true bool need_repl_output; + // limit mode flag + bool flag_limited_mode; + const std::unordered_set unsafe_system_api = { + // builtin + "__system", "__input", + // io + "__fout", "__open", "__write", "__stat" + // bits + "__fld", "__sfld", "__setfld", + "__buf", + // fg + "__logprint", + // dylib + "__dlopen", "__dlclose", "__dlcallv", "__dlcall", + // unix + "__pipe", "__fork", "__waitpid", "__chdir", + "__environ", "__getcwd", "__getenv" + }; // file mapper for file -> index std::unordered_map file_map; @@ -141,7 +159,7 @@ public: public: codegen() = default; - const error& compile(parse&, linker&, bool); + const error& compile(parse&, linker&, bool, bool); void print(std::ostream&); void symbol_dump(std::ostream&) const; }; diff --git a/src/nasal_err.h b/src/nasal_err.h index bbeeb2a..3aec9bf 100644 --- a/src/nasal_err.h +++ b/src/nasal_err.h @@ -31,7 +31,7 @@ protected: std::vector res; public: - flstream():file("") {} + flstream(): file("") {} void load(const std::string&); const std::string& operator[](usize n) const {return res[n];} const auto& name() const {return file;} @@ -39,23 +39,23 @@ public: usize size() const {return res.size();} }; -class error:public flstream { +class error: public flstream { private: u32 cnt; // counter for errors std::string identation(usize len) { - return std::string(len,' '); + return std::string(len, ' '); } std::string leftpad(u32 num, usize len) { auto tmp = std::to_string(num); while(tmp.length()get_expressions()) { new_tree_root->add_expression(i); @@ -179,32 +179,48 @@ code_block* linker::import_regular_file( // check self import, avoid infinite loading loop if (check_self_import(filename)) { err.err("link", - "self-referenced module <" + filename + ">:\n" + - " reference path: " + generate_self_import_path(filename) + node->get_location(), + "self-referenced module <" + filename + ">, " + + "reference path: " + generate_self_import_path(filename) ); return new code_block({0, 0, 0, 0, filename}); } check_exist_or_record_file(filename); - + module_load_stack.push_back(filename); + // avoid stack overflow + if (module_load_stack.size()>MAX_RECURSION_DEPTH) { + err.err("link", + node->get_location(), + "too deep module import stack (>" + + std::to_string(MAX_RECURSION_DEPTH) + ")." + ); + return new code_block({0, 0, 0, 0, filename}); + } // start importing... lexer nasal_lexer; parse nasal_parser; if (nasal_lexer.scan(filename).geterr()) { - err.err("link", "error occurred when analysing <" + filename + ">"); + err.err("link", + node->get_location(), + "error occurred when analysing <" + filename + ">" + ); return new code_block({0, 0, 0, 0, filename}); } if (nasal_parser.compile(nasal_lexer).geterr()) { - err.err("link", "error occurred when analysing <" + filename + ">"); + err.err("link", + node->get_location(), + "error occurred when analysing <" + filename + ">" + ); return new code_block({0, 0, 0, 0, filename}); } // swap result out auto parse_result = nasal_parser.swap(nullptr); // check if parse result has 'import' - auto result = load(parse_result, filename); + load(parse_result, filename); module_load_stack.pop_back(); - return result; + return parse_result; } code_block* linker::import_nasal_lib() { @@ -238,10 +254,12 @@ code_block* linker::import_nasal_lib() { // swap result out auto parse_result = nasal_parser.swap(nullptr); // check if library has 'import' (in fact it should not) - return load(parse_result, path); + load(parse_result, path); + return parse_result; } std::string linker::generate_module_name(const std::string& file_path) { + // import("...") may trigger this error module name auto error_name = "module@[" + file_path + "]"; if (!file_path.length()) { return error_name; @@ -316,13 +334,19 @@ return_expr* linker::generate_module_return(code_block* block) { } definition_expr* linker::generate_module_definition(code_block* block) { + // generate ast node like this: + // var {module_name} = (func() { + // ... # module itself + // })(); auto def = new definition_expr(block->get_location()); def->set_identifier(new identifier( block->get_location(), generate_module_name(block->get_location().file) )); + // (func() {...})(); auto call = new call_expr(block->get_location()); + // func() {...} auto func = new function(block->get_location()); func->set_code_block(block); func->get_code_block()->add_expression(generate_module_return(block)); @@ -333,18 +357,7 @@ definition_expr* linker::generate_module_definition(code_block* block) { return def; } -code_block* linker::load(code_block* program_root, const std::string& filename) { - auto tree = new code_block({0, 0, 0, 0, filename}); - // load library, this ast will be linked with root directly - // so no extra namespace is generated - if (!library_loaded) { - auto nasal_lib_code_block = import_nasal_lib(); - // insert nasal lib code to the back of tree - link(tree, nasal_lib_code_block); - delete nasal_lib_code_block; - library_loaded = true; - } - +void linker::load(code_block* program_root, const std::string& filename) { // load imported modules std::unordered_set used_modules = {}; for(auto& import_node : program_root->get_expressions()) { @@ -353,46 +366,46 @@ code_block* linker::load(code_block* program_root, const std::string& filename) } // parse file and get ast auto module_code_block = import_regular_file(import_node, used_modules); - auto replace_node = new null_expr(import_node->get_location()); - // after importing the regular file as module, delete this node - delete import_node; - // and replace the node with null_expr node - import_node = replace_node; - // avoid repeatedly importing the same module + // avoid repeatedly importing the same module in one file const auto& module_path = module_code_block->get_location().file; if (used_modules.count(module_path)) { delete module_code_block; + auto replace_node = new null_expr(import_node->get_location()); + // after importing the regular file as module, delete this node + delete import_node; + // and replace the node with null_expr node + import_node = replace_node; continue; } - + used_modules.insert(module_path); + delete import_node; // then we generate a function warping the code block, // and export the necessary global symbols in this code block // by generate a return statement, with a hashmap return value - used_modules.insert(module_path); - tree->add_expression(generate_module_definition(module_code_block)); + import_node = generate_module_definition(module_code_block); } - - // insert program root to the back of tree - link(tree, program_root); - return tree; } -const error& linker::link( - parse& parse, const std::string& self, bool spath = false) { +const error& linker::link(parse& parse, bool spath = false) { // switch for showing path when errors occur show_path_flag = spath; // initializing file map - this_file = self; - imported_files = {self}; - module_load_stack = {self}; + this_file = parse.tree()->get_location().file; + imported_files = {this_file}; + module_load_stack = {this_file}; // scan root and import files // then generate a new ast and return to import_ast - auto new_tree_root = load(parse.tree(), self); - auto old_tree_root = parse.swap(new_tree_root); - delete old_tree_root; + // dfs load file + auto library = import_nasal_lib(); + // load used modules of this file + load(parse.tree(), this_file); + // then insert the whole tree into library tree root + merge_tree(library, parse.tree()); + // swap tree root, and delete old root + delete parse.swap(library); return err; } diff --git a/src/nasal_import.h b/src/nasal_import.h index 692c922..420d6d6 100644 --- a/src/nasal_import.h +++ b/src/nasal_import.h @@ -23,8 +23,8 @@ namespace nasal { class linker { private: + const u32 MAX_RECURSION_DEPTH = 256; bool show_path_flag; - bool library_loaded; std::string this_file; error err; std::vector imported_files; @@ -36,7 +36,7 @@ private: bool check_exist_or_record_file(const std::string&); bool check_self_import(const std::string&); std::string generate_self_import_path(const std::string&); - void link(code_block*, code_block*); + void merge_tree(code_block*, code_block*); std::string get_path(expr*); std::string find_real_file_path(const std::string&, const span&); code_block* import_regular_file(expr*, std::unordered_set&); @@ -44,11 +44,11 @@ private: std::string generate_module_name(const std::string&); return_expr* generate_module_return(code_block*); definition_expr* generate_module_definition(code_block*); - code_block* load(code_block*, const std::string&); + void load(code_block*, const std::string&); public: linker(); - const error& link(parse&, const std::string&, bool); + const error& link(parse&, bool); const auto& get_file_list() const {return imported_files;} }; diff --git a/src/nasal_misc.cpp b/src/nasal_misc.cpp index 11f8297..13f7f30 100644 --- a/src/nasal_misc.cpp +++ b/src/nasal_misc.cpp @@ -97,6 +97,38 @@ bool is_superh() { #endif } +const char* get_platform() { + if (is_windows()) { + return "windows"; + } else if (is_linux()) { + return "linux"; + } else if (is_macos()) { + return "macOS"; + } + return "unknown platform"; +} + +const char* get_arch() { + if (is_x86()) { + return "x86"; + } else if (is_x86_64()) { + return "x86-64"; + } else if (is_amd64()) { + return "amd64"; + } else if (is_arm()) { + return "arm"; + } else if (is_aarch64()) { + return "aarch64"; + } else if (is_ia64()) { + return "ia64"; + } else if (is_powerpc()) { + return "powerpc"; + } else if (is_superh()) { + return "superh"; + } + return "unknown arch"; +} + f64 hex_to_f64(const char* str) { f64 ret = 0; for(; *str; ++str) { diff --git a/src/nasal_vm.h b/src/nasal_vm.h index 8653818..8da79d6 100644 --- a/src/nasal_vm.h +++ b/src/nasal_vm.h @@ -45,6 +45,9 @@ protected: bool first_exec_flag = true; bool allow_repl_output = false; + /* limited mode, will not load unsafe system api if switch on */ + bool flag_limited_mode = false; + /* vm initializing function */ void init( const std::vector&, @@ -192,6 +195,8 @@ public: void set_repl_mode_flag(bool flag) {is_repl_mode = flag;} /* set repl output flag */ void set_allow_repl_output_flag(bool flag) {allow_repl_output = flag;} + /* set limit mode flag */ + void set_limit_mode_flag(bool flag) {flag_limited_mode = flag;} }; inline bool vm::cond(var& val) { diff --git a/src/repl.cpp b/src/repl.cpp index 2773700..10860b3 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -92,12 +92,12 @@ bool repl::run() { return false; } - if (nasal_linker->link(*nasal_parser, "", true).geterr()) { + if (nasal_linker->link(*nasal_parser, true).geterr()) { return false; } nasal_opt->do_optimization(nasal_parser->tree()); - if (nasal_codegen->compile(*nasal_parser, *nasal_linker, true).geterr()) { + if (nasal_codegen->compile(*nasal_parser, *nasal_linker, true, false).geterr()) { return false; } @@ -120,7 +120,7 @@ void repl::execute() { std::cout << "[nasal-repl] Initialization complete.\n\n"; // finish initialization, output version info - std::cout << "Nasal REPL interpreter version " << __nasver; + std::cout << "Nasal REPL interpreter version " << __nasver__; std::cout << " (" << __DATE__ << " " << __TIME__ << ")\n"; help(); diff --git a/test/langtons-ant.nas b/test/langtons-ant.nas new file mode 100644 index 0000000..9eed9a9 --- /dev/null +++ b/test/langtons-ant.nas @@ -0,0 +1,97 @@ +use std.unix; + +srand(); + +var ant = { + new: func(color, pos_x, pos_y) { + return { + move: int(rand()*4), + color: color, + pos_x: pos_x, + pos_y: pos_y + }; + } +}; + +# generate agents +var ants = [ + ant.new(10, 30, 15), + ant.new(11, 31, 16), + ant.new(4, 29, 13), + ant.new(9, 30, 14), + ant.new(13, 30, 12), + ant.new(99, 25, 18) +]; + +# initialize game map +var map = []; +var map_color = []; +setsize(map, 60*30); +setsize(map_color, 60*30); +forindex(var i; map) { + map[i] = 0; + map_color[i] = 0; +} +foreach(var a; ants) { + map_color[a.pos_x + a.pos_y*60] = a.color; +} + +var print_map = func { + var res = "\e[1;1H"; + for(var y = 0; y<30; y += 1) { + for(var x = 0; x<60; x += 1) { + res ~= "\e[38;5;"~map_color[x + y*60]~";1m"; + res ~= map[x + y*60] ~ " \e[0m"; + } + res ~= "\n"; + } + print(res); +} + +var move = func { + var move_step = [ + [0, -1], + [1, 0], + [0, 1], + [-1, 0] + ]; + var temp_map = []; + setsize(temp_map, 60*30); + forindex(var i; map) { + temp_map[i] = map[i]; + } + foreach(var a; ants) { + var map_state = map[a.pos_x + a.pos_y*60]; + temp_map[a.pos_x + a.pos_y*60] = map_state==0? 1:0; + if (map_state==1) { + a.move -= 1; + if (a.move < 0) { + a.move = 3; + } + } else { + a.move += 1; + if (a.move > 3) { + a.move = 0; + } + } + a.pos_x += move_step[a.move][0]; + a.pos_y += move_step[a.move][1]; + if (a.pos_x < 0) { a.pos_x = 59; } + if (a.pos_x > 59) { a.pos_x = 0; } + if (a.pos_y < 0) { a.pos_y = 29; } + if (a.pos_y > 29) { a.pos_y = 0; } + + if (map_color[a.pos_x + a.pos_y*60]!=a.color) { + map_color[a.pos_x + a.pos_y*60] = a.color; + } else { + map_color[a.pos_x + a.pos_y*60] = 0; + } + } + map = temp_map; +} + +while(1) { + print_map(); + move(); + unix.sleep(1/24); +} \ No newline at end of file