Octave, GNU Octave, Matlab, Scientific Computing, Language, Interpreter, Compiler, C++, LAPACK, Fortran, Fun , GNU

Sunday, November 4, 2007

Octave ternary operator

Ternary operator
Like in the earlier blog post, I tried building a ternary operator into Octave.
The well known C-language '?' operator, is what I wanted to build into Octave. The '?' operator, has a grammar that evaluates to an expression;

'?' ':'

This was quite experimental, but not very difficult to do. I had to add a few lines to the lexer tokens, and a single rule for the parser. Finally, I had to put in place the parse-tree class for ternary oeprator, and fill in Octave specific details. The details are minor functions in the spirit of the rest of the evaluator.

After compiling the whole thing, it worked; but my parser rules were a little off, and the parse errors were not reported. This was serious, until I noticed an empty statement rule in the parser; that fixed the ternary operator worked well!

Lexical analyzer
I added the '?' token, as QUESTION_TERNOP, to the lex.l lexer file. It is defined as QUESTION_TERNOP ((\?)) .

then we have a macro defined on lines of BIN_OP_RETURN(), as TERN_OP_RETURN(). Next the real token returning rule section of the lexer has the matching code

"?" { TERN_OP_RETURN (QUESTION_TERNOP, false); }

as above.


Parser
I added the following rule, and the corresponding semantic action in make_ternop() function. The style in writing parser actions is to put the goo into a single function, apparently. Also, we need to inform the parser-generator ( yacc ), about the precendence and non-terminals used for the ternary operator.

// Build a ternary expression.
static tree_expression *
make_ternary_op (int op, tree_expression *cond, tree_expression *lhs,
tree_expression *rhs, token *tok_val );

Using the non-terminal as ternary_expr, the rule itself is

ternary_expr : prefix_expr QUESTION_TERNOP prefix_expr ':' prefix_expr
{ $$ = make_ternary_op (QUESTION_TERNOP, $1, $3, $5,$2); }

where ternary_expr, goes into the production of simple_expr.

The make_ternary_op(...) code runs like this,

static tree_expression *
make_ternary_op ( int op, tree_expression *expr1, tree_expression *op1,
tree_expression *op2 , token *tok_val)
{
octave_value::ternary_op opx = octave_value::unknown_ternary_op;

switch ( op )
{
case QUESTION_TERNOP:
//add some test cases for catching errors.
opx=octave_value::op_question;
break;

default:
// panic_impossible ();
//must not reach here.
break;
}

int l = tok_val->line ();
int c = tok_val->column ();

//
// c-tor used:
//
// tree_ternary_expression (tree_expression *cnd, tree_expression *a, tree_expression *b,
// int l = -1, int c = -1,
// octave_value::ternary_op t
// = octave_value::unknown_ternary_op)
//
tree_ternary_expression *e
= new tree_ternary_expression (expr1, op1, op2, l, c, opx);

return fold (e); // need to modify fold.
}

and fold() function does some simple constant folding optimization; this treats simple cases like, ( const ) ? (true-expr) : (false-expr), to be pre-computed based on the value of the constant expression.

The meat of the operator is implemented in the class tree_ternary_expression, which is


/*

Copyright (C) 1996, 1997, 2000, 2002, 2003, 2004, 2005, 2006, 2007
John W. Eaton

Copyright (C) 2007 Muthiah Annamalai

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING. If not, see
.

*/

#if !defined (octave_tree_ternop_h)
#define octave_tree_ternop_h 1

#include

class tree_walker;

class octave_value;
class octave_value_list;
class octave_lvalue;

#include "ov.h"
#include "pt-exp.h"

//
// Ternary expressions support class.
// (op_cond) ? op_lhs : op_rhs
//

