	private TraverseResult traverseOnce(Result<IValue> subject, CasesOrRules casesOrRules, 
			DIRECTION direction, 
			PROGRESS progress){
		Type subjectType = subject.getType();
		boolean matched = false;
		boolean changed = false;
		Result<IValue> result = subject;
		if(subjectType.isStringType()){
			return traverseString(subject, casesOrRules);
		}
		if(direction == DIRECTION.TopDown){
			TraverseResult tr = traverseTop(subject, casesOrRules);
			matched |= tr.matched;
			changed |= tr.changed;
			if((progress == PROGRESS.Breaking) && changed){
				return tr;
			}
			subject = tr.value;
		}
		if(subjectType.isAbstractDataType()){
			IConstructor cons = (IConstructor)subject.getValue();
			if(cons.arity() == 0){
				result = subject;
			} else {
				IValue args[] = new IValue[cons.arity()];
				for(int i = 0; i < cons.arity(); i++){
					IValue child = cons.get(i);
					Type childType = cons.getConstructorType().getFieldType(i);
					TraverseResult tr = traverseOnce(ResultFactory.makeResult(childType, child, makeEvContext()), casesOrRules, direction, progress);
					matched |= tr.matched;
					changed |= tr.changed;
					args[i] = tr.value.getValue();
				}
				IConstructor rcons = vf.constructor(cons.getConstructorType(), args);
				result = applyRules(makeResult(subjectType, rcons.setAnnotations(cons.getAnnotations()), makeEvContext()));
			}
		} else
			if(subjectType.isNodeType()){
				INode node = (INode)subject.getValue();
				if(node.arity() == 0){
					result = subject;
				} else {
					IValue args[] = new IValue[node.arity()];
					for(int i = 0; i < node.arity(); i++){
						IValue child = node.get(i);
						TraverseResult tr = traverseOnce(ResultFactory.makeResult(tf.valueType(), child, makeEvContext()), casesOrRules, direction, progress);
						matched |= tr.matched;
						changed |= tr.changed;
						args[i] = tr.value.getValue();
					}
					result = applyRules(makeResult(tf.nodeType(), vf.node(node.getName(), args).setAnnotations(node.getAnnotations()), makeEvContext()));
				}
			} else
				if(subjectType.isListType()){
					IList list = (IList) subject;
					int len = list.length();
					if(len > 0){
						IListWriter w = list.getType().writer(vf);
						Type elemType = list.getType().getElementType();
						for(int i = len - 1; i >= 0; i--){
							IValue elem = list.get(i);
							TraverseResult tr = traverseOnce(ResultFactory.makeResult(elemType, elem, makeEvContext()), casesOrRules, direction, progress);
							matched |= tr.matched;
							changed |= tr.changed;
							w.insert(tr.value.getValue());
						}
						result = makeResult(subjectType, w.done(), makeEvContext());
					} else {
						result = subject;
					}
				} else 
					if(subjectType.isSetType()){
						ISet set = (ISet) subject;
						if(!set.isEmpty()){
							ISetWriter w = set.getType().writer(vf);
							Type elemType = set.getType().getElementType();
							for (IValue v : set){
								TraverseResult tr = traverseOnce(ResultFactory.makeResult(elemType, v, makeEvContext()), casesOrRules, direction, progress);
								matched |= tr.matched;
								changed |= tr.changed;
								w.insert(tr.value.getValue());
							}
							result = makeResult(subjectType, w.done(), makeEvContext());
						} else {
							result = subject;
						}
					} else
						if (subjectType.isMapType()) {
							IMap map = (IMap) subject.getValue();
							if(!map.isEmpty()){
								IMapWriter w = map.getType().writer(vf);
								Iterator<Entry<IValue,IValue>> iter = map.entryIterator();
								Type keyType = map.getKeyType();
								Type valueType = map.getValueType();
								while (iter.hasNext()) {
									Entry<IValue,IValue> entry = iter.next();
									TraverseResult tr = traverseOnce(ResultFactory.makeResult(keyType, entry.getKey(), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									IValue newKey = tr.value.getValue();
									tr = traverseOnce(ResultFactory.makeResult(valueType, entry.getValue(), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									IValue newValue = tr.value.getValue();
									w.put(newKey, newValue);
								}
								result = makeResult(subjectType, w.done(), makeEvContext());
							} else {
								result = subject;
							}
						} else
							if(subjectType.isTupleType()){
								ITuple tuple = (ITuple) subject.getValue();
								int arity = tuple.arity();
								IValue args[] = new IValue[arity];
								for(int i = 0; i < arity; i++){
									Type fieldType = subjectType.getFieldType(i);
									TraverseResult tr = traverseOnce(ResultFactory.makeResult(fieldType, tuple.get(i), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									args[i] = tr.value.getValue();
								}
								result = makeResult(subjectType, vf.tuple(args), makeEvContext());
							} else {
								result = subject;
							}
		if(direction == DIRECTION.BottomUp){
			if((progress == PROGRESS.Breaking) && changed){
				return new TraverseResult(matched, result, changed);
			}
			TraverseResult tr = traverseTop(result, casesOrRules);
			matched |= tr.matched;
			changed |= tr.changed;
			return new TraverseResult(matched, tr.value, changed);
		}
		return new TraverseResult(matched,result,changed);
	}

	private TraverseResult traverseOnce(Result<IValue> subject, CasesOrRules casesOrRules, 
			DIRECTION direction, 
			PROGRESS progress){
		Type subjectType = subject.getType();
		boolean matched = false;
		boolean changed = false;
		Result<IValue> result = subject;
		if(subjectType.isStringType()){
			return traverseString(subject, casesOrRules);
		}
		if(direction == DIRECTION.TopDown){
			TraverseResult tr = traverseTop(subject, casesOrRules);
			matched |= tr.matched;
			changed |= tr.changed;
			if((progress == PROGRESS.Breaking) && changed){
				return tr;
			}
			subject = tr.value;
		}
		if(subjectType.isAbstractDataType()){
			IConstructor cons = (IConstructor)subject.getValue();
			if(cons.arity() == 0){
				result = subject;
			} else {
				IValue args[] = new IValue[cons.arity()];
				for(int i = 0; i < cons.arity(); i++){
					IValue child = cons.get(i);
					Type childType = cons.getConstructorType().getFieldType(i);
					TraverseResult tr = traverseOnce(ResultFactory.makeResult(childType, child, makeEvContext()), casesOrRules, direction, progress);
					matched |= tr.matched;
					changed |= tr.changed;
					args[i] = tr.value.getValue();
				}
				IConstructor rcons = vf.constructor(cons.getConstructorType(), args);
				result = applyRules(makeResult(subjectType, rcons.setAnnotations(cons.getAnnotations()), makeEvContext()));
			}
		} else
			if(subjectType.isNodeType()){
				INode node = (INode)subject.getValue();
				if(node.arity() == 0){
					result = subject;
				} else {
					IValue args[] = new IValue[node.arity()];
					for(int i = 0; i < node.arity(); i++){
						IValue child = node.get(i);
						TraverseResult tr = traverseOnce(ResultFactory.makeResult(tf.valueType(), child, makeEvContext()), casesOrRules, direction, progress);
						matched |= tr.matched;
						changed |= tr.changed;
						args[i] = tr.value.getValue();
					}
					result = applyRules(makeResult(tf.nodeType(), vf.node(node.getName(), args).setAnnotations(node.getAnnotations()), makeEvContext()));
				}
			} else
				if(subjectType.isListType()){
					IList list = (IList) subject.getValue();
					int len = list.length();
					if(len > 0){
						IListWriter w = list.getType().writer(vf);
						Type elemType = list.getType().getElementType();
						for(int i = len - 1; i >= 0; i--){
							IValue elem = list.get(i);
							TraverseResult tr = traverseOnce(ResultFactory.makeResult(elemType, elem, makeEvContext()), casesOrRules, direction, progress);
							matched |= tr.matched;
							changed |= tr.changed;
							w.insert(tr.value.getValue());
						}
						result = makeResult(subjectType, w.done(), makeEvContext());
					} else {
						result = subject;
					}
				} else 
					if(subjectType.isSetType()){
						ISet set = (ISet) subject;
						if(!set.isEmpty()){
							ISetWriter w = set.getType().writer(vf);
							Type elemType = set.getType().getElementType();
							for (IValue v : set){
								TraverseResult tr = traverseOnce(ResultFactory.makeResult(elemType, v, makeEvContext()), casesOrRules, direction, progress);
								matched |= tr.matched;
								changed |= tr.changed;
								w.insert(tr.value.getValue());
							}
							result = makeResult(subjectType, w.done(), makeEvContext());
						} else {
							result = subject;
						}
					} else
						if (subjectType.isMapType()) {
							IMap map = (IMap) subject.getValue();
							if(!map.isEmpty()){
								IMapWriter w = map.getType().writer(vf);
								Iterator<Entry<IValue,IValue>> iter = map.entryIterator();
								Type keyType = map.getKeyType();
								Type valueType = map.getValueType();
								while (iter.hasNext()) {
									Entry<IValue,IValue> entry = iter.next();
									TraverseResult tr = traverseOnce(ResultFactory.makeResult(keyType, entry.getKey(), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									IValue newKey = tr.value.getValue();
									tr = traverseOnce(ResultFactory.makeResult(valueType, entry.getValue(), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									IValue newValue = tr.value.getValue();
									w.put(newKey, newValue);
								}
								result = makeResult(subjectType, w.done(), makeEvContext());
							} else {
								result = subject;
							}
						} else
							if(subjectType.isTupleType()){
								ITuple tuple = (ITuple) subject.getValue();
								int arity = tuple.arity();
								IValue args[] = new IValue[arity];
								for(int i = 0; i < arity; i++){
									Type fieldType = subjectType.getFieldType(i);
									TraverseResult tr = traverseOnce(ResultFactory.makeResult(fieldType, tuple.get(i), makeEvContext()), casesOrRules, direction, progress);
									matched |= tr.matched;
									changed |= tr.changed;
									args[i] = tr.value.getValue();
								}
								result = makeResult(subjectType, vf.tuple(args), makeEvContext());
							} else {
								result = subject;
							}
		if(direction == DIRECTION.BottomUp){
			if((progress == PROGRESS.Breaking) && changed){
				return new TraverseResult(matched, result, changed);
			}
			TraverseResult tr = traverseTop(result, casesOrRules);
			matched |= tr.matched;
			changed |= tr.changed;
			return new TraverseResult(matched, tr.value, changed);
		}
		return new TraverseResult(matched,result,changed);
	}
