docs: function parsePatchOperation(
  	recordTypes, recordTypeDesc, patchOpDef, opInd,
  	involvedPropPaths, updatedPropPaths) {
  
  	const invalidOp = msg => new common.X2SyntaxError(
  		`Invalid patch operation #${opInd + 1}: ${msg}`);
  
  	// operation definition must be an object
  	if (((typeof patchOpDef) !== 'object') || (patchOpDef === null))
  		throw invalidOp('operation specification is not an object.');
  
  	// validate the op
  	if ((typeof patchOpDef.op) !== 'string')
  		throw invalidOp('op is missing or is not a string.');
  
  	// process the operation
  	let pathPtr;
  	switch (patchOpDef.op) {
  	case 'add':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, false, PTRUSE.SET);
  		return new AddRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths),
  			validatePatchOperationValue(
  				recordTypes, patchOpDef.op, opInd, pathPtr, patchOpDef.value,
  				true)
  		);
  	case 'merge':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, true, PTRUSE.SET);
  		if (!Array.isArray(patchOpDef.patch))
  			throw invalidOp('patch is not an array.');
  		return new MergeRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths),
  			validatePatchOperationValue(
  				recordTypes, patchOpDef.op, opInd, pathPtr, patchOpDef.value,
  				true),
  			patchOpDef.patch.map(
  				(mergePatchOpDef, mergeOpInd) => parsePatchOperation(
  					recordTypes, recordTypeDesc, mergePatchOpDef, mergeOpInd,
  					involvedPropPaths, updatedPropPaths))
  		);
  	case 'remove':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, true, PTRUSE.ERASE);
  		return new RemoveRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths)
  		);
  	case 'replace':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, false, PTRUSE.SET);
  		return new ReplaceRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths),
  			validatePatchOperationValue(
  				recordTypes, patchOpDef.op, opInd, pathPtr, patchOpDef.value,
  				true)
  		);
  	case 'move':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, false, PTRUSE.SET);
  		return new MoveRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths),
  			addInvolvedProperty(
  				validatePatchOperationFrom(
  					patchOpDef.op, opInd, pathPtr, resolvePropPointer(
  						recordTypeDesc, patchOpDef.from, true, PTRUSE.ERASE),
  					true),
  				involvedPropPaths, updatedPropPaths)
  		);
  	case 'copy':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, false, PTRUSE.SET);
  		return new CopyRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths, updatedPropPaths),
  			addInvolvedProperty(
  				validatePatchOperationFrom(
  					patchOpDef.op, opInd, pathPtr, resolvePropPointer(
  						recordTypeDesc, patchOpDef.from, true, PTRUSE.READ),
  					false),
  				involvedPropPaths)
  		);
  	case 'test':
  		pathPtr = resolvePropPointer(
  			recordTypeDesc, patchOpDef.path, true, PTRUSE.READ);
  		return new TestRecordPatchOperation(
  			addInvolvedProperty(pathPtr, involvedPropPaths),
  			validatePatchOperationValue(
  				recordTypes, patchOpDef.op, opInd, pathPtr, patchOpDef.value,
  				false)
  		);
  	default:
  		throw new common.X2SyntaxError(
  			`Invalid patch operation: unknown operation "${patchOpDef.op}".`);
  	}
  }