Index: generic/gentclAPI.decls =================================================================== diff -u -r8c7e00e2907123cab46942451824724f71f658c8 -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- generic/gentclAPI.decls (.../gentclAPI.decls) (revision 8c7e00e2907123cab46942451824724f71f658c8) +++ generic/gentclAPI.decls (.../gentclAPI.decls) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -42,6 +42,9 @@ {-argName "command" -required 1 -type tclobj} {-argName "args" -type args} } +xotclCmd dot XOTclDotCmd { + {-argName "args" -type allargs} +} xotclCmd finalize XOTclFinalizeObjCmd { } xotclCmd interp XOTclInterpObjCmd { @@ -69,10 +72,6 @@ {-argName "method" -required 1 -type tclobj} {-argName "args" -type args} } -#move to right place -xotclCmd dot XOTclDotCmd { - {-argName "args" -type allargs} -} xotclCmd namespace_copycmds XOTclNSCopyCmds { {-argName "fromNs" -required 1 -type tclobj} {-argName "toNs" -required 1 -type tclobj} @@ -85,7 +84,8 @@ {-argName "name" -required 1 -type tclobj} } xotclCmd relation XOTclRelationCmd { - {-argName "object" -required 1 -type object} + {-argName "object" -type object} + {-argName "-per-object"} {-argName "relationtype" -required 1 -type "mixin|instmixin|object-mixin|class-mixin|filter|instfilter|object-filter|class-filter|class|superclass|rootclass"} {-argName "value" -required 0 -type tclobj} } Index: generic/predefined.h =================================================================== diff -u -r9ebd1309a52b27ab92e9e3cce07037767efe4a4f -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- generic/predefined.h (.../predefined.h) (revision 9ebd1309a52b27ab92e9e3cce07037767efe4a4f) +++ generic/predefined.h (.../predefined.h) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -94,6 +94,8 @@ "set parameterdefinitions [list]\n" "set slots [::xotcl2::objectInfo slotobjects $obj]\n" "foreach slot $slots {\n" +"if {[::xotcl::is $obj type ::xotcl::Object] &&\n" +"([$slot name] eq \"mixin\" || [$slot name] eq \"filter\")} continue\n" "set parameterdefinition \"-[namespace tail $slot]\"\n" "set opts [list]\n" "if {[$slot exists required] && [$slot required]} {\n" @@ -117,7 +119,10 @@ "set parameterdefinitions [::xotcl::parametersFromSlots [self]]\n" "if {[::xotcl::is [self] class]} {\n" "lappend parameterdefinitions -parameter:method,optional}\n" -"lappend parameterdefinitions -noinit:method,optional,noarg -volatile:method,optional,noarg arg:initcmd,optional\n" +"lappend parameterdefinitions \\\n" +"-noinit:method,optional,noarg \\\n" +"-volatile:method,optional,noarg \\\n" +"arg:initcmd,optional\n" "return $parameterdefinitions}\n" "::xotcl2::Class create ::xotcl2::ParameterType\n" "foreach cmd [info command ::xotcl::cmd::ParameterType::*] {\n" @@ -150,6 +155,7 @@ "{manager \"[::xotcl::self]\"}\n" "{multivalued false}\n" "{per-object false}\n" +"{forward-per-object}\n" "{required false}\n" "default\n" "type}\n" @@ -184,16 +190,19 @@ "${.domain} invalidateobjectparameter\n" "::xotcl::dispatch ${.domain} ::xotcl::cmd::Class::forward \\\n" "{*}[expr {${.per-object} ? \"-per-object\" : \"\"}] ${.name} \\\n" -"-default [${.manager} defaultmethods] ${.manager} %1 %self %proc}}\n" +"-default [${.manager} defaultmethods] ${.manager} %1 %self \\\n" +"{*}[expr {[info exists .forward-per-object] ? \"-per-object\" : \"\"}] \\\n" +"%proc}}\n" "::xotcl::MetaSlot create ::xotcl::InfoSlot\n" "createBootstrapAttributeSlots ::xotcl::InfoSlot {\n" "{multivalued true}\n" "{elementtype ::xotcl2::Class}}\n" "::xotcl::relation ::xotcl::InfoSlot superclass ::xotcl::Slot\n" -"::xotcl::InfoSlot method get {obj prop} {$obj info $prop}\n" -"::xotcl::InfoSlot method add {obj prop value {pos 0}} {\n" +"::xotcl::InfoSlot method get {obj -per-object:switch prop} {$obj info $prop}\n" +"::xotcl::InfoSlot method add {obj -per-object:switch prop value {pos 0}} {\n" "if {![set .multivalued]} {\n" "error \"Property $prop of ${.domain}->$obj ist not multivalued\"}\n" +"puts stderr \"adding infoslot: $obj $prop [linsert [$obj info $prop] $pos $value]\"\n" "$obj $prop [linsert [$obj info $prop] $pos $value]}\n" "::xotcl::InfoSlot method delete {-nocomplain:switch obj prop value} {\n" "set old [$obj info $prop]\n" @@ -215,7 +224,7 @@ "::xotcl::relation ::xotcl::InterceptorSlot superclass ::xotcl::InfoSlot\n" "::xotcl::alias ::xotcl::InterceptorSlot set ::xotcl::relation ;# for backwards compatibility\n" "::xotcl::alias ::xotcl::InterceptorSlot assign ::xotcl::relation\n" -"::xotcl::InterceptorSlot method add {obj prop value {pos 0}} {\n" +"::xotcl::InterceptorSlot method add {obj -per-object:switch prop value {pos 0}} {\n" "if {![set .multivalued]} {\n" "error \"Property $prop of ${.domain}->$obj ist not multivalued\"}\n" "$obj $prop [linsert [$obj info $prop -guards] $pos $value]}\n" Index: generic/predefined.xotcl =================================================================== diff -u -r9ebd1309a52b27ab92e9e3cce07037767efe4a4f -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- generic/predefined.xotcl (.../predefined.xotcl) (revision 9ebd1309a52b27ab92e9e3cce07037767efe4a4f) +++ generic/predefined.xotcl (.../predefined.xotcl) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -214,6 +214,10 @@ set parameterdefinitions [list] set slots [::xotcl2::objectInfo slotobjects $obj] foreach slot $slots { + # skip some lots for xotcl1; TODO: maybe different parameterFromSlots for xotcl1? + if {[::xotcl::is $obj type ::xotcl::Object] && + ([$slot name] eq "mixin" || [$slot name] eq "filter") + } continue set parameterdefinition "-[namespace tail $slot]" set opts [list] @@ -250,7 +254,10 @@ if {[::xotcl::is [self] class]} { lappend parameterdefinitions -parameter:method,optional } - lappend parameterdefinitions -noinit:method,optional,noarg -volatile:method,optional,noarg arg:initcmd,optional + lappend parameterdefinitions \ + -noinit:method,optional,noarg \ + -volatile:method,optional,noarg \ + arg:initcmd,optional # for the time being, use: #lappend parameterdefinitions args #puts stderr "*** parameter definition for [self]: $parameterdefinitions" @@ -319,6 +326,7 @@ {manager "[::xotcl::self]"} {multivalued false} {per-object false} + {forward-per-object} {required false} default type @@ -376,7 +384,9 @@ # since the domain object might be xotcl1 or 2, use dispatch ::xotcl::dispatch ${.domain} ::xotcl::cmd::Class::forward \ {*}[expr {${.per-object} ? "-per-object" : ""}] ${.name} \ - -default [${.manager} defaultmethods] ${.manager} %1 %self %proc + -default [${.manager} defaultmethods] ${.manager} %1 %self \ + {*}[expr {[info exists .forward-per-object] ? "-per-object" : ""}] \ + %proc } } @@ -389,11 +399,12 @@ {elementtype ::xotcl2::Class} } ::xotcl::relation ::xotcl::InfoSlot superclass ::xotcl::Slot -::xotcl::InfoSlot method get {obj prop} {$obj info $prop} -::xotcl::InfoSlot method add {obj prop value {pos 0}} { +::xotcl::InfoSlot method get {obj -per-object:switch prop} {$obj info $prop} +::xotcl::InfoSlot method add {obj -per-object:switch prop value {pos 0}} { if {![set .multivalued]} { error "Property $prop of ${.domain}->$obj ist not multivalued" } + puts stderr "adding infoslot: $obj $prop [linsert [$obj info $prop] $pos $value]" $obj $prop [linsert [$obj info $prop] $pos $value] } ::xotcl::InfoSlot method delete {-nocomplain:switch obj prop value} { @@ -433,7 +444,7 @@ ::xotcl::alias ::xotcl::InterceptorSlot set ::xotcl::relation ;# for backwards compatibility ::xotcl::alias ::xotcl::InterceptorSlot assign ::xotcl::relation -::xotcl::InterceptorSlot method add {obj prop value {pos 0}} { +::xotcl::InterceptorSlot method add {obj -per-object:switch prop value {pos 0}} { if {![set .multivalued]} { error "Property $prop of ${.domain}->$obj ist not multivalued" } Index: generic/tclAPI.h =================================================================== diff -u -r8c7e00e2907123cab46942451824724f71f658c8 -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- generic/tclAPI.h (.../tclAPI.h) (revision 8c7e00e2907123cab46942451824724f71f658c8) +++ generic/tclAPI.h (.../tclAPI.h) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -276,7 +276,7 @@ static int XOTclNSCopyCmds(Tcl_Interp *interp, Tcl_Obj *fromNs, Tcl_Obj *toNs); static int XOTclNSCopyVars(Tcl_Interp *interp, Tcl_Obj *fromNs, Tcl_Obj *toNs); static int XOTclQualifyObjCmd(Tcl_Interp *interp, Tcl_Obj *name); -static int XOTclRelationCmd(Tcl_Interp *interp, XOTclObject *object, int relationtype, Tcl_Obj *value); +static int XOTclRelationCmd(Tcl_Interp *interp, XOTclObject *object, int withPer_object, int relationtype, Tcl_Obj *value); static int XOTclSetInstvarCmd(Tcl_Interp *interp, XOTclObject *object, Tcl_Obj *variable, Tcl_Obj *value); enum { @@ -2236,11 +2236,12 @@ return TCL_ERROR; } else { XOTclObject *object = (XOTclObject *)pc.clientData[0]; - int relationtype = (int )pc.clientData[1]; - Tcl_Obj *value = (Tcl_Obj *)pc.clientData[2]; + int withPer_object = (int )pc.clientData[1]; + int relationtype = (int )pc.clientData[2]; + Tcl_Obj *value = (Tcl_Obj *)pc.clientData[3]; parseContextRelease(&pc); - return XOTclRelationCmd(interp, object, relationtype, value); + return XOTclRelationCmd(interp, object, withPer_object, relationtype, value); } } @@ -2671,8 +2672,9 @@ {"::xotcl::__qualify", XOTclQualifyObjCmdStub, 1, { {"name", 1, 0, convertToTclobj}} }, -{"::xotcl::relation", XOTclRelationCmdStub, 3, { - {"object", 1, 0, convertToObject}, +{"::xotcl::relation", XOTclRelationCmdStub, 4, { + {"object", 0, 0, convertToObject}, + {"-per-object", 0, 0, convertToString}, {"relationtype", 1, 0, convertToRelationtype}, {"value", 0, 0, convertToTclobj}} }, Index: generic/xotcl.c =================================================================== diff -u -r8c7e00e2907123cab46942451824724f71f658c8 -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- generic/xotcl.c (.../xotcl.c) (revision 8c7e00e2907123cab46942451824724f71f658c8) +++ generic/xotcl.c (.../xotcl.c) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -9655,80 +9655,95 @@ } static int +MethodTypeMatches(Tcl_Interp *interp, int methodType, Tcl_Command cmd, + XOTclObject *object, char *key, int withPer_object) { + Tcl_Command importedCmd; + Tcl_ObjCmdProc *proc, *resolvedProc; + + proc = Tcl_Command_objProc(cmd); + importedCmd = GetOriginalCommand(cmd); + resolvedProc = Tcl_Command_objProc(importedCmd); + + if (methodType == XOTCL_METHODTYPE_ALIAS) { + if (!(proc == XOTclProcAliasMethod || AliasGet(interp, object->cmdName, key, withPer_object))) { + return 0; + } + } else { + if (proc == XOTclProcAliasMethod) { + if ((methodType & XOTCL_METHODTYPE_ALIAS) == 0) return 0; + } + /* the following cases are disjoint */ + if (CmdIsProc(importedCmd)) { + /*fprintf(stderr,"%s scripted %d\n",key, methodType & XOTCL_METHODTYPE_SCRIPTED);*/ + if ((methodType & XOTCL_METHODTYPE_SCRIPTED) == 0) return 0; + } else if (resolvedProc == XOTclForwardMethod) { + if ((methodType & XOTCL_METHODTYPE_FORWARDER) == 0) return 0; + } else if (resolvedProc == XOTclSetterMethod) { + if ((methodType & XOTCL_METHODTYPE_SETTER) == 0) return 0; + } else if (resolvedProc == XOTclObjDispatch) { + if ((methodType & XOTCL_METHODTYPE_OBJECT) == 0) return 0; + } else if ((methodType & XOTCL_METHODTYPE_OTHER) == 0) { + /*fprintf(stderr,"OTHER %s not wanted %.4x\n",key, methodType);*/ + return 0; + } + /* XOTclObjscopedMethod ??? */ + } + return 1; +} + +static int ListMethodKeys(Tcl_Interp *interp, Tcl_HashTable *table, char *pattern, int methodType, Tcl_HashTable *dups, XOTclObject *object, int withPer_object) { Tcl_HashSearch hSrch; - Tcl_HashEntry *hPtr = table ? Tcl_FirstHashEntry(table, &hSrch) : 0; + Tcl_HashEntry *hPtr, *duphPtr; + Tcl_Command cmd; + char *key; + int new; - /* TODO: could be made faster, when pattern contains no wild cards */ - - for (; hPtr; hPtr = Tcl_NextHashEntry(&hSrch)) { - char *key = Tcl_GetHashKey(table, hPtr); - Tcl_Command importedCmd, cmd = (Tcl_Command)Tcl_GetHashValue(hPtr); - Tcl_ObjCmdProc *proc, *resolvedProc; - - proc = Tcl_Command_objProc(cmd); - importedCmd = GetOriginalCommand(cmd); - resolvedProc = Tcl_Command_objProc(importedCmd); - -#if 0 - if (proc == XOTclProcAliasMethod || proc == XOTclObjscopedMethod) { - AliasCmdClientData *tcd = Tcl_Command_objClientData(cmd); - /* TODO: resolve our chain */ - assert(tcd); - resolvedProc = tcd->objProc; +#if 1 + if (pattern && noMetaChars(pattern)) { + /* We have a pattern that can be used for direct lookup; no need to iterate */ + hPtr = table ? XOTcl_FindHashEntry(table, pattern) : 0; + if (hPtr) { + key = Tcl_GetHashKey(table, hPtr); + cmd = (Tcl_Command)Tcl_GetHashValue(hPtr); + if (MethodTypeMatches(interp, methodType, cmd, object, key, withPer_object)) { + if (dups) { + duphPtr = Tcl_CreateHashEntry(dups, key, &new); + if (new) { + Tcl_AppendElement(interp, key); + } + } else { + Tcl_AppendElement(interp, key); + } + } } + return TCL_OK; + + } else { +#else + { #endif + hPtr = table ? Tcl_FirstHashEntry(table, &hSrch) : 0; - if (pattern && !Tcl_StringMatch(key, pattern)) continue; - if (methodType == XOTCL_METHODTYPE_ALIAS) { - if (!(proc == XOTclProcAliasMethod || AliasGet(interp, object->cmdName, key, withPer_object))) { - continue; + for (; hPtr; hPtr = Tcl_NextHashEntry(&hSrch)) { + key = Tcl_GetHashKey(table, hPtr); + cmd = (Tcl_Command)Tcl_GetHashValue(hPtr); + + if (pattern && !Tcl_StringMatch(key, pattern)) continue; + if (!MethodTypeMatches(interp, methodType, cmd, object, key, withPer_object)) continue; + + if (dups) { + duphPtr = Tcl_CreateHashEntry(dups, key, &new); + if (!new) continue; } - } else { - if (proc == XOTclProcAliasMethod) { - if ((methodType & XOTCL_METHODTYPE_ALIAS) == 0) continue; - } - /* the following cases are disjoint */ - if (CmdIsProc(importedCmd)) { - /*fprintf(stderr,"%s scripted %d\n",key, methodType & XOTCL_METHODTYPE_SCRIPTED);*/ - if ((methodType & XOTCL_METHODTYPE_SCRIPTED) == 0) continue; - } else if (resolvedProc == XOTclForwardMethod) { - if ((methodType & XOTCL_METHODTYPE_FORWARDER) == 0) continue; - } else if (resolvedProc == XOTclSetterMethod) { - if ((methodType & XOTCL_METHODTYPE_SETTER) == 0) continue; - } else if (resolvedProc == XOTclObjDispatch) { - if ((methodType & XOTCL_METHODTYPE_OBJECT) == 0) continue; - } else if ((methodType & XOTCL_METHODTYPE_OTHER) == 0) { - /*fprintf(stderr,"OTHER %s not wanted %.4x\n",key, methodType);*/ + + if (((Command *) cmd)->flags & XOTCL_CMD_PROTECTED_METHOD) { + /*fprintf(stderr, "--- dont list protected name '%s'\n", key);*/ continue; - } - } - /* - if (noCmds && proc != RUNTIME_STATE(interp)->objInterpProc) continue; - if (noProcs && proc == RUNTIME_STATE(interp)->objInterpProc) continue; - if (onlyForwarder && proc != XOTclForwardMethod) continue; - if (onlySetter && proc != XOTclSetterMethod) continue; - */ - /* XOTclObjscopedMethod ??? */ - - if (dups) { - int new; - Tcl_HashEntry *duphPtr; - duphPtr = Tcl_CreateHashEntry(dups, key, &new); - if (!new) { - /*fprintf(stderr, "preexisting entry %s\n", key);*/ - continue; - } else { - /*fprintf(stderr, "new entry %s\n", key);*/ } + Tcl_AppendElement(interp, key); } - - if (((Command *) cmd)->flags & XOTCL_CMD_PROTECTED_METHOD) { - /*fprintf(stderr, "--- dont list protected name '%s'\n", key);*/ - continue; - } - Tcl_AppendElement(interp, key); } /*fprintf(stderr, "listkeys returns '%s'\n", ObjStr(Tcl_GetObjResult(interp)));*/ return TCL_OK; @@ -9829,8 +9844,8 @@ int noMixins, int inContext) { XOTclClasses *pl; Tcl_HashTable *cmdTable, dupsTable, *dups = &dupsTable; - Tcl_InitHashTable(dups, TCL_STRING_KEYS); + /*fprintf(stderr, "listMethods %s %d %d\n", pattern, noMixins, inContext);*/ if (withDefined) { @@ -9839,11 +9854,18 @@ } else { cmdTable = object->nsPtr ? Tcl_Namespace_cmdTable(object->nsPtr) : NULL; } - ListMethodKeys(interp, cmdTable, pattern, methodType, dups, object, withPer_object); - Tcl_DeleteHashTable(dups); + ListMethodKeys(interp, cmdTable, pattern, methodType, NULL, object, withPer_object); return TCL_OK; } + /* + * TODO: we could make this faster for patterns without metachars + * by letting ListMethodKeys() to signal us when an entry was found. + * we wait, until the we decided about "info methods defined" + * vs. "info method search" vs. "info defined" etc. + */ + + Tcl_InitHashTable(dups, TCL_STRING_KEYS); if (object->nsPtr) { cmdTable = Tcl_Namespace_cmdTable(object->nsPtr); ListMethodKeys(interp, cmdTable, pattern, methodType, dups, object, withPer_object); @@ -10890,14 +10912,50 @@ return TCL_OK; } -static int XOTclRelationCmd(Tcl_Interp *interp, XOTclObject *object, int relationtype, Tcl_Obj *value) { + static int XOTclRelationCmd(Tcl_Interp *interp, XOTclObject *object, + int withPer_object, int relationtype, Tcl_Obj *value) { int oc; Tcl_Obj **ov; XOTclObject *nobj = NULL; XOTclClass *cl = NULL; XOTclObjectOpt *objopt = NULL; XOTclClassOpt *clopt = NULL, *nclopt = NULL; int i; + /*fprintf(stderr, "XOTclRelationCmd %s %d rel=%d val='%s'\n", + objectName(object),withPer_object,relationtype,value?ObjStr(value):"NULL"); + */ + if (withPer_object) { + switch (relationtype) { + case RelationtypeClass_mixinIdx: + case RelationtypeInstmixinIdx: + relationtype = RelationtypeObject_mixinIdx; + break; + case RelationtypeClass_filterIdx: + case RelationtypeInstfilterIdx: + relationtype = RelationtypeObject_filterIdx; + break; + } + } else { + switch (relationtype) { + case RelationtypeObject_mixinIdx: + case RelationtypeMixinIdx: + if ( + XOTclObjectIsClass(object) + ) { + relationtype = RelationtypeClass_mixinIdx; + } + break; + case RelationtypeObject_filterIdx: + case RelationtypeFilterIdx: + if ( + XOTclObjectIsClass(object) + ) { + /*relationtype = RelationtypeClass_filterIdx;*/ + } + break; + } + } + switch (relationtype) { case RelationtypeObject_mixinIdx: case RelationtypeMixinIdx: @@ -11455,7 +11513,7 @@ int relIdx; result = convertToRelationtype(interp, paramPtr->nameObj, paramPtr, (ClientData)&relIdx); if (result == TCL_OK) { - result = XOTclRelationCmd(interp, obj, relIdx, newValue); + result = XOTclRelationCmd(interp, obj, 0 /*fixme*/, relIdx, newValue); } if (result != TCL_OK) { XOTcl_PopFrame(interp, obj); Index: library/lib/xotcl1.xotcl =================================================================== diff -u -r9ebd1309a52b27ab92e9e3cce07037767efe4a4f -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- library/lib/xotcl1.xotcl (.../xotcl1.xotcl) (revision 9ebd1309a52b27ab92e9e3cce07037767efe4a4f) +++ library/lib/xotcl1.xotcl (.../xotcl1.xotcl) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -85,8 +85,32 @@ {__default_metaclass ::xotcl::Class} } - ::xotcl::register_system_slots ::xotcl +############################################ +# system slots +############################################ + proc register_system_slots1 {os} { + ${os}::Object alloc ${os}::Class::slot + ${os}::Object alloc ${os}::Object::slot + + ::xotcl::InfoSlot create ${os}::Class::slot::superclass -type relation + ::xotcl::alias ${os}::Class::slot::superclass assign ::xotcl::relation + ::xotcl::InfoSlot create ${os}::Object::slot::class -type relation + ::xotcl::alias ${os}::Object::slot::class assign ::xotcl::relation + ::xotcl::InterceptorSlot create ${os}::Object::slot::mixin \ + -forward-per-object true \ + -type relation + ::xotcl::InterceptorSlot create ${os}::Object::slot::filter \ + -forward-per-object true \ + -elementtype "" -type relation + ::xotcl::InterceptorSlot create ${os}::Class::slot::instmixin \ + -type relation + ::xotcl::InterceptorSlot create ${os}::Class::slot::instfilter \ + -elementtype "" \ + -type relation + } + ::xotcl::register_system_slots1 ::xotcl + ######################## # Info definition ######################## Index: tests/slottest.xotcl =================================================================== diff -u -r217d826e64107056ae97176552cae3c776991b9e -r8f79347327f3c5f73faf86e87ebd6c8306265fbb --- tests/slottest.xotcl (.../slottest.xotcl) (revision 217d826e64107056ae97176552cae3c776991b9e) +++ tests/slottest.xotcl (.../slottest.xotcl) (revision 8f79347327f3c5f73faf86e87ebd6c8306265fbb) @@ -84,6 +84,7 @@ Class M Class O -mixin M +? {O mixin} ::M ? {catch {Object o -mixin check1 M}} 1 ? {O mixin} ::M Class M2