From f6704915bb83d544d2c86543c1245c88424798ed Mon Sep 17 00:00:00 2001 From: LIU Hao Date: Mon, 23 Oct 2023 23:09:27 +0800 Subject: [PATCH] statement_sequence,air_node: Generalize for-each loops Closes #135. --- asteria/compiler/statement_sequence.cpp | 31 ++++++++--------- asteria/runtime/air_node.cpp | 45 +++++++++++++------------ doc/syntax.txt | 2 +- test/Makefile.inc.am | 1 + test/for_each.cpp | 40 ++++++++++++++++++++++ 5 files changed, 81 insertions(+), 38 deletions(-) create mode 100644 test/for_each.cpp diff --git a/asteria/compiler/statement_sequence.cpp b/asteria/compiler/statement_sequence.cpp index 84d7f07d9..2245f8928 100644 --- a/asteria/compiler/statement_sequence.cpp +++ b/asteria/compiler/statement_sequence.cpp @@ -927,25 +927,27 @@ do_accept_for_complement_range_opt(Token_Stream& tstrm, const Source_Location& o Scope_Flags scfl) { // for-complement-range ::= - // "each" identifier "," identifier "->" expression ")" statement + // "each" identifier ( ( "," | ":" | "=" ) identifier ) ? "->" expression ")" statement auto qkwrd = do_accept_keyword_opt(tstrm, { keyword_each }); if(!qkwrd) return nullopt; - auto qkname = do_accept_identifier_opt(tstrm, true); - if(!qkname) + cow_string key; + auto qmapped = do_accept_identifier_opt(tstrm, true); + if(!qmapped) throw Compiler_Error(Compiler_Error::M_status(), compiler_status_identifier_expected, tstrm.next_sloc()); - auto kpunct = do_accept_punctuator_opt(tstrm, { punctuator_comma }); - if(!kpunct) - throw Compiler_Error(Compiler_Error::M_status(), - compiler_status_comma_expected, tstrm.next_sloc()); - - auto qvname = do_accept_identifier_opt(tstrm, true); - if(!qvname) - throw Compiler_Error(Compiler_Error::M_status(), - compiler_status_identifier_expected, tstrm.next_sloc()); + auto kpunct = do_accept_punctuator_opt(tstrm, { punctuator_comma, punctuator_colon, punctuator_assign }); + if(kpunct) { + // Move the first identifier into the key and expect the name for mapped + // references. + key = ::std::move(*qmapped); + qmapped = do_accept_identifier_opt(tstrm, true); + if(!qmapped) + throw Compiler_Error(Compiler_Error::M_status(), + compiler_status_identifier_expected, tstrm.next_sloc()); + } kpunct = do_accept_punctuator_opt(tstrm, { punctuator_arrow }); if(!kpunct) @@ -969,9 +971,8 @@ do_accept_for_complement_range_opt(Token_Stream& tstrm, const Source_Location& o throw Compiler_Error(Compiler_Error::M_status(), compiler_status_statement_expected, tstrm.next_sloc()); - Statement::S_for_each xstmt = { ::std::move(*qkname), ::std::move(*qvname), - ::std::move(sloc_init), ::std::move(*qinit), - ::std::move(*qblock) }; + Statement::S_for_each xstmt = { ::std::move(key), ::std::move(*qmapped), ::std::move(sloc_init), + ::std::move(*qinit), ::std::move(*qblock) }; return ::std::move(xstmt); } diff --git a/asteria/runtime/air_node.cpp b/asteria/runtime/air_node.cpp index 0194dcbd1..d5b5f6936 100644 --- a/asteria/runtime/air_node.cpp +++ b/asteria/runtime/air_node.cpp @@ -339,7 +339,8 @@ rebind_opt(Abstract_Context& ctx) const S_for_each_statement bound = altr; Analytic_Context ctx_for(Analytic_Context::M_plain(), ctx); - ctx_for.insert_named_reference(altr.name_key); + if(!altr.name_key.empty()) + ctx_for.insert_named_reference(altr.name_key); ctx_for.insert_named_reference(altr.name_mapped); do_rebind_nodes(dirty, bound.code_init, ctx_for); @@ -1123,9 +1124,8 @@ solidify(AVMC_Queue& queue) const Executive_Context ctx_for(Executive_Context::M_plain(), ctx); // Create key and mapped references. - auto& key = ctx_for.insert_named_reference(sp.name_key); - auto& mapped = ctx_for.insert_named_reference(sp.name_mapped); refcnt_ptr kvar; + auto& mapped = ctx_for.insert_named_reference(sp.name_mapped); // Evaluate the range initializer and set the range up, which isn't // going to change for all loops. @@ -1140,19 +1140,21 @@ solidify(AVMC_Queue& queue) const } else if(range.is_array()) { const auto& arr = range.as_array(); + mapped.push_modifier(Reference_Modifier::S_array_head()); // placeholder for(int64_t i = 0; i < arr.ssize(); ++i) { // Set the key variable which is the subscript of the mapped // element in the array. - if(!kvar) { - kvar = ctx.global().garbage_collector()->create_variable(); - key.set_variable(kvar); + if(!sp.name_key.empty()) { + if(!kvar) { + kvar = ctx.global().garbage_collector()->create_variable(); + ctx_for.insert_named_reference(sp.name_key).set_variable(kvar); + } + kvar->initialize(i); + kvar->set_immutable(); } - else - mapped.pop_modifier(); - - kvar->initialize(i); - kvar->set_immutable(); + // Set the mapped reference. + mapped.pop_modifier(); Reference_Modifier::S_array_index xmod = { i }; mapped.push_modifier(::std::move(xmod)); mapped.dereference_readonly(); @@ -1171,19 +1173,21 @@ solidify(AVMC_Queue& queue) const } else if(range.is_object()) { const auto& obj = range.as_object(); + mapped.push_modifier(Reference_Modifier::S_array_head()); // placeholder for(auto it = obj.begin(); it != obj.end(); ++it) { // Set the key variable which is the name of the mapped element // in the object. - if(!kvar) { - kvar = ctx.global().garbage_collector()->create_variable(); - key.set_variable(kvar); + if(!sp.name_key.empty()) { + if(!kvar) { + kvar = ctx.global().garbage_collector()->create_variable(); + ctx_for.insert_named_reference(sp.name_key).set_variable(kvar); + } + kvar->initialize(it->first.rdstr()); + kvar->set_immutable(); } - else - mapped.pop_modifier(); - - kvar->initialize(it->first.rdstr()); - kvar->set_immutable(); + // Set the mapped reference. + mapped.pop_modifier(); Reference_Modifier::S_object_key xmod = { it->first }; mapped.push_modifier(::std::move(xmod)); mapped.dereference_readonly(); @@ -1197,9 +1201,6 @@ solidify(AVMC_Queue& queue) const else if(::rocket::is_none_of(status, { air_status_next, air_status_continue_unspec, air_status_continue_for })) break; - - // Restore the mapped reference. - mapped.pop_modifier(); } return status; } diff --git a/doc/syntax.txt b/doc/syntax.txt index 493b11f30..c9d1014d6 100644 --- a/doc/syntax.txt +++ b/doc/syntax.txt @@ -128,7 +128,7 @@ for-complement ::= for-complement-range | for-complement-triplet for-complement-range ::= - "each" identifier "," identifier "->" expression ")" statement + "each" identifier ( ( "," | ":" | "=" ) identifier ) ? "->" expression ")" statement for-complement-triplet ::= for-initializer expression ? ";" expression ? ")" statement diff --git a/test/Makefile.inc.am b/test/Makefile.inc.am index aaa00b17a..3e14d43b9 100644 --- a/test/Makefile.inc.am +++ b/test/Makefile.inc.am @@ -57,6 +57,7 @@ check_PROGRAMS += \ %reldir%/ptc_hooks_throw.test \ %reldir%/ptc_hooks_return.test \ %reldir%/switch_defer.test \ + %reldir%/for_each.test \ ${END} EXTRA_DIST += \ diff --git a/test/for_each.cpp b/test/for_each.cpp new file mode 100644 index 000000000..7b775ed48 --- /dev/null +++ b/test/for_each.cpp @@ -0,0 +1,40 @@ +// This file is part of Asteria. +// Copyleft 2018 - 2023, LH_Mouse. All wrongs reserved. + +#include "utils.hpp" +#include "../asteria/simple_script.hpp" +using namespace ::asteria; + +int main() + { + Simple_Script code; + code.reload_string( + sref(__FILE__), __LINE__, sref(R"__( +/////////////////////////////////////////////////////////////////////////////// + + var output; + + output = ""; + for(each k, v -> ["a","b","c"]) + output += std.string.format("$1=$2;", k, v); + assert output == "0=a;1=b;2=c;"; + + output = ""; + for(each k: v -> ["a","b","c"]) + output += std.string.format("$1=$2;", k, v); + assert output == "0=a;1=b;2=c;"; + + output = ""; + for(each k = v -> ["a","b","c"]) + output += std.string.format("$1=$2;", k, v); + assert output == "0=a;1=b;2=c;"; + + output = ""; + for(each v -> ["a","b","c"]) + output += std.string.format("$1;", v); + assert output == "a;b;c;"; + +/////////////////////////////////////////////////////////////////////////////// + )__")); + code.execute(); + }