Nasal: fix parsing octal/hex numbers in strings as well as during lexing.

Parse the same number formats (octal, dec, hex) in literals and tokens. Was
previously quite a mess, and is still not the best solution, as lexing and
string parsing uses different implementations, although they are meant to
do exactly the same conversions.
This commit is contained in:
Thomas Geymayer 2014-06-26 09:44:36 +02:00
parent 3bbb272ad5
commit 49bcf49db1
4 changed files with 129 additions and 14 deletions

View File

@ -40,4 +40,9 @@ endif(ENABLE_TESTS)
add_boost_test(cppbind_ghost
SOURCES cppbind_test_ghost.cxx
LIBRARIES ${TEST_LIBS}
)
add_boost_test(nasal_num
SOURCES nasal_num_test.cxx
LIBRARIES ${TEST_LIBS}
)

View File

@ -0,0 +1,92 @@
#define BOOST_TEST_MODULE cppbind
#include <BoostTestTargetConfig.h>
#include "NasalCallContext.hxx"
class TestContext:
public nasal::CallContext
{
public:
TestContext():
CallContext(naNewContext(), 0, 0)
{}
~TestContext()
{
naFreeContext(c);
}
template<class T>
T from_str(const std::string& str)
{
return from_nasal<T>(to_nasal(str));
}
naRef exec(const std::string& code_str, nasal::Me me)
{
int err_line = -1;
naRef code = naParseCode( c, to_nasal("<TextContext::exec>"), 0,
(char*)code_str.c_str(), code_str.length(),
&err_line );
if( !naIsCode(code) )
throw std::runtime_error("Failed to parse code: " + code_str);
return naCallMethod(code, me, 0, 0, naNil());
}
template<class T>
T exec(const std::string& code)
{
return from_nasal<T>(exec(code, naNil()));
}
template<class T>
T convert(const std::string& str)
{
return from_nasal<T>(to_nasal(str));
}
};
static void runNumTests( double (TestContext::*test_double)(const std::string&),
int (TestContext::*test_int)(const std::string&) )
{
TestContext c;
BOOST_CHECK_CLOSE((c.*test_double)("0.5"), 0.5, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)(".6"), 0.6, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("-.7"), -0.7, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("-0.8"), -0.8, 1e-5);
BOOST_CHECK_SMALL((c.*test_double)("0.0"), 1e-5);
BOOST_CHECK_SMALL((c.*test_double)("-.0"), 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("1.23e4"), 1.23e4, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("1.23e-4"), 1.23e-4, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("-1.23e4"), -1.23e4, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("-1.23e-4"), -1.23e-4, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("1e-4"), 1e-4, 1e-5);
BOOST_CHECK_CLOSE((c.*test_double)("-1e-4"), -1e-4, 1e-5);
BOOST_CHECK_EQUAL((c.*test_int)("123"), 123);
BOOST_CHECK_EQUAL((c.*test_int)("-958"), -958);
BOOST_CHECK_CLOSE((c.*test_int)("-1e7"), -1e7, 1e-5);
BOOST_CHECK_CLOSE((c.*test_int)("2E07"), 2e07, 1e-5);
BOOST_CHECK_EQUAL((c.*test_int)("0755"), 0755);
BOOST_CHECK_EQUAL((c.*test_int)("0055"), 055);
BOOST_CHECK_EQUAL((c.*test_int)("-0155"), -0155);
BOOST_CHECK_EQUAL((c.*test_int)("0x755"), 0x755);
BOOST_CHECK_EQUAL((c.*test_int)("0x055"), 0x55);
BOOST_CHECK_EQUAL((c.*test_int)("-0x155"), -0x155);
}
BOOST_AUTO_TEST_CASE( parse_num )
{
runNumTests(&TestContext::convert<double>, &TestContext::convert<int>);
}
BOOST_AUTO_TEST_CASE( lex_num )
{
runNumTests(&TestContext::exec<double>, &TestContext::exec<int>);
}

View File

@ -252,12 +252,12 @@ static int lexStringLiteral(struct Parser* p, int index, char q)
return i+1;
}
static int lexHexLiteral(struct Parser* p, int index)
static int lexIntLiteral(struct Parser* p, int index, int base)
{
int nib, i = index;
double d = 0;
while(i < p->len && (nib = hex(p->buf[i])) >= 0) {
d = d*16 + nib;
while(i < p->len && (nib = hex(p->buf[i])) >= 0 && nib < base) {
d = d * base + nib;
i++;
}
newToken(p, index, TOK_LITERAL, 0, 0, d);
@ -273,8 +273,12 @@ static int lexNumLiteral(struct Parser* p, int index)
unsigned char* buf = (unsigned char*)p->buf;
double d;
if(buf[i] == '0' && i+2<len && buf[i+1] == 'x' && ISHEX(buf[i+2]))
return lexHexLiteral(p, index+2);
if(buf[i] == '0') {
if(i+2<len && buf[i+1] == 'x' && ISHEX(buf[i+2]))
return lexIntLiteral(p, index+2, 16);
if(i+1<len && ISNUM(buf[i+1]) )
return lexIntLiteral(p, index+1, 8);
}
while(i<len && ISNUM(buf[i])) i++;
if(i<len && buf[i] == '.') {

View File

@ -132,15 +132,25 @@ void naStr_gcclean(struct naStr* str)
// remaining 67%.
////////////////////////////////////////////////////////////////////////
// Reads an unsigned decimal out of the scalar starting at i, stores
// it in v, and returns the next index to start at. Zero-length
// decimal numbers are allowed, and are returned as zero.
static int readdec(unsigned char* s, int len, int i, double* v)
// TODO unify with number conversion in lex.c
static int hex(char c)
{
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}
// Reads an unsigned integer out of the scalar starting at i, stores
// it in v, and returns the next index to start at. Zero-length
// integer numbers are allowed, and are returned as zero.
static int readint(unsigned char* s, int len, int i, double* v, int base)
{
int val;
*v = 0;
if(i >= len) return len;
while(i < len && s[i] >= '0' && s[i] <= '9') {
*v= (*v) * 10 + (s[i] - '0');
while(i < len && (val = hex(s[i])) >= 0 && val < base) {
*v= (*v) * base + val;
i++;
}
return i;
@ -151,12 +161,16 @@ static int readdec(unsigned char* s, int len, int i, double* v)
// decimal numbers are allowed, and are returned as zero.
static int readsigned(unsigned char* s, int len, int i, double* v)
{
int i0 = i, i2;
int i0 = i, i2, base = 10;
double sgn=1, val;
if(i >= len) { *v = 0; return len; }
if(s[i] == '+') { i++; }
else if(s[i] == '-') { i++; sgn = -1; }
i2 = readdec(s, len, i, &val);
if(s[i] == '0') {
i++; base = 8;
if( i < len && s[i] == 'x' ) { i++; base = 16; }
}
i2 = readint(s, len, i, &val, base);
if(i0 == i && i2 == i) {
*v = 0;
return i0; // don't successfully parse bare "+" or "-"
@ -201,7 +215,7 @@ static int tonum(unsigned char* s, int len, double* result)
// Read the fractional part, if any
if(i < len && s[i] == '.') {
i++;
fraclen = readdec(s, len, i, &frac) - i;
fraclen = readint(s, len, i, &frac, 10) - i;
i += fraclen;
}