diff --git a/src/exceptions.h b/src/exceptions.h index 9d01e14f..851726eb 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,6 +1,14 @@ -#include +#pragma once -class RecursionException: public std::runtime_error { +#include + +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 14e92f0b..0d72aea4 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; @@ -550,9 +555,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 << ")"; } } @@ -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); @@ -595,7 +602,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 +654,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 << ")"; } ExpressionLcForC::ExpressionLcForC(const AssignmentList &arglist, const AssignmentList &incrargs, Expression *cond, Expression *expr) @@ -657,6 +664,8 @@ ExpressionLcForC::ExpressionLcForC(const AssignmentList &arglist, const Assignme ValuePtr ExpressionLcForC::evaluate(const Context *context) const { + ExperimentalFeatureException::check(Feature::ExperimentalForCExpression); + Value::VectorType vec; Context c(context); @@ -703,7 +712,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/feature.cc b/src/feature.cc index 79df7cce..a3538abb 100644 --- a/src/feature.cc +++ b/src/feature.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -18,6 +19,9 @@ 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."); +const Feature Feature::ExperimentalForCExpression("lc-for-c", "Enable C-style for expression in list comprehensions."); Feature::Feature(const std::string &name, const std::string &description) : enabled(false), name(name), description(description) @@ -76,3 +80,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..bd559df5 100644 --- a/src/feature.h +++ b/src/feature.h @@ -6,12 +6,18 @@ #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; + static const Feature ExperimentalForCExpression; + const std::string& get_name() const; const std::string& get_description() const; @@ -37,3 +43,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/src/parser.y b/src/parser.y index f6438cc0..fefc778b 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; @@ -482,8 +483,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-experimental.scad b/testdata/scad/functions/list-comprehensions-experimental.scad new file mode 100644 index 00000000..373b43f2 --- /dev/null +++ b/testdata/scad/functions/list-comprehensions-experimental.scad @@ -0,0 +1,10 @@ +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 ]); +echo([ for (i=2;i<=10;i=i+2) i ]); +echo([ for (i=1,n=1;i<=4;i=i+1,n=(n+i)*i) [i,n] ]); diff --git a/testdata/scad/functions/list-comprehensions.scad b/testdata/scad/functions/list-comprehensions.scad index 9a66f238..bd1093fe 100644 --- a/testdata/scad/functions/list-comprehensions.scad +++ b/testdata/scad/functions/list-comprehensions.scad @@ -29,11 +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 = [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 ]); -echo([ for (i=2;i<=10;i=i+2) i ]); -echo([ for (i=1,n=1;i<=4;i=i+1,n=(n+i)*i) [i,n] ]); diff --git a/testdata/scad/misc/allexpressions.scad b/testdata/scad/misc/allexpressions.scad index 76bef891..267b5d56 100644 --- a/testdata/scad/misc/allexpressions.scad +++ b/testdata/scad/misc/allexpressions.scad @@ -31,6 +31,7 @@ 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,b=1;a < 5;a=a+1,b=b+2) [a,b*b] ]; \ No newline at end of file +hh = [for (a = [0 : 3]) if (a < 2) ( if (a < 1) ["+", a] ) else ["-", a] ]; +ii = [for (a=0,b=1;a < 5;a=a+1,b=b+2) [a,b*b] ]; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fce48fcd..a9c7f7a0 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" "--enable=lc-for-c") 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 a5564d5b..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: [2, 4, 3, 9, 4, 16] -ECHO: ["a", "b", -5, -7, -9, 1, 2, 4, 8, 16, "c", 42, true] -ECHO: [2, 4, 6, 8, 10] -ECHO: [[1, 1], [2, 6], [3, 27], [4, 124]] 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..880e4abb --- /dev/null +++ b/tests/regression/echotest/list-comprehensions-experimental-expected.echo @@ -0,0 +1,9 @@ +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] +ECHO: [2, 4, 6, 8, 10] +ECHO: [[1, 1], [2, 6], [3, 27], [4, 124]] diff --git a/tests/regression/moduledumptest/allexpressions-expected.ast b/tests/regression/moduledumptest/allexpressions-expected.ast index a88caa68..5963714f 100644 --- a/tests/regression/moduledumptest/allexpressions-expected.ast +++ b/tests/regression/moduledumptest/allexpressions-expected.ast @@ -29,9 +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]]; -hh = [for(a = 0, b = 1;(a < 5);a = (a + 1), b = (b + 2)) [a, (b * b)]]; - +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]))]; +ii = [for(a = 0, b = 1;(a < 5);a = (a + 1), b = (b + 2)) [a, (b * b)]];