Skip to main content Skip to navigation

Status update: Linux


Hey all! Just a quick heads up on my GSoC project.

P.S.: this post was amended on June 4, 2020 to correct the actual post date. It should be June 4, not June 1. The timestamp was updated accordingly.

SeExpr prototype: fixed!

My layer generator is now working under Linux!

AppImage screenshot showing SeExpr rendering the pattern correctly under Linux.

When I showed the floating point truncation bug to Wolthera, her first idea was:

amyspark: another thing you could think about is whether you are dealing with a floating-point localization issue…

I didn’t believe her, seeing that it only happened inside Krita. I converted Disney’s existing imageSynth2 demo and compiled it inside our toolchain to see if it was the compiler instead, but to no avail.

Without any other options left, I jumped deep inside the rabbit hole that is SeExpr’s parser, and started by tracing the calls that yield the (truncated) constants.

The state dump I posted before says a class called N7SeExpr211ExprNumNodeE represents them; this is just a mangled name for the ExprNumNode class. I put a breakpoint on the value() call, but the value had already been truncated. I tested with the constructor itself, but wasn’t able to get the actual value, as it’d been <optimized out> according to gdb.

However, the breakpoint’s stacktrace shows me there’s a Bison source file in between!

Which is no help, except that I knew Bison generates a C++ version, and that Disney already bundles it. Armed with this knowledge and the stacktrace’s line number, I went to the source:

#line 333 "/disney/users/jberlin/projects/seexpr2/src/SeExpr2/ExprParser.y"
                                { (SeExpr2val.n) = NODE1((SeExpr2loc).first_column,(SeExpr2loc).last_column,NumNode, (SeExpr2vsp[0].d)); /*printf("line %d",@$.last_column);*/}
#line 2263 ""

Indeed, the value comes from SeExpr2vsp[0].d. This is a pointer to a structure called SeExprYYSTYPE, and which is nothing but the parser’s state storage!

#if ! defined SeExprYYSTYPE && ! defined SeExprYYSTYPE_IS_DECLARED
union SeExprYYSTYPE
#line 77 "/disney/users/jberlin/projects/seexpr2/src/SeExpr2/ExprParser.y"

    SeExpr2::ExprNode* n; /* a node is returned for all non-terminals to
		      build the parse tree from the leaves up. */
    double d;      // return value for number tokens
    char* s;       /* return value for name tokens.  Note: the string
		      is allocated with strdup() in the lexer and must
		      be freed with free() */
    struct {
        SeExpr2::ExprType::Type     type;
        int                  dim;
        SeExpr2::ExprType::Lifetime lifetime;
    } t;  // return value for types
    SeExpr2::ExprType::Lifetime l; // return value for lifetime qualifiers

#line 235 ""

typedef union SeExprYYSTYPE SeExprYYSTYPE;

Now I knew that the d member of this union has the parsed value (yes, SeExpr2vsp[0].d showed it as truncated already), and that the rule for building nodes with numeric constants was called “NUMBER”:

| NUMBER			{ $$ = NODE1(@$.first_column,@$.last_column,NumNode, $1); /*printf("line %d",@$.last_column);*/}

I went to the lexer (generated version here, source here) and searched for NUMBER nodes being returned.

Guess what I found?

{REAL}			{ yylval.d = atof(yytext); return NUMBER; }

Turns out, she was right all the time!

atof is a Standard Library function that interprets strings into the floating-point numbers they represent. There is a little phrase in the docs (emphasis mine), that seems to have been overlooked:

nonempty sequence of decimal digits optionally containing decimal-point character (as determined by the current C locale) (defines significand)

My system’s locale is es_ES.UTF-8. This means that, whenever SeExpr parsed a numeric constant, the decimal point separator expected by the parsing function was a comma. This means the library would never work properly in my system, as it already consumes the commas to separater parameters! I think this bug wasn’t found by Disney developers because it would only happen on shells with very specific locales.

Calling SeExpr with the expected C locale would work, but it was a hack. I already have their source code, so I could fix this at the root by replacing atof calls with a locale-independent function. A quick StackOverflow search led me to an alternative parsing function called crack_atof. I found the original source, and since it was MIT-licensed, I adapted it to suit the atof calling convention, and bundled it with the Platform.h header in SeExpr.

What’s next?

Now that all OSes are working, next thing to do is to provide a better UI, and start dogfooding the layer. I’ve spoken with Boudewijn, and I plan to publish a new prototype using Disney’s widgets. These are obviously unsuitable for production usage in KDE applications (for reasons I’ll detail in a future post), so we plan to release a MWP AppImage and gather feedback before determining the next steps.

Obviously, I still want to add the unit tests and post a bit more on the innards! Just too many homework deadlines right now.

Until next time, and thank you so much Wolthera,