class
tree_ternary_expression : public tree_expression
{
public:

tree_ternary_expression (int l = -1, int c = -1,
octave_value::ternary_op t
= octave_value::unknown_ternary_op)
: tree_expression (l, c), op_cond(0), op_lhs (0), op_rhs (0), etype (t) { }

tree_ternary_expression (tree_expression *cnd, tree_expression *a, tree_expression *b,
int l = -1, int c = -1,
octave_value::ternary_op t
= octave_value::unknown_ternary_op)
: tree_expression (l, c), op_cond(cnd), op_lhs (a), op_rhs (b), etype (t) { }

~tree_ternary_expression (void)
{
delete op_cond;
delete op_lhs;
delete op_rhs;
}

bool has_magic_end (void) const
{
return ((op_lhs && op_lhs->has_magic_end ())
|| (op_rhs && op_rhs->has_magic_end ()));
}

bool is_ternary_expression (void) const { return true; }

bool rvalue_ok (void) const { return true; }

octave_value rvalue (void);

octave_value_list rvalue (int nargout);

void eval_error (void);

std::string oper (void) const;

octave_value::ternary_op op_type (void) const { return etype; }

tree_expression *cond (void) { return op_cond; }
tree_expression *lhs (void) { return op_lhs; }
tree_expression *rhs (void) { return op_rhs; }

tree_expression *dup (symbol_table *sym_tab);

void accept (tree_walker& tw);

protected:

// The operands for the expression.
tree_expression *op_cond;
tree_expression *op_lhs;
tree_expression *op_rhs;

private:

// The type of the expression.
octave_value::ternary_op etype;

// No copying!

tree_ternary_expression (const tree_ternary_expression&);

tree_ternary_expression& operator = (const tree_ternary_expression&);
};

#endif

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/



and source code in,


/*

Copyright (C) 1996, 1997, 2000, 2001, 2002, 2004, 2005, 2006, 2007
John W. Eaton

Copyright (C) 2007 Muthiah Annamalai

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING. If not, see
.

*/

#ifdef HAVE_CONFIG_H
#include
#endif

#include "error.h"
#include "oct-obj.h"
#include "ov.h"
#include "pt-ternop.h"
#include "pt-bp.h"
#include "pt-walk.h"

// Ternary expressions.

octave_value_list
tree_ternary_expression::rvalue (int nargout)
{
octave_value_list retval;

if (nargout > 1)
error ("ternary operator `%s': invalid number of output arguments",
oper () . c_str ());
else
retval = rvalue ();

return retval;
}

//
// is rvalue the value of the actual expression?
// is this where the "interpreter" executes the real thing.
// let me know.
//
octave_value
tree_ternary_expression::rvalue (void)
{
octave_value retval;

MAYBE_DO_BREAKPOINT;

if (error_state)
return retval;

if (op_cond)
{
octave_value a = op_cond->rvalue ();

if (error_state)
eval_error ();
else if (a.is_defined () && op_lhs && op_rhs )
{
switch ( etype )
{
case octave_value::op_question:
// implement a lazy evaluator.

if ( a.bool_value() )
{
octave_value b = op_lhs->rvalue ();

if ( b.is_defined () )
{
if (error_state)
eval_error ();
retval = b;
}
else
eval_error();
}
else
{
octave_value c = op_rhs->rvalue ();

if ( c.is_defined () )
{
if (error_state)
eval_error ();

retval = c;
}
else
eval_error ();
}

break;

default:
eval_error();
}
}
else
eval_error ();
}
else
eval_error ();
return retval;
}

void
tree_ternary_expression::eval_error (void)
{
::error ("evaluating ternary operator `%s' near line %d, column %d",
oper () . c_str (), line (), column ());
}

std::string
tree_ternary_expression::oper (void) const
{
return octave_value::ternary_op_as_string (etype);
}

tree_expression *
tree_ternary_expression::dup (symbol_table *sym_tab)
{
tree_ternary_expression *new_be
= new tree_ternary_expression (op_cond? op_cond->dup(sym_tab) : 0 ,
op_lhs ? op_lhs->dup (sym_tab) : 0,
op_rhs ? op_rhs->dup (sym_tab) : 0,
line (), column (), etype);

new_be->copy_base (*this);

return new_be;
}

void
tree_ternary_expression::accept (tree_walker& tw)
{
tw.visit_ternary_expression (*this);
}


/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/


The whole "operator" is implemented simply within the rvalue() member function.


Finishing Touches
Finally, adding touches to the makefiles, and regenerating the whole scripts, we can run and compile the code. For the brave hearted, the code is posted here. As ususal the code is GPL'ed, and derives heavily on JWE's code.

The outcome of this experiment was pretty much fun, and I got a tour of the Octave sources, while running Octave-code like this,

% test cases for the new ternary operator
( 1 > 0 ) ? 1 : 0
printf("%s\n", (length("hello") > length("world")) ? "mistake" : "correct" )
printf("octave %s reached version 3.0\n", strncmp( version ,"3",1) ? "has" : "hasn't" )
num=round(rand()*1000)
whats_she_telling_me = (isprime(num)) ? "prime & true" : "composite & false"

and I was sure, I must have been one of the few to run Octave on drugs.
-Muthu

Creative Commons License