diff --git a/CHANGELOG.md b/CHANGELOG.md index e59c2ee5..bad58237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Other new features: * When zooming to fit, constraints are also considered. * When selecting a point and a line, projected distance to to current workplane is displayed. + * In expressions, numbers can contain the digit group separator, "_". * The "=" key is bound to "Zoom In", like "+" key. * The numpad decimal separator key is bound to "." regardless of locale. diff --git a/src/expr.cpp b/src/expr.cpp index e00a1143..7bc2cf5d 100644 --- a/src/expr.cpp +++ b/src/expr.cpp @@ -612,7 +612,6 @@ public: char ReadChar(); char PeekChar(); - double ReadNumber(); std::string ReadWord(); void SkipSpace(); @@ -620,6 +619,7 @@ public: Token PopOperand(std::string *error); int Precedence(Token token); + Token LexNumber(std::string *error); Token Lex(std::string *error); bool Reduce(std::string *error); bool Parse(std::string *error, size_t reduceUntil = 0); @@ -650,14 +650,6 @@ char ExprParser::PeekChar() { return input[inputPos]; } -double ExprParser::ReadNumber() { - char *endptr; - double d = strtod(input + inputPos, &endptr); - unsigned len = endptr - (input + inputPos); - inputPos += len; - return d; -} - std::string ExprParser::ReadWord() { std::string s; @@ -676,6 +668,31 @@ void ExprParser::SkipSpace() { } } +ExprParser::Token ExprParser::LexNumber(std::string *error) { + std::string s; + + while(char c = PeekChar()) { + if(!((c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '.' || c == '_')) break; + if(c == '_') { + ReadChar(); + continue; + } + s.push_back(ReadChar()); + } + + char *endptr; + double d = strtod(s.c_str(), &endptr); + + Token t = Token::From(); + if(endptr == &*s.end()) { + t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT); + t.expr->v = d; + } else { + *error = "'" + s + "' is not a valid number"; + } + return t; +} + ExprParser::Token ExprParser::Lex(std::string *error) { SkipSpace(); @@ -705,9 +722,7 @@ ExprParser::Token ExprParser::Lex(std::string *error) { *error = "'" + s + "' is not a valid variable, function or constant"; } } else if(isdigit(c) || c == '.') { - double d = ReadNumber(); - t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT); - t.expr->v = d; + return LexNumber(error); } else if(ispunct(c)) { ReadChar(); if(c == '+') { diff --git a/test/core/expr/test.cpp b/test/core/expr/test.cpp index ab3829b5..8ae4c757 100644 --- a/test/core/expr/test.cpp +++ b/test/core/expr/test.cpp @@ -26,6 +26,8 @@ TEST_CASE(literal) { CHECK_TRUE(e->Eval() == 42); CHECK_PARSE(e, "42.5"); CHECK_TRUE(e->Eval() == 42.5); + CHECK_PARSE(e, "1_000_000"); + CHECK_TRUE(e->Eval() == 1000000); } TEST_CASE(unary_ops) { @@ -95,6 +97,8 @@ TEST_CASE(errors) { "Unexpected character"); CHECK_PARSE_ERR("notavar", "'notavar' is not a valid variable, function or constant"); + CHECK_PARSE_ERR("1e2e3", + "'1e2e3' is not a valid number"); CHECK_PARSE_ERR("_", "'_' is not a valid operator"); CHECK_PARSE_ERR("2 2",