diff --git a/README.md b/README.md index edf6e676..13e067c7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ advantages, including: * Modern C++14 API (no macros, strongly typed enums, lambdas, ranges etc.) * C++14 lambdas can be used as commands (including captures and type inference) * Error handling including recovery (protected call system similar to Lua) +* Loop control statements (`break` and `continue`) * No manual memory mangement, values manage themselves * Clean codebase that is easy to read and contribute to * Support for arbitrary size integers and floats (can be set at compile time) @@ -40,7 +41,6 @@ There are some features that are a work in progress and will come later: * A degree of thread safety (see below) * Custom allocator support (control over how heap memory is allocated) * Coroutines -* Loop control statements (`break` and `continue`) The API is currently very unstable, as is the actual codebase. Therefore you should not use the project in production environments just yet, but you're diff --git a/include/cubescript/cubescript.hh b/include/cubescript/cubescript.hh index b793a8bc..ac6f4a06 100644 --- a/include/cubescript/cubescript.hh +++ b/include/cubescript/cubescript.hh @@ -351,6 +351,10 @@ private: struct CsErrorException; struct CsSharedState; +enum class CsLoopState { + Normal = 0, Break, Continue +}; + struct OSTD_EXPORT CsState { friend struct CsErrorException; @@ -505,6 +509,13 @@ struct OSTD_EXPORT CsState { void run(ostd::ConstCharRange code); void run(CsIdent *id, CsValueRange args); + CsLoopState run_loop(CsBytecode *code, CsValue &ret); + CsLoopState run_loop(CsBytecode *code); + + bool is_in_loop() const { + return p_inloop; + } + ostd::Maybe run_file_str(ostd::ConstCharRange fname); ostd::Maybe run_file_int(ostd::ConstCharRange fname); ostd::Maybe run_file_float(ostd::ConstCharRange fname); @@ -551,10 +562,15 @@ struct OSTD_EXPORT CsState { private: CsIdent *add_ident(CsIdent *id); + int p_inloop = 0; + CsAllocCb p_allocf; void *p_aptr; + char p_errbuf[512]; + CsHookCb p_callhook; + CsStream *p_out, *p_err; }; diff --git a/src/cs_vm.cc b/src/cs_vm.cc index 4b2e9e80..2778ec99 100644 --- a/src/cs_vm.cc +++ b/src/cs_vm.cc @@ -1750,6 +1750,28 @@ void CsState::run(CsIdent *id, CsValueRange args) { run(id, args, ret); } +CsLoopState CsState::run_loop(CsBytecode *code, CsValue &ret) { + ++p_inloop; + try { + run(code, ret); + } catch (CsBreakException) { + --p_inloop; + return CsLoopState::Break; + } catch (CsContinueException) { + --p_inloop; + return CsLoopState::Continue; + } catch (...) { + --p_inloop; + throw; + } + return CsLoopState::Normal; +} + +CsLoopState CsState::run_loop(CsBytecode *code) { + CsValue ret; + return run_loop(code, ret); +} + static bool cs_run_file( CsState &cs, ostd::ConstCharRange fname, CsValue &ret ) { diff --git a/src/cs_vm.hh b/src/cs_vm.hh index 5dba94cc..5e9e4171 100644 --- a/src/cs_vm.hh +++ b/src/cs_vm.hh @@ -94,6 +94,12 @@ struct CsSharedState { CsVector identmap; }; +struct CsBreakException { +}; + +struct CsContinueException { +}; + template constexpr ostd::Size CsTypeStorageSize = (sizeof(T) - 1) / sizeof(ostd::Uint32) + 1; diff --git a/src/cubescript.cc b/src/cubescript.cc index ba8747a4..7bc9d12a 100644 --- a/src/cubescript.cc +++ b/src/cubescript.cc @@ -1039,8 +1039,15 @@ static inline void cs_do_loop( if (cond && !cs.run_bool(cond)) { break; } - cs.run_int(body); + switch (cs.run_loop(body)) { + case CsLoopState::Break: + goto end; + default: /* continue and normal */ + break; + } } +end: + return; } static inline void cs_loop_conc( @@ -1056,13 +1063,21 @@ static inline void cs_loop_conc( idv.set_int(offset + i * step); idv.push(); CsValue v; - cs.run(body, v); + switch (cs.run_loop(body, v)) { + case CsLoopState::Break: + goto end; + case CsLoopState::Continue: + continue; + default: + break; + } CsString vstr = ostd::move(v.get_str()); if (space && i) { s.push(' '); } s.push_n(vstr.data(), vstr.size()); } +end: s.push('\0'); ostd::Size len = s.size() - 1; res.set_mstr(ostd::CharRange(s.disown(), len)); @@ -1070,7 +1085,7 @@ static inline void cs_loop_conc( void cs_init_lib_base(CsState &gcs) { gcs.new_command("error", "s", [](CsState &cs, CsValueRange args, CsValue &) { - throw cscript::CsErrorException(cs, args[0].get_strr()); + throw CsErrorException(cs, args[0].get_strr()); }); gcs.new_command("pcall", "err", []( @@ -1102,6 +1117,22 @@ void cs_init_lib_base(CsState &gcs) { } }); + gcs.new_command("break", "", [](CsState &cs, auto, auto &) { + if (cs.is_in_loop()) { + throw CsBreakException(); + } else { + throw CsErrorException(cs, "no loop to break"); + } + }); + + gcs.new_command("continue", "", [](CsState &cs, auto, auto &) { + if (cs.is_in_loop()) { + throw CsContinueException(); + } else { + throw CsErrorException(cs, "no loop to continue"); + } + }); + gcs.new_command("?", "tTT", [](CsState &, CsValueRange args, CsValue &res) { if (args[0].get_bool()) { res = ostd::move(args[1]); @@ -1262,8 +1293,15 @@ void cs_init_lib_base(CsState &gcs) { ) { CsBytecode *cond = args[0].get_code(), *body = args[1].get_code(); while (cs.run_bool(cond)) { - cs.run_int(body); + switch (cs.run_loop(body)) { + case CsLoopState::Break: + goto end; + default: /* continue and normal */ + break; + } } +end: + return; }); gcs.new_command("loopconcat", "rie", []( diff --git a/src/lib_list.cc b/src/lib_list.cc index 9128c0bb..9a253ec0 100644 --- a/src/lib_list.cc +++ b/src/lib_list.cc @@ -86,10 +86,18 @@ static void cs_loop_list_conc( r.push(' '); } CsValue v; - cs.run(body, v); + switch (cs.run_loop(body, v)) { + case CsLoopState::Break: + goto end; + case CsLoopState::Continue: + continue; + default: + break; + } CsString vstr = ostd::move(v.get_str()); r.push_n(vstr.data(), vstr.size()); } +end: r.push('\0'); ostd::Size len = r.size(); res.set_mstr(ostd::CharRange(r.disown(), len - 1)); @@ -311,8 +319,15 @@ void cs_init_lib_list(CsState &gcs) { for (util::ListParser p(args[1].get_strr()); p.parse(); ++n) { idv.set_mstr(p.element().disown()); idv.push(); - cs.run_int(body); + switch (cs.run_loop(body)) { + case CsLoopState::Break: + goto end; + default: /* continue and normal */ + break; + } } +end: + return; }); gcs.new_command("looplist2", "rrse", []( @@ -329,8 +344,15 @@ void cs_init_lib_list(CsState &gcs) { idv2.set_mstr(p.parse() ? p.element().disown() : cs_dup_ostr("")); idv1.push(); idv2.push(); - cs.run_int(body); + switch (cs.run_loop(body)) { + case CsLoopState::Break: + goto end; + default: /* continue and normal */ + break; + } } +end: + return; }); gcs.new_command("looplist3", "rrrse", []( @@ -351,8 +373,15 @@ void cs_init_lib_list(CsState &gcs) { idv1.push(); idv2.push(); idv3.push(); - cs.run_int(body); + switch (cs.run_loop(body)) { + case CsLoopState::Break: + goto end; + default: /* continue and normal */ + break; + } } +end: + return; }); gcs.new_command("looplistconcat", "rse", [](