From b081a627c0923b4fb4e31d88877ea8adeef898e4 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 7 Jan 2016 20:57:16 +0100 Subject: [PATCH 1/3] Allow parenthesis around list comprehension expressions. This mainly allows to group nested if/else expressions, otherwise else would always bind to the nearest if. --- src/expr.cc | 10 +++++----- src/parser.y | 14 ++++++++++++-- testdata/scad/functions/list-comprehensions.scad | 3 +++ testdata/scad/misc/allexpressions.scad | 3 ++- .../echotest/list-comprehensions-expected.echo | 2 ++ .../moduledumptest/allexpressions-expected.ast | 9 +++++---- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/expr.cc b/src/expr.cc index d8a80896..495bc821 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -550,9 +550,9 @@ ValuePtr ExpressionLcIf::evaluate(const Context *context) const void ExpressionLcIf::print(std::ostream &stream) const { - stream << "if(" << *this->cond << ") " << *this->first; + stream << "if(" << *this->cond << ") (" << *this->first << ")"; if (this->second) { - stream << " else " << *this->second; + stream << " else (" << *this->second << ")"; } } @@ -595,7 +595,7 @@ ValuePtr ExpressionLcEach::evaluate(const Context *context) const void ExpressionLcEach::print(std::ostream &stream) const { - stream << "each " << *this->first; + stream << "each (" << *this->first << ")"; } ExpressionLcFor::ExpressionLcFor(const AssignmentList &arglist, Expression *expr) @@ -647,7 +647,7 @@ ValuePtr ExpressionLcFor::evaluate(const Context *context) const void ExpressionLcFor::print(std::ostream &stream) const { - stream << "for(" << this->call_arguments << ") " << *this->first; + stream << "for(" << this->call_arguments << ") (" << *this->first << ")"; } ExpressionLcLet::ExpressionLcLet(const AssignmentList &arglist, Expression *expr) @@ -664,7 +664,7 @@ ValuePtr ExpressionLcLet::evaluate(const Context *context) const void ExpressionLcLet::print(std::ostream &stream) const { - stream << "let(" << this->call_arguments << ") " << *this->first; + stream << "let(" << this->call_arguments << ") (" << *this->first << ")"; } std::ostream &operator<<(std::ostream &stream, const Expression &expr) diff --git a/src/parser.y b/src/parser.y index 904dd4f2..a9a9aeef 100644 --- a/src/parser.y +++ b/src/parser.y @@ -121,6 +121,7 @@ std::string parser_source_path; %type expr %type vector_expr %type list_comprehension_elements +%type list_comprehension_elements_p %type list_comprehension_elements_or_expr %type module_instantiation @@ -444,7 +445,7 @@ expr: list_comprehension_elements: /* The last set element may not be a "let" (as that would instead be parsed as an expression) */ - TOK_LET '(' arguments_call ')' list_comprehension_elements + TOK_LET '(' arguments_call ')' list_comprehension_elements_p { $$ = new ExpressionLcLet(*$3, $5); delete $3; @@ -476,8 +477,17 @@ list_comprehension_elements: } ; -list_comprehension_elements_or_expr: +// list_comprehension_elements with optional parenthesis +list_comprehension_elements_p: list_comprehension_elements + | '(' list_comprehension_elements ')' + { + $$ = $2; + } + ; + +list_comprehension_elements_or_expr: + list_comprehension_elements_p | expr ; diff --git a/testdata/scad/functions/list-comprehensions.scad b/testdata/scad/functions/list-comprehensions.scad index b7b39c3e..ac66d67b 100644 --- a/testdata/scad/functions/list-comprehensions.scad +++ b/testdata/scad/functions/list-comprehensions.scad @@ -32,6 +32,9 @@ echo([ for (a = [2:3]) a * 2, for (a = [5:9]) if ((a % 2) == 0) [ a, a + 1 ] , - echo([ for (a = [0,1,2]) if (a == 1) "-" else "+" ]); echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" ]); echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" else "-" ]); +echo([ for (a = [0 : 3]) if (a < 2) if (a < 1) ["+", a] else ["-", a] ]); +echo([ for (a = [0 : 3]) if (a < 2) ( if (a < 1) ["+", a] ) else ["-", a] ]); echo([ for (a = [2 : 4]) each [ a, a*a ] ]); function f() = [ for (a = [0 : 4]) pow(2, a) ]; echo([ each ["a", "b"], each [-5 : -2 : -9], each f(), each "c", each 42, each true ]); + diff --git a/testdata/scad/misc/allexpressions.scad b/testdata/scad/misc/allexpressions.scad index 50fbef19..b404dad7 100644 --- a/testdata/scad/misc/allexpressions.scad +++ b/testdata/scad/misc/allexpressions.scad @@ -31,5 +31,6 @@ bb = n[o]; cc = let(a=1) a; dd = [for (a=[0,1]) let(b=a) if (true) b]; ee = ["abc", for (a=[0,1]) let(b=a) if (true) b, true, for(c=[1:3]) c, 3]; -ff = [for (a=[0,1]) if (a == 0) "A" else "B"]; +ff = [for (a=[0,1]) if (a == 0) "A" else ( "B" )]; gg = [each [ "a", 0, false ]]; +hh = [for (a = [0 : 3]) if (a < 2) ( if (a < 1) ["+", a] ) else ["-", a] ]; diff --git a/tests/regression/echotest/list-comprehensions-expected.echo b/tests/regression/echotest/list-comprehensions-expected.echo index 8eab4523..88ca4d83 100644 --- a/tests/regression/echotest/list-comprehensions-expected.echo +++ b/tests/regression/echotest/list-comprehensions-expected.echo @@ -29,5 +29,7 @@ ECHO: [4, 6, [6, 7], [8, 9], -1] ECHO: ["+", "-", "+"] ECHO: ["A", "B"] ECHO: ["-", "A", "B"] +ECHO: [["+", 0], ["-", 1]] +ECHO: [["+", 0], ["-", 2], ["-", 3]] ECHO: [2, 4, 3, 9, 4, 16] ECHO: ["a", "b", -5, -7, -9, 1, 2, 4, 8, 16, "c", 42, true] diff --git a/tests/regression/moduledumptest/allexpressions-expected.ast b/tests/regression/moduledumptest/allexpressions-expected.ast index 7b57e894..4a010f6c 100644 --- a/tests/regression/moduledumptest/allexpressions-expected.ast +++ b/tests/regression/moduledumptest/allexpressions-expected.ast @@ -29,8 +29,9 @@ z = j; aa = (k ? l : m); bb = n[o]; cc = let(a = 1) a; -dd = [for(a = [0, 1]) let(b = a) if(true) b]; -ee = ["abc", for(a = [0, 1]) let(b = a) if(true) b, true, for(c = [1 : 3]) c, 3]; -ff = [for(a = [0, 1]) if((a == 0)) "A" else "B"]; -gg = [each ["a", 0, false]]; +dd = [for(a = [0, 1]) (let(b = a) (if(true) (b)))]; +ee = ["abc", for(a = [0, 1]) (let(b = a) (if(true) (b))), true, for(c = [1 : 3]) (c), 3]; +ff = [for(a = [0, 1]) (if((a == 0)) ("A") else ("B"))]; +gg = [each (["a", 0, false])]; +hh = [for(a = [0 : 3]) (if((a < 2)) (if((a < 1)) (["+", a])) else (["-", a]))]; From a65a55db4fc321eccf6f5b7c968de189e7d3a6c9 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Fri, 8 Jan 2016 00:46:57 +0100 Subject: [PATCH 2/3] Flag both "each" and "else" expressions as experimental. This also moves the new test cases to a separate test case (echotest_list-comprehensions-experimental) which will be run with "--enable=lc-each --enable=lc-else". --- src/exceptions.h | 12 +++++-- src/expr.cc | 7 ++++ src/feature.cc | 23 +++++++++++++ src/feature.h | 15 +++++++++ src/module.cc | 2 +- .../list-comprehensions-experimental.scad | 8 +++++ .../scad/functions/list-comprehensions.scad | 9 ------ tests/CMakeLists.txt | 32 ++++++++++++------- .../list-comprehensions-expected.echo | 7 ---- ...-comprehensions-experimental-expected.echo | 7 ++++ 10 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 testdata/scad/functions/list-comprehensions-experimental.scad create mode 100644 tests/regression/echotest/list-comprehensions-experimental-expected.echo diff --git a/src/exceptions.h b/src/exceptions.h index 9d01e14f..cdd83304 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,6 +1,14 @@ +#pragma once + #include -class RecursionException: public std::runtime_error { +class EvaluationException : public std::runtime_error { +public: + EvaluationException(const std::string &what_arg) : std::runtime_error(what_arg) {} + virtual ~EvaluationException() throw() {} +}; + +class RecursionException: public EvaluationException { public: static RecursionException create(const char *recursiontype, const std::string &name) { std::stringstream out; @@ -10,5 +18,5 @@ public: virtual ~RecursionException() throw() {} private: - RecursionException(const std::string &what_arg) : std::runtime_error(what_arg) {} + RecursionException(const std::string &what_arg) : EvaluationException(what_arg) {} }; diff --git a/src/expr.cc b/src/expr.cc index 495bc821..2daa1a37 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -33,6 +33,7 @@ #include "printutils.h" #include "stackcheck.h" #include "exceptions.h" +#include "feature.h" #include #include @@ -534,6 +535,10 @@ ExpressionLcIf::ExpressionLcIf(Expression *cond, Expression *exprIf, Expression ValuePtr ExpressionLcIf::evaluate(const Context *context) const { + if (this->second) { + ExperimentalFeatureException::check(Feature::ExperimentalElseExpression); + } + const Expression *expr = this->cond->evaluate(context) ? this->first : this->second; Value::VectorType vec; @@ -563,6 +568,8 @@ ExpressionLcEach::ExpressionLcEach(Expression *expr) ValuePtr ExpressionLcEach::evaluate(const Context *context) const { + ExperimentalFeatureException::check(Feature::ExperimentalEachExpression); + Value::VectorType vec; ValuePtr v = this->first->evaluate(context); diff --git a/src/feature.cc b/src/feature.cc index 79df7cce..9c23d974 100644 --- a/src/feature.cc +++ b/src/feature.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -18,6 +19,8 @@ Feature::list_t Feature::feature_list; * argument to enable the option and for saving the option value in GUI * context. */ +const Feature Feature::ExperimentalEachExpression("lc-each", "Enable each expression in list comprehensions."); +const Feature Feature::ExperimentalElseExpression("lc-else", "Enable else expression in list comprehensions."); Feature::Feature(const std::string &name, const std::string &description) : enabled(false), name(name), description(description) @@ -76,3 +79,23 @@ void Feature::dump_features() std::cout << "Feature('" << it->first << "') = " << (it->second->is_enabled() ? "enabled" : "disabled") << std::endl; } } + +ExperimentalFeatureException::ExperimentalFeatureException(const std::string &what_arg) + : EvaluationException(what_arg) +{ + +} + +ExperimentalFeatureException::~ExperimentalFeatureException() throw() +{ + +} + +void ExperimentalFeatureException::check(const Feature &feature) +{ + if (!feature.is_enabled()) { + std::stringstream out; + out << "ERROR: Experimental feature not enabled: '" << feature.get_name() << "'. Please check preferences."; + throw ExperimentalFeatureException(out.str()); + } +} diff --git a/src/feature.h b/src/feature.h index 579ba3c0..5fec17ba 100644 --- a/src/feature.h +++ b/src/feature.h @@ -6,12 +6,17 @@ #include #include +#include "exceptions.h" + class Feature { public: typedef std::vector list_t; typedef list_t::iterator iterator; + static const Feature ExperimentalEachExpression; + static const Feature ExperimentalElseExpression; + const std::string& get_name() const; const std::string& get_description() const; @@ -37,3 +42,13 @@ private: Feature(const std::string &name, const std::string &description); virtual ~Feature(); }; + +class ExperimentalFeatureException : public EvaluationException +{ +public: + static void check(const Feature &feature); + virtual ~ExperimentalFeatureException() throw(); + +private: + ExperimentalFeatureException(const std::string &what_arg); +}; diff --git a/src/module.cc b/src/module.cc index 2018d5cf..c03cb9f8 100644 --- a/src/module.cc +++ b/src/module.cc @@ -355,7 +355,7 @@ AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiat std::vector instantiatednodes = this->scope.instantiateChildren(context); node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end()); } - catch (RecursionException &e) { + catch (EvaluationException &e) { PRINT(e.what()); } diff --git a/testdata/scad/functions/list-comprehensions-experimental.scad b/testdata/scad/functions/list-comprehensions-experimental.scad new file mode 100644 index 00000000..d258451a --- /dev/null +++ b/testdata/scad/functions/list-comprehensions-experimental.scad @@ -0,0 +1,8 @@ +echo([ for (a = [0,1,2]) if (a == 1) "-" else "+" ]); +echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" ]); +echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" else "-" ]); +echo([ for (a = [0 : 3]) if (a < 2) if (a < 1) ["+", a] else ["-", a] ]); +echo([ for (a = [0 : 3]) if (a < 2) ( if (a < 1) ["+", a] ) else ["-", a] ]); +echo([ for (a = [2 : 4]) each [ a, a*a ] ]); +function f() = [ for (a = [0 : 4]) pow(2, a) ]; +echo([ each ["a", "b"], each [-5 : -2 : -9], each f(), each "c", each 42, each true ]); diff --git a/testdata/scad/functions/list-comprehensions.scad b/testdata/scad/functions/list-comprehensions.scad index ac66d67b..bd1093fe 100644 --- a/testdata/scad/functions/list-comprehensions.scad +++ b/testdata/scad/functions/list-comprehensions.scad @@ -29,12 +29,3 @@ echo([ for (a=[0:1]) if (true) for (b=[a:2]) [b,a]]); echo([ for (a=[0:1]) if (true) if (true) for (b=[a:2]) [b,a]]); echo([ -1, for (a = [0:1:3]) a, for (b = [3:-1:0]) b, -1 ]); echo([ for (a = [2:3]) a * 2, for (a = [5:9]) if ((a % 2) == 0) [ a, a + 1 ] , -1 ]); -echo([ for (a = [0,1,2]) if (a == 1) "-" else "+" ]); -echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" ]); -echo([ for (a = [0,1,2]) if (a > 0) if (a == 1) "A" else "B" else "-" ]); -echo([ for (a = [0 : 3]) if (a < 2) if (a < 1) ["+", a] else ["-", a] ]); -echo([ for (a = [0 : 3]) if (a < 2) ( if (a < 1) ["+", a] ) else ["-", a] ]); -echo([ for (a = [2 : 4]) each [ a, a*a ] ]); -function f() = [ for (a = [0 : 4]) pow(2, a) ]; -echo([ each ["a", "b"], each [-5 : -2 : -9], each f(), each "c", each 42, each true ]); - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fce48fcd..21749e1e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -888,6 +888,17 @@ macro(disable_tests) endforeach() endmacro() +# +# Tags tests as experimental. This will add all the --enable= +# options for the tagged tests. +# +macro(experimental_tests) + foreach (TESTNAME ${ARGN}) +# message("Marking as experimental ${TESTNAME}") + list(APPEND EXPERIMENTAL_TESTS ${TESTNAME}) + endforeach() +endmacro() + # # Tags the given tests as belonging to the given CONFIG, i.e. will # only be executed when run using ctest -C @@ -981,16 +992,12 @@ function(add_cmdline_test TESTCMD_BASENAME) if (${DISABLED} EQUAL -1) - set(EXPERIMENTAL_OPTION "") - string(REGEX MATCH "csgtexttest" match_result1 ${TEST_FULLNAME}) - string(REGEX MATCH "cgalstlsanity" match_result2 ${TEST_FULLNAME}) - string(REGEX MATCH "dumptest" match_result3 ${TEST_FULLNAME}) - if( "${match_result1}" STREQUAL "" ) - if( "${match_result2}" STREQUAL "" ) - if( "${match_result3}" STREQUAL "" ) - set(EXPERIMENTAL_OPTION "") - endif() - endif() + list(FIND EXPERIMENTAL_TESTS ${TEST_FULLNAME} EXPERIMENTAL) + + if (${EXPERIMENTAL} EQUAL -1) + set(EXPERIMENTAL_OPTION "") + else() + set(EXPERIMENTAL_OPTION "--enable=lc-each" "--enable=lc-else") endif() # 2D tests should be viewed from the top, not an angle. @@ -1169,8 +1176,7 @@ list(APPEND DUMPTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/escape-test ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allmodules.scad) list(APPEND CGALPNGTEST_2D_FILES ${FEATURES_2D_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_2D_FILES}) -list(APPEND CGALPNGTEST_3D_FILES ${FEATURES_3D_FILES} ${DEPRECATED_3D_FILES} ${ISSUES_3D_FILES} - ${EXAMPLE_3D_FILES}) +list(APPEND CGALPNGTEST_3D_FILES ${FEATURES_3D_FILES} ${DEPRECATED_3D_FILES} ${ISSUES_3D_FILES} ${EXAMPLE_3D_FILES}) list(APPEND CGALPNGTEST_3D_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/transform-nan-inf-tests.scad @@ -1299,6 +1305,8 @@ disable_tests(csgpngtest_primitive-inf-tests cgalpngtest_empty-shape-tests csgpngtest_issue1258) +experimental_tests(echotest_list-comprehensions-experimental) + # Test config handling # Heavy tests are tests taking more than 10 seconds on a development computer diff --git a/tests/regression/echotest/list-comprehensions-expected.echo b/tests/regression/echotest/list-comprehensions-expected.echo index 88ca4d83..8c8c2dc8 100644 --- a/tests/regression/echotest/list-comprehensions-expected.echo +++ b/tests/regression/echotest/list-comprehensions-expected.echo @@ -26,10 +26,3 @@ ECHO: [[0, 0], [1, 0], [2, 0], [1, 1], [2, 1]] ECHO: [[0, 0], [1, 0], [2, 0], [1, 1], [2, 1]] ECHO: [-1, 0, 1, 2, 3, 3, 2, 1, 0, -1] ECHO: [4, 6, [6, 7], [8, 9], -1] -ECHO: ["+", "-", "+"] -ECHO: ["A", "B"] -ECHO: ["-", "A", "B"] -ECHO: [["+", 0], ["-", 1]] -ECHO: [["+", 0], ["-", 2], ["-", 3]] -ECHO: [2, 4, 3, 9, 4, 16] -ECHO: ["a", "b", -5, -7, -9, 1, 2, 4, 8, 16, "c", 42, true] diff --git a/tests/regression/echotest/list-comprehensions-experimental-expected.echo b/tests/regression/echotest/list-comprehensions-experimental-expected.echo new file mode 100644 index 00000000..93d2c95e --- /dev/null +++ b/tests/regression/echotest/list-comprehensions-experimental-expected.echo @@ -0,0 +1,7 @@ +ECHO: ["+", "-", "+"] +ECHO: ["A", "B"] +ECHO: ["-", "A", "B"] +ECHO: [["+", 0], ["-", 1]] +ECHO: [["+", 0], ["-", 2], ["-", 3]] +ECHO: [2, 4, 3, 9, 4, 16] +ECHO: ["a", "b", -5, -7, -9, 1, 2, 4, 8, 16, "c", 42, true] From 6e30eae31f4aeb8c4164e3d7d387be66f0ebec93 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Fri, 8 Jan 2016 14:17:28 +0100 Subject: [PATCH 3/3] Fix include for standard exceptions. --- src/exceptions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exceptions.h b/src/exceptions.h index cdd83304..851726eb 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class EvaluationException : public std::runtime_error { public: