# type util
class PistonTypeError(Exception):pass
def _get_type(t):return t if isinstance(t,type)else type(t)
def _cmp_type(t,o):return _get_type(t)==_get_type(o)
def _ensure_iterable(o):return o if isinstance(o,(list,tuple))else [o]
def _type_of(t,o):return _get_type(t) in[_get_type(e)for e in _ensure_iterable(o)]
def _type_mismatch(t,o):raise PistonTypeError("type mismatch. expected "+("|".join([_get_type(e).__name__ for e in o])+" but got "+_get_type(t).__name__))
def _assert_type(t,o):_type_mismatch(t,o)if not _type_of(t,o)else None
def _length_error(e,l):raise ValueError("expected length "+str(len(e))+" but got "+str(l))
def _assert_length(e,l):_length_error(e,l)if len(e)!=l else None
# match util
def _value_cb(v):return lambda:v
def _call_fn(cb,args,n):
    n_args=cb.__code__.co_argcount
    if n_args<0:return cb()
    else:
        template="res=call({args})";str_args=[f'"{arg}"' if isinstance(arg, str)else str(arg)for arg in args];arg_dif=n_args-n
        if arg_dif<0:str_args=str_args[:arg_dif]
        else:l=["None"]*arg_dif;str_args.extend(l)
        f_call=template.format(args=",".join(str_args));ns=dict(__name__="match");ns["call"]=cb;exec(f_call,ns);return ns["res"]
class _Matchable(object):
    def get_values(self):raise NotImplementedError
    def get_n_values(self):return len(self.get_values())
    def match(self,*cases,default):
        for case in cases:
            _assert_type(case,(tuple,list));_assert_length(case,2);comp,call=case;call=call if callable(call)else _value_cb(call);
            if self.__eq__(comp):return _call_fn(call,self.get_values(),self.get_n_values())
        if default:
            call=default if callable(default)else _value_cb(default);return _call_fn(call,self.get_values(),self.get_n_values())