From 4db3cb45fa735484e4937772c6426ce23abf0b41 Mon Sep 17 00:00:00 2001 From: Ge Wang Date: Fri, 15 Nov 2024 21:12:18 -0800 Subject: [PATCH] update string operations to use overloads; fix associated reference counting --- examples/string/strops.ck | 4 +- src/core/chuck_dl.cpp | 34 ++++ src/core/chuck_dl.h | 5 +- src/core/chuck_emit.cpp | 127 ++++--------- src/core/chuck_instr.cpp | 228 +++++++++++------------ src/core/chuck_instr.h | 32 ++-- src/core/chuck_lang.cpp | 252 +++++++++++++++++++++++++- src/core/chuck_lang.h | 11 ++ src/core/chuck_type.cpp | 63 ++++--- src/test/01-Basic/259-strops.ck | 61 +++++++ src/test/01-Basic/260-strops2.ck | 60 ++++++ src/test/01-Basic/260-strops2.txt | 18 ++ src/test/01-Basic/261-null-compare.ck | 6 + 13 files changed, 660 insertions(+), 241 deletions(-) create mode 100644 src/test/01-Basic/259-strops.ck create mode 100644 src/test/01-Basic/260-strops2.ck create mode 100644 src/test/01-Basic/260-strops2.txt create mode 100644 src/test/01-Basic/261-null-compare.ck diff --git a/examples/string/strops.ck b/examples/string/strops.ck index 3a715c150..d33ca697f 100644 --- a/examples/string/strops.ck +++ b/examples/string/strops.ck @@ -46,9 +46,9 @@ assert( "foo" + "bar" == "foobar", "16" ); "bar" +=> s; assert( s == "foobar", "17" ); assert( "bar" + 10 == "bar10", "18" ); -assert( "bar" + 10.0 == "bar10.0000", "19" ); +assert( "bar" + 10.0 == "bar10.000000", "19" ); assert( 10 + "bar" == "10bar", "20" ); -assert( 10.0 + "bar" == "10.0000bar", "21" ); +assert( 10.0 + "bar" == "10.000000bar", "21" ); assert( "foo" + "bar" + "cle" == "foobarcle", "22" ); assert( "FoO".lower() == "foo", "23" ); assert( "foo".upper() == "FOO", "24" ); diff --git a/src/core/chuck_dl.cpp b/src/core/chuck_dl.cpp index 771134b80..b41a11f14 100644 --- a/src/core/chuck_dl.cpp +++ b/src/core/chuck_dl.cpp @@ -951,6 +951,40 @@ Chuck_DL_Value * CK_DLL_CALL make_new_mvar( const char * t, const char * n, t_CK { return new Chuck_DL_Value( t, n, c ); } Chuck_DL_Value * CK_DLL_CALL make_new_svar( const char * t, const char * n, t_CKBOOL c, void * a ) { return new Chuck_DL_Value( t, n, c, a ); } +Chuck_DL_Func * make_new_op_binary( const char * t, ae_Operator op, f_gfun gfun ) +{ + // allocate + Chuck_DL_Func * f = new Chuck_DL_Func( t, (string("@operator")+op2str(op)).c_str(), (t_CKUINT)gfun, ae_fp_gfun ); + // set operator overload kind + f->opOverloadKind = ckte_op_overload_BINARY; + // set the op + f->op2overload = op; + // return + return f; +} +Chuck_DL_Func * make_new_op_prefix( const char * t, ae_Operator op, f_gfun gfun ) +{ + // allocate + Chuck_DL_Func * f = new Chuck_DL_Func( t, (string("@operator")+op2str(op)).c_str(), (t_CKUINT)gfun, ae_fp_gfun ); + // set operator overload kind + f->opOverloadKind = ckte_op_overload_UNARY_PRE; + // set the op + f->op2overload = op; + // return + return f; +} +Chuck_DL_Func * make_new_op_postfix( const char * t, ae_Operator op, f_gfun gfun ) +{ + // allocate + Chuck_DL_Func * f = new Chuck_DL_Func( t, (string("@operator")+op2str(op)).c_str(), (t_CKUINT)gfun, ae_fp_gfun ); + // set operator overload kind + f->opOverloadKind = ckte_op_overload_UNARY_POST; + // set the op + f->op2overload = op; + // return + return f; +} + diff --git a/src/core/chuck_dl.h b/src/core/chuck_dl.h index 93fe9dae5..236719b60 100644 --- a/src/core/chuck_dl.h +++ b/src/core/chuck_dl.h @@ -883,11 +883,14 @@ struct Chuck_DL_Arg //------------------------------------------------------------------------------ -// alternative functions to make stuff +// alternative functions to make stuff` //------------------------------------------------------------------------------ Chuck_DL_Func * make_new_ctor( f_ctor ctor ); Chuck_DL_Func * make_new_mfun( const char * t, const char * n, f_mfun mfun ); Chuck_DL_Func * make_new_sfun( const char * t, const char * n, f_sfun sfun ); +Chuck_DL_Func * make_new_op_binary( const char * t, ae_Operator op, f_gfun gfun ); +Chuck_DL_Func * make_new_op_prefix( const char * t, ae_Operator op, f_gfun gfun ); +Chuck_DL_Func * make_new_op_postfix( const char * t, ae_Operator op, f_gfun gfun ); Chuck_DL_Value * make_new_arg( const char * t, const char * n ); Chuck_DL_Value * make_new_mvar( const char * t, const char * n, t_CKBOOL c = FALSE ); Chuck_DL_Value * make_new_svar( const char * t, const char * n, t_CKBOOL c, void * a ); diff --git a/src/core/chuck_emit.cpp b/src/core/chuck_emit.cpp index 3648bf0c2..2059a76db 100644 --- a/src/core/chuck_emit.cpp +++ b/src/core/chuck_emit.cpp @@ -2116,57 +2116,6 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a { emit->append( instr = new Chuck_Instr_Add_double ); } - // string + string - else if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) - { - // concatenate - emit->append( instr = new Chuck_Instr_Add_string ); - instr->set_linepos( lhs->line ); - } - // left: string - else if( isa( t_left, emit->env->ckt_string ) ) - { - // + int - if( isa( t_right, emit->env->ckt_int ) ) - { - emit->append( instr = new Chuck_Instr_Add_string_int ); - instr->set_linepos( lhs->line ); - } - else if( isa( t_right, emit->env->ckt_float ) ) - { - emit->append( instr = new Chuck_Instr_Add_string_float ); - instr->set_linepos( lhs->line ); - } - else - { - EM_error2( lhs->where, - "(emit): internal error: unhandled op '%s' %s '%s'", - t_left->c_name(), op2str( op ), t_right->c_name() ); - return FALSE; - } - } - // right: string - else if( isa( t_right, emit->env->ckt_string ) ) - { - // + int - if( isa( t_left, emit->env->ckt_int ) ) - { - emit->append( instr = new Chuck_Instr_Add_int_string ); - instr->set_linepos( rhs->line ); - } - else if( isa( t_left, emit->env->ckt_float ) ) - { - emit->append( instr = new Chuck_Instr_Add_float_string ); - instr->set_linepos( rhs->line ); - } - else - { - EM_error2( lhs->where, - "(emit): internal error: unhandled op '%s' %s '%s'", - t_left->c_name(), op2str( op ), t_right->c_name() ); - return FALSE; - } - } else // other types { switch( left ) @@ -2210,13 +2159,13 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a emit->append( instr = new Chuck_Instr_Time_Advance ); instr->set_linepos( lhs->line ); } - // time + dur + // time +=> dur else if( ( left == te_dur && right == te_time ) || ( left == te_time && right == te_dur ) ) { emit->append( instr = new Chuck_Instr_Add_double_Assign ); } - // string + string + // string +=> string else if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) { // concatenate @@ -2780,13 +2729,13 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a // -------------------------------- bool ----------------------------------- case ae_op_eq: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) - && !isa( t_left, emit->env->ckt_null ) && !isa( t_right, emit->env->ckt_null ) ) // !null - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } - else if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) + // && !isa( t_left, emit->env->ckt_null ) && !isa( t_right, emit->env->ckt_null ) ) // !null + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } else + if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || ( isa( t_left, emit->env->ckt_object ) && isnull( emit->env, t_right ) ) || ( isnull( emit->env,t_left ) && isa( t_right, emit->env->ckt_object ) ) || ( isnull( emit->env,t_left ) && isnull( emit->env, t_right ) ) ) @@ -2826,14 +2775,14 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a break; case ae_op_neq: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) - && !isa( t_left, emit->env->ckt_null ) && !isa( t_right, emit->env->ckt_null ) ) // !null - // added 1.3.2.0 (spencer) - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } - else if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) + // && !isa( t_left, emit->env->ckt_null ) && !isa( t_right, emit->env->ckt_null ) ) // !null + // // added 1.3.2.0 (spencer) + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } else + if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || ( isa( t_left, emit->env->ckt_object ) && isnull( emit->env, t_right ) ) || ( isnull( emit->env,t_left ) && isa( t_right, emit->env->ckt_object ) ) || ( isnull( emit->env,t_left ) && isnull( emit->env, t_right ) ) ) @@ -2884,11 +2833,11 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a break; default: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } break; } break; @@ -2906,12 +2855,12 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a break; default: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } - else if( isa( t_left, emit->env->ckt_io ) ) + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } else + if( isa( t_left, emit->env->ckt_io ) ) { // output if( isa( t_right, emit->env->ckt_int ) ) @@ -2972,11 +2921,11 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a break; default: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } break; } break; @@ -2994,11 +2943,11 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a break; default: - if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) - { - emit->append( instr = new Chuck_Instr_Op_string( op ) ); - instr->set_linepos( lhs->line ); - } + // if( isa( t_left, emit->env->ckt_string ) && isa( t_right, emit->env->ckt_string ) ) + // { + // emit->append( instr = new Chuck_Instr_Op_string( op ) ); + // instr->set_linepos( lhs->line ); + // } break; } break; diff --git a/src/core/chuck_instr.cpp b/src/core/chuck_instr.cpp index 5ef6c7aa5..525e7685d 100644 --- a/src/core/chuck_instr.cpp +++ b/src/core/chuck_instr.cpp @@ -1971,48 +1971,49 @@ void Chuck_Instr_vec4_Divide_float_Assign::execute( Chuck_VM * vm, Chuck_VM_Shre //----------------------------------------------------------------------------- // name: execute() // desc: string + string +// (no longer used; string concat now handled by op overloading) //----------------------------------------------------------------------------- -void Chuck_Instr_Add_string::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) -{ - t_CKUINT *& reg_sp = (t_CKUINT *&)shred->reg->sp; - Chuck_String * lhs = NULL; - Chuck_String * rhs = NULL; - Chuck_String * result = NULL; - - // pop word from reg stack - pop_( reg_sp, 2 ); - // left - lhs = (Chuck_String *)(*(reg_sp)); - // right - rhs = (Chuck_String *)(*(reg_sp+1)); - - // make sure no null - if( !rhs || !lhs ) goto null_pointer; - - // make new string - result = (Chuck_String *)instantiate_and_initialize_object( vm->env()->ckt_string, shred ); - - // concat - // result->str = lhs->str + rhs->str; - result->set( lhs->str() + rhs->str() ); - - // push the reference value to reg stack - push_( reg_sp, (t_CKUINT)(result) ); - - return; - -null_pointer: - // we have a problem - EM_exception( - "NullPointer: (string + string) on line[%lu] in shred[id=%lu:%s]", - m_linepos, shred->xid, shred->name.c_str() ); // , shred->pc ); - goto done; - -done: - // do something! - shred->is_running = FALSE; - shred->is_done = TRUE; -} +//void Chuck_Instr_Add_string::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +//{ +// t_CKUINT *& reg_sp = (t_CKUINT *&)shred->reg->sp; +// Chuck_String * lhs = NULL; +// Chuck_String * rhs = NULL; +// Chuck_String * result = NULL; +// +// // pop word from reg stack +// pop_( reg_sp, 2 ); +// // left +// lhs = (Chuck_String *)(*(reg_sp)); +// // right +// rhs = (Chuck_String *)(*(reg_sp+1)); +// +// // make sure no null +// if( !rhs || !lhs ) goto null_pointer; +// +// // make new string +// result = (Chuck_String *)instantiate_and_initialize_object( vm->env()->ckt_string, shred ); +// +// // concat +// // result->str = lhs->str + rhs->str; +// result->set( lhs->str() + rhs->str() ); +// +// // push the reference value to reg stack +// push_( reg_sp, (t_CKUINT)(result) ); +// +// return; +// +//null_pointer: +// // we have a problem +// EM_exception( +// "NullPointer: (string + string) on line[%lu] in shred[id=%lu:%s]", +// m_linepos, shred->xid, shred->name.c_str() ); // , shred->pc ); +// goto done; +// +//done: +// // do something! +// shred->is_running = FALSE; +// shred->is_done = TRUE; +//} @@ -4509,9 +4510,11 @@ Chuck_Object * instantiate_and_initialize_object( Chuck_Type * type, Chuck_VM * //----------------------------------------------------------------------------- // name: instantiate_and_initialize_object() -// desc: you probably shouldn't call this version. call the one that takes a -// shred if you have a non-null shred, otherwise call the one that -// takes a vm +// desc: instiate and initialize a ChucK Object of a particular Type +// NOTE: the returned Object will have a reference count of 0 +// NOTE: you probably shouldn't call this version. call the one that +// takes a shred if you have a non-null shred, otherwise call the one +// that takes a vm //----------------------------------------------------------------------------- Chuck_Object * instantiate_and_initialize_object( Chuck_Type * type, Chuck_VM_Shred * shred, Chuck_VM * vm ) { @@ -8259,75 +8262,76 @@ void Chuck_Instr_Cast_Runtime_Verify::execute( Chuck_VM * vm, Chuck_VM_Shred * s //----------------------------------------------------------------------------- // name: execute() -// desc: ... +// desc: various string ops +// (no longer used; string ops now handled more properly by op overloads) //----------------------------------------------------------------------------- -void Chuck_Instr_Op_string::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) -{ - t_CKUINT *& sp = (t_CKUINT *&)shred->reg->sp; - Chuck_String * lhs = NULL; - Chuck_String * rhs = NULL; - - // pop - pop_( sp, 2 ); - // get the string references - lhs = (Chuck_String *)*sp; - rhs = (Chuck_String *)*(sp + 1); - // neither should be null - if( !lhs || !rhs ) goto null_pointer; - - // look - switch( m_val ) - { - case ae_op_eq: - push_( sp, lhs->str() == rhs->str() ); - break; - - case ae_op_neq: - push_( sp, lhs->str() != rhs->str() ); - break; - - case ae_op_lt: - push_( sp, lhs->str() < rhs->str() ); - break; - - case ae_op_le: - push_( sp, lhs->str() <= rhs->str() ); - break; - - case ae_op_gt: - push_( sp, lhs->str() > rhs->str() ); - break; - - case ae_op_ge: - push_( sp, lhs->str() >= rhs->str() ); - break; - - default: - goto invalid_op; - break; - } - - return; - -null_pointer: - // we have a problem - EM_exception( - "NullPointer: (string op) on line[%lu] in shred[id=%lu:%s]", - m_linepos, shred->xid, shred->name.c_str() ); - goto done; - -invalid_op: - // we have a problem - EM_exception( - "InvalidStringOp: '%s' on line[%lu] in shred[id=%lu:%s]", - op2str((ae_Operator)m_val), m_linepos, shred->xid, shred->name.c_str() ); - goto done; - -done: - // do something! - shred->is_running = FALSE; - shred->is_done = TRUE; -} +//void Chuck_Instr_Op_string::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +//{ +// t_CKUINT *& sp = (t_CKUINT *&)shred->reg->sp; +// Chuck_String * lhs = NULL; +// Chuck_String * rhs = NULL; +// +// // pop +// pop_( sp, 2 ); +// // get the string references +// lhs = (Chuck_String *)*sp; +// rhs = (Chuck_String *)*(sp + 1); +// // neither should be null +// if( !lhs || !rhs ) goto null_pointer; +// +// // look +// switch( m_val ) +// { +// case ae_op_eq: +// push_( sp, lhs->str() == rhs->str() ); +// break; +// +// case ae_op_neq: +// push_( sp, lhs->str() != rhs->str() ); +// break; +// +// case ae_op_lt: +// push_( sp, lhs->str() < rhs->str() ); +// break; +// +// case ae_op_le: +// push_( sp, lhs->str() <= rhs->str() ); +// break; +// +// case ae_op_gt: +// push_( sp, lhs->str() > rhs->str() ); +// break; +// +// case ae_op_ge: +// push_( sp, lhs->str() >= rhs->str() ); +// break; +// +// default: +// goto invalid_op; +// break; +// } +// +// return; +// +//null_pointer: +// // we have a problem +// EM_exception( +// "NullPointer: (string op) on line[%lu] in shred[id=%lu:%s]", +// m_linepos, shred->xid, shred->name.c_str() ); +// goto done; +// +//invalid_op: +// // we have a problem +// EM_exception( +// "InvalidStringOp: '%s' on line[%lu] in shred[id=%lu:%s]", +// op2str((ae_Operator)m_val), m_linepos, shred->xid, shred->name.c_str() ); +// goto done; +// +//done: +// // do something! +// shred->is_running = FALSE; +// shred->is_done = TRUE; +//} diff --git a/src/core/chuck_instr.h b/src/core/chuck_instr.h index 033aaf7de..342779aa2 100644 --- a/src/core/chuck_instr.h +++ b/src/core/chuck_instr.h @@ -1249,13 +1249,14 @@ struct Chuck_Instr_vec4_Divide_float_Assign : public Chuck_Instr_Binary_Op //----------------------------------------------------------------------------- // name: struct Chuck_Instr_Add_string -// desc: ... +// desc: string + string +// (no longer used; string concat now handled by op overloading) //----------------------------------------------------------------------------- -struct Chuck_Instr_Add_string : public Chuck_Instr_Binary_Op -{ -public: - virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); -}; +//struct Chuck_Instr_Add_string : public Chuck_Instr_Binary_Op +//{ +//public: +// virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); +//}; @@ -4392,16 +4393,17 @@ struct Chuck_Instr_Cast_Runtime_Verify : public Chuck_Instr //----------------------------------------------------------------------------- // name: struct Chuck_Instr_Op_string -// desc: ... +// desc: // desc: various string ops +// (no longer used; string ops now handled more properly by op overloads) //----------------------------------------------------------------------------- -struct Chuck_Instr_Op_string : public Chuck_Instr_Unary_Op -{ -public: - Chuck_Instr_Op_string( t_CKUINT v ) { this->set( v ); } - -public: - virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); -}; +//struct Chuck_Instr_Op_string : public Chuck_Instr_Unary_Op +//{ +//public: +// Chuck_Instr_Op_string( t_CKUINT v ) { this->set( v ); } +// +//public: +// virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); +//}; diff --git a/src/core/chuck_lang.cpp b/src/core/chuck_lang.cpp index 4356eb5c2..62c7b8408 100644 --- a/src/core/chuck_lang.cpp +++ b/src/core/chuck_lang.cpp @@ -1096,6 +1096,70 @@ t_CKBOOL init_class_string( Chuck_Env * env, Chuck_Type * type ) // end the class import type_engine_import_class_end( env ); + // add operator overload: string+string | 1.5.4.2 (ge) + func = make_new_op_binary( "string", ae_op_plus, string_op_string_plus_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: int+string | 1.5.4.2 (ge) + func = make_new_op_binary( "string", ae_op_plus, string_op_int_plus_string ); + func->add_arg( "int", "ival" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + // add operator overload: string+int | 1.5.4.2 (ge) + func = make_new_op_binary( "string", ae_op_plus, string_op_string_plus_int ); + func->add_arg( "string", "obj" ); + func->add_arg( "int", "ival" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: int+string | 1.5.4.2 (ge) + func = make_new_op_binary( "string", ae_op_plus, string_op_float_plus_string ); + func->add_arg( "float", "fval" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + // add operator overload: string+int | 1.5.4.2 (ge) + func = make_new_op_binary( "string", ae_op_plus, string_op_string_plus_float ); + func->add_arg( "string", "obj" ); + func->add_arg( "float", "fval" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string == string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_eq, string_op_string_eq_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string != string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_neq, string_op_string_neq_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string < string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_lt, string_op_string_lt_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string <= string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_le, string_op_string_le_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string > string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_gt, string_op_string_gt_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + + // add operator overload: string >= string | 1.5.4.2 (ge) + func = make_new_op_binary( "int", ae_op_ge, string_op_string_ge_string ); + func->add_arg( "string", "obj" ); + func->add_arg( "string", "str" ); + if( !type_engine_import_op_overload( env, func ) ) goto error_post_class; + return TRUE; error: @@ -1103,6 +1167,8 @@ t_CKBOOL init_class_string( Chuck_Env * env, Chuck_Type * type ) // end the class import type_engine_import_class_end( env ); +error_post_class: + return FALSE; } @@ -2975,6 +3041,12 @@ CK_DLL_MFUN(string_findStr) Chuck_String * str = (Chuck_String *) SELF; Chuck_String * the_str = GET_NEXT_STRING(ARGS); + if( the_str == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "string argument in .find()" ); + return; + } + string::size_type index = str->str().find(the_str->str()); if(index == string::npos) @@ -2989,9 +3061,15 @@ CK_DLL_MFUN(string_findStrStart) Chuck_String * the_str = GET_NEXT_STRING(ARGS); t_CKINT start = GET_NEXT_INT(ARGS); + if( the_str == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "string argument in .find()" ); + return; + } + if(start < 0 || start >= str->str().length()) { - ck_throw_exception(SHRED, "IndexOutOfBounds", start); + ck_throw_exception( SHRED, "IndexOutOfBounds", start ); RETURN->v_int = -1; return; } @@ -3043,6 +3121,12 @@ CK_DLL_MFUN(string_rfindStr) Chuck_String * str = (Chuck_String *) SELF; Chuck_String * the_str = GET_NEXT_STRING(ARGS); + if( the_str == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "string argument in .find()" ); + return; + } + string::size_type index = str->str().rfind(the_str->str()); if(index == string::npos) @@ -3057,6 +3141,12 @@ CK_DLL_MFUN(string_rfindStrStart) Chuck_String * the_str = GET_NEXT_STRING(ARGS); t_CKINT start = GET_NEXT_INT(ARGS); + if( the_str == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "string argument in .find()" ); + return; + } + if(start < 0 || start >= str->str().length()) { ck_throw_exception(SHRED, "IndexOutOfBounds", start); @@ -3151,6 +3241,166 @@ CK_DLL_MFUN( string_get_at ) } */ +// 1.5.4.2 (ge) added +CK_DLL_GFUN( string_op_string_plus_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + if( left == NULL || right == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "argument(s) to @operator+(string,string)" ); + return; + } + + string lhs = left->str(); + string rhs = right->str(); + + // return value + RETURN->v_string = (Chuck_String *)instantiate_and_initialize_object(SHRED->vm_ref->env()->ckt_string, SHRED); + RETURN->v_string->set( lhs + rhs ); +} + +CK_DLL_GFUN( string_op_int_plus_string ) +{ + t_CKINT left = GET_NEXT_INT(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + if( right == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "argument(s) to @operator+(int,string)" ); + return; + } + + string rhs = right->str(); + + // return value + RETURN->v_string = (Chuck_String *)instantiate_and_initialize_object(SHRED->vm_ref->env()->ckt_string, SHRED); + RETURN->v_string->set( std::to_string(left) + rhs ); +} + +CK_DLL_GFUN( string_op_string_plus_int ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + t_CKINT right = GET_NEXT_INT(ARGS); + + if( left == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "argument(s) to @operator+(string,int)" ); + return; + } + + string lhs = left->str(); + + // return value + RETURN->v_string = (Chuck_String *)instantiate_and_initialize_object(SHRED->vm_ref->env()->ckt_string, SHRED); + RETURN->v_string->set( lhs + std::to_string(right) ); +} + +CK_DLL_GFUN( string_op_float_plus_string ) +{ + t_CKFLOAT left = GET_NEXT_FLOAT(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + if( right == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "argument(s) to @operator+(float,string)" ); + return; + } + + string rhs = right->str(); + + // return value + RETURN->v_string = (Chuck_String *)instantiate_and_initialize_object(SHRED->vm_ref->env()->ckt_string, SHRED); + RETURN->v_string->set( std::to_string(left) + rhs ); +} + +CK_DLL_GFUN( string_op_string_plus_float ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + t_CKFLOAT right = GET_NEXT_FLOAT(ARGS); + + if( left == NULL ) // 1.5.4.2 (ge) added + { + ck_throw_exception( SHRED, "NullPointer", "argument(s) to @operator+(string,float)" ); + return; + } + + string lhs = left->str(); + + // return value + RETURN->v_string = (Chuck_String *)instantiate_and_initialize_object(SHRED->vm_ref->env()->ckt_string, SHRED); + RETURN->v_string->set( lhs + std::to_string(right) ); +} + +CK_DLL_GFUN( string_op_string_eq_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() == right->str()); + // only one is null + else if( (left && !right) || (!left && right) ) RETURN->v_int = FALSE; + // both null + else RETURN->v_int = TRUE; +} + +CK_DLL_GFUN( string_op_string_neq_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() != right->str()); + // if at least one is null + else RETURN->v_int = FALSE; +} + +CK_DLL_GFUN( string_op_string_lt_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() < right->str()); + // if at least one is null + else RETURN->v_int = FALSE; +} + +CK_DLL_GFUN( string_op_string_le_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() <= right->str()); + // if at least one is null + else RETURN->v_int = FALSE; +} + +CK_DLL_GFUN( string_op_string_gt_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() > right->str()); + // if at least one is null + else RETURN->v_int = FALSE; +} + +CK_DLL_GFUN( string_op_string_ge_string ) +{ + Chuck_String * left = GET_NEXT_STRING(ARGS); + Chuck_String * right = GET_NEXT_STRING(ARGS); + + // if both non-null + if( left && right ) RETURN->v_int = (left->str() >= right->str()); + // if at least one is null + else RETURN->v_int = FALSE; +} + //----------------------------------------------------------------------------- // array API diff --git a/src/core/chuck_lang.h b/src/core/chuck_lang.h index 83aaf5d96..d2c64b36b 100644 --- a/src/core/chuck_lang.h +++ b/src/core/chuck_lang.h @@ -253,6 +253,17 @@ CK_DLL_MFUN( string_erase); CK_DLL_MFUN( string_toInt); CK_DLL_MFUN( string_toFloat); CK_DLL_MFUN( string_parent); +CK_DLL_GFUN( string_op_string_plus_string ); // 1.5.4.2 (ge) added +CK_DLL_GFUN( string_op_int_plus_string ); +CK_DLL_GFUN( string_op_string_plus_int ); +CK_DLL_GFUN( string_op_float_plus_string ); +CK_DLL_GFUN( string_op_string_plus_float ); +CK_DLL_GFUN( string_op_string_eq_string ); +CK_DLL_GFUN( string_op_string_neq_string ); +CK_DLL_GFUN( string_op_string_lt_string ); +CK_DLL_GFUN( string_op_string_le_string ); +CK_DLL_GFUN( string_op_string_gt_string ); +CK_DLL_GFUN( string_op_string_ge_string ); //----------------------------------------------------------------------------- diff --git a/src/core/chuck_type.cpp b/src/core/chuck_type.cpp index e795d24f3..a893fe6a7 100644 --- a/src/core/chuck_type.cpp +++ b/src/core/chuck_type.cpp @@ -101,6 +101,8 @@ t_CKBOOL type_engine_check_func_def( Chuck_Env * env, a_Func_Def func_def ); t_CKBOOL type_engine_check_class_def( Chuck_Env * env, a_Class_Def class_def ); // helpers +void type_engine_init_op_overload_builtin( Chuck_Env * env ); +// check for const Chuck_Value * type_engine_check_const( Chuck_Env * env, a_Exp exp ); // convert dot member expression to string for printing string type_engine_print_exp_dot_member( Chuck_Env * env, a_Exp_Dot_Member member ); @@ -561,6 +563,13 @@ t_CKBOOL type_engine_init( Chuck_Carrier * carrier ) EM_log( CK_LOG_HERALD, "adding base classes..." ); EM_pushlog(); + // initialize operator overloading (part 1) + // 1.5.4.2 (ge) broken up into two parts; moved part 1 to here: + // before initializing builtin types -- since some types, + // e.g., string, now overload operators + // (also see: type_engine_init_op_overload_builtin()) + if( !type_engine_init_op_overload( env ) ) return FALSE; + //------------------------- // initialize internal classes; for now these are assumed to not error out // NOTE: alternately, could test for return values and bailing out gracefully @@ -717,8 +726,15 @@ t_CKBOOL type_engine_init( Chuck_Carrier * carrier ) // commit the global namespace env->global()->commit(); - // initialize operator mappings - if( !type_engine_init_op_overload( env ) ) return FALSE; + // initialize operator overloas (part 2: reserve builtin overloads) + // 1.5.4.2 (ge) this was broken up into two parts due to the need to + // initialize op overloads in general before initializing the builtin + // types like string, which now overload operators instead of hard-coding + // string operations; (also see type_engine_init_op_overload()) + type_engine_init_op_overload_builtin( env ); + + // important: preserve all entries (or they will be cleared on next reset) + env->op_registry.preserve(); // clear/create/reset [user] namespace | 1.5.4.0 (ge) added/moved here // subsequent definitions (e.g., public classes) would be added @@ -2135,14 +2151,14 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp switch( op ) { case ae_op_plus: - // string + int/float - if( isa( left, env->ckt_string ) ) - { - // right is string or int/float - if( isa( right, env->ckt_string ) || isa( right, env->ckt_int ) - || isa( right, env->ckt_float ) ) - break; - } +// // string + int/float +// if( isa( left, env->ckt_string ) ) +// { +// // right is string or int/float +// if( isa( right, env->ckt_string ) || isa( right, env->ckt_int ) +// || isa( right, env->ckt_float ) ) +// break; +// } case ae_op_plus_chuck: // int/float + string if( isa( left, env->ckt_string ) || isa( left, env->ckt_int ) || isa( left, env->ckt_float ) ) @@ -2297,11 +2313,11 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp CK_COMMUTE( te_vec2, te_vec4 ) return env->ckt_vec4; // 1.5.1.7 CK_COMMUTE( te_vec3, te_vec4 ) return env->ckt_vec4; // 1.3.5.3 CK_COMMUTE( te_dur, te_time ) return env->ckt_time; - if( isa( left, env->ckt_string ) && isa( right, env->ckt_string ) ) return env->ckt_string; - if( isa( left, env->ckt_string ) && isa( right, env->ckt_int ) ) return env->ckt_string; - if( isa( left, env->ckt_string ) && isa( right, env->ckt_float ) ) return env->ckt_string; - if( isa( left, env->ckt_int ) && isa( right, env->ckt_string ) ) return env->ckt_string; - if( isa( left, env->ckt_float ) && isa( right, env->ckt_string ) ) return env->ckt_string; +// if( isa( left, env->ckt_string ) && isa( right, env->ckt_string ) ) return env->ckt_string; +// if( isa( left, env->ckt_string ) && isa( right, env->ckt_int ) ) return env->ckt_string; +// if( isa( left, env->ckt_string ) && isa( right, env->ckt_float ) ) return env->ckt_string; +// if( isa( left, env->ckt_int ) && isa( right, env->ckt_string ) ) return env->ckt_string; +// if( isa( left, env->ckt_float ) && isa( right, env->ckt_string ) ) return env->ckt_string; break; case ae_op_minus: @@ -2396,7 +2412,6 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp CK_LR( te_vec2, te_vec2 ) return env->ckt_int; // 1.5.1.7 CK_LR( te_vec3, te_vec3 ) return env->ckt_int; // 1.3.5.3 CK_LR( te_vec4, te_vec4 ) return env->ckt_int; // 1.3.5.3 - if( isa( left, env->ckt_object ) && isa( right, env->ckt_object ) ) return env->ckt_int; case ae_op_lt: case ae_op_le: { @@ -2488,6 +2503,14 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp // if we have a hit if( ret ) return ret; + // if no match, catch reference compare | 1.5.4.2 (ge) moved to after op overload check + if( op == ae_op_eq || op == ae_op_neq ) + { + // if both are Object references + // NB string == string is overloaded by default + if( isa( left, env->ckt_object ) && isa( right, env->ckt_object ) ) return env->ckt_int; + } + // no match EM_error2( binary->where, "cannot resolve operator '%s' on types '%s' and '%s'", @@ -7425,9 +7448,6 @@ void type_engine_init_op_overload_builtin( Chuck_Env * env ) registry->reserve( env->ckt_vec2, ae_op_plus, env->ckt_vec3, TRUE ); // commute | 1.5.1.7 registry->reserve( env->ckt_vec2, ae_op_plus, env->ckt_vec4, TRUE ); // commute | 1.5.1.7 registry->reserve( env->ckt_vec3, ae_op_plus, env->ckt_vec4, TRUE ); // commute - registry->reserve( env->ckt_object, ae_op_plus, env->ckt_string ); // object +=> string - registry->reserve( env->ckt_int, ae_op_plus, env->ckt_string, TRUE ); // int/float +=> string - registry->reserve( env->ckt_float, ae_op_plus, env->ckt_string, TRUE ); // string +=> int/float // - registry->reserve( env->ckt_time, ae_op_minus, env->ckt_time ); registry->reserve( env->ckt_time, ae_op_minus, env->ckt_dur ); @@ -7744,8 +7764,9 @@ t_CKBOOL type_engine_init_op_overload( Chuck_Env * env ) registry->add( ae_op_ungruck_right )->configure( TRUE, false, false ); registry->add( ae_op_ungruck_left )->configure( TRUE, false, false ); - // mark built-in overload - type_engine_init_op_overload_builtin( env ); + // mark built-in op overloads + // 1.5.4.2 (ge) moved to later ("part 2") + // type_engine_init_op_overload_builtin( env ); // pop log EM_poplog(); diff --git a/src/test/01-Basic/259-strops.ck b/src/test/01-Basic/259-strops.ck new file mode 100644 index 000000000..317dfd2c3 --- /dev/null +++ b/src/test/01-Basic/259-strops.ck @@ -0,0 +1,61 @@ +// strops.ck some basic string operations +// +// for string API documentation, see: +// https://chuck.stanford.edu/doc/reference/base.html#string + +// three strings +"hello" => string foo; +"hello" => string bar; +"there" => string geh; + +// makeshift assert +fun void assert( int condition, string text ) +{ + if( !condition ) + { + <<< "assertion failed: ", text >>>; + me.exit(); + } +} + +// equality +assert( foo == foo, "1" ); +assert( foo == bar, "2" ); +assert( "abc" == "abc", "3" ); +assert( "hello" == foo, "4" ); + +assert( foo != geh, "5" ); +assert( "x" != "y", "6" ); +assert( foo != "there", "7" ); + +// lt +assert( foo < geh, "8" ); +assert( foo < "hello!", "9" ); +assert( foo <= foo, "10" ); +assert( foo <= "there", "11" ); + +// gt +assert( foo > "abc", "12" ); +assert( foo > "b", "13" ); +assert( foo >= foo, "14" ); +assert( foo >= bar, "15" ); + +// concatenation +assert( "foo" + "bar" == "foobar", "16" ); +"foo" => string s; +"bar" +=> s; +assert( s == "foobar", "17" ); +assert( "bar" + 10 == "bar10", "18" ); +// "bar10.000000" -- using find in case # of 0s after decimal differs +assert( ("bar" + 10.0).find("bar10.") == 0, "19" ); +assert( 10 + "bar" == "10bar", "20" ); +// "10.000000bar" -- using find in case # of 0s after decimal differs +assert( (10.0 + "bar").find("bar") > 2, "21" ); +assert( "foo" + "bar" + "cle" == "foobarcle", "22" ); +assert( "FoO".lower() == "foo", "23" ); +assert( "foo".upper() == "FOO", "24" ); +assert( " foo ".trim() == "foo", "25" ); +assert( " foo ".ltrim() == "foo ", "26" ); +assert( " foo ".rtrim() == " foo", "27" ); + +<<< "success" >>>; diff --git a/src/test/01-Basic/260-strops2.ck b/src/test/01-Basic/260-strops2.ck new file mode 100644 index 000000000..5bf88e372 --- /dev/null +++ b/src/test/01-Basic/260-strops2.ck @@ -0,0 +1,60 @@ +// strops2.ck: more string operations +// +// for string API documentation, see: +// https://chuck.stanford.edu/doc/reference/base.html#string + +// a string +"the quick brown fox jumped over the lazy dog" @=> string str; + +// print string +<<< "str:", str >>>; + +// read the first char (as int) +<<< "str.charAt( 0 ):", str.charAt( 0 ) >>>; +// read 10th char +<<< "str.charAt( 10 ):", str.charAt( 10 ) >>>; +// test character by index +str.setCharAt( 0, 'T' ); +<<< "str.setCharAt( 0, 'T' ):", str >>>; + +// test substring -- from the 10th char +<<< "str.substring( 10 ):", str.substring( 10 ) >>>; +// test substring -- from 10th char, for 10 chars +<<< "str.substring( 10, 10 ):", str.substring(10, 10) >>>; + +// insert +str.insert( 36, "old " ); +<<< "str.insert( 36, \"old \" ):", str >>>; + +// test erase +str.erase( 40, 5 ); +<<< "str.erase( 40, 5 ):", str >>>; + +// test replace +str.replace( 40, "cat" ); +<<< "str.replace( 40, \"cat\" ):", str >>>; +// test replace +str.replace( 4, 5, "slow" ); +<<< "str.replace( 4, 5, \"slow\" ):", str >>>; + +// test replace | 1.5.1.3 or higher +// str.replace( "slow brown", "hungry" ); +// <<< "str.replace( \"slow brown\", \"hungry\" ):", str >>>; + +// test find -- index of letter, return -1 if not found +<<< "str.find('x'):", str.find('x') >>>; +// test find +<<< "str.find('x', 15):", str.find('x', 15) >>>; +// test find +<<< "str.find(\"fox\"):", str.find("fox") >>>; +// test find +<<< "str.find(\"fox\", 20):", str.find("fox", 20) >>>; + +// test rfind +<<< "str.rfind('o'):", str.rfind('o') >>>; +// test rfind +<<< "str.rfind('o', 20):", str.rfind('o', 20) >>>; +// test rfind +<<< "str.rfind(\"fox\"):", str.rfind("fox") >>>; +// test rfind +<<< "str.rfind(\"fox\", 20):", str.rfind("fox", 20) >>>; diff --git a/src/test/01-Basic/260-strops2.txt b/src/test/01-Basic/260-strops2.txt new file mode 100644 index 000000000..4a974ade9 --- /dev/null +++ b/src/test/01-Basic/260-strops2.txt @@ -0,0 +1,18 @@ +str: the quick brown fox jumped over the lazy dog +str.charAt( 0 ): 116 +str.charAt( 10 ): 98 +str.setCharAt( 0, 'T' ): The quick brown fox jumped over the lazy dog +str.substring( 10 ): brown fox jumped over the lazy dog +str.substring( 10, 10 ): brown fox +str.insert( 36, "old " ): The quick brown fox jumped over the old lazy dog +str.erase( 40, 5 ): The quick brown fox jumped over the old dog +str.replace( 40, "cat" ): The quick brown fox jumped over the old cat +str.replace( 4, 5, "slow" ): The slow brown fox jumped over the old cat +str.find('x'): 17 +str.find('x', 15): 17 +str.find("fox"): 15 +str.find("fox", 20): -1 +str.rfind('o'): 35 +str.rfind('o', 20): 16 +str.rfind("fox"): 15 +str.rfind("fox", 20): 15 diff --git a/src/test/01-Basic/261-null-compare.ck b/src/test/01-Basic/261-null-compare.ck new file mode 100644 index 000000000..b4b729134 --- /dev/null +++ b/src/test/01-Basic/261-null-compare.ck @@ -0,0 +1,6 @@ +// verifying == and != for null references + +null @=> Object a; +Object @ b; + +if( (a==b) && !(a!=b) ) <<< "success" >>>